上一次我们用C语言实现了三子棋,这一次我们来实现扫雷。
三子棋链接:高质量C语言实现三子棋
扫雷的游戏规则:“扫雷的规则我们随便点一个格子,方格即被打开并显示出方格中的数字,方格中数字则表示其周围的8个方格隐藏了几颗雷,点开的数字是几,则说明该数字旁边的8个位置中有几个雷,如果挖开的是地则会输掉游戏。”
读者可以参照下面的游戏图例感受。
下面我将创建一个一个函数来拼凑出扫雷游戏。
目录
一.打印菜单
二.游戏菜单互动功能
三.游戏函数
1. 初始化棋盘
2.布置雷
3.打印棋盘
4.扫雷
四.拓展
#include
void menu()
{
printf("*********************************\n");
printf("****** 1.五子棋 *********\n");
printf("****** 0.退出游戏 *********\n");
printf("*********************************\n");
}
int main()
{
menu();
return 0;
}
函数:menu()
功能:打印游戏菜单
这里先写一个函数打印菜单,下一步我们来完善互动功能。
int main()
{
int input = 0;
do
{
menu();
printf("请选择->\n");
scanf("%d", &input);
switch (input)
{
case 1:
//game();游戏函数,暂时没写
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);//当输入0退出游戏时,input为0,循环停止
return 0;
}
下一步就是写游戏函数了。
游戏思路:(这次实现的是初级难度的扫雷,棋盘9X9,10个雷)
我们先创建两个二维数组,一个数组布置雷,方便我们统计雷的数量,另一个数组打印出来给用户看,棋盘大小为11X11。用户输入一个合法坐标,我们判断该坐标是不是雷,如果是就游戏结束,不是就遍历该坐标周围8个位置,通过布置雷的那个棋盘统计雷的数量,比如说是3个,就在给用户看的数组上面把对应的坐标替换成字符‘3’。
(11X11的棋盘是为了方便统计棋盘边缘的雷的数量,这样就不会越界访问了,用户进行扫雷的棋盘是9X9)
如果有疑问也不要紧,沿着我的思路慢慢看下去就会豁然开朗了。
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
void game()
{
char board[11][11] = { 0 };//放置雷的棋盘
char show[11][11] = { 0 };//呈现给用户看的棋盘
InitBoard(show, ROW, COL, '*');//初始化棋盘的函数,下面会讲
InitBoard(board, ROW, COL, '0');
}
我们先创建两个11X11的char型的二维数组。
ROW和COL是我们对两个数组操作时需要用到的,ROWS和COLS是我们传递数组给函数时告诉函数这是11X11的数组。
void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{//上面的ROWS和COLS是告诉函数我们传的是一个11X11的数组
int i = 1;
int j = 1;
if (ch == '0')//这里判断初始化的数组是不是布置雷的
{
for (i = 0; i <= 10; i++)
{
for (j = 0; j <= 10; j++)
{
board[i][j] = ch;//将整个数组都初始化为字符'0'
}
}
return;//初始化完成,退出函数
}
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
board[i][j] = ch;//将呈现给用户看的棋盘都初始化为字符'*',代表这个坐标未被扫描
}
}
}
函数:InitBoard(char ,int ,int )
功能:初始化两个棋盘。
void game()
{
char board[11][11] = { 0 };//放置雷的棋盘
char show[11][11] = { 0 };//呈现给用户看的棋盘
InitBoard(show, ROW, COL, '*');//初始化棋盘的函数,下面会讲
InitBoard(board, ROW, COL, '0');
SetBoard(board, ROW, COL);//布置雷
}
上面是游戏函数的代码,下面是布置雷函数的代码。
void SetBoard(char board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int num = 0;
while (num < 10)//统计放置的雷有没有达到10个
{
x = rand() % 9 + 1;//这里是随机生成X轴坐标,范围1-9
y = rand() % 9 + 1;//这里是随机生成X轴坐标,范围1-9
if (board[x][y] == '0')//判断随机生成的坐标是不是雷
{
board[x][y] = '1';//不是雷就在这放雷
num++;
}
}
}
函数:SetBoard(char ,int ,int)
功能:在board[11][11]棋盘上布置雷
这个随机坐标函数是rand()函数,使用之前我们需要先引入
头文件,在主函数的开头设置随机数生成器,以时间戳为变化的参数,时间戳需要引入 头文件。
#include
#include
int main()
{
srand((unsigned int)time(NULL));//设置随机数生成器
//srand()和time(),time函数的参数设置为空指针,强制转换为无符号整形(unsigned int)
return 0;
}
void game()
{
char board[11][11] = { 0 };//放置雷的棋盘
char show[11][11] = { 0 };//呈现给用户看的棋盘
InitBoard(show, ROW, COL, '*');//初始化棋盘的函数
InitBoard(board, ROW, COL, '0');
SetBoard(board, ROW, COL);//布置雷
PrintBoard(show, ROW, COL);//实际游戏只会打印show棋盘
PrintBoard(board, ROW, COL);//这里为了检查是否有布置好雷,所以打印布置雷的棋盘
}
上面是游戏函数,下面是打印棋盘的函数
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 1;
int j = 1;
for (i = 0; i <= row; i++)
{
printf("%d ", i);//第一列打印对应行的函数,是棋盘的坐标
for (j = 1; j <= col; j++)
{
if (!i)//判断是不是第一行
{
printf("%d ", j);//是的话就打印1-9,是棋盘的坐标
continue;
}
printf("%c ", board[i][j]);//打印9X9棋盘内容
}
printf("\n");
}
printf("-------------------\n");
}
函数:PrintBoard(char ,int ,int)
功能:打印棋盘
打印棋盘的时候打印了坐标,方便用户输入坐标
效果图如下(上面的棋盘是显示给用户看的,下面的棋盘是布置雷的,1代表雷)
int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col)
{
static int step = 0;//设置静态变量统计玩家走的步数
int sum = 0;
if (step == 71)//一共81个棋子,10个雷,只需走完71步说明扫雷成功
{
printf("恭喜你扫雷成功!\n");
step = 0;//游戏结束重置步数
return 0;
}
int x = 0;
int y = 0;
printf("请选择排雷的坐标\n");
while (1)
{
scanf("%d %d", &x, &y);
if (x < 1 || x>9 || y < 1 || y>9 || show[x][y] != '*')//判断坐标是否合法
{
printf("该坐标已经被扫过了或者非法,请重新输入坐标\n");
continue;//回到输入坐标的地方
}
if (board[x][y] == '1')//通过玩家输入的坐标在board棋盘上判断对应坐标是否为雷
{
printf("你被炸死了,游戏结束\n");
PrintBoard(board, ROW, COL);//玩家踩到雷之后打印布置雷的棋盘让玩家回顾
step = 0;//游戏结束重置步数
return 0;//跳出函数
}
else
break;//如果坐标合法并且该坐标没有雷则开始遍历该坐标周围8个位置
}
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
sum += board[i][j];//这里是把字符的ASCLL码值加起来
}
}
show[x][y] = sum - 8 * 48;//字符0的ASCLL码值是48,上面把坐标周围以及坐标自身的ASCLL码值加起来,最后再减8个0的ACSLL码值得到的就是周围的雷的数量的ASCLL码值(读者可自行验算!!!)
step++;//扫了一次雷,步数加一
return 1;//返回1,游戏函数的while循环继续
}
函数:Play(char ,char ,int ,int )
功能:首先判断玩家是否扫雷成功,然后判断给出的坐标是否有雷并且是否合法,再进行扫雷
效果图:(上面的1-9和左边的1-9是坐标,方便玩家快速查找到对应位置的坐标,棋盘中间的数字代表周围8个位置有几个雷,0代表没有雷。)
如上图,我们不难发现,坐标为5 5的位置是字符0,说明周围8个位置没有雷,那么我们能不能让他把周围8个格子替换成空格,然后重新遍历周围8个位置,每一个位置再遍历自己周围没有被遍历过的位置,把周围没有雷的替换成空格,直到遇到周围8个位置有雷的,就像下面这样。
这样就可以避免自己一个一个手动输入坐标。
这个可以看成一个连锁反应,周围8格没有雷,就把周围8格的每一格的 周围8格中 没有被遍历过的 遍历一次,一直这样,直到遇到边界或者周围有雷的位置。
很显然,这可以用到递归,我们来专门写一个遍历周围8个位置的函数,用来替换上面讲过的Play函数最后面的判断代码(也就是两个for循环组成的那个代码)。
int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col)
{
int sum = 0;
if (step == 71)//一共81个棋子,10个雷,只需走完71步说明扫雷成功
{
printf("恭喜你扫雷成功!\n");
return 0;
}
int x = 0;
int y = 0;
printf("请选择排雷的坐标\n");
while (1)
{
scanf("%d %d", &x, &y);
if (x < 1 || x>9 || y < 1 || y>9 || show[x][y] != '*')//判断坐标是否合法
{
printf("该坐标已经被扫过了或者非法,请重新输入坐标\n");
continue;//回到输入坐标的地方
}
if (board[x][y] == '1')//通过玩家输入的坐标在board棋盘上判断对应坐标是否为雷
{
printf("你被炸死了,游戏结束\n");
PrintBoard(board, ROW, COL);//玩家踩到雷之后打印布置雷的棋盘让玩家回顾
return 0;//跳出函数
}
else
break;//如果坐标合法并且该坐标没有雷则开始遍历该坐标周围8个位置
}
Judge(show, board, x, y);//整个函数只有这里和开头有变化(静态变量step被设置成全局变量了)
return 1;//返回1,游戏函数的while循环继续
}
接下来就是用递归实现的大规模扫雷功能的函数了
int Judge(char show[ROWS][COLS], char board[ROWS][COLS], int x, int y)
{
int sum = 0;
if (x < 1 || x > 9 || y < 1 || y > 9 || show[x][y] != '*')//判断坐标是否合法,以及最重要的通过show棋盘来判断该坐标是否已经被扫过了
return 0;//如果是则退出函数
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
sum += board[i][j];//这里用来统计该坐标周围8个位置的雷的数量
}
}
if ((sum - 8 * 48) == '0')//判断该坐标周围8个位置是否有雷
{
show[x][y] = 32;//如果这个坐标周围没有雷则会被替换成空格
step++;//步数加一
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
Judge(show, board, i, j);//这里就是对该坐标周围8个坐标的 周围 没有被遍历过的坐标遍历一次(统计坐标周围是否有雷),形成递归
}
}
}
else
{
show[x][y] = sum - 8 * 48;//如果有雷则像之前的Play函数一样把统计的雷的数量以字符形式放到show棋盘对应的坐标上
step++;//步数加一
}
return 0;
}
如果读者不太清楚这其中的原理可以自己拿笔和纸动手画一画,一切就会豁然开朗。
在这里我把step变成了全局变量,为了方便在递归函数中进行步数统计。而step的初始化我放在了game函数的开头。
下面是游戏全部的代码:
#include
#include
#include
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
int step = 0;
void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{//上面的ROWS和COLS是告诉函数我们传的是一个11X11的数组
int i = 1;
int j = 1;
if (ch == '0')//这里判断初始化的数组是不是布置雷的
{
for (i = 0; i <= 10; i++)
{
for (j = 0; j <= 10; j++)
{
board[i][j] = ch;//将整个数组都初始化为字符'0'
}
}
return;//初始化完成,退出函数
}
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
board[i][j] = ch;//将呈现给用户看的棋盘都初始化为字符'*',代表这个坐标未被扫描
}
}
}
void SetBoard(char board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int num = 0;
while (num < 10)//统计放置的雷有没有达到10个
{
x = rand() % 9 + 1;//这里是随机生成X轴坐标,范围1-9
y = rand() % 9 + 1;//这里是随机生成X轴坐标,范围1-9
if (board[x][y] == '0')//判断随机生成的坐标是不是雷
{
board[x][y] = '1';//不是雷就在这放雷
num++;
}
}
}
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 1;
int j = 1;
for (i = 0; i <= row; i++)
{
printf("%d ", i);//第一列打印对应行的函数,是棋盘的坐标
for (j = 1; j <= col; j++)
{
if (!i)//判断是不是第一行
{
printf("%d ", j);//是的话就打印1-9,是棋盘的坐标
continue;
}
printf("%c ", board[i][j]);//打印9X9棋盘内容
}
printf("\n");
}
printf("-------------------\n");
}
int Judge(char show[ROWS][COLS], char board[ROWS][COLS], int x, int y)
{
int sum = 0;
if (x < 1 || x > 9 || y < 1 || y > 9 || show[x][y] != '*')//判断坐标是否合法,以及最重要的通过show棋盘来判断该坐标是否已经被扫过了
return 0;//如果是则退出函数
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
sum += board[i][j];//这里用来统计该坐标周围8个位置的雷的数量
}
}
if ((sum - 8 * 48) == '0')//判断该坐标周围8个位置是否有雷
{
show[x][y] = 32;//如果这个坐标周围没有雷则会被替换成空格
step++;//步数加一
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
Judge(show, board, i, j);//这里就是对该坐标周围8个坐标的 周围 没有被遍历过的坐标遍历一次(统计坐标周围是否有雷),形成递归
}
}
}
else
{
show[x][y] = sum - 8 * 48;//如果有雷则像之前的Play函数一样把统计的雷的数量以字符形式放到show棋盘对应的坐标上
step++;//步数加一
}
return 0;
}
int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col)
{
int sum = 0;
if (step == 71)//一共81个棋子,10个雷,只需走完71步说明扫雷成功
{
printf("恭喜你扫雷成功!\n");
return 0;
}
int x = 0;
int y = 0;
printf("请选择排雷的坐标\n");
while (1)
{
scanf("%d %d", &x, &y);
if (x < 1 || x>9 || y < 1 || y>9 || show[x][y] != '*')//判断坐标是否合法
{
printf("该坐标已经被扫过了或者非法,请重新输入坐标\n");
continue;//回到输入坐标的地方
}
if (board[x][y] == '1')//通过玩家输入的坐标在board棋盘上判断对应坐标是否为雷
{
printf("你被炸死了,游戏结束\n");
PrintBoard(board, ROW, COL);//玩家踩到雷之后打印布置雷的棋盘让玩家回顾
return 0;//跳出函数
}
else
break;//如果坐标合法并且该坐标没有雷则开始遍历该坐标周围8个位置
}
Judge(show, board, x, y);//整个函数只有这里有变化
return 1;//返回1,游戏函数的while循环继续
}
void game()
{
step = 0;//全局变量step,每局游戏开始前重置步数
char board[11][11] = { 0 };//放置雷的棋盘
char show[11][11] = { 0 };//呈现给用户看的棋盘
InitBoard(show, ROW, COL, '*');//初始化棋盘的函数
InitBoard(board, ROW, COL, '0');
SetBoard(board, ROW, COL);//布置雷
do {
PrintBoard(show, ROW, COL);//循环打印show棋盘
} while (Play(show, board, ROW, COL));//当游戏结束时Play返回0,结束循坏跳出game函数
}
void menu()
{
printf("*********************************\n");
printf("****** 1.五子棋 *********\n");
printf("****** 0.退出游戏 *********\n");
printf("*********************************\n");
}
int main()
{
srand((unsigned int)time(NULL));//设置随机数生成器
//srand()和time(),time函数的参数设置为空指针,强制转换为无符号整形(unsigned int)
int input = 0;
do
{
menu();
printf("请选择->\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);//当输入0退出游戏时,input为0,循环停止
return 0;
}
当然这样看起来比较臃肿,可以参考我三子棋的代码优化,可以优化成下面这样子。
高质量C语言实现三子棋
game.h(这是自己创建的头文件,放的是所有函数的声明和引用的库函数的头文件)
#include
#include
#include
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
void menu();
void InitBoard(char board[ROWS][COLS], int row, int col, char ch);
void SetBoard(char board[ROWS][COLS], int row, int col);
void PrintBoard(char board[ROWS][COLS], int row, int col);
int Judge(char show[ROWS][COLS], char board[ROWS][COLS], int x, int y);
int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col);
test.c
#include"game.h"
int step = 0;
void game()
{
step= 0;
char board[11][11] = { 0 };
char show[11][11] = { 0 };
InitBoard(show,ROW,COL,'*');
InitBoard(board, ROW, COL,'0');
SetBoard(board,ROW,COL);
do {
PrintBoard(show, ROW, COL);
} while (Play(show, board, 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");
break;
}
} while (input);
return 0;
}
game.c
#include"game.h"
extern int step;
void menu()
{
printf("*****************************\n");
printf("****** 1.play ******\n");
printf("****** 0.exit ******\n");
printf("*****************************\n");
}
void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{
int i = 1;
int j = 1;
if (ch == '0')
{
for (i = 0; i <= 10; i++)
{
for (j = 0; j <= 10; j++)
{
board[i][j] = ch;
}
}
return;
}
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
board[i][j] = ch;
}
}
}
void SetBoard(char board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int num = 0;
while (num < 10)
{
x = rand() % 9 + 1;
y = rand() % 9 + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
num++;
}
}
}
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 1;
int j = 1;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
if (!i)
{
printf("%d ", j);
continue;
}
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("-------------------------------------\n");
}
int Judge(char show[ROWS][COLS], char board[ROWS][COLS], int x, int y)
{
int sum = 0;
if (x < 1 || x > 9 || y < 1 || y > 9 || show[x][y] != '*')
return 0;
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
sum += board[i][j];
}
}
if ((sum - 8 * 48) == '0')
{
show[x][y] = 32;
step++;
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
Judge(show, board, i, j);
}
}
}
else
{
show[x][y] = sum - 8 * 48;
step++;
}
return 0;
}
int Play(char show[ROWS][COLS], char board[ROWS][COLS], int row, int col)
{
if (step == 71)
{
printf("恭喜你扫雷成功!\n");
return 0;
}
int x = 0;
int y = 0;
printf("请选择排雷的坐标\n");
while (1)
{
scanf("%d %d", &x, &y);
if (x < 1 || x>9 || y < 1 || y>9 || show[x][y] != '*')
{
printf("该坐标已经被扫过了或者非法,请重新输入坐标\n");
continue;
}
if (board[x][y] == '1')
{
printf("你被炸死了,游戏结束\n");
PrintBoard(board, ROW, COL);
return 0;
}
else
break;
}
Judge(show, board, x, y);
return 1;
}
本次扫雷的小游戏到这里就结束了,非常感谢各位读者能看到这里,希望这篇文章也对你们有一点帮助,如果有什么问题或者意见都可以在评论区告诉我,我也会认真解释和采纳的。