当前位置: 首页 > news >正文

基础项目——扫雷(c++)

目录

  • 前言
  • 一、环境配置
  • 二、基础框架
  • 三、关闭事件
  • 四、资源加载
  • 五、初始地图
  • 六、常量定义
  • 七、地图随机
  • 八、点击排雷
  • 九、格子类化
  • 十、 地图类化
  • 十一、 接口优化
  • 十二、 文件拆分
  • 十三、游戏重开

前言

各位小伙伴们,这期我们一起学习出贪吃蛇以外另一个基础的项目——扫雷,这个项目需要我们自己到网站下载一些需要用的库,所以和贪吃蛇比起相对来说要复杂一点,这期涉及到面向对象的知识,还有文件拆分。

一、环境配置

首先要下载 SFML:https://www.sfml-dev.org/download/sfml/2.6.1/,解压到本地放代码的文件夹。
在这里插入图片描述
因为我们需要#include <SFML/Graphics.hpp>,这个头文件,创建好一个项目,我们在配置它的环境。点击项目,右键属性,选择这里的 C/C++,选择【附加包含目录】,选择 SFML 的 include 目录,点击确定。
在这里插入图片描述
然后点击 附加库 -> 常规,附加库目录,选择 SFML 的 lib 目录:
在这里插入图片描述
再点击下面的【输入】,选择右边的【附加依赖项】,把这些内容拷贝进去:
(这里需要注意,对于 sfml-xxx-d.lib 是适用于 debug 模式的,sfml-xxx.lib 是适用于 release 模式的)
sfml-graphics-d.lib
sfml-window-d.lib
sfml-system-d.lib
sfml-audio-d.lib
opengl32.lib
freetype.lib
winmm.lib
gdi32.lib
在这里插入图片描述
最后点击应用,就配置完毕了。
然后再把 SFML-2.6.0\bin 目录下的 动态链接库文件 dll 都拷贝到项目目录下。
在这里插入图片描述
我们写个 main 函数来测试一下。

#include <SFML/Graphics.hpp>int main() {sf::RenderWindow app(sf::VideoMode(816, 576), "MineSweeper");while (app.isOpen()) {}return 0;
}

在这里插入图片描述

二、基础框架

1、命名空间
sf 是这个库的命名空间,基本上所有的接口都是从这里取的,利用两个冒号来获取相应的函数或者类,如果不想写这段前缀呢,我们可以和 std 一样,在代码前面写上这么一句话:

using namespace sf;

这样一来,这些前缀就都可以去掉了。
2、窗口创建

RenderWindow win(VideoMode(816, 576), "MineSweeper");

这段代码呢,就是实例化了一个窗口对象,RenderWindow 是一个类,win 是一个对象名,这一段就是有参构造函数的传参,我们可以 F12 进去看它的定义。
这里的 VideoMode 也是一个类,这两个参数是 VideoMode 构造函数的传参,分别代表了窗口的宽高。
3、字符集
VideoMode 中构造函数的第二个传参,是一个字符串,代表了窗口的标题,然后我们实现一个 while 循环。

while (win.isOpen()) {}

并且循环条件是 win.isOpen() 为 True ,这个函数的含义就是当窗口 win 被打开的时候就返回真,那么一旦关闭,就会返回假,这时候程序就会结束掉,我们来运行一下。
我们看到窗口左上角有个标题,这时候我希望这个标题是中文的,可以改下这个字符串。
在这里插入图片描述

三、关闭事件

#include <SFML/Graphics.hpp>
#include <iostream>
using namespace sf;int main() {RenderWindow win(VideoMode(816, 576), L"扫雷");while (win.isOpen()) {Event e;while (win.pollEvent(e)) {if (e.type == Event::Closed) {std::cout << "按下关闭按钮" << std::endl;win.close();}}}return 0;
}

四、资源加载

