1:扫雷游戏的介绍:
hello,hello,小伙伴们来晚了,想必大家都玩过扫雷,下面我整理用c语言写扫雷的过程详解与思路:
扫雷(Minesweeper)是一款经典的单人益智游戏,起源于1980年代,最初在个人电脑上流行。它的目标是在没有触碰到地雷的情况下,揭开尽可能多的方格。以下是扫雷游戏的一些基本介绍:
### 游戏规则
1. **棋盘**: 游戏通常在一个由多个方格组成的网格上进行,网格的大小和地雷的数量可以根据难度进行设置(例如:9*9,8*8、16*16等)。
2. **地雷**: 在网格中随机分布着一定数量的地雷(例如10个地雷)。
3. **揭开方格**: 玩家点击一个方格进行揭开。如果点击的是地雷,游戏结束;如果点击的不是地雷,则显示该方格周围地雷的数量(0到8)。
4. **数字提示**: 每个方格上的数字表示该方格周围8个方格中地雷的数量。玩家可以根据这些数字推测周围地雷的位置。
5. **标记地雷**: 玩家可以右键点击某个方格来标记他们认为是地雷的方格。标记的方格会显示一个小旗子。
6. **胜利条件**: 玩家需要揭开所有非地雷的方格,或者恰当标记所有地雷,从而完成游戏。
### 策略与技巧
- **逻辑推理**: 玩扫雷不仅需要运气,还需要通过数字进行逻辑推理,判断地雷的可能位置。
- **安全区**: 当一个方格显示“0”时,可以自动揭开其周围所有未揭开的方格,因为这些方格中不可能有地雷。
- **标记与排除法**: 当几个方格组合在一起时,可能会出现一些特定的模式,通过这些模式可以推断出地雷的位置。
2:扫雷的实现过程
首先,我把扫雷这个项目分成三个文件,分别是test.c:用于代码的主逻辑;game.c:用于游戏的实现;game.h:我将文件的声明和函数的声明和定义放到三个文件,这样便于我们写代码,调试起来也更灵活方便,代码修改起来也更方便,也适合我们合作。
如下图:
接下来,我们就要实现代码的主逻辑了,为了增加趣味性和说明我可以先打印菜单和提供玩家选择,为了美观,我把菜单封装成一个函数。玩家既可以选择玩游戏,也可以选择退出游戏,如果选择错误,也会提醒玩家,所以我可以使用switch条件分语句供玩家选择,选择1:扫雷
选择0:退出游戏;选择其他:default报错。当然,不管玩家玩不玩,我都会打印菜单,供玩家选择,那么do,while循环很合适,我把玩家选择放为循环判断条件,以保证玩家玩一把不过瘾能够再玩。
具体代码如下:
#include <stdio.h>
void menu()
{
printf("***********************\n");
printf("********Play .1********\n");
printf("********Exit .0********\n");
printf("***********************\n");
}
int main()
{
int input = 0;
do
{
menu();//打印菜单
printf("请选择:>\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("扫雷\n");//玩游戏的主题逻辑
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入:\n");
}
} while(input);
return 0;
}
为了好测试,我将玩游戏这里先简单代替,进行测试,写一步测试一步,方便后面的代码实现。测试结果如下:
现在我们的核心任务就是考虑扫雷这个游戏到底是如何实现的:
首先,我们考虑第一个事情,假如我想统计 (2,3)这个坐标周围的八个格子,那么很好统计,那么如果我想统计(0.0)或者(8,1)附近的雷呢,如果还是一如既往的返回周围的八个格子,很显然,数组越界了,那么,为了防止数组越界,我们给数组每一行一列增加一,即11*11的二维数组,这样无论怎么访问,怎么统计雷的个数,都不会越界了。
其次:扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息,所以我们需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放 信息。 假设我们下一个9*9的扫雷,那么我们就需要有这个棋盘,我们就可以用二维数组来实现棋盘,那我们在考虑,在扫雷游戏,我们需要生成雷,我们还需要扫完这个格子,周围8个格子的信息。假设我将雷初始化为1,而没有雷的地方初始化为0,扫完这个格子,判断周围有几个雷在显示;那么就会产生矛盾,这个格子到底是雷呢,还是我们扫完后排雷的信息。因此,我们可以考虑,用两个二维数组来实现,一个来存放雷(mine数组),一个来显示排雷的信息(show数组);这样,如果我们排完一个格子,就会在排雷信息这个数组来显示。同时为了保持神秘,show数组开始时初始化为字符'*',为了保持两个数组的类型⼀致,可以使⽤同⼀ 套函数处理,mine数组最开始也初始化为字符'0',布置雷改成'1'。于是我们对这两个二维数组进行初始化:
函数实现如下:
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化棋盘
InitBoard(mine, ROWS, COLS,'0');//存放布置雷
InitBoard(show, ROWS, COLS, '*');//存放已经排雷的信息
}
我分别在game()函数中再封装初始化棋盘的函数,然后在game.h对齐声明,game.c文件实现,以下函数的实现都是如此,这里不过多说明。
为了后面避免代码的重复,也为了我们对棋盘的修改,比如,我不想玩9*9,所以我将重复的数字进行了定义,如下:
#define ROW 9
#define COL 9
#define ROWS ROW +2
#define COLS COL +2
#define EASY_COUNT 10
然后,我们可以打印出来看看,封装了一个DisplayBoard(show, ROW, COL);
函数实现如下:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i, j;
printf("----------扫雷----------\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ",board[i][j]);
}
printf("\n");
}
printf("----------扫雷----------\n");
}
接下来就是设置雷了,封装了一个SetMine(mine, ROW, COL)函数,在设置雷的过程中,需要电脑随机生成雷,我们需要调用rand()函数,不过在使用rand()函数之前,我们需要先设置生成器起点srand((unsigned int)time(NULL)),让time函数返回的时间戳值,这样我们就可以获得不同的种子,生成随机数了,time函数的返回值时time_t,需要我们进行强制类型转换。
设置雷函数具体实现如下:
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;//储存雷的个数
while (count)
{
int x = rand() % row + 1;
int y = rand() % row + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
接下来就是重要的一步:统计输入的坐标周围雷的个数。假设现在玩家输入坐标(x,y),那么我如何统计他周围8个坐标的个数呢?我们可以分别列出它们的坐标,然后如果是雷,那么坐标周围雷的个数就增加一,不是雷,增加0,将这8个坐标对应雷的个数加起来,就是(x,y)这个坐标周围8个格子的雷的个数。但需要注意的是,我们假设雷是字符‘1’,非雷是字符‘0’;那么如果我想得到数字1(即雷的个数)即:1=‘1’-‘0’ ;0 = ‘0’-‘0’;这样对于每一个格子,我只需要将他们都加起来,最后减去 8 * ‘0’即可,得到输入的坐标周围雷数字个数。
然后我们扫雷游戏什么时候结束:即排完所有雷,那么我们就可以进行一个while循环,循环判断条件是row*col-EASY_COUNT >win 在前71次都不跳出循环,第72次跳出,则代表排雷成功。具体代码如下:
int GetMineCount(char board[ROWS][COLS], int x, int y)
{
return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] + board[x][y - 1] +
board[x][y + 1] + board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] - 8 * '0';
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
printf("请输入坐标:>\n");
int x = 0;
int y = 0;
int win = 0;
while (row * col - EASY_COUNT > win)
{
scanf("%d %d", &x, &y);
if (show[x][y] == '*')//防止坐标被重复下过
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你输了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int ret = GetMineCount(mine, x, y);//获取排这个坐标成功后周围8个格子的雷的个数
show[x][y] = ret + '0';
win++;
DisplayBoard(show, ROW, COL);
}
}
else
printf("坐标非法,请重新输入:\n");
}
else
{
printf("这个坐标输过了,换个坐标试试\n");
}
}
if (row * col - EASY_COUNT == win)
{
printf("hehe,排雷成功!\n");
}
}
好了,现在我们的扫雷游戏基本上就可以实现了,但是如果我们还想继续增加几个功能,比如,1.如果玩家推测出这个雷,标记雷的功能;2.如果排查位置不是雷,周围也没有雷,可以展开周围的⼀⽚的功能;3.计时的功能。那么我们对此进行实现。
我们先考虑标记雷的功能,假设玩家如果知道这个地方是雷,进行标记,那么,是否需要每次下棋前,都提供玩家选择是否标记雷,然后在show数组显示,玩家标记的雷记作 '#',选择(0,0)退出标记,如果标记再打印show数组。当然这也是排雷中的过程,所以加到排雷的过程即可。具体代码如下:
/标记雷
void FlagMine(char board[ROWS][COLS], int row, int col)
{
printf("请输入要标记雷的坐标:>\n");
printf("输入(0, 0)退出标记\n");
int x = 0;
int y = 0;
while ( 1)
{
scanf("%d %d", &x, &y);
if (x == 0 && y == 0)
{
printf("退出标记\n");
break;
}
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x][y] == '*')
{
board[x][y] = '#';
break;
}
else
printf("这个坐标不能标记,请重新输入\n");
}
else
{
printf("坐标非法,请重新输入:\n");
}
}
}
接着,我们讨论第二个问题:如果排查位置不是雷,周围也没有雷,可以展开周围的⼀⽚的功能。
我们先思考,假设还是有个坐标(x,y)我想满足这个条件需要三个条件,1:这个坐标不是雷 2.这个坐标周围没有雷. 3.这个坐标没有被排查过。分析这个问题我们可以通过扫雷游戏 展开一片这个图:
现在我们用这个函数SaveSpace(mine,show,x,y);实现,它周围没有雷,函数递归地展开安全区域。 如果当前位置没有周围的雷,就递归展开它周围的邻接块。然后他的邻居块再继续展开,直到展开到遇到雷为止,并显示雷的信息,如果不是雷,那么递归下去,具体代码实现如下:
void SaveSpace(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x<1 || x>ROWS || y<1 || y>COLS || show[x][y] != '*')
return;
else
{
int minecount = GetMineCount(mine, x, y);
show[x][y] = minecount + '0';//揭示雷的个数
if (minecount == 0)
{
SaveSpace(mine, show, x - 1, y - 1); // 上左
SaveSpace(mine, show, x - 1, y); // 上
SaveSpace(mine, show, x - 1, y + 1); // 上右
SaveSpace(mine, show, x, y - 1); // 左
SaveSpace(mine, show, x, y + 1); // 右
SaveSpace(mine, show, x + 1, y - 1); // 下左
SaveSpace(mine, show, x + 1, y); // 下
SaveSpace(mine, show, x + 1, y + 1); // 下右
}
}
}
3:当玩家看到棋盘时,开始计时,排雷成功,结束计时即可。
3.扫雷游戏的代码实现
game.h
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW +2
#define COLS COL +2
#define EASY_COUNT 10
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//标记雷
void FlagMine(char board[ROWS][COLS], int row, int col);
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
printf("***********************\n");
printf("********Play .1********\n");
printf("********Exit .0********\n");
printf("***********************\n");
}
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化棋盘
InitBoard(mine, ROWS, COLS,'0');//存放布置雷
InitBoard(show, ROWS, COLS, '*');//存放已经排雷的信息
//打印棋盘
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//排雷
FindMine(mine, show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入:\n");
}
} while(input);
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i, j;
printf("----------扫雷----------\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ",board[i][j]);
}
printf("\n");
}
printf("----------扫雷----------\n");
printf("\n");
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;//储存雷的个数
while (count)
{
int x = rand() % row + 1;
int y = rand() % row + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
int GetMineCount(char board[ROWS][COLS],int x,int y)
{
return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] + board[x][y - 1] +
board[x][y + 1] + board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] - 8 * '0';
}
void SaveSpace(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x<1 || x>ROWS || y<1 || y>COLS || show[x][y] != '*')
return;
else
{
int minecount = GetMineCount(mine, x, y);
show[x][y] = minecount + '0';//揭示雷的个数
if (minecount == 0)
{
SaveSpace(mine, show, x - 1, y - 1); // 上左
SaveSpace(mine, show, x - 1, y); // 上
SaveSpace(mine, show, x - 1, y + 1); // 上右
SaveSpace(mine, show, x, y - 1); // 左
SaveSpace(mine, show, x, y + 1); // 右
SaveSpace(mine, show, x + 1, y - 1); // 下左
SaveSpace(mine, show, x + 1, y); // 下
SaveSpace(mine, show, x + 1, y + 1); // 下右
}
}
}
//标记雷
void FlagMine(char board[ROWS][COLS], int row, int col)
{
printf("请输入要标记雷的坐标:>\n");
printf("输入(0, 0)退出标记\n");
int x = 0;
int y = 0;
while ( 1)
{
scanf("%d %d", &x, &y);
if (x == 0 && y == 0)
{
printf("退出标记\n");
break;
}
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x][y] == '*')
{
board[x][y] = '#';
break;
}
else
printf("这个坐标不能标记,请重新输入\n");
}
else
{
printf("坐标非法,请重新输入:\n");
}
}
}
//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
time_t start_time = time(NULL);//记录时间开始
while (row * col - EASY_COUNT > win)
{
printf("请输入排查雷坐标:>");
scanf("%d %d", &x, &y);
if (show[x][y] == '*')//防止坐标被重复下过
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你输了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int ret = GetMineCount(mine, x, y);//获取排这个坐标成功后周围8个格子的雷的个数
//1.这个坐标不是雷
//2.这个坐标周围没有雷
//3.这个坐标没有被排查过
if (mine[x][y] == '0' && ret == 0 && mine[x][y] != '1')
SaveSpace(mine,show,x,y);
else{
show[x][y] = ret + '0';//不为'0'则显示周围雷数
}
win++;
DisplayBoard(show, ROW, COL);
//标记雷
FlagMine(show, ROW, COL);
DisplayBoard(show, ROW, COL);
}
}
else
printf("坐标非法,请重新输入:\n");
}
else
{
printf("这个坐标输过了,换个坐标试试\n");
}
}
if (row * col - EASY_COUNT == win)
{
printf("hehe,排雷成功!\n");
time_t end_time = time(NULL);//记录时间结束
double elapsed_time = difftime(end_time, start_time);
printf("经过的时间: %.f 秒\n", elapsed_time);
}
}
这仅是大概功能的实现,谢谢大家指正,感谢看到这里的你!
完!