小游戏和GUI编程(6) | 基于 SFML 的井字棋

小游戏和GUI编程(6) | 基于 SFML 的井字棋

0. 简介

使用 SFML 实现井字棋(tic-tac-toe), 规划如下:

  • 了解规则, 使用命令行实现(已经实现了)
  • 使用 SFML,提供极简的交互(预计 1 小时)
  • 制作 SVG 图像, 美化界面(预计 1 小时)

1. 基于命令行的实现

实现了两个用户 X 和 O 的交互下棋, 判断了输赢、 平局:

  • 有胜负: 每个用户落下棋子后, 检查整个棋盘中的能获胜的 8 个线段上三个点,如果都等于当前落子的值(X或O)那么赢了
  • 没胜负: 如果没有判断出有人赢了, 并且扫描棋盘网格出现了空格, 那么继续下棋; 没有扫描到空格, 说明没法落子了,是平局。
#include <stdio.h>
#include <string.h>char board[3][3];void show_board()
{for (int i = 0; i < 3; i++){if (i > 0) printf("-----\n");for (int j = 0; j < 3; j++){if (j > 0) printf("|");printf("%c", board[i][j]);}printf("\n");}
}char user = 'X';enum State {PLAYING = 0,WIN = 1,DRAW = 2
};
State state = PLAYING;
bool played = false;bool user_play()
{printf("[user=%c] please input position ([1-3] [1-3])): ", user);int x;int y;scanf("%d %d", &x, &y);if (x < 1 || x > 3 || y < 1 || y > 3){printf("invalid position\n");return false;}if (board[x-1][y-1] != ' '){printf("invalid position\n");return false;}board[x-1][y-1] = user;return true;
}void update_user()
{if (!played) return;if (user == 'X'){user = 'O';}else{user = 'X';}
}void judge()
{int data[8][6] = {{0, 0, 0, 1, 0, 2},{1, 0, 1, 1, 1, 2},{2, 0, 2, 1, 2, 2},{0, 0, 1, 0, 2, 0},{0, 1, 1, 1, 2, 1},{0, 2, 1, 2, 2, 2},{0, 0, 1, 1, 2, 2},{0, 2, 1, 1, 2, 0}};for (int i = 0; i < 8; i++){int x0 = data[i][0];int y0 = data[i][1];int x1 = data[i][2];int y1 = data[i][3];int x2 = data[i][4];int y2 = data[i][5];if (board[x0][y0] == user && board[x1][y1] == user && board[x2][y2] == user){state = WIN;return;}}for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){if (board[i][j] == ' '){return;}}}state = DRAW;
}int main()
{for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){board[i][j] = ' ';}}while (true){show_board();judge();if (state != PLAYING) break;update_user();played = user_play();}if (state == WIN){printf("Game end, user %c win\n", user);}else if (state == DRAW){printf("Game end, draw\n");}return 0;
}

2. 基于 SFML 的极简实现

所谓极简是说, 先不考虑美观性, 实现鼠标交互落子即可。

规划:

  • 2.1 绘制窗口
  • 2.2 鼠标点击后绘制单个棋子
  • 2.3 绘制棋盘网格
  • 2.4 轮流绘制棋子
  • 2.5 显示局面信息

2.1 绘制窗口

#include <SFML/Graphics.hpp>int main()
{constexpr int win_width = 500;constexpr int win_height = 500;const std::string title = "Tic Tac Toe SFML";sf::RenderWindow window(sf::VideoMode(win_width, win_height), title);while (window.isOpen()){sf::Event event;while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }}window.clear();// draw everything here...window.display();}return 0;
}

2.2 鼠标点击后绘制单个棋子

拆解为两部分: 获取到鼠标点击的位置; 根据位置绘制棋子。

根据位置绘制棋子
先直接绘制棋子 ‘X’. 可以使用 sf::RectangleShape 创建线段对象, 它具有宽度, 旋转它45° 和-45° 可以得到交叉效果, 比较麻烦的地方在于, 需要复杂的计算才能确保交叉点是中心点。

另一个思路是使用 sf::Vertext, 通过在两个 vertex 之间用 sf::Lines 类型执行渲染, 省去了计算, 缺点是线段的宽度很窄。 不过够用了。

window.clear(sf::Color::White);// draw everything here...
// A    B
// +----+
// |    |
// +----+
// C    D
sf::Vector2f A(100, 100);
sf::Vector2f D(150, 150);// draw line AD
sf::Vertex vertex[2];
vertex[0].position = A;
vertex[0].color  = sf::Color::Blue;
vertex[1].position = D;
vertex[1].color = sf::Color::Blue;
window.draw(vertex, 2, sf::Lines);// draw line BC
sf::Vector2f B(150, 100);
sf::Vector2f C(100, 150);
vertex[0].position = B;
vertex[0].color  = sf::Color::Blue;
vertex[1].position = C;
vertex[1].color = sf::Color::Blue;
window.draw(vertex, 2, sf::Lines);window.display();

在这里插入图片描述

获取鼠标位置

以获取到的鼠标点击位置为中心, 上下左右各自扩展 50 个像素, 得到的 ABCD 区域里面, 绘制 ‘X’.