接下来我们准备好这么一张图片,如果不会画图,可以直接用我的这张图,用画图工具打开以后,大概是宽为 1152,高为 96 的图片,每个小格子的大小是 96 x 96。
在这里插入图片描述
1、纹理对象
首先我们创建一个纹理对象,并且把这张图加载到内存中,纹理是游戏开发中一个比较重要的概念,可以理解成贴图, 2D游戏中,不同的对象,让人能够产生不同的视觉效果,就是利用不同的纹理实现的。

Texture t;
t.loadFromFile("mine.png");

2、精灵
然后我们再实例化一个精灵,并且把刚才准备好的纹理对象,作为初始化参数,传给它。精灵可以这么去理解,我拿到一个纹理的某一个矩形区域,然后可以对它进行平移、缩放、旋转 等等变换,然后绘制到屏幕上的这么一个东西,我们叫它精灵。

Sprite s(t);

在原先这张纹理贴图上,(96, 0) 的坐标上,取出一个 (96, 96) 的矩形,并且设置坐标为 (16, 16),然后把它的缩放值设置为原来的 1/2 ,这样就变成了一个 48 x 48 的矩形,然后调用 draw 接口绘制到 win 对象上面去,这时候其实屏幕上还没有东西,直到调用 display 以后,才会真正把它绘制到窗口上。
可以这么去理解,draw 调用完,实际上还没有真正的绘制到窗口上,只是把要画的内容,画到了一张画布上面,display 调用完,才会最终把这张画布的内容,一次性绘制到你的窗口上。

 s.setTextureRect(IntRect(96, 0, 96, 96));s.setPosition(16, 16);s.setScale(Vector2f(0.5, 0.5));win.draw(s);win.display();

在这里插入图片描述
接下来我们来写这么一段话:

int r = rand() % 12;
s.setTextureRect(IntRect(96 * r, 0, 96, 96));

随机一个 0 到 11 的数字,然后让它乘上 96 ,去改变这个纹理矩形左上角的 x 坐标,来看看效果,你会发现每一帧,都在改变图片,而实际上 0 到 11 就代表了扫雷这个游戏中,每个会用到的格子。

五、初始地图

定义一个 showGrid 的二维数组,是一个 15 列 x 10 行 的地图,代表实际显示出来的地图元素,一开始都为 10, 10 就是这个图片,代表的是一个未知的元素。

int showGrid[16][11];
for (int i = 1; i <= 15; ++i) {for (int j = 1; j <= 10; ++j) {showGrid[i][j] = 10;}
}

然后在绘制的时候,遍历每一个地图元素,处理精灵的纹理、位置以及缩放,并且绘制到 win 这个对象上。

for (int i = 1; i <= 15; ++i) {for (int j = 1; j <= 10; ++j) {s.setTextureRect(IntRect(96 * showGrid[i][j], 0, 96, 96));s.setPosition(i * 48, j * 48);s.setScale(Vector2f(0.5, 0.5));win.draw(s);}
}

最终一次性展现到窗口上,运行。这样我们就得到了一张初始的地图。

在这里插入图片描述

六、常量定义

这个时候我们发现,有太多数字了,这个我们叫它们 magic number,很难看,而且维护起来极其麻烦,所以我们想办法把数字变成常量。
首先引入第一个常量:

const int ORI_GRID_SIZE = 96;

它代表了在这张图片中,每个格子的像素大小,是 96。

const int GRID_SIZE = 48;

而 GRID_SIZE 呢,则代表显示到窗口的时候,每个格子实际的像素大小。
然后定义 MAP_COL 和 MAP_ROW ,分别代表这个扫雷地图,有多少列多少行:

const int MAP_COL = 15;
const int MAP_ROW = 10;

然后把之前 15 和 10 的地方,都替换掉(注意下面有个 10 是不能替换,因为含义不同):

int showGrid[MAP_COL+1][MAP_ROW+1];
for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {showGrid[i][j] = 10;      // 这个10可不是 MAP_ROW}
}

