扫雷游戏相信大家都玩过,今天我们自己用C语言代码来实现一个扫雷游戏吧!
目录
由于代码比较多,我们把整个程序拆分为两个源文件test.c和game.c以及一个头文件game.h,这样会更有条理,并且方便后期维护。
游戏框架
和之前的三子棋游戏一样,我们先制作一个简易的菜单,然后在do...while语句中嵌套一个switch...case语句,根据玩家的选择来执行相应的命令,选择1玩游戏,选择0退出程序,选择其他的则提示玩家选择错误,重新选择:
void menu()
{
printf("*****************************\n");
printf("******** 1.play *********\n");
printf("******** 0.exit *********\n");
printf("*****************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>\n");
scanf("%d", &input);
switch(input)
{
case 1:
printf("欢迎来到扫雷游戏\n");
//game();
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误,请重新选择:>\n");
break;
}
} while (input);
return 0;
}
程序执行结果:
说明我们的游戏框架没有问题,接下来开始实现game()函数:
首先,我们来理一下思路,要玩扫雷一定得有雷可扫,所以我们第一步应该先布置雷,第二步再排查雷。所以我们需要创建两个二维数组:
一个数组用来存放布置好的雷的信息;
一个数组用来存放排查出的雷的信息;
但是这两个数组的类型是什么?大小是多少?
上图中,我们可以看到,游戏一开始,所有未被排查的坐标都是空白格子,那么在代码中,我们就可以用一个字符‘*’来表示这些空白格子,而在存放布置好的雷的数组中,我们可以用字符‘1’表示雷,字符‘0’表示没有雷,这样也方便后面计算一个坐标附件有多少颗雷,所以数组的类型我们可以设为char类型。
在排查雷的过程中,我们每输入一个坐标就需要排查这个坐标附近的另外8个坐标是否有雷。但是,在排查最外边一圈的坐标时,就会出现越界访问的情况:
所以我们需要在这个9*9的棋盘的四周再加一圈:
把它变成一个11*11的棋盘,在多出来的位置都放上字符‘0’表示没有雷,这样既可以正常玩,也不会出现越界访问的情况,所以数组的大小应该为11行11列(两个数组的大小保持一致,这样可以使它们对应的坐标相同)。
由于9*9和11*11后面会多次用到,所以我们在头文件中定义几个符号:
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
void game()
{
char bomb[ROWS][COLS] = { 0 };//存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
}
初始化函数
数组创建完毕之后,我们需要把它们初始化一下,写一个初始化函数吧:
布置雷的数组初始化为‘0’,排查雷的数组初始化为‘*’ :
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch)
{
int i = 0;
int j = 0;
for (i = 0;i < rows;i++)
{
for (j = 0;j < cols;j++)
{
board[i][j] = ch;
}
}
}
打印函数
数组初始化完毕,我们把棋盘打印出来看一下,打印的时候,我们只需要打印中间9*9的位置,多加的那一圈不需要打印:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
printf("%c ", board[i][j]);
}
printf("\n");//打印完一行之后换行
}
}
现在我们运行一下代码,看看有没有问题:
说明我们的初始化函数和打印函数都没有问题。
优化打印棋盘代码
我们的打印代码可以优化一下,给它加上行号和列号,这样更方便玩家输入坐标:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
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");//打印完一行之后换行
}
}
优化后的结果:
设置雷的函数
设置雷的时候,我们可以调用两次rand()函数生成两个随机数作为雷的横纵坐标 ,但是坐标的范围需要在1~9之间,所以我们用生成的随机数%9,得到0~8的数字,然后+1就可以得到1~9的数字(调用rand()函数之前,我们先在main()函数内部调用srand()函数,并且用time()函数的返回值作为srand()函数的参数,使生成的随机数更加随机):
void SetBomb(char board[ROWS][COLS], int row, int col)
{
//设置10个雷
int count = COUNT;
while (count)
{
//生成雷的随机下标
int x = (rand() % row) + 1;//范围在1—9之间
int y = (rand() % col) + 1;//范围在1—9之间
if (board[x][y] == '0')//字符'0'说明没有雷
{
board[x][y] = '1';
count--;
}
}
}
这里我们在头文件中定义了一个符号COUNT表示雷的数量,以便后期修改雷数:
#define COUNT 10
排查雷的函数
设置好了雷之后,我们就要排查雷了,排查雷时,当玩家输入要排查位置的坐标后,我们要判断坐标是否合法(横纵坐标的范围在1~9之间),坐标是否已经被排查过,坐标处是否有雷,并执行相应的命令:
转化为代码:
void FindBomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int tmp = 0;
while (tmp < row * col - COUNT)//10个雷,最多排查71次后跳出循环
{
printf("请输入要排查的雷的坐标:>\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法性
{
if (show[x][y] == '*')//判断坐标是否被排查过
{
if (bomb[x][y] == '1')//判断坐标处是不是雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(bomb, ROW, COL);//打印出棋盘,让玩家“死”的瞑目
break;
}
else
{
int ret = Get_Bomb_Count(bomb, x, y);
show[x][y] = ret + '0';//把ret转化为字符ret
DisplayBoard(show, ROW, COL);//更新棋盘信息
tmp++;//玩家胜利的条件
}
}
else
{
printf("该位置已经排查过,请重新输入:>\n");
}
}
else
{
printf("请输入合法的坐标:>\n");
}
}
if (tmp == row * col - COUNT)
{
printf("恭喜你胜利了\n");
}
}
解析:
while()循环的判断条件是根据tmp的值,tmp初始值为0,每当玩家排查出一个没有雷的坐标时,tmp++,而9*9的棋盘一共有10个坐标是有雷的,9*9-10个坐标是没有雷的,即row*col-COUNT,所以当tmp<row*col-COUNT时,说明还有不是雷的坐标没有被排查完,一旦tmp=row*col-COUNT时,说明所有不是雷的坐标都被排查完了,玩家胜利了,而在这个循环过程中,玩家一旦踩到雷,我们就break跳出循环,结束游戏。
当玩家输入的坐标处没有雷的话,我们就要写一个函数来统计该坐标周围的8个坐标的雷的数量,即上面代码中的Get_Bomb_Count()函数,我们用ret来接收这个函数的返回值,因为这个函数的返回值为int类型,而show数组是一个char类型的数组,所以要给ret+'0',得到相应数字的字符,根据ASCII码表,0~9的数字和它们对应的字符‘0’~字符‘9’的差值都为48,即差了个‘0’的值:
Get_Bomb_Count()函数的实现:
static int Get_Bomb_Count(char bomb[ROWS][COLS], int x, int y)
{
return bomb[x][y - 1] +
bomb[x + 1][y - 1] +
bomb[x + 1][y] +
bomb[x + 1][y + 1] +
bomb[x][y + 1] +
bomb[x - 1][y + 1] +
bomb[x - 1][y] +
bomb[x - 1][y - 1] - 8 * '0';
}
坐标(x,y)与它周围的八个坐标:
Get_Bomb_Count()函数实现之后,我们的扫雷游戏就可以玩了~
不过我们的代码只是实现了扫雷游戏的基本逻辑,还有很多需要优化的地方:
1、玩家可以标记或取消标记某一个疑似为雷的坐标;
2、玩家输入一个坐标后,游戏可以自动排查该坐标周围区域是否有雷。
优化程序
在玩家排雷之前,我们可以先给出一个菜单,选择1是排雷,选择2是标记雷,选择3是取消标记:
接下来,我们就需要再次实现4个函数:
1、排查坐标周围雷的函数
2、标记雷的函数
3、取消标记的函数
4、判断胜利的函数
由于现在我们的代码可以排查一个坐标周围雷的情况,所以判断胜利的条件也要发生改变。
排查坐标周围雷的函数
void Around_Bomb_count(char bomb[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1)//判断坐标是否越界
{
return;
}
if (show[x][y] != '*')//判断坐标是否被排查过
{
return;
}
int count = Get_Bomb_Count(bomb, x, y);
if (count > 0)
{
show[x][y] = count + '0';
return;
}
else if (count == 0)
{
show[x][y] = ' ';
Around_Bomb_count(bomb, show, x - 1, y);
Around_Bomb_count(bomb, show, x - 1, y - 1);
Around_Bomb_count(bomb, show, x, y - 1);
Around_Bomb_count(bomb, show, x + 1, y - 1);
Around_Bomb_count(bomb, show, x + 1, y);
Around_Bomb_count(bomb, show, x + 1, y + 1);
Around_Bomb_count(bomb, show, x, y + 1);
Around_Bomb_count(bomb, show, x - 1, y + 1);
}
}
解析:每次进入这个函数,我们首先要判断坐标是否越界,以及这个坐标是否已经被排查过(会出现一个坐标被排查很多次的情况),然后再调用之前的Get_Bomb_Count()函数计算这个坐标处雷的个数,如果这个坐标处有雷,就返回,不执行后面的语句;如果这个坐标处没雷,我们就把这个坐标置为空格(感觉比置为0更好看),并且把这个坐标周围的8个坐标作为新的参数传给这个函数,以递归的方式实现拓展式排雷。
没雷的情况下,置为‘0’和置为空格的效果对比:
标记雷的函数
void MarkBomb(char show[ROWS][COLS], int x, int y)//标记雷
{
if (show[x][y] == '*')
show[x][y] = '#';
}
坐标为’*‘的时候,即未被排查过的坐标才可以被标记。
取消标记的函数
void CancelMark(char show[ROWS][COLS], int x, int y)//取消标记
{
if (show[x][y] == '#')
show[x][y] = '*';
}
坐标为‘#’的时候,即被标记过的坐标才可以被取消标记。
判断胜利的函数
int IsWin(char show[ROWS][COLS], int row,int col)//判断是否胜利
{
int i = 0;
int j = 0;
int count = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)//遍历整个棋盘
{
if (show[i][j] == '*' || show[i][j] == '#')
count++;//未被排查的坐标和被标记的坐标数量之和
}
}
//未被排查的坐标和被标记的坐标数量之和如果等于雷的数量
//说明玩家已经排完了所有的非雷坐标,玩家胜利了
//玩家胜利返回1,没有胜利返回0
if (count == COUNT)
{
return 1;
}
else
return 0;
}
更新排查雷函数
void FindBomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int z = 0;
int ret = 0;
while (1)
{
menu2();
printf("请选择:>\n");
scanf("%d", &z);
if (z == 1)
{
printf("请输入要排查的雷的坐标:>\n");
again:
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法性
{
if (show[x][y] == '*')//判断坐标是否被排查过
{
if (bomb[x][y] == '1')//判断坐标处是不是雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(bomb, ROW, COL);//打印出棋盘,让玩家“死”的瞑目
break;
}
else
{
Around_Bomb_count(bomb, show, x, y);//排查该坐标周围8个坐标雷的个数
DisplayBoard(show, ROW, COL);//更新棋盘信息
}
}
else
{
printf("该位置已经被排查过,请重新输入:>\n");
goto again;
}
}
else
{
printf("请输入合法的坐标:>\n");
goto again;
}
}
else if (z == 2)
{
printf("请输入要标记的坐标:>\n");
scanf("%d %d", &x, &y);
MarkBomb(show, x, y);//标记某一坐标
DisplayBoard(show, ROW, COL);
}
else if(z == 3)
{
printf("请输入要取消标记的坐标:>\n");
scanf("%d %d", &x, &y);
CancelMark(show, x, y);//取消标记
DisplayBoard(show, ROW, COL);
}
else
{
printf("输入错误,请重新输入:>\n");
}
ret = IsWin(show, row, col);//判断胜利
if (ret == 1)
{
break;
}
}
if (ret == 1)
{
printf("恭喜你胜利了\n");
}
}
整个程序的代码:
tese.c
#include "game1.h"
void menu()
{
printf("*****************************\n");
printf("******** 1.play *********\n");
printf("******** 0.exit *********\n");
printf("*****************************\n");
}
void game()
{
char bomb[ROWS][COLS] = { 0 };//存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
//初始化棋盘
InitBoard(bomb, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
//DisplayBoard(bomb, ROW, COL);
DisplayBoard(show, ROW, COL);
//设置雷
SetBomb(bomb, ROW, COL);
//DisplayBoard(bomb, ROW, COL);
//排查雷
FindBomb(bomb, show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>\n");
scanf("%d", &input);
switch(input)
{
case 1:
printf("欢迎来到扫雷游戏\n");
game();
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误,请重新选择:>\n");
break;
}
} while (input);
return 0;
}
game.h
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//符号定义
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define COUNT 10
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//设置雷
void SetBomb(char board[ROWS][COLS], int row, int col);
//排查雷
void FindBomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c
#include "game1.h"
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch)
{
int i = 0;
int j = 0;
for (i = 0;i < rows;i++)
{
for (j = 0;j < cols;j++)
{
board[i][j] = ch;
}
}
}
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
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");//打印完一行之后换行
}
}
//设置雷
void SetBomb(char board[ROWS][COLS], int row, int col)
{
//设置10个雷
int count = COUNT;
while (count)
{
//生成雷的随机下标
int x = (rand() % row) + 1;//范围在1—9之间
int y = (rand() % col) + 1;//范围在1—9之间
if (board[x][y] == '0')//字符'0'说明没有雷
{
board[x][y] = '1';
count--;
}
}
}
//排查一个坐标是不是雷
static int Get_Bomb_Count(char bomb[ROWS][COLS], int x, int y)
{
return bomb[x][y - 1] +
bomb[x + 1][y - 1] +
bomb[x + 1][y] +
bomb[x + 1][y + 1] +
bomb[x][y + 1] +
bomb[x - 1][y + 1] +
bomb[x - 1][y] +
bomb[x - 1][y - 1] - 8 * '0';
}
//排查坐标周围的雷
void Around_Bomb_count(char bomb[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1)//判断坐标是否越界
{
return;
}
if (show[x][y] != '*')//判断坐标是否被排查过
{
return;
}
int count = Get_Bomb_Count(bomb, x, y);
if (count > 0)
{
show[x][y] = count + '0';
return;
}
else if (count == 0)
{
show[x][y] = ' ';
Around_Bomb_count(bomb, show, x - 1, y);
Around_Bomb_count(bomb, show, x - 1, y - 1);
Around_Bomb_count(bomb, show, x, y - 1);
Around_Bomb_count(bomb, show, x + 1, y - 1);
Around_Bomb_count(bomb, show, x + 1, y);
Around_Bomb_count(bomb, show, x + 1, y + 1);
Around_Bomb_count(bomb, show, x, y + 1);
Around_Bomb_count(bomb, show, x - 1, y + 1);
}
}
void menu2()
{
printf("****************************\n");
printf("**********1.排查雷**********\n");
printf("**********2.标记雷**********\n");
printf("**********3.取消标记********\n");
printf("****************************\n");
}
void MarkBomb(char show[ROWS][COLS], int x, int y)//标记雷
{
if (show[x][y] == '*')
show[x][y] = '#';
}
void CancelMark(char show[ROWS][COLS], int x, int y)//取消标记
{
if (show[x][y] == '#')
show[x][y] = '*';
}
int IsWin(char show[ROWS][COLS], int row,int col)//判断是否胜利
{
int i = 0;
int j = 0;
int count = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)//遍历整个棋盘
{
if (show[i][j] == '*' || show[i][j] == '#')
count++;//未被排查的坐标和被标记的坐标数量之和
}
}
//未被排查的坐标和被标记的坐标数量之和如果等于雷的数量
//说明玩家已经排完了所有的非雷坐标,玩家胜利了
//玩家胜利返回1,没有胜利返回0
if (count == COUNT)
{
return 1;
}
else
return 0;
}
void FindBomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int z = 0;
int ret = 0;
while (1)
{
menu2();
printf("请选择:>\n");
scanf("%d", &z);
if (z == 1)
{
printf("请输入要排查的雷的坐标:>\n");
again:
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法性
{
if (show[x][y] == '*')//判断坐标是否被排查过
{
if (bomb[x][y] == '1')//判断坐标处是不是雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(bomb, ROW, COL);//打印出棋盘,让玩家“死”的瞑目
break;
}
else
{
Around_Bomb_count(bomb, show, x, y);//排查该坐标周围8个坐标雷的个数
DisplayBoard(show, ROW, COL);//更新棋盘信息
}
}
else
{
printf("该位置已经被排查过,请重新输入:>\n");
goto again;
}
}
else
{
printf("请输入合法的坐标:>\n");
goto again;
}
}
else if (z == 2)
{
printf("请输入要标记的坐标:>\n");
scanf("%d %d", &x, &y);
MarkBomb(show, x, y);//标记某一坐标
DisplayBoard(show, ROW, COL);
}
else if(z == 3)
{
printf("请输入要取消标记的坐标:>\n");
scanf("%d %d", &x, &y);
CancelMark(show, x, y);//取消标记
DisplayBoard(show, ROW, COL);
}
else
{
printf("输入错误,请重新输入:>\n");
}
ret = IsWin(show, row, col);//判断胜利
if (ret == 1)
{
break;
}
}
if (ret == 1)
{
printf("恭喜你胜利了\n");
}
}
感谢大佬的浏览~