小游戏和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.mzph.cn/news/684665.shtml

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

相关文章

MySQL安装问题:由于找不到MSVCP120.dll,无法继续执行代码.重新安装程序可能会解决此问题。

出现的问题&#xff1a; 解决&#xff1a;由于没有安装微软常用运行库合集64位导致的问题 下载vcredist_x64 https://www.microsoft.com/zh-CN/download/details.aspx?id40784 下载完成后&#xff0c;点击运行解决问题。

《VulnHub》GoldenEye:1

title: 《VulnHub》GoldenEye&#xff1a;1 date: 2024-02-16 14:53:49 updated: 2024-02-16 15:08:49 categories: WriteUp&#xff1a;Cyber-Range excerpt: 主机发现、目标信息扫描、源码 js 文件泄露敏感信息、hydra 爆破邮件服务&#xff08;pop3&#xff09;、邮件泄露敏…

撑住!再好的命,也有坎坷的时候

再好的命&#xff0c;其实都有为难的时候&#xff0c;都有经历磨难的时候。要想真正强大起来&#xff0c;都要度过一段没人帮忙&#xff0c;所有事情都是自己一个人撑&#xff0c;所有情绪和思想&#xff0c;都只有自己知道的日子。但只要咬牙撑过去&#xff0c;一切就都不一样…

【JAVA-Day86】守护线程

守护线程 守护线程摘要引言1. 了解守护线程&#xff1a;它是什么&#xff1f;&#x1f47b;特点和用途示例代码 2. 为何我们需要守护线程&#xff1f;&#x1f47b;辅助性任务处理不阻止程序的正常运行重要的清理工作示例代码&#x1f4da; 3. 如何创建和管理守护线程&#xff…

使用Taro开发鸿蒙原生应用——快速上手,鸿蒙应用开发指南

导读 本指南为开发者提供了使用 Taro 框架开发鸿蒙原生应用的快速入门方法。Taro&#xff0c;作为一个多端统一开发框架&#xff0c;让开发者能够使用一套代码同时适配多个平台&#xff0c;包括鸿蒙系统。文章将详细介绍如何配置开发环境&#xff0c;以及如何利用 Taro 的特性…

真假难辨 - Sora(OpenAI)/世界模拟器的技术报告

目录 引言技术报告汉译版英文原版 引言 Sora是OpenAI在2024年2月15日发布的世界模拟器&#xff0c;功能是通过文本可以生成一分钟的高保真视频。由于较高的视频质量&#xff0c;引起了巨大关注。下面是三个示例&#xff0c;在示例之后给出了其技术报告&#xff1a; tokyo-wal…

树形dp 笔记

树的最长路径 给定一棵树&#xff0c;树中包含 n 个结点&#xff08;编号1~n&#xff09;和 n−1 条无向边&#xff0c;每条边都有一个权值。 现在请你找到树中的一条最长路径。 换句话说&#xff0c;要找到一条路径&#xff0c;使得使得路径两端的点的距离最远。 注意&…

Base64编码的优点与缺点

title: Base64编码的优点与缺点 date: 2024/2/16 14:06:37 updated: 2024/2/16 14:06:37 tags: Base64编码ASCII转换数据传输文本存储安全性数据膨胀字符串解码 Base64编码是一种将二进制数据转换为可打印ASCII字符的编码方式。它被广泛应用于数据传输和存储&#xff0c;以提升…

Pytest测试技巧之Fixture:模块化管理测试数据

在 Pytest 测试中&#xff0c;有效管理测试数据是提高测试质量和可维护性的关键。本文将深入探讨 Pytest 中的 Fixture&#xff0c;特别是如何利用 Fixture 实现测试数据的模块化管理&#xff0c;以提高测试用例的清晰度和可复用性。 什么是Fixture&#xff1f; 在 Pytest 中&a…

迎新年,送新手福利, 送2篇nhanes文章全套复现代码

美国国家健康与营养调查&#xff08; NHANES, National Health and Nutrition Examination Survey&#xff09;是一项基于人群的横断面调查&#xff0c;旨在收集有关美国家庭人口健康和营养的信息。 地址为&#xff1a;https://wwwn.cdc.gov/nchs/nhanes/Default.aspx 本次赠送…

2024年【T电梯修理】报名考试及T电梯修理考试报名

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【T电梯修理】报名考试及T电梯修理考试报名&#xff0c;包含T电梯修理报名考试答案和解析及T电梯修理考试报名练习。安全生产模拟考试一点通结合国家T电梯修理考试最新大纲及T电梯修理考试真题汇总&#xff0c;…

【Redis快速入门】Redis三种集群搭建配置(主从集群、哨兵集群、分片集群)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

[嵌入式系统-14]:常见实时嵌入式操作系统比较:RT-Thread、uC/OS-II和FreeRTOS、Linux

目录 一、实时嵌入式操作系统 1.1 概述 1.2 什么“实时” 1.3 什么是硬实时和软实时 1.4 什么是嵌入式 1.5 什么操作系统 二、常见重量级操作系统 三、常见轻量级嵌入式操作系统 3.1 概述 3.2 FreeRTOS 3.3 uC/OS-II 3.4 RT-Thread 3.5 RT-Thread、uC/OS-II、Free…

【数据结构】并查集

并查集是简单的数据结构&#xff0c;学会并查集&#xff0c;为图打好基础。 并查集的概念 是树状的数据结构&#xff0c;用于处理相交集合的合并与查询 通常用森林表示&#xff0c;一片森林表示一个集合 并查集一般需要完成 查找元素属于哪个集合查看两个元素是否属于同一个集…

JDBC 核心 API

引入 mysql-jdbc 驱动 驱动 jar 版本的选择&#xff1a;推荐使用 8.0.25&#xff0c;省略时区设置java 工程导入依赖 项目创建 lib 文件夹导入驱动依赖 jar 包jar 包右键 - 添加为库 JDBC 基本使用步骤 注册驱动获取连接创建发送 sql 语句对象发送 sql 语句&#xff0c;并获…

GPT SOVITS项目 一分钟克隆 (文字输出)

步骤流程&#xff1a;&#xff08;首先使用UVR 提取人声文件&#xff0c;然后按下面步骤进行&#xff09; 注意这里提交的音频是参考的音频

深度学习:Pytorch安装的torch与torchvision的cuda版本冲突问题与解决历程记录

今天不小心将conda环境中的一个pytorch环境中的torch包给搞混了&#xff0c;将其更新了一下&#xff0c;发生了一些问题&#xff1a; 当时运行了一下这个代码&#xff1a; pip install torchvision --upgrade 导致了环境中包的混乱&#xff1a; 只能说欲哭无泪&#xff0c;当…

C语言strstr函数

简介 strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是&#xff0c;则该函数返回 str1字符串从 str2第一次出现的位置开始到 str1结尾的字符串&#xff1b;否则&#xff0c;返回NULL。 实验 #include "stdio.h" #include "string.h"c…

相机图像质量研究(20)常见问题总结:CMOS期间对成像的影响--全局快门/卷帘快门

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

【前端高频面试题--git篇】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;前端高频面试题 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 前端高频面试题--git篇 往期精彩内容常用命令git add 和 git stage 有什么区别怎么使用git连接…