遍历每个格子,进行渲染:

s.setScale(Vector2f(GRID_SIZE * 1.0 / ORI_GRID_SIZE, GRID_SIZE * 1.0 / ORI_GRID_SIZE));
for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {s.setTextureRect(IntRect(ORI_GRID_SIZE * showGrid[i][j], 0, ORI_GRID_SIZE, ORI_GRID_SIZE));s.setPosition(i * GRID_SIZE, j * GRID_SIZE);win.draw(s);}
}

而对于整个窗口大小,可以是 格子数 乘上 (列数 + 2),左边加一列格子,右边加一列格子,上下也是一样的,所以窗口大小可以定义成这样的常量:

const int WIN_W = GRID_SIZE * (1 + MAP_COL + 1);
const int WIN_H = GRID_SIZE * (1 + MAP_ROW + 1);

最后,showGrid 里面还有一个 10,这个我们可以用枚举来实现:

enum GridType {GT_EMPTY = 0,GT_COUNT_1 = 1,GT_COUNT_2 = 2,GT_COUNT_3 = 3,GT_COUNT_4 = 4,GT_COUNT_5 = 5,GT_COUNT_6 = 6,GT_COUNT_7 = 7,GT_COUNT_8 = 8,GT_BOMB = 9,GT_HIDE = 10,GT_FLAG = 11
};

七、地图随机

showGrid 代表的是显示出来的格子类型,所以再定义一个 grid,代表真实的格子类型,并且利用随机函数,1/6 的概率是炸弹,5/6的概率是空。

 GridType grid[MAP_COL + 1][MAP_ROW + 1];GridType showGrid[MAP_COL+1][MAP_ROW+1];for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {showGrid[i][j] = GridType::GT_HIDE;if (rand() % 6 == 0) {grid[i][j] = GridType::GT_BOMB;}else {grid[i][j] = GridType::GT_EMPTY;}}}

定义周围的八个方向

const int DIR[8][2] = {{-1, -1}, {-1, 0}, {-1, 1},{0, -1}, {0, 1},{1, -1}, {1, 0}, {1, 1},
};

并且统计每个非炸弹的格子的周围八个方向,进行计数,从而改变当前格子的类型

 for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {if (grid[i][j] == GridType::GT_EMPTY) {int cnt = 0;for (int k = 0; k < 8; ++k) {int ti = i + DIR[k][0];int tj = j + DIR[k][1];if (grid[ti][tj] == GridType::GT_BOMB) {++cnt;}}grid[i][j] = (GridType)cnt;}}}

然后只需要在显示之前,把所有的实际格子内容,赋值给对应的显示格子,就相当于摊牌了。运行一下看看效果。

showGrid[i][j] = grid[i][j];

在这里插入图片描述

八、点击排雷

获取鼠标点击到的格子位置

Vector2i pos = Mouse::getPosition(win);
int x = pos.x / GRID_SIZE;
int y = pos.y / GRID_SIZE;

并且处理鼠标左键 和 鼠标右键 的 按下事件

if (e.type == Event::MouseButtonPressed) {if (e.key.code == Mouse::Left) {showGrid[x][y] = grid[x][y];}else if (e.key.code == Mouse::Right) {showGrid[x][y] = GridType::GT_FLAG;}
}

最后,如果当前的格子被确认是雷,那么所有格子都公开,游戏结束:

 if( showGrid[x][y] == GridType::GT_BOMB) showGrid[i][j] = grid[i][j];

九、格子类化

接下来我们采用面向对象的思想,来改造下这个代码,首先是一个格子,目前用了两个数据来存储,一个是实际的格子类型,一个是显示的格子类型,我现在可以把它封装到一个类里面,定义两个私有成员变量。
分别用 m_realGridType 和 m_showGridType 来表示。
然后实现它们的 set 和 get 成员函数。并且把相关代码也进行替换。

