接下来我们开始学习如何用一段简单的代码完成一个简单的扫雷游戏。以下是我个人对于扫雷游戏浅陋的理解,如有不足,请指出问题,谢谢大家。
首先,我们需要简单理解一下扫雷,扫雷是什么呢?
扫雷是一款经典的计算机游戏,目标是在一个方形区域中揭开地雷以外的所有方块。游戏区域被分成网格,每个方块要么是空白方块,要么是地雷。玩家通过点击方块来揭开其内容。
开始游戏时,玩家选择游戏难度,这决定了游戏区域的大小和地雷的数量。玩家先选择一个方块揭开,如果揭开的是地雷,游戏失败。如果揭开的是空白方块,方块上会显示周围八个方块中地雷的数量,如果数字为0,表示周围没有地雷,可以安全地揭开周围的方块。如果揭开的方块周围有地雷,玩家需要根据数字提示来判断周围哪些方块是地雷,然后进行标记。
玩家可以使用标记来帮助记录地雷的位置,以便在后续游戏中避开这些方块。一旦玩家揭开所有地雷以外的方块,游戏胜利。
游戏逻辑以及代码实现:
好的,我们已经理解了扫雷游戏的基本机制。但在正式写代码之前,我们还需要再想想一个整体的思路是如何规划的。
进入这个工程前,我们先需要创建三个文件,两个源文件和一个头文件。如下图所示:
这两个源文件是用来干什么的呢?
geme.c 文件中写游戏中函数的实现等
test.c 文件中写游戏的测试逻辑
头文件是用来干什么的呢?有以下两个作用(其中两个):
1.函数声明:头文件常用于声明函数。这允许函数在多个源文件(
.c
或.cpp
文件)中被使用,而无需在每个文件中重新定义它们。通过在头文件中声明函数,你可以在多个源文件中共享同一函数的定义。2.宏定义:宏是预处理器指令,常用于定义在程序中反复使用的常量或执行简单的文本替换。头文件是定义宏的理想场所,因为它们可以被多个源文件包含。
总而言之,头文件的使用减少了代码重复,并提高了代码的模块化和可维护性。在 C 和 C++ 中,头文件通常具有
.h
或.hpp
扩展名,并通过#include
指令在源文件中包含。
在这里,game.h 是用来写游戏需要的数据类型和函数声明等。
创建完三个文件后,我们要在两个源文件 game.c 和 test.c 中包含这个game.h头文件。如下图:
一开始,我们先为游戏创建一个菜单,代码实现如下:
int main()
{
test();
return 0;
}
void menu()
{
printf("*********************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("*********************\n");
}
void test()
{
int input = 0;
do
{
menu();
printf("请输入:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏结束,退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
接下来,我们需要用二维数组创建一个棋盘,大小是9*9。如下图所示:
然后我们会想到,用数字 0 代表此方格没有地雷,用数字 1 表示此方格有地雷,但是再想想,如果此方格有地雷,它显示的是数字 1 ,如果此方格没有地雷,它周边的8个方格中恰好只有1个地雷,那么它显示的也是数字 1 ,这样是不是就有歧义了,所以我们要避免这种情况。解决办法就是用二维数组创建两个棋盘,一个用来储存布置地雷的信息,另外一个用来储存排查出的地雷的信息。我们需要先创建两个棋盘,并且初始化它们,储存地雷的信息的棋盘用字符 ‘0’ 初始化,储存排查的地雷的信息的棋盘用字符 ‘*’ 初始化,这里有人可能就会有疑问,为什么储存地雷的信息的棋盘也用字符而不用数字,原因就是保持两个二维数组的类型是一致的,方便我们后续的操作。
但是还有一个问题,排查地雷的时候我们计算的是你输入的要排查的坐标的周围8个方格中有多少个地雷,如果在游戏区域中间的位置还好,假如要排查的坐标去到了游戏区域边缘呢,这就会有越界的风险。如下图:
所以,我们需要再在外围多加一圈,以保证不会发生越界的情况。这样的话,为了保持两个棋盘的一致,两个棋盘的大小都需要变成 11*11 。接下来,我们先在头文件game.h中用 #define 定义行和列。如下图:
ROW*COL 是我们等下实际操作的范围,ROWS*COLS 是为了确保不会发生越界,并且两个棋盘创建时要保持一致,在test.c中创建。如下图:
创建完成后,我们需要一个函数以完成对这两个棋盘的初始化(储存布置雷的信息的棋盘初始化为 ‘0’ ,储存排查的雷的信息的棋盘初始化为 ‘*’ )。如下图:
---------在 test.c 文件中
初始化时是对 11*11 的棋盘全部初始化,展示的时候却只展示 9*9 的棋盘。
----------在 game.c 文件中
----------在 game.h 文件中我们需要声明一下这个函数
我们注意到,传参的时候我们在最后传了字符 ‘0’ 和 字符 ‘*’ 过去,这是因为这两个二维数组的大小是一样的,初始化的时候它们仅仅只是初始化的内容不同,所以在最后传一个字符,我们仅仅需要一个函数就可以解决初始化两个二维数组的问题了。
初始化完成后,接下来就是要把这两个棋盘打印出来检查一下是否符合我们的预期。所以我们需要定义一个新的函数以便能打印这两个棋盘。如下图:
在 test.c 文件中
虽然我们创建的时候大小是 11*11 ,但是游玩游戏的人实际看到的就只是 9*9 。
在 game.c 文件中
在 game.h文件中需要先声明函数
代码运行结果如下:
由此可见,两个棋盘的初始化都成功了。
接下来,我们需要把储存布置地雷的信息的棋盘隐藏起来,再对储存排查出的地雷的信息的棋盘加上行和列的表示以及在最上方加上“-----扫雷游戏-----”对边界加以区分,以方便游玩游戏的人准确地输入要排查的坐标。
在 game.c 中
运行结果如下图:
好的,没问题,优化完成,现在可以清晰地输入要排查的坐标了。
接下来,就是随机地在 9*9 大小的棋盘上布置地雷了 。既然要随机,那么我们就需要用到 srand 函数和 rand 函数以及 time 函数了。首先我们需要在 game.h 中使用头文件 <stdlib.h> 和 <time.h> 。如下图:
在 9*9 大小的棋盘中我们需要布设10个地雷,所以我们先在 game.h 文件中定义地雷数量。如下图:
在 test.c 文件中
因为我们是对 11*11 大小的棋盘之中的 9*9 大小的区域进行操作,所以这里只需要传 ROW 和 COL (都是为9)这两个参数。
在 game.h 文件中声明函数
在 game.c 文件中
在这里,我们先定义一个变量 int count = EASY_COUNT ,然后使用 while 循环,每次成功布雷后 count 就减去一,坐标(x , y) 是随机生成的 ,当 x (或者y)= rand()% 9 的时候,它的值会等于 0 ~ 8 之间的一个随机数,所以我们只需要再加上 1 ,就可以使它的值变为 1~9 之间的一个随机数了。if 语句用来判断该坐标是否已经布置过地雷了,只有在没布置过地雷的坐标布置地雷才算作一次成功地布雷。
最后是最重要的一部分,也是最终的一部分,那就是排雷了。
在 test.c 文件中,创建一个排雷的函数,这里需要我们把两个棋盘的数据都传过去。如下图:
在 game.h 文件中需要声明一下这个函数
在 game.c 文件中
在计算排查出的格子周围的8个格子中有多少个地雷时,我们还需要创建一个计算地雷的函数。如下图:
计算完成后,返回周边8个格子中有多少个地雷的值。计算地雷数量的函数如下:
在这里计算时,我们需要注意的是我们看到的 0 和 1 都不是数字 0 和 1 ,而是字符 ‘0’ 和 ‘1’ ,所以我们计算时首先得知道它们对应的ASCII码值,‘0’ 对应的ASCII码值为 48 ,‘1’ 对应的ASCII码值为 49 ,到这里时,我们就能想到 ‘1’ - ‘0’ 的值不是恰好等于 1 吗?接下来就容易多啦!我们只需要在每次计算一个格子的时候用它的值减去 ‘0’ ,就能得到数字 1 或者 0 了,最后把每个格子的计算结果再加起来,就能得到我们最终的答案了。但在最后输出到 show 这个棋盘的时候,不要忘记再把计算的结果加上 ‘0’ 再显示出来哦,因为在棋盘上展示出来的数据都是 char(字符) 类型的。
在最后,我将展示整个扫雷游戏的全部代码。如下所示:
在 test.c 文件中
#include "game.h"
void menu()
{
printf("*********************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("*********************\n");
}
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS,'*');
SetMine(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
FindMine(mine, show, ROW, COL);
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请输入:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏结束,退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
在 game.c 文件中
#include "game.h"
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
printf("-----扫雷游戏-----\n");
int i = 0;
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
int j = 1;
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
void SetMine(char arr[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
int MineCount(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int sum = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
sum += (mine[i][j] - '0');
}
}
return sum;
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int win = 0;
int x = 0;
int y = 0;
while (win<row*col - EASY_COUNT)
{
printf("请输入要排查的坐标:\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int count = MineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("已排查过此坐标,请重新输入坐标\n");
}
}
else
{
printf("非法坐标,请重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,扫雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
在 game.h 文件中
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define EASY_COUNT 10
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char arr[ROWS][COLS], int row, int col);
void SetMine(char arr[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
到这里就结束了,我们下次再见!!!