基础项目——扫雷(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);}}
}