class Grid {
public:Grid() {m_realGridType = GridType::GT_EMPTY;m_showGridType = GridType::GT_EMPTY;}void SetRealGridType(GridType realGType) {m_realGridType = realGType;}void SetShowGridType(GridType realGType) {m_showGridType = realGType;}GridType GetShowGridType() {return m_showGridType;}void ShowGrid() {m_showGridType = m_realGridType;}bool IsEmpty() const {return m_realGridType == GridType::GT_EMPTY;}bool IsRealBomb() const {return m_realGridType == GridType::GT_BOMB;}bool IsShowBomb() const {return m_showGridType == GridType::GT_BOMB;}
private:GridType m_realGridType;GridType m_showGridType;
};

改造下初始化地图的代码:

 Grid grid[MAP_COL + 1][MAP_ROW + 1];for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {grid[i][j].SetShowGridType(GridType::GT_HIDE);if (rand() % 6 == 0) {grid[i][j].SetRealGridType(GridType::GT_BOMB);}else {grid[i][j].SetRealGridType(GridType::GT_EMPTY);}}}for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {if (grid[i][j].IsEmpty()) {int cnt = 0;for (int k = 0; k < 8; ++k) {int ti = i + DIR[k][0];int tj = j + DIR[k][1];if (grid[ti][tj].IsRealBomb()) {++cnt;}}if (cnt > 0) {grid[i][j].SetRealGridType((GridType)cnt);}}}}

改造下鼠标按键的代码:

  if (e.key.code == Mouse::Left) {grid[x][y].ShowGrid();}else if (e.key.code == Mouse::Right) {grid[x][y].SetShowGridType(GridType::GT_FLAG);}

改造下渲染的代码:

  if( grid[x][y].IsShowBomb()) grid[i][j].ShowGrid();s.setTextureRect(IntRect(ORI_GRID_SIZE * grid[i][j].GetShowGridType(), 0, ORI_GRID_SIZE, ORI_GRID_SIZE));

十、 地图类化

除了把格子用类来实现,整个地图也可以用类来实现。

class Map {
public:void init() {for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {grid[i][j].SetShowGridType(GridType::GT_HIDE);if (rand() % 6 == 0) {grid[i][j].SetRealGridType(GridType::GT_BOMB);}else {grid[i][j].SetRealGridType(GridType::GT_EMPTY);}}}for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {if (grid[i][j].IsEmpty()) {int cnt = 0;for (int k = 0; k < 8; ++k) {int ti = i + DIR[k][0];int tj = j + DIR[k][1];if (grid[ti][tj].IsRealBomb()) {++cnt;}}if (cnt > 0) {grid[i][j].SetRealGridType((GridType)cnt);}}}}}void handleMouseEvent(Event& e, int x, int y) {if (e.type == Event::MouseButtonPressed) {if (e.key.code == Mouse::Left) {grid[x][y].ShowGrid();}else if (e.key.code == Mouse::Right) {grid[x][y].SetShowGridType(GridType::GT_FLAG);}}}void draw(RenderWindow& win, Sprite& s, int x, int y) {s.setScale(Vector2f(GRID_SIZE * 1.0 / ORI_GRID_SIZE, GRID_SIZE * 1.0 / ORI_GRID_SIZE));for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {if (grid[x][y].IsShowBomb())grid[i][j].ShowGrid();s.setTextureRect(IntRect(ORI_GRID_SIZE * grid[i][j].GetShowGridType(), 0, ORI_GRID_SIZE, ORI_GRID_SIZE));s.setPosition(i * GRID_SIZE, j * GRID_SIZE);win.draw(s);}}}private:Grid grid[MAP_COL + 1][MAP_ROW + 1];
};
int main() {RenderWindow win(VideoMode(WIN_W, WIN_H), L"扫雷");Texture t;t.loadFromFile("mine.png");Sprite s(t);Map mp;mp.init();while (win.isOpen()) {Vector2i pos = Mouse::getPosition(win);int x = pos.x / GRID_SIZE;int y = pos.y / GRID_SIZE;Event e;while (win.pollEvent(e)) {if (e.type == Event::Closed) {std::cout << "按下关闭按钮" << std::endl;win.close();}mp.handleMouseEvent(e, x, y);}mp.draw(win, s, x, y);win.display();}return 0;
}

