目录
- 前言
- 一、环境配置
- 二、基础框架
- 三、关闭事件
- 四、资源加载
- 五、初始地图
- 六、常量定义
- 七、地图随机
- 八、点击排雷
- 九、格子类化
- 十、 地图类化
- 十一、 接口优化
- 十二、 文件拆分
- 十三、游戏重开
前言
各位小伙伴们,这期我们一起学习出贪吃蛇以外另一个基础的项目——扫雷,这个项目需要我们自己到网站下载一些需要用的库,所以和贪吃蛇比起相对来说要复杂一点,这期涉及到面向对象的知识,还有文件拆分。
一、环境配置
首先要下载 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 once
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
};
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 once
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
};
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);
}
}
}