和三子棋一样,扫雷的核心思想也是二维数组的利用。
扫雷游戏还用到:循环语句、分支语句、函数的递归以及指针等知识,具体知识在前几篇文章中有过讲解。
利用C语言在命令窗口实现扫雷游戏:
1、输入方式:玩家输入坐标,如果不是雷,游戏继续;如果是雷,游戏结束。
2、坐标的判定:如果该坐标周边有雷,则显示周围雷的信息;如果该坐标周围没有雷,则一次展开多项,直到展开到雷的附近。
3、游戏结束判定:当玩家触碰到雷,或者棋盘上所有非雷的区域被玩家全部找到。
目录
主菜单
主菜单用do while循环结构,实现玩完一次游戏不过瘾,还可以接着玩。(do while 循环可以保证整个循环至少运行一次)
//text.c
#include "game.h"
void menu()//菜单
{
printf("********************\n");
printf("***** 1.paly *****\n");
printf("***** 0.exit *****\n");
printf("********************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择(1/0):>\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
为了之后测试可以精确找到bug位置所在,我们要养成每写完一部分代码,就测试一下的良好习惯。这里我们测试一下主菜单部分的代码是否可以正常运行。
游戏部分
1.设置棋盘及初始化
(一)设置棋盘
棋盘的规划:
mine棋盘:存储布雷的信息(不给玩家显示)
show棋盘:玩家游戏的棋盘。(给玩家显示)
棋盘的行和列:
1、通过#define定义常量的方式来设定,方便以后代码的更新。
2、为了之后之后访问二维数组时不越界,设置棋盘时,行和列都要+2。(如果想玩9×9的棋盘,那么在最初设置棋盘时要设置成11×11)
//game.h
//设置棋盘的行和列
#define ROW 11
#define COL 11
//text.c
void game()
{
//将布雷的信息存储到mine中
char mine[ROW][COL] = { 0 };
//将显示的信息存储到show中
char show[ROW][COL] = { 0 };
}
(二)初始化
Initboard:利用for循环遍历二维数组的每一个元素,将棋盘初始化。
初始化mine棋盘:布雷的棋盘初始化全为’0‘。(这里的0为字符0)
初始化show棋盘:显示的棋盘初始化全为’*‘。
//game.h
//初始化
void Initboard(char board[ROW][COL], int row, int col, char set);
//game.c
//初始化棋盘
void Initboard(char board[ROW][COL], int row,int col,char set)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = set;
}
}
}
//text.c
void game()
{
//将布雷的信息存储到mine中
char mine[ROW][COL] = { 0 };
//将显示的信息存储到show中
char show[ROW][COL] = { 0 };
//布雷的棋盘初始化全为0
Initboard(mine, ROW, COL, '0');
//显示的棋盘初始化全为*
Initboard(show, ROW, COL, '*');
}
定义完棋盘后,进入调试模式,通过监视窗口来监视我们的两副棋盘是否正确。
2.布雷
雷的数量也是用define定义,方便之后的修改。
Setting:通过rand随机生成雷的坐标,每次生成坐标时需要判断该坐标是否合法。并且注意,坐标的范围是棋盘游戏的范围,而不是最初设定时的范围。
(若要使用rand函数,需要在主函数中对rand函数进行修饰:srand((unsigned int)time(NULL));,否则每次生成的随机值将一样)
//game.h
//布雷和打印的行、列
#define ROWS ROW-2
#define COLS COL-2
//布雷的数量
#define Easy 10
//game.c
//布雷
void Setting(char board[ROW][COL], int rows, int cols)
{
int count = Easy;
while (count)
{
int x = rand() % rows + 1;
int y = rand() % rows + 1;
if (board[x][y] != 1)
{
board[x][y] = '1';
count--;
}
}
}
//text.c
void game()
{
//将布雷的信息存储到mine中
char mine[ROW][COL] = { 0 };
//将显示的信息存储到show中
char show[ROW][COL] = { 0 };
//布雷的棋盘初始化全为0
Initboard(mine, ROW, COL, '0');
//显示的棋盘初始化全为*
Initboard(show, ROW, COL, '*');
//布雷
Setting(mine, ROWS, COLS);
}
//main函数
srand((unsigned int)time(NULL));
布置完雷后,也是进入调试模式来检查代码运行是否正确。
3.打印棋盘
Dispaly:只需要打印show棋盘即可,定义这里打印棋盘与初始化的方法一致,都是用for循环遍历每个元素。唯一不同的地方在于为了方便输入坐标,这里将行号和列号在第一行和第一列打印。(打印完行号后需要换行,打印列号则不用)
//game.h
//打印棋盘
void Display(char board[ROW][COL], int rows, int cols);
//text.c
//打印棋盘
void Display(char board[ROW][COL], int rows, int cols)
{
int i = 0;
int j = 0;
for (i = 0; i <= rows; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= rows; i++)
{
printf("%d ", i);
for (j = 1; j <= cols; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("\n");
}
4.Cue提示雷的信息
在扫雷游戏中,如果该坐标不是雷,并且周围一圈有雷时,会提示周围雷的个数。
Cue:将玩家输入的x、y参数传给Cue函数,用for循环遍历周围一圈的坐标,用临时变量count来记录周围雷的个数。将布设的雷的坐标设定为’1‘。
//提示
int Cue(char mine[ROW][COL], int x, int y)
{
int i = 0;;
int j = 0;
int count = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (mine[x + i][y + j] == '1')
{
count++;
}
}
}
return count;
}
5.Unfold展开功能
在扫雷游戏中,如果该坐标周边有雷,则显示周围雷的信息;如果该坐标周围没有雷,则一次展开多项,直到展开到雷的附近。
这里主要利用Unfold函数的递归,配合定义的Cue函数来实现。
递归需要满足以下两点:
1、设定递归结束条件。
- 坐标超限
- 该坐标是雷
- 该坐标已被查找
- 该坐标已标记雷的信息
2、已查找过的坐标不可重复查找,否则将会陷入死递归。
- mine[x][y] = '2'
- 将查找过的坐标标记为非0,非1的数字即可
此外,还需要在函数外部定义count,count为棋子的总数-布雷的数量,用来评判游戏结束。
外部传count参数时需要进行取地址,内部函数递归时,count已经是地址了,不要再取地址了。
//多个展开
void Unfold(char mine[ROW][COL], char show[ROW][COL], int x, int y,int* count)
{
int i = 0;
int j = 0;
//终结标志:
//1、坐标超限
//2、该坐标是雷
//3、该坐标已被查找
if (1 <= x && x <= 9 && 1 <= y && y <= 9 && mine[x][y] == '0' && show[x][y] == '*')
{
mine[x][y] = '2';//查找过的坐标在mine里进行标记
if (Cue(mine, x, y))//周围有雷提示雷的数量
{
show[x][y] = '0' + Cue(mine, x, y);//将整数转换成字符
(*count)--;
}
else
{
show[x][y] = ' ';//没有雷就显示空格
(*count)--;
for (i = -1; i <= 1; i++)//针对(x,y)周围八个格子进行查找
{
for (j = -1; j <= 1; j++)
{
Unfold(mine, show, x + i, y + j,count);//进行递归,这里的count已经是地址了,不要再取地址了
}
}
}
}
}
6.Guess排查
在玩家输入坐标后,需要对该坐标进行判断:
- 坐标非法,重新输入
- 坐标合法,进入Unfold函数,检查是否展开,并提示雷的信息
- 坐标是炸弹,游戏结束
这里循环的依据就是count,当count为0时,跳出循环,宣布游戏结果。
//game.h
//排查
void Guess(char mine[ROW][COL], char show[ROW][COL], int rows, int cols);
//game.c
//排查
void Guess(char mine[ROW][COL], char show[ROW][COL], int rows, int cols)
{
int x = 0;
int y = 0;
//记录排查次数
int count = rows * cols - Easy;
while (count)
{
scanf("%d %d", &x, &y);
//判断坐标是否正确
//1、坐标在范围内
//2、坐标没被使用
if (1 <= x && x <= 9 && 1 <= y && y <= 9 && show[x][y] == '*')
{
if (mine[x][y] != '1')
{
printf("Continue......\n");//不是雷就继续
show[x][y] = ' ';
Display(show, ROWS, COLS);
count--;
}
else
{
printf("很遗憾,再接再厉\n");
DisplayEnd(mine,show, ROWS, COLS);//将布雷的情况打印出来。
break;
}
}
else
printf("坐标非法,请重新输入\n");
}
if (count == 0)
{
printf("恭喜你\n");
Display(show, ROWS, COLS);
}
}
在游戏结束后,还需要将游戏的结果及布雷的情况打印出来,这里不单单只是打印mine棋盘,需要将mine和show棋盘结合一下。
//打印结局
void DisplayEnd(char mine[ROW][COL], char show[ROW][COL], int rows, int cols)
{
{
int i = 0;
int j = 0;
for (i = 0; i <= rows; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= rows; i++)
{
printf("%d ", i);
for (j = 1; j <= cols; j++)
{
if (mine[i][j] == '1')//如果是雷就打印出来
{
printf("%c ",mine[i][j]);//与打印棋盘唯一不同之处
}
else
{
printf("%c ", show[i][j]);
}
}
printf("\n");
}
printf("\n");
}
}
试玩
看来运气并不好,没有展开一片区域,换个坐标试试。
尝试输入错误的坐标,检查系统是否能检测出来。
(4,5)明显是个雷,让我们看一下结果是否如我们预料到那样。
通过更改define定义的常量,来更改游戏的难度。当然也可以把扫雷做成界面的形式,具体的UI设计也非常简单,这里只讲基础的算法知识,UI的设计可以自行学习。
整体代码
整体代码如下,当然还有很多值得优化的地方,大家有任何想法可以互相交流。
(一)game.h
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//设置棋盘的行、列
#define ROW 11
#define COL 11
//布雷和打印的行、列
#define ROWS ROW-2
#define COLS COL-2
//布雷的数量
#define Easy 10
//初始化
void Initboard(char board[ROW][COL], int row, int col, char set);
//布雷
void Setting(char board[ROW][COL], int rows, int cols);
//打印棋盘
void Display(char board[ROW][COL], int rows, int cols);
//打印结局
void DisplayEnd(char mine[ROW][COL], char show[ROW][COL], int rows, int cols);
//排查
void Guess(char mine[ROW][COL], char show[ROW][COL], int rows, int cols);
(二)game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//初始化棋盘
void Initboard(char board[ROW][COL], int row, int col, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = set;
}
}
}
//布雷
void Setting(char board[ROW][COL], int rows, int cols)
{
int count = Easy;
while (count)
{
int x = rand() % rows + 1;
int y = rand() % rows + 1;
if (board[x][y] != 1)
{
board[x][y] = '1';
count--;
}
}
}
//打印棋盘
void Display(char board[ROW][COL], int rows, int cols)
{
int i = 0;
int j = 0;
for (i = 0; i <= rows; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= rows; i++)
{
printf("%d ", i);
for (j = 1; j <= cols; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("\n");
}
//打印结局
void DisplayEnd(char mine[ROW][COL], char show[ROW][COL], int rows, int cols)
{
{
int i = 0;
int j = 0;
for (i = 0; i <= rows; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= rows; i++)
{
printf("%d ", i);
for (j = 1; j <= cols; j++)
{
if (mine[i][j] == '1')
{
printf("@ ");
}
else
{
printf("%c ", show[i][j]);
}
}
printf("\n");
}
printf("\n");
}
}
//提示周围雷的信息
int Cue(char mine[ROW][COL], int x, int y)
{
int i = 0;;
int j = 0;
int count = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (mine[x + i][y + j] == '1')
{
count++;
}
}
}
return count;
}
//多个展开
void Unfold(char mine[ROW][COL], char show[ROW][COL], int x, int y,int* count)
{
int i = 0;
int j = 0;
//终结标志:
//1、坐标超限
//2、该坐标是雷
//3、该坐标已被查找
if (1 <= x && x <= 9 && 1 <= y && y <= 9 && mine[x][y] == '0' && show[x][y] == '*')
{
mine[x][y] = '2';//查找过的坐标在mine里进行标记
if (Cue(mine, x, y))//周围有雷提示雷的数量
{
show[x][y] = '0' + Cue(mine, x, y);//将整数转换成字符
(*count)--;
}
else
{
show[x][y] = ' ';//没有雷就显示空格
(*count)--;
for (i = -1; i <= 1; i++)//针对(x,y)周围八个格子进行查找
{
for (j = -1; j <= 1; j++)
{
Unfold(mine, show, x + i, y + j,count);//进行递归,这里的count已经是地址了,不要再取地址了
}
}
}
}
}
//排查
void Guess(char mine[ROW][COL], char show[ROW][COL], int rows, int cols)
{
int x = 0;
int y = 0;
//记录排查次数
int count = rows * cols - Easy;
while (count)
{
printf("请输入坐标:>\n");
scanf("%d %d", &x, &y);
//判断坐标是否正确
//1、坐标在范围内
//2、坐标没被使用
if (1 <= x && x <= 9 && 1 <= y && y <= 9 && show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("很遗憾,再接再厉\n");
DisplayEnd(mine, show, ROWS, COLS);//将布雷的情况打印出来。
break;
}
else
{
//不是雷就继续
Unfold(mine, show, x, y,&count);
Display(show, ROWS, COLS);
printf("Continue......\n");
}
}
else
printf("坐标非法,请重新输入\n");
}
if (count == 0)
{
printf("恭喜你\n");
Display(show, ROWS, COLS);
}
}
(三)main.c
#define _CRT_SECURE_NO_WARNINGS
//扫雷
#include "game.h"
void menu()
{
printf("********************\n");
printf("***** 1.paly *****\n");
printf("***** 0.exit *****\n");
printf("********************\n");
}
void game()
{
//将布雷的信息存储到mine中
char mine[ROW][COL] = { 0 };
//将显示的信息存储到show中
char show[ROW][COL] = { 0 };
//布雷的棋盘初始化全为0
Initboard(mine, ROW, COL, '0');
//显示的棋盘初始化全为*
Initboard(show, ROW, COL, '*');
//布雷
Setting(mine, ROWS, COLS);
//打印棋盘
Display(show, ROWS, COLS);
//排查
Guess(mine, show, ROWS, COLS);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择(1/0):>\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}