十一、 接口优化

接下来我们来看,这个地图类对外提供的接口,只有三个了。一个是初始化,一个是处理事件,一个是渲染,并且渲染接口每次都把 窗口 和 精灵 传进去,实际上是没有必要的,因为在这个大循环里,这两个对象,是不变的。
所以这两个对象,实际上,可以作为 Map 类的成员变量,当然这里必须用指针。如果不传指针,就会通过拷贝构造函数,生成一个新的对象,这不是我们想要的,我需要它还是原来那个对象。

private:RenderWindow*    win;Sprite*          sprite;Grid             grid[MAP_COL + 1][MAP_ROW + 1];
};

然后修改 Map 类的初始化函数,如下:

void init(RenderWindow* win, Sprite* sprite) {this->win = win;this->sprite = sprite;...
}mp.init(&win, &s);

这里记住要传指针,所以把 win 和 s 的地址传进去就好了。最后修改 draw 函数(因为是指针,所以所有的 . 变成 -> 就好了):

void draw(int x, int y) {sprite->setScale(Vector2f(GRID_SIZE * 1.0 / ORI_GRID_SIZE, GRID_SIZE * 1.0 / ORI_GRID_SIZE));for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {if (grid[x][y].IsShowBomb()) {grid[i][j].ShowGrid();}sprite->setTextureRect(IntRect(ORI_GRID_SIZE * grid[i][j].GetShowGridType(), 0, ORI_GRID_SIZE, ORI_GRID_SIZE));sprite->setPosition(i * GRID_SIZE, j * GRID_SIZE);win->draw(*sprite);}}
}

sprite 作为 RenderWindow 的成员函数 draw 的参数,是一个对象,所以采用 * 进行解引用。

十二、 文件拆分

这个时候我们发现这个文件行数太多了,所以我们想办法把类拆到其它文件去。首先,先把 Grid 类的内容拆出去,新建一个 Grid.h 文件,实现如下:

#pragma onceenum GridType {GT_EMPTY = 0,GT_COUNT_1 = 1,GT_COUNT_2 = 2,GT_COUNT_3 = 3,GT_COUNT_4 = 4,GT_COUNT_5 = 5,GT_COUNT_6 = 6,GT_COUNT_7 = 7,GT_COUNT_8 = 8,GT_BOMB = 9,GT_HIDE = 10,GT_FLAG = 11
};class Grid {
public:Grid() {m_realGridType = GridType::GT_EMPTY;m_showGridType = GridType::GT_EMPTY;}void SetRealGridType(GridType realGType) {m_realGridType = realGType;}void SetShowGridType(GridType realGType) {m_showGridType = realGType;}GridType GetShowGridType() {return m_showGridType;}void ShowGrid() {m_showGridType = m_realGridType;}bool IsEmpty() const {return m_realGridType == GridType::GT_EMPTY;}bool IsRealBomb() const {return m_realGridType == GridType::GT_BOMB;}bool IsShowBomb() const {return m_showGridType == GridType::GT_BOMB;}
private:GridType m_realGridType;GridType m_showGridType;
};

然后在 main.cpp 里面,写上这么一句话:
#include “Grid.h”
这时候,我们希望 .h 文件里面不要有函数的实现,只保留声明。然后在源文件里,新建一个 Grid.cpp 文件,把函数的实现写在这个文件里。