获取鼠标点击位置, 是在 sfml 教程的 window - keyboard, mouse event 里:

    sf::Vector2i localPosition(-1, -1);while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }if (sf::Mouse::isButtonPressed(sf::Mouse::Left)){// get the local mouse position (relative to a window)localPosition = sf::Mouse::getPosition(window);}}

为了避免屏幕闪烁, 如果当前帧没有获取到新的鼠标位置, 那么 A,B,C,D 四个点的坐标不变,仍然执行渲染和绘制; 如果鼠标点击了, 才更新 A,B,C,D。 效果:

在这里插入图片描述

对应代码:

#include <SFML/Graphics.hpp>
#include <iostream>int main()
{constexpr int win_width = 500;constexpr int win_height = 500;const std::string title = "Tic Tac Toe SFML";sf::RenderWindow window(sf::VideoMode(win_width, win_height), title);sf::Vector2f A, B, C, D;while (window.isOpen()){sf::Event event;sf::Vector2i localPosition(-1, -1);while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }if (sf::Mouse::isButtonPressed(sf::Mouse::Left)){// get the local mouse position (relative to a window)localPosition = sf::Mouse::getPosition(window);}}window.clear();constexpr int grid_len = 50;if (localPosition != sf::Vector2i(-1, -1)){// print the local position to consolestd::cout << "localPosition: " << localPosition.x << ", " << localPosition.y << std::endl;A = sf::Vector2f(localPosition.x - grid_len, localPosition.y - grid_len);D = sf::Vector2f(localPosition.x + grid_len, localPosition.y + grid_len);B = sf::Vector2f(localPosition.x + grid_len, localPosition.y - grid_len);C = sf::Vector2f(localPosition.x - grid_len, localPosition.y + grid_len);}// draw everything here...// A    B// +----+// |    |// +----+// C    D// draw line ADsf::Vertex vertex[2];vertex[0].position = A;vertex[0].color  = sf::Color::Yellow;vertex[1].position = D;vertex[1].color = sf::Color::Yellow;window.draw(vertex, 2, sf::Lines);// draw line BCvertex[0].position = B;vertex[0].color  = sf::Color::Yellow;vertex[1].position = C;vertex[1].color = sf::Color::Yellow;window.draw(vertex, 2, sf::Lines);window.display();}return 0;
}

2.3 绘制棋盘网格

拆解为 2 个部分: 绘制 3x3 的网格; 对于每个网格,如果鼠标点击了它,就执行绘制。

绘制3x3网格

横向 2 条线, 纵向 2 条线。 绘制它们后就得到了网格。

window.clear(sf::Color::White);sf::Color grid_color(74, 74, 74);// draw a 3x3 grid lines
sf::RectangleShape horizon_line1(sf::Vector2f(grid_len * 3, 8));
horizon_line1.setPosition(100, 100 + grid_len);
horizon_line1.setFillColor(grid_color);
window.draw(horizon_line1);sf::RectangleShape horizon_line2(sf::Vector2f(grid_len * 3, 8));
horizon_line2.setPosition(100, 100 + 2 * grid_len);
horizon_line2.setFillColor(grid_color);
window.draw(horizon_line2);sf::RectangleShape vertical_line1(sf::Vector2f(8, grid_len * 3));
vertical_line1.setPosition(100 + grid_len, 100);
vertical_line1.setFillColor(grid_color);
window.draw(vertical_line1);sf::RectangleShape vertical_line2(sf::Vector2f(8, grid_len * 3));
vertical_line2.setPosition(100 + 2 * grid_len, 100);
vertical_line2.setFillColor(grid_color);
window.draw(vertical_line2);window.display();

在这里插入图片描述

每个网格内,如果鼠标有点击则执行绘制

根据前一步绘制的网格, 可以确定每个小格子的坐标范围。 根据鼠标点击获取的位置,遍历每个格子, 如果是在当前格子内部, 那么执行绘制。 简单起见, 为了区分绘制内容,只要点击了当前小格子就执行绘制.

先将刚刚回执的网格的代码做重构, 根据起始点 p00(100, 100) 和网格宽度 grid_len = 50 进行绘制, 而不是硬编码每个线段的起点。

根据网格点的起始位置, 可以定义 9 个格子的A,B,C,D坐标取值。 为了避免绘制时的闪烁,定义 class Grid, 由每个网格自己决定是否绘制, 也就是提供 void draw(sf::RenderWindow& window) 函数, 如果鼠标左键点击时落在格子范围内, 那么 shouldRender 变量更新为 true, 执行有效的渲染:

// main 函数
for (int i = 0; i < 9; i++)
{// if the mouse is inside the grid, draw a crossif (localPosition.x >= grid[i].A.x && localPosition.x <= grid[i].D.x &&localPosition.y >= grid[i].A.y && localPosition.y <= grid[i].D.y){printf("inside grid[%d]: localPosition: %d, %d\n", i, localPosition.x, localPosition.y);grid[i].shouldRender = true;}grid[i].draw(window);
}// Grid::draw() 函数
void draw(sf::RenderWindow& window)
{if (shouldRender){sf::Vertex vertex[2];vertex[0].position = A;vertex[0].color  = sf::Color::Blue;vertex[1].position = D;vertex[1].color = sf::Color::Blue;window.draw(vertex, 2, sf::Lines);vertex[0].position = B;vertex[0].color  = sf::Color::Blue;vertex[1].position = C;vertex[1].color = sf::Color::Blue;window.draw(vertex, 2, sf::Lines);}
}

效果:
在这里插入图片描述

完整代码:

int draw_grid_and_response_mouse()
{constexpr int win_width = 350;constexpr int win_height = 350;const std::string title = "Tic Tac Toe SFML";sf::RenderWindow window(sf::VideoMode(win_width, win_height), title);constexpr int grid_len = 50;sf::Color grid_color(74, 74, 74);// p00   p01   p02   p03// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p10   p11   p12   p13// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p20   p21   p22   p23// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p30   p31   p32   p33sf::Vector2f p00(100, 100);sf::Vector2f p01(100 + grid_len, 100);sf::Vector2f p02(100 + 2 * grid_len, 100);sf::Vector2f p10(100, 100 + grid_len);sf::Vector2f p11(100 + grid_len, 100 + grid_len);sf::Vector2f p12(100 + 2 * grid_len, 100 + grid_len);sf::Vector2f p20(100, 100 + 2 * grid_len);sf::Vector2f p21(100 + grid_len, 100 + 2 * grid_len);sf::Vector2f p22(100 + 2 * grid_len, 100 + 2 * grid_len);std::array<Grid, 9> grid;grid[0].update(p00, grid_len);grid[1].update(p01, grid_len);grid[2].update(p02, grid_len);grid[3].update(p10, grid_len);grid[4].update(p11, grid_len);grid[5].update(p12, grid_len);grid[6].update(p20, grid_len);grid[7].update(p21, grid_len);grid[8].update(p22, grid_len);while (window.isOpen()){sf::Event event;sf::Vector2i localPosition(-1, -1);while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }if (sf::Mouse::isButtonPressed(sf::Mouse::Left)){// get the local mouse position (relative to a window)localPosition = sf::Mouse::getPosition(window);}}window.clear(sf::Color::White);// draw a 3x3 grid linessf::RectangleShape horizon_line1(sf::Vector2f(grid_len * 3, 8));horizon_line1.setPosition(p10);horizon_line1.setFillColor(grid_color);window.draw(horizon_line1);sf::RectangleShape horizon_line2(sf::Vector2f(grid_len * 3, 8));horizon_line2.setPosition(p20);horizon_line2.setFillColor(grid_color);window.draw(horizon_line2);sf::RectangleShape vertical_line1(sf::Vector2f(8, grid_len * 3));vertical_line1.setPosition(p01);vertical_line1.setFillColor(grid_color);window.draw(vertical_line1);sf::RectangleShape vertical_line2(sf::Vector2f(8, grid_len * 3));vertical_line2.setPosition(p02);vertical_line2.setFillColor(grid_color);window.draw(vertical_line2);for (int i = 0; i < 9; i++){// if the mouse is inside the grid, draw a crossif (localPosition.x >= grid[i].A.x && localPosition.x <= grid[i].D.x &&localPosition.y >= grid[i].A.y && localPosition.y <= grid[i].D.y){printf("inside grid[%d]: localPosition: %d, %d\n", i, localPosition.x, localPosition.y);grid[i].shouldRender = true;}grid[i].draw(window);}window.display();}return 0;
}

2.4 轮流绘制棋子

拆解为如下部分: 绘制单个圆形⭕️, 交替绘制 ❌ 和 ⭕️.

绘制单个圆形

sf::CircleShape circle(grid_len / 3);
circle.setFillColor(bg_color);
circle.setOutlineThickness(4);
circle.setOutlineColor(sf::Color::Red);
circle.setPosition(200, 200);
window.draw(circle);

交替绘制 ❌ 和 ⭕️

每个网格被点击后, 要根据现有情况来执行绘制: 如果之前没有绘制过(没有落子过), 则绘制当前用户的棋子; 如果已经绘制过, 那么不能绘制, 说明当前用户落子无效, 要换一个地方落子。 如果鼠标点击的位置不在 9 个格子的范围内,也是落子无效。

在这里插入图片描述

#include <SFML/Graphics.hpp>
#include <iostream>
#include <array>char user = 'X';enum class DrawShape
{NONE = 0,CROSS = 1, CIRCLE = 2
};class Grid
{
public:Grid() = default;Grid(sf::Vector2f start_pos, int a_grid_len){update(start_pos, a_grid_len);}void update(sf::Vector2f start_pos, int a_grid_len){grid_len = a_grid_len;A = start_pos;B = sf::Vector2f(start_pos.x + grid_len, start_pos.y);C = sf::Vector2f(start_pos.x, start_pos.y + grid_len);D = sf::Vector2f(start_pos.x + grid_len, start_pos.y + grid_len);}void draw(sf::RenderWindow& window){if (drawShape == DrawShape::CROSS){drawCross(window);}else if (drawShape == DrawShape::CIRCLE){drawCircle(window);}}void drawCross(sf::RenderWindow& window){sf::Vertex vertex[2];vertex[0].position = A;vertex[0].color  = sf::Color::Blue;vertex[1].position = D;vertex[1].color = sf::Color::Blue;window.draw(vertex, 2, sf::Lines);vertex[0].position = B;vertex[0].color  = sf::Color::Blue;vertex[1].position = C;vertex[1].color = sf::Color::Blue;window.draw(vertex, 2, sf::Lines);}void drawCircle(sf::RenderWindow& window){// draw a circlefloat radius = grid_len / 3;sf::CircleShape circle(radius);int thickness = 4;circle.setOutlineThickness(thickness);circle.setOutlineColor(sf::Color::Red);// M is middle point of A and Dsf::Vector2f M((A.x + D.x) / 2, (A.y + D.y) / 2);sf::Vector2f circle_position(M.x - radius + thickness, M.y - radius + thickness);circle.setPosition(circle_position);window.draw(circle);}sf::Vector2f A, B, C, D;DrawShape drawShape{};int grid_len;
};int draw_grid_and_response_mouse()
{constexpr int win_width = 350;constexpr int win_height = 350;const std::string title = "Tic Tac Toe SFML";sf::RenderWindow window(sf::VideoMode(win_width, win_height), title);constexpr int grid_len = 50;sf::Color grid_color(74, 74, 74);// p00   p01   p02   p03// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p10   p11   p12   p13// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p20   p21   p22   p23// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p30   p31   p32   p33sf::Vector2f p00(100, 100);sf::Vector2f p01(100 + grid_len, 100);sf::Vector2f p02(100 + 2 * grid_len, 100);sf::Vector2f p10(100, 100 + grid_len);sf::Vector2f p11(100 + grid_len, 100 + grid_len);sf::Vector2f p12(100 + 2 * grid_len, 100 + grid_len);sf::Vector2f p20(100, 100 + 2 * grid_len);sf::Vector2f p21(100 + grid_len, 100 + 2 * grid_len);sf::Vector2f p22(100 + 2 * grid_len, 100 + 2 * grid_len);std::array<Grid, 9> grid;grid[0].update(p00, grid_len);grid[1].update(p01, grid_len);grid[2].update(p02, grid_len);grid[3].update(p10, grid_len);grid[4].update(p11, grid_len);grid[5].update(p12, grid_len);grid[6].update(p20, grid_len);grid[7].update(p21, grid_len);grid[8].update(p22, grid_len);bool played = false;while (window.isOpen()){sf::Event event;sf::Vector2i localPosition(-1, -1);while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }if (sf::Mouse::isButtonPressed(sf::Mouse::Left)){// get the local mouse position (relative to a window)localPosition = sf::Mouse::getPosition(window);}}window.clear(sf::Color::White);// draw a 3x3 grid linessf::RectangleShape horizon_line1(sf::Vector2f(grid_len * 3, 8));horizon_line1.setPosition(p10);horizon_line1.setFillColor(grid_color);window.draw(horizon_line1);sf::RectangleShape horizon_line2(sf::Vector2f(grid_len * 3, 8));horizon_line2.setPosition(p20);horizon_line2.setFillColor(grid_color);window.draw(horizon_line2);sf::RectangleShape vertical_line1(sf::Vector2f(8, grid_len * 3));vertical_line1.setPosition(p01);vertical_line1.setFillColor(grid_color);window.draw(vertical_line1);sf::RectangleShape vertical_line2(sf::Vector2f(8, grid_len * 3));vertical_line2.setPosition(p02);vertical_line2.setFillColor(grid_color);window.draw(vertical_line2);for (int i = 0; i < 9; i++){// if the mouse is inside the grid, draw a crossif (localPosition.x >= grid[i].A.x && localPosition.x <= grid[i].D.x &&localPosition.y >= grid[i].A.y && localPosition.y <= grid[i].D.y &&grid[i].drawShape == DrawShape::NONE){printf("inside grid[%d]: localPosition: %d, %d\n", i, localPosition.x, localPosition.y);if (user == 'X'){grid[i].drawShape = DrawShape::CROSS;}else if (user == 'O'){grid[i].drawShape = DrawShape::CIRCLE;}played = true;}grid[i].draw(window);}if (played){user = (user == 'X') ? 'O' : 'X';played = false;}window.display();}return 0;
}

2.5 显示局面信息

显示这些信息:

  • 轮到哪个用户落子, X 还是 O?
  • 落子后判断输赢
  • 如果平局, 显示平局
  • 允许中途或结束时,重新来一局

显示轮到谁落子

// draw a text to show the current user
sf::Text text;
text.setFont(font);
text.setString("Current user: " + std::string(1, user));
text.setCharacterSize(24);
text.setFillColor(sf::Color::Black);
text.setPosition(10, 10);
window.draw(text);

落子后判断输赢

在基于控制台的实现中, 使用的是 char board[3][3] 记录棋子, 相比于 enum class DrawShape 要直观的多。 因此我们抛弃 enum class DrawShape, 在 Grid 类中使用 char 类型的数据来存储当前网格里的棋子情况:

class Grid
{
public:...void draw(sf::RenderWindow& window){if (data == 'X'){drawCross(window);}else if (data == 'O'){drawCircle(window);}}
private:char data = ' ';
};

输赢局面的判断: 如果是有效落子, 那么在 8 条线段上分别判断。 如果判断出来赢了, 则更新 state; 如果没有有效落子, 检查是否存在能落子的地方 (’ '), 如果没有能落子的地方, 说明是平局(Draw):

if (played)
{int data[8][6] = {{0, 0, 0, 1, 0, 2},{1, 0, 1, 1, 1, 2},{2, 0, 2, 1, 2, 2},{0, 0, 1, 0, 2, 0},{0, 1, 1, 1, 2, 1},{0, 2, 1, 2, 2, 2},{0, 0, 1, 1, 2, 2},{0, 2, 1, 1, 2, 0}};   for (int i = 0; i < 8; i++){int x0 = data[i][0];int y0 = data[i][1];int x1 = data[i][2];int y1 = data[i][3];int x2 = data[i][4];int y2 = data[i][5];int idx0 = x0 * 3 + y0;int idx1 = x1 * 3 + y1;int idx2 = x2 * 3 + y2;if (grid[idx0].data == user && grid[idx1].data == user && grid[idx2].data == user){if (user == 'X'){state = PlayState::X_WIN;}else{state = PlayState::O_WIN;}break;}}user = (user == 'X') ? 'O' : 'X'; 
}if (!played)
{state = PlayState::DRAW;for (int i = 0; i < 9; i++){if (grid[i].data == ' '){state = PlayState::PLAYING;break;}}
}

顺带, 在界面上通过绘制文字的方式, 显示当前轮到谁下子、 谁赢了、 是否平局信息, 以及随时可以点击 “restart” 重来一局:

        sf::Vector2f boxPos(130, 300);sf::Vector2f boxSize(100, 24);sf::RectangleShape box(boxSize);box.setFillColor(sf::Color::Red);box.setPosition(boxPos);window.draw(box);// A B// C Dsf::Vector2f boxA(boxPos);sf::Vector2f boxB(boxPos.x + boxSize.x, boxPos.y);sf::Vector2f boxC(boxPos.x, boxPos.y + boxSize.y);sf::Vector2f boxD(boxPos.x + boxSize.x, boxPos.y + boxSize.y);if (localPosition.x >= boxA.x && localPosition.y >= boxA.y &&localPosition.x <= boxD.x && localPosition.y <= boxD.y){for (int i = 0; i < 9; i++){grid[i].data = ' ';}user = 'X';state = PlayState::PLAYING;}// draw a button with text "restart"sf::Text restart;restart.setFont(font);restart.setString("Restart");restart.setCharacterSize(24);restart.setFillColor(sf::Color::White);restart.setPosition(boxPos);window.draw(restart);

效果如下:

在这里插入图片描述

完整代码是

#include <SFML/Graphics.hpp>
#include <iostream>
#include <array>enum class PlayState
{PLAYING = 0,X_WIN = 1,O_WIN = 2,DRAW = 3
};class Grid
{
public:Grid() = default;Grid(sf::Vector2f start_pos, int a_grid_len){update(start_pos, a_grid_len);}void update(sf::Vector2f start_pos, int a_grid_len){grid_len = a_grid_len;A = start_pos;B = sf::Vector2f(start_pos.x + grid_len, start_pos.y);C = sf::Vector2f(start_pos.x, start_pos.y + grid_len);D = sf::Vector2f(start_pos.x + grid_len, start_pos.y + grid_len);}void draw(sf::RenderWindow& window){if (data == 'X'){drawCross(window);}else if (data == 'O'){drawCircle(window);}}void drawCross(sf::RenderWindow& window){sf::Vertex vertex[2];vertex[0].position = A;vertex[0].color  = sf::Color::Blue;vertex[1].position = D;vertex[1].color = sf::Color::Blue;window.draw(vertex, 2, sf::Lines);vertex[0].position = B;vertex[0].color  = sf::Color::Blue;vertex[1].position = C;vertex[1].color = sf::Color::Blue;window.draw(vertex, 2, sf::Lines);}void drawCircle(sf::RenderWindow& window){// draw a circlefloat radius = grid_len / 3;sf::CircleShape circle(radius);int thickness = 4;circle.setOutlineThickness(thickness);circle.setOutlineColor(sf::Color::Red);// M is middle point of A and Dsf::Vector2f M((A.x + D.x) / 2, (A.y + D.y) / 2);sf::Vector2f circle_position(M.x - radius + thickness, M.y - radius + thickness);circle.setPosition(circle_position);window.draw(circle);}sf::Vector2f A, B, C, D;char data = ' ';int grid_len;
};char user = 'X';int draw_grid_and_response_mouse()
{constexpr int win_width = 350;constexpr int win_height = 350;const std::string title = "Tic Tac Toe SFML";sf::RenderWindow window(sf::VideoMode(win_width, win_height), title);constexpr int grid_len = 50;sf::Color grid_color(74, 74, 74);// p00   p01   p02   p03// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p10   p11   p12   p13// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p20   p21   p22   p23// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p30   p31   p32   p33sf::Vector2f p00(100, 100);sf::Vector2f p01(100 + grid_len, 100);sf::Vector2f p02(100 + 2 * grid_len, 100);sf::Vector2f p10(100, 100 + grid_len);sf::Vector2f p11(100 + grid_len, 100 + grid_len);sf::Vector2f p12(100 + 2 * grid_len, 100 + grid_len);sf::Vector2f p20(100, 100 + 2 * grid_len);sf::Vector2f p21(100 + grid_len, 100 + 2 * grid_len);sf::Vector2f p22(100 + 2 * grid_len, 100 + 2 * grid_len);std::array<Grid, 9> grid;grid[0].update(p00, grid_len);grid[1].update(p01, grid_len);grid[2].update(p02, grid_len);grid[3].update(p10, grid_len);grid[4].update(p11, grid_len);grid[5].update(p12, grid_len);grid[6].update(p20, grid_len);grid[7].update(p21, grid_len);grid[8].update(p22, grid_len);sf::Font font;const std::string asset_dir = "../Resources";if (!font.loadFromFile(asset_dir + "/Arial.ttf")){std::cerr << "failed to load font\n";return 1;}PlayState state = PlayState::PLAYING;while (window.isOpen()){sf::Event event;sf::Vector2i localPosition(-1, -1);while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }if (sf::Mouse::isButtonPressed(sf::Mouse::Left)){// get the local mouse position (relative to a window)localPosition = sf::Mouse::getPosition(window);}}window.clear(sf::Color::White);// draw a text to show the current usersf::Text text;text.setFont(font);std::string state_str;if (state == PlayState::DRAW){state_str = "Draw";}else if (state == PlayState::X_WIN){state_str = "User X wins!";}else if (state == PlayState::O_WIN){state_str = "User O wins!";}else{state_str = "Current user: " + std::string(1, user);}printf("state: %s\n", state_str.c_str());text.setString(state_str);text.setCharacterSize(24);text.setFillColor(sf::Color::Black);text.setPosition(10, 10);window.draw(text);// draw a 3x3 grid linessf::RectangleShape horizon_line1(sf::Vector2f(grid_len * 3, 8));horizon_line1.setPosition(p10);horizon_line1.setFillColor(grid_color);window.draw(horizon_line1);sf::RectangleShape horizon_line2(sf::Vector2f(grid_len * 3, 8));horizon_line2.setPosition(p20);horizon_line2.setFillColor(grid_color);window.draw(horizon_line2);sf::RectangleShape vertical_line1(sf::Vector2f(8, grid_len * 3));vertical_line1.setPosition(p01);vertical_line1.setFillColor(grid_color);window.draw(vertical_line1);sf::RectangleShape vertical_line2(sf::Vector2f(8, grid_len * 3));vertical_line2.setPosition(p02);vertical_line2.setFillColor(grid_color);window.draw(vertical_line2);bool played = false;if (state == PlayState::PLAYING){for (int i = 0; i < 9; i++){// if the mouse is inside the grid, draw a crossif (localPosition.x >= grid[i].A.x && localPosition.x <= grid[i].D.x &&localPosition.y >= grid[i].A.y && localPosition.y <= grid[i].D.y &&grid[i].data == ' '){printf("inside grid[%d]: localPosition: %d, %d\n", i, localPosition.x, localPosition.y);grid[i].data = user;played = true;}grid[i].draw(window);if (played){break;}}if (played){int data[8][6] = {{0, 0, 0, 1, 0, 2},{1, 0, 1, 1, 1, 2},{2, 0, 2, 1, 2, 2},{0, 0, 1, 0, 2, 0},{0, 1, 1, 1, 2, 1},{0, 2, 1, 2, 2, 2},{0, 0, 1, 1, 2, 2},{0, 2, 1, 1, 2, 0}};   for (int i = 0; i < 8; i++){int x0 = data[i][0];int y0 = data[i][1];int x1 = data[i][2];int y1 = data[i][3];int x2 = data[i][4];int y2 = data[i][5];int idx0 = x0 * 3 + y0;int idx1 = x1 * 3 + y1;int idx2 = x2 * 3 + y2;if (grid[idx0].data == user && grid[idx1].data == user && grid[idx2].data == user){if (user == 'X'){state = PlayState::X_WIN;}else{state = PlayState::O_WIN;}break;}}user = (user == 'X') ? 'O' : 'X'; }if (!played){state = PlayState::DRAW;for (int i = 0; i < 9; i++){if (grid[i].data == ' '){state = PlayState::PLAYING;break;}}}}else{for (int i = 0; i < 9; i++){grid[i].draw(window);}}sf::Vector2f boxPos(130, 300);sf::Vector2f boxSize(100, 24);sf::RectangleShape box(boxSize);box.setFillColor(sf::Color::Red);box.setPosition(boxPos);window.draw(box);// A B// C Dsf::Vector2f boxA(boxPos);sf::Vector2f boxB(boxPos.x + boxSize.x, boxPos.y);sf::Vector2f boxC(boxPos.x, boxPos.y + boxSize.y);sf::Vector2f boxD(boxPos.x + boxSize.x, boxPos.y + boxSize.y);if (localPosition.x >= boxA.x && localPosition.y >= boxA.y &&localPosition.x <= boxD.x && localPosition.y <= boxD.y){for (int i = 0; i < 9; i++){grid[i].data = ' ';}user = 'X';state = PlayState::PLAYING;}// draw a button with text "restart"sf::Text restart;restart.setFont(font);restart.setString("Restart");restart.setCharacterSize(24);restart.setFillColor(sf::Color::White);restart.setPosition(boxPos);window.draw(restart);window.display();}return 0;
}int main()
{draw_grid_and_response_mouse();return 0;
}

3. 制作 SVG 图像, 美化界面

任务分解为: 制作 ‘X’ 和 ‘O’ 棋子的 svg 图像, 使用 SFML 导入 SVG 图像并替代先前棋子的绘制。

使用在线工具(1, 2), 结合 inkscape 和 VSCode svg 插件, 得到 X 和 O 的 svg 图像:

ttt-cross.svg:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svgwidth="300"height="300"version="1.1"id="svg1"sodipodi:docname="ttt-cross.svg"inkscape:version="1.3.2 (091e20e, 2023-11-25)"xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"xmlns="http://www.w3.org/2000/svg"xmlns:svg="http://www.w3.org/2000/svg"><defsid="defs1" /><sodipodi:namedviewid="namedview1"pagecolor="#ffffff"bordercolor="#000000"borderopacity="0.25"inkscape:showpageshadow="2"inkscape:pageopacity="0.0"inkscape:pagecheckerboard="0"inkscape:deskcolor="#d1d1d1"inkscape:zoom="1.616"inkscape:cx="247.83416"inkscape:cy="162.74752"inkscape:window-width="1408"inkscape:window-height="953"inkscape:window-x="0"inkscape:window-y="25"inkscape:window-maximized="0"inkscape:current-layer="svg1" /><!-- Created with SVG-edit - https://github.com/SVG-Edit/svgedit--><gclass="layer"id="g1"transform="translate(25.371288,-13.61386)"><titleid="title1">Layer 1</title><rectfill="#747474"height="27"id="svg_5"stroke="#747474"transform="rotate(45,229.99949,92.78901)"width="300"x="50"y="200" /><rectfill="#747474"height="27"id="svg_7"stroke="#747474"transform="rotate(135,175,175.58344)"width="300"x="50"y="200" /></g>
</svg>

ttt-circle.svg:

<svg id="svgelem" height="140" width="140" xmlns="http://www.w3.org/2000/svg"><circle cx="70" cy="70" r="50" stroke="#eee8cf" stroke-width="10" fill="none" />
</svg>

SFML 不支持 svg 的导入, 因此转换为 png 后再使用:

cairosvg ttt-cross.svg -o ttt-cross.png 
cairosvg ttt-circle.svg -o ttt-circle.png 

4. 最终结果

运行效果:
在这里插入图片描述

代码:

#include <SFML/Graphics.hpp>
#include <iostream>
#include <array>enum class PlayState
{PLAYING = 0,X_WIN = 1,O_WIN = 2,DRAW = 3
};class Grid
{
public:Grid() = default;Grid(sf::Vector2f start_pos, int a_grid_len){update(start_pos, a_grid_len);}void update(sf::Vector2f start_pos, int a_grid_len){grid_len = a_grid_len;A = start_pos;B = sf::Vector2f(start_pos.x + grid_len, start_pos.y);C = sf::Vector2f(start_pos.x, start_pos.y + grid_len);D = sf::Vector2f(start_pos.x + grid_len, start_pos.y + grid_len);}void draw(sf::RenderWindow& window){if (data == 'X'){drawCross(window);}else if (data == 'O'){drawCircle(window);}}void drawCross(sf::RenderWindow& window){if (0){sf::Vertex vertex[2];vertex[0].position = A;vertex[0].color  = sf::Color::Blue;vertex[1].position = D;vertex[1].color = sf::Color::Blue;window.draw(vertex, 2, sf::Lines);vertex[0].position = B;vertex[0].color  = sf::Color::Blue;vertex[1].position = C;vertex[1].color = sf::Color::Blue;window.draw(vertex, 2, sf::Lines);}else{// load a svg file to create a texturesf::Texture texture;if (!texture.loadFromFile("../ttt-cross.png")){std::cerr << "failed to load cross image\n";return;}// draw a sprite in box region A, Dsf::Sprite sprite(texture);sprite.setPosition(A);sprite.setScale(0.2, 0.2);window.draw(sprite);}}void drawCircle(sf::RenderWindow& window){if (0){// draw a circlefloat radius = grid_len / 3;sf::CircleShape circle(radius);int thickness = 4;circle.setOutlineThickness(thickness);circle.setOutlineColor(sf::Color::Red);// M is middle point of A and Dsf::Vector2f M((A.x + D.x) / 2, (A.y + D.y) / 2);sf::Vector2f circle_position(M.x - radius + thickness, M.y - radius + thickness);circle.setPosition(circle_position);window.draw(circle);}else{// load a svg file to create a texturesf::Texture texture;if (!texture.loadFromFile("../ttt-circle.png")){std::cerr << "failed to load circle image\n";return;}// draw a sprite in box region A, Dsf::Sprite sprite(texture);sprite.setPosition(A);sprite.setScale(0.4, 0.4);window.draw(sprite);   }}sf::Vector2f A, B, C, D;char data = ' ';int grid_len;
};char user = 'X';int draw_grid_and_response_mouse()
{constexpr int win_width = 350;constexpr int win_height = 350;const std::string title = "Tic Tac Toe SFML";sf::RenderWindow window(sf::VideoMode(win_width, win_height), title);constexpr int grid_len = 50;sf::Color grid_color(64, 148, 135);sf::Color bg_color(78, 177, 163);// p00   p01   p02   p03// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p10   p11   p12   p13// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p20   p21   p22   p23// +-----+-----+-----+// |     |     |     |// +-----+-----+-----+// p30   p31   p32   p33sf::Vector2f p00(100, 100);sf::Vector2f p01(100 + grid_len, 100);sf::Vector2f p02(100 + 2 * grid_len, 100);sf::Vector2f p10(100, 100 + grid_len);sf::Vector2f p11(100 + grid_len, 100 + grid_len);sf::Vector2f p12(100 + 2 * grid_len, 100 + grid_len);sf::Vector2f p20(100, 100 + 2 * grid_len);sf::Vector2f p21(100 + grid_len, 100 + 2 * grid_len);sf::Vector2f p22(100 + 2 * grid_len, 100 + 2 * grid_len);std::array<Grid, 9> grid;grid[0].update(p00, grid_len);grid[1].update(p01, grid_len);grid[2].update(p02, grid_len);grid[3].update(p10, grid_len);grid[4].update(p11, grid_len);grid[5].update(p12, grid_len);grid[6].update(p20, grid_len);grid[7].update(p21, grid_len);grid[8].update(p22, grid_len);sf::Font font;const std::string asset_dir = "../Resources";if (!font.loadFromFile(asset_dir + "/Arial.ttf")){std::cerr << "failed to load font\n";return 1;}PlayState state = PlayState::PLAYING;while (window.isOpen()){sf::Event event;sf::Vector2i localPosition(-1, -1);while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }if (sf::Mouse::isButtonPressed(sf::Mouse::Left)){// get the local mouse position (relative to a window)localPosition = sf::Mouse::getPosition(window);}}window.clear(bg_color);// draw a text to show the current usersf::Text text;text.setFont(font);std::string state_str;if (state == PlayState::DRAW){state_str = "Draw";}else if (state == PlayState::X_WIN){state_str = "User X wins!";}else if (state == PlayState::O_WIN){state_str = "User O wins!";}else{state_str = "Current user: " + std::string(1, user);}printf("state: %s\n", state_str.c_str());text.setString(state_str);text.setCharacterSize(24);text.setFillColor(sf::Color::Black);text.setPosition(10, 10);window.draw(text);// draw a 3x3 grid linessf::RectangleShape horizon_line1(sf::Vector2f(grid_len * 3, 8));horizon_line1.setPosition(p10);horizon_line1.setFillColor(grid_color);window.draw(horizon_line1);sf::RectangleShape horizon_line2(sf::Vector2f(grid_len * 3, 8));horizon_line2.setPosition(p20);horizon_line2.setFillColor(grid_color);window.draw(horizon_line2);sf::RectangleShape vertical_line1(sf::Vector2f(8, grid_len * 3));vertical_line1.setPosition(p01);vertical_line1.setFillColor(grid_color);window.draw(vertical_line1);sf::RectangleShape vertical_line2(sf::Vector2f(8, grid_len * 3));vertical_line2.setPosition(p02);vertical_line2.setFillColor(grid_color);window.draw(vertical_line2);bool played = false;if (state == PlayState::PLAYING){for (int i = 0; i < 9; i++){// if the mouse is inside the grid, draw a crossif (localPosition.x >= grid[i].A.x && localPosition.x <= grid[i].D.x &&localPosition.y >= grid[i].A.y && localPosition.y <= grid[i].D.y &&grid[i].data == ' '){printf("inside grid[%d]: localPosition: %d, %d\n", i, localPosition.x, localPosition.y);grid[i].data = user;played = true;}grid[i].draw(window);if (played){break;}}if (played){int data[8][6] = {{0, 0, 0, 1, 0, 2},{1, 0, 1, 1, 1, 2},{2, 0, 2, 1, 2, 2},{0, 0, 1, 0, 2, 0},{0, 1, 1, 1, 2, 1},{0, 2, 1, 2, 2, 2},{0, 0, 1, 1, 2, 2},{0, 2, 1, 1, 2, 0}};   for (int i = 0; i < 8; i++){int x0 = data[i][0];int y0 = data[i][1];int x1 = data[i][2];int y1 = data[i][3];int x2 = data[i][4];int y2 = data[i][5];int idx0 = x0 * 3 + y0;int idx1 = x1 * 3 + y1;int idx2 = x2 * 3 + y2;if (grid[idx0].data == user && grid[idx1].data == user && grid[idx2].data == user){if (user == 'X'){state = PlayState::X_WIN;}else{state = PlayState::O_WIN;}break;}}user = (user == 'X') ? 'O' : 'X'; }if (!played){state = PlayState::DRAW;for (int i = 0; i < 9; i++){if (grid[i].data == ' '){state = PlayState::PLAYING;break;}}}}else{for (int i = 0; i < 9; i++){grid[i].draw(window);}}sf::Vector2f boxPos(130, 300);sf::Vector2f boxSize(80, 30);// A B// C Dsf::Vector2f boxA(boxPos);sf::Vector2f boxB(boxPos.x + boxSize.x, boxPos.y);sf::Vector2f boxC(boxPos.x, boxPos.y + boxSize.y);sf::Vector2f boxD(boxPos.x + boxSize.x, boxPos.y + boxSize.y);if (localPosition.x >= boxA.x && localPosition.y >= boxA.y &&localPosition.x <= boxD.x && localPosition.y <= boxD.y){for (int i = 0; i < 9; i++){grid[i].data = ' ';}user = 'X';state = PlayState::PLAYING;}// draw a button with text "restart"sf::Text restart;restart.setFont(font);restart.setString("Restart");restart.setCharacterSize(24);restart.setFillColor(sf::Color(35, 44, 44));restart.setPosition(boxPos);restart.setOutlineColor(sf::Color(35, 44, 44));window.draw(restart);window.display();}return 0;
}int main()
{draw_grid_and_response_mouse();return 0;
}

总结

花了 4 个半小时, 在先前写好了控制台版本 tic-tac-toe 的基础上, 使用 SFML 做了简陋的界面, 让游戏先运行起来能够正常玩; 然后制作了 SVG 图像, 仿照谷歌搜索结果里的在线版本的界面, 稍微美化了一下显示效果。

在使用 SFML 制作界面的过程中, 没有一上来就苛求制作好看的 ‘X’ 形状, 因为它后期可以重新调整; 本来引入了 DrawShape 枚举类型, 不过后来发现有点冗余, 在 class Grid 中用 char data 更方便。

对于绘制的最终结果, 不是一蹴而就的, 而是分别写了小型函数来验证, ‘X’ 可以单独绘制正确, 并且利用 class Grid 类的 draw() 方法, 根据落子的情况做了绘制(没有落子则不绘制)。

对于输赢局面的判别, 是先前控制台版本代码里有的, 直接拿来用了。 先写控制台版本看来确实可以加速迭代, 套着一套 GUI 的时候想逻辑, 需要对界面代码的编写比较熟悉才会不卡壳, 为了避免卡壳, 用熟悉的控制台去写, 是很方便的。

这个基于 SFML 的 tic-tac-toe 可以进一步扩展, 例如增加音效, 在获胜的时候用动态效果把三个棋子连接起来, 增加人机对战模式, 并利用 alpha-beta 剪枝算法进行搜索优化。

References

  • https://en.sfml-dev.org/forums/index.php?topic=21620.0
  • https://www.sfml-dev.org/tutorials/2.6/window-inputs.php
  • https://www.sfml-dev.org/tutorials/2.6/graphics-shape.php
  • https://github.com/skiff/TicTacToe
  • https://github.com/juchem/tic-tac-toe
  • https://svgedit.netlify.app/editor/index.html
  • https://www.nhooo.com/note/qa09md.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/258365.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分省年度数据集(1990-2021年)

一、数据介绍 数据名称&#xff1a;分省年度数据集&#xff08;1990-2021年&#xff09; 数据来源&#xff1a;国家统计局-分省年度数据 数据范围&#xff1a;1990-2021年&#xff0c;包括31个省份 指标数量 &#xff1a;2981个指标 数据整理&#xff1a;自主整理 更新时…

线性代数的本质 2 线性组合、张成的空间、基

基于3Blue1Brown视频的笔记 一种新的看待方式 对于一个向量&#xff0c;比如说&#xff0c;如何看待其中的3和-2&#xff1f; 一开始&#xff0c;我们往往将其看作长度&#xff08;从向量的首走到尾部&#xff0c;分别在x和y上走的长度&#xff09;。 在有了数乘后&#xff0…

建造者模式-Builder Pattern

原文地址:https://jaune162.blog/design-pattern/builder-pattern/ 引言 现在一般大型的业务系统中的消息通知的形式都会有多种,比如短信、站内信、钉钉通知、邮箱等形式。虽然信息内容相同,但是展现形式缺不同。如短信使用的是纯文本的形式,钉钉使用的一般是Markdown的形…

【实战】一、Jest 前端自动化测试框架基础入门(二) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(二)

文章目录 一、Jest 前端自动化测试框架基础入门5.Jest 中的匹配器toBe 匹配器toEqual匹配器toBeNull匹配器toBeUndefined匹配器和toBeDefined匹配器toBeTruthy匹配器toBeFalsy匹配器数字相关的匹配器字符串相关的匹配器数组相关的匹配器异常情况的匹配器 6.Jest 命令行工具的使…

C++中类的6个默认成员函数 【拷贝构造函数】

文章目录 拷贝构造函数的使用拷贝构造对于自定义类型【浅拷贝】深拷贝拷贝构造函数典型调用场景 拷贝构造函数的使用 在前几章学习对象的时候&#xff0c;我们有的时候需要一个与已存在对象一某一样的新对象 那在创建对象时&#xff0c;可否创建一个与已存在对象一某一样的新对…

Qlik Sense : 条形图

条形图 “条形图适合比较多个值。维度轴显示所比较的类别条目&#xff0c;度量轴显示每个类别条目的值。” Qlik Sense中的条形图是一种数据可视化工具&#xff0c;用于展示不同类别或维度之间的比较。它通过水平或垂直的条形表示数据&#xff0c;并根据数值的大小进行排序。…

win10 环境下Python 3.8按装fastapi paddlepaddle 进行图片文字识别1

###按装 用conda 创建python 3.8的环境&#xff0c;可参看本人python下的其它文章。 在pycharm开发环境下按装相关的模块&#xff1a; pip install -i https://pypi.tuna.tsinghua.edu.cn/simple fastapi pip install -i https://pypi.tuna.tsinghua.edu.cn/simple "uvi…

《Go 简易速速上手小册》第1章:Go 语言基础(2024 最新版)

文章目录 1.1 Go 语言的安装与环境配置1.1.1 基础知识讲解案例 Demo&#xff1a;简单的 Go 程序 1.1.2 重点案例&#xff1a;搭建一个 Go Web 服务准备工作步骤 1&#xff1a;创建项目目录步骤 2&#xff1a;编写 Web 服务代码步骤 3&#xff1a;运行你的 Web 服务步骤 4&#…

寒假 6

1.现有无序序列数组为{23,24,12,5,33,5,34,7}&#xff0c;请使用以下排序实现编程。 函数1:请使用冒泡排序实现升序排序 函数2︰请使用简单选择排序实现升序排序 函数3:请使用直接插入排序实现升序排序 函数4∶请使用插入排序实现升序排序 #include <stdio.h> #inclu…

牛客小白月赛87

说明 年后第一次写题&#xff0c;已经麻了&#xff0c;这次的题很简单但居然只写了两道题。有种本该发挥80分的水平&#xff0c;但是只做出了20分的水平的感觉。不过剩下几个题&#xff08;除了G题&#xff09;&#xff0c;比完赛一小时内就AC了。欢迎大家交流学习。&#xff0…

【2024年毕设系列】如何使用Anaconda和Pycharm

【2024年毕设系列】如何使用Anaconda和Pycharm 视频教程地址&#xff1a;【2024毕设系列】Anaconda和Pycharm如何使用_哔哩哔哩 Hi&#xff0c;各位好久不见&#xff0c;这里是肆十二&#xff0c;首先在这里给大伙拜年了。 诸位过完年之后估计又要开始为了大作业和毕业设计头疼…

【蓝桥杯单片机入门记录】LED灯(附多个例程)

目录 一、LED灯概述 1.1 LED发光原理 1.2电路原理图 1.3电路实物图 1.4 开发板LED灯原理图 1.4.1共阳极LED灯操控原理&#xff08;本开发板&#xff09; &#xff08;非实际原理图&#xff0c;便于理解版本&#xff09;由图可以看出&#xff0c;每个LED灯的左边&#xf…

关于在分布式环境中RVN和使用场景的介绍3

简介 在《关于在分布式环境中RVN和使用场景的介绍2》和《关于在分布式环境中RVN和使用场景的介绍1》中我们介绍了RVN的概念和在一些具体用例中的使用。在本文中我们讨论一下在分布式环境中使用RVN需要注意的问题。 问题 我们在收到一条待处理的事件时&#xff0c;需要检查该…

css3实现炫彩字体

这个字体颜色是动态变化的&#xff0c;直接上代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title&…

Java 新手如何使用Spring MVC 中的查询字符串和查询参数

目录 前言 什么是查询字符串和查询参数&#xff1f; Spring MVC中的查询参数 处理可选参数 处理多个值 处理查询参数的默认值 处理查询字符串 示例&#xff1a;创建一个RESTful服务 总结 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家…

【Node.js】path 模块进行路径处理

Node.js 执行 JS 代码时&#xff0c;代码中的路径都是以终端所在文件夹出发查找相对路径&#xff0c;而不是以我们认为的从代码本身出发&#xff0c;会遇到问题&#xff0c;所以在 Node.js 要执行的代码中&#xff0c;访问其他文件&#xff0c;建议使用绝对路径 实例&#xff1…

【Rust】使用Rust实现一个简单的shell

一、Rust Rust是一门系统编程语言&#xff0c;由Mozilla开发并开源&#xff0c;专注于安全、速度和并发性。它的主要目标是解决传统系统编程语言&#xff08;如C和C&#xff09;中常见的内存安全和并发问题&#xff0c;同时保持高性能和底层控制能力。 Rust的特点包括&#x…

车载诊断协议DoIP系列 —— 车辆以太网节点需求汇总

车载诊断协议DoIP系列 —— 车辆以太网节点需求汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,…

《CSS 简易速速上手小册》第7章:CSS 预处理器与框架(2024 最新版)

文章目录 7.1 Sass&#xff1a;更高效的 CSS 编写7.1.1 基础知识7.1.2 重点案例&#xff1a;主题颜色和字体管理7.1.3 拓展案例 1&#xff1a;响应式辅助类7.1.4 拓展案例 2&#xff1a;深色模式支持 7.2 Bootstrap&#xff1a;快速原型设计和开发7.2.1 基础知识7.2.2 重点案例…

【NLP】MHA、MQA、GQA机制的区别

Note LLama2的注意力机制使用了GQA。三种机制的图如下&#xff1a; MHA机制&#xff08;Multi-head Attention&#xff09; MHA&#xff08;Multi-head Attention&#xff09;是标准的多头注意力机制&#xff0c;包含h个Query、Key 和 Value 矩阵。所有注意力头的 Key 和 V…