首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

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

  • 25-01-30 23:59
  • 3176
  • 9188
blog.csdn.net

目录

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

前言

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

一、环境配置

首先要下载 SFML:https://www.sfml-dev.org/download/sfml/2.6.1/,解压到本地放代码的文件夹。
在这里插入图片描述
因为我们需要#include ,这个头文件,创建好一个项目,我们在配置它的环境。点击项目,右键属性,选择这里的 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 

int main() {
    sf::RenderWindow app(sf::VideoMode(816, 576), "MineSweeper");
    while (app.isOpen()) {

    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述

二、基础框架

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

using namespace sf;
  • 1

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

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

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

while (win.isOpen()) {

}
  • 1
  • 2
  • 3

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

三、关闭事件

#include 
#include 
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

四、资源加载

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

Texture t;
t.loadFromFile("mine.png");
  • 1
  • 2

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

Sprite s(t);
  • 1

在原先这张纹理贴图上,(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();
  • 1
  • 2
  • 3
  • 4
  • 5

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

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

随机一个 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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后在绘制的时候,遍历每一个地图元素,处理精灵的纹理、位置以及缩放,并且绘制到 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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

在这里插入图片描述

六、常量定义

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

const int ORI_GRID_SIZE = 96;
  • 1

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

const int GRID_SIZE = 48;
  • 1

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

const int MAP_COL = 15;
const int MAP_ROW = 10;
  • 1
  • 2

然后把之前 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
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

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

最后,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
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

七、地图随机

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;
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

定义周围的八个方向

const int DIR[8][2] = {
    {-1, -1}, {-1, 0}, {-1, 1},
    {0, -1}, {0, 1},
    {1, -1}, {1, 0}, {1, 1},
};
  • 1
  • 2
  • 3
  • 4
  • 5

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

 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;
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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

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

在这里插入图片描述

八、点击排雷

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

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

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

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

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

九、格子类化

接下来我们采用面向对象的思想,来改造下这个代码,首先是一个格子,目前用了两个数据来存储,一个是实际的格子类型,一个是显示的格子类型,我现在可以把它封装到一个类里面,定义两个私有成员变量。
分别用 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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

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

 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);
                }
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

改造下鼠标按键的代码:

  if (e.key.code == Mouse::Left) {
                grid[x][y].ShowGrid();
            }
            else if (e.key.code == Mouse::Right) {
                grid[x][y].SetShowGridType(GridType::GT_FLAG);
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

改造下渲染的代码:

  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));
  • 1
  • 2
  • 3

十、 地图类化

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

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];
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

十一、 接口优化

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

private:
    RenderWindow*    win;
    Sprite*          sprite;
    Grid             grid[MAP_COL + 1][MAP_ROW + 1];
};
  • 1
  • 2
  • 3
  • 4
  • 5

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

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


mp.init(&win, &s);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里记住要传指针,所以把 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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

然后在 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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

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

#pragma once

#include 
#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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

十三、游戏重开

然后一个游戏结束以后,我们希望它能够重开,在 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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
注:本文转载自blog.csdn.net的曙曙学编程的文章"https://blog.csdn.net/ycs66/article/details/145123445"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top