#pragma onceenum GridType {GT_EMPTY = 0,GT_COUNT_1 = 1,GT_COUNT_2 = 2,GT_COUNT_3 = 3,GT_COUNT_4 = 4,GT_COUNT_5 = 5,GT_COUNT_6 = 6,GT_COUNT_7 = 7,GT_COUNT_8 = 8,GT_BOMB = 9,GT_HIDE = 10,GT_FLAG = 11
};class Grid {
public:Grid();void SetRealGridType(GridType realGType);void SetShowGridType(GridType realGType);GridType GetShowGridType();void ShowGrid();bool IsEmpty() const;bool IsRealBomb() const;bool IsShowBomb() const;
private:GridType m_realGridType;GridType m_showGridType;
};

Grid.cpp 如下:

#include "Grid.h"Grid::Grid() {m_realGridType = GridType::GT_EMPTY;m_showGridType = GridType::GT_EMPTY;
}void Grid::SetRealGridType(GridType realGType) {m_realGridType = realGType;
}
void Grid::SetShowGridType(GridType realGType) {m_showGridType = realGType;
}
GridType Grid::GetShowGridType() {return m_showGridType;
}
void Grid::ShowGrid() {m_showGridType = m_realGridType;
}
bool Grid::IsEmpty() const {return m_realGridType == GridType::GT_EMPTY;
}
bool Grid::IsRealBomb() const {return m_realGridType == GridType::GT_BOMB;
}
bool Grid::IsShowBomb() const {return m_showGridType == GridType::GT_BOMB;
}

同样,在实现一个 Map.h 和 Map.cpp。

#pragma once#include <SFML/Graphics.hpp>
#include "Grid.h"
using namespace sf;class Map {
public:void init(RenderWindow* win, Sprite* sprite);void handleMouseEvent(Event& e, int x, int y);void draw(int x, int y);private:RenderWindow* win;Sprite*    sprite;Grid      grid[MAP_COL + 1][MAP_ROW + 1];
};#include "Map.h"
#include "Grid.h"void Map::init(RenderWindow* win, Sprite* sprite) {this->win = win;this->sprite = sprite;for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {grid[i][j].SetShowGridType(GridType::GT_HIDE);if (rand() % 6 == 0) {grid[i][j].SetRealGridType(GridType::GT_BOMB);}else {grid[i][j].SetRealGridType(GridType::GT_EMPTY);}}}for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {if (grid[i][j].IsEmpty()) {int cnt = 0;for (int k = 0; k < 8; ++k) {int ti = i + DIR[k][0];int tj = j + DIR[k][1];if (grid[ti][tj].IsRealBomb()) {++cnt;}}if (cnt > 0) {grid[i][j].SetRealGridType((GridType)cnt);}}}}
}void Map::handleMouseEvent(Event& e, int x, int y) {if (e.type == Event::MouseButtonPressed) {if (e.key.code == Mouse::Left) {grid[x][y].ShowGrid();}else if (e.key.code == Mouse::Right) {grid[x][y].SetShowGridType(GridType::GT_FLAG);}}
}void Map::draw(int x, int y) {sprite->setScale(Vector2f(GRID_SIZE * 1.0 / ORI_GRID_SIZE, GRID_SIZE * 1.0 / ORI_GRID_SIZE));for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {if (grid[x][y].IsShowBomb()) {grid[i][j].ShowGrid();}sprite->setTextureRect(IntRect(ORI_GRID_SIZE * grid[i][j].GetShowGridType(), 0, ORI_GRID_SIZE, ORI_GRID_SIZE));sprite->setPosition(i * GRID_SIZE, j * GRID_SIZE);win->draw(*sprite);}}
}

十三、游戏重开

然后一个游戏结束以后,我们希望它能够重开,在 Map 中,引入一个私有成员变量 isRunning,并且引入一个 initGame 的函数,围绕 isRuning 进行逻辑修改。
一旦按到一个雷,那么 isRuning 就从 true 变成 false,然后左键按下的时候,根据 isRunning 是 true 还是 false 做不同的处理,如果为 true,则保留原先的逻辑;如果为 false,则重新初始化游戏,开始新的一局。

#include "Map.h"
#include "Grid.h"void Map::init(RenderWindow* win, Sprite* sprite) {this->win = win;this->sprite = sprite;    initGame();
}void Map::initGame() {this->isRunning = true;for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {grid[i][j].SetShowGridType(GridType::GT_HIDE);if (rand() % 6 == 0) {grid[i][j].SetRealGridType(GridType::GT_BOMB);}else {grid[i][j].SetRealGridType(GridType::GT_EMPTY);}}}for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {if (grid[i][j].IsEmpty()) {int cnt = 0;for (int k = 0; k < 8; ++k) {int ti = i + DIR[k][0];int tj = j + DIR[k][1];if (grid[ti][tj].IsRealBomb()) {++cnt;}}if (cnt > 0) {grid[i][j].SetRealGridType((GridType)cnt);}}}}
}void Map::handleMouseEvent(Event& e, int x, int y) {if (e.type == Event::MouseButtonPressed) {if (e.key.code == Mouse::Left) {if (isRunning) {grid[x][y].ShowGrid();if (grid[x][y].IsShowBomb()) {isRunning = false;}}else {initGame();}}else if (e.key.code == Mouse::Right) {grid[x][y].SetShowGridType(GridType::GT_FLAG);}}
}void Map::draw(int x, int y) {sprite->setScale(Vector2f(GRID_SIZE * 1.0 / ORI_GRID_SIZE, GRID_SIZE * 1.0 / ORI_GRID_SIZE));for (int i = 1; i <= MAP_COL; ++i) {for (int j = 1; j <= MAP_ROW; ++j) {if (!isRunning) {grid[i][j].ShowGrid();}sprite->setTextureRect(IntRect(ORI_GRID_SIZE * grid[i][j].GetShowGridType(), 0, ORI_GRID_SIZE, ORI_GRID_SIZE));sprite->setPosition(i * GRID_SIZE, j * GRID_SIZE);win->draw(*sprite);}}
}
http://www.lryc.cn/news/526735.html

相关文章:

  • docker安装elk6.7.1-搜集java日志
  • 自然语言处理(NLP)入门:基础概念与应用场景
  • AI News(1/21/2025):OpenAI 安全疏忽:ChatGPT漏洞引发DDoS风险/OpenAI 代理工具即将发布
  • Linux——包源管理工具
  • C++解决走迷宫问题:DFS、BFS算法应用
  • 机器学习09-Pytorch功能拆解
  • BLE透传方案,IoT短距无线通信的“中坚力量”
  • Linux 中的poll、select和epoll有什么区别?
  • 单片机-STM32 WIFI模块--ESP8266 (十二)
  • linux日志排查相关命令
  • 每日一题-二叉搜索树与双向链表
  • 【多视图学习】Self-Weighted Contrastive Fusion for Deep Multi-View Clustering
  • ASK-HAR:多尺度特征提取的深度学习模型
  • C语言:数据的存储
  • 深入理解动态规划(dp)--(提前要对dfs有了解)
  • 单片机基础模块学习——数码管(二)
  • 【大数据】机器学习----------强化学习机器学习阶段尾声
  • flink写parquet解决timestamp时间格式字段问题
  • redis实现lamp架构缓存
  • 正则表达式中常见的贪婪词
  • CF 339A.Helpful Maths(Java实现)
  • SQL 指南
  • DDD架构实战第七讲总结:分层模型和代码组织
  • Python “字典” 实战案例:5个项目开发实例
  • (一)QT的简介与环境配置WIN11
  • 在 Windows 系统上,将 Ubuntu 从 C 盘 迁移到 D 盘
  • vue2的$el.querySelector在vue3中怎么写
  • GPSd定时检测保活TCP GPS源
  • IDEA中Maven使用的踩坑与最佳实践
  • 使用 Python 调用 OpenAI 的接口初识