扫雷
1.先如图创建一个test.c和game.c文件,再创建一个game.h文件。test.c文件是放扫雷框架的文件也是我们最后运行的文件。game.c文件是用来放扫雷游戏中各个函数的实现。game.h这个头文件的作用是用来声明函数。这样的作法能让我们代码整体看起来好看一些,不会太长。
2.看下方的代码(在test.c里),main函数中只有一个Test函数,而这个Test函数就是我们扫雷游戏的开始。
int main()
{
Test();
return 0;
}
我们现在要做的就是往Test( );一点一点的加入扫雷的代码。现构造一个Test( )函数,代码如下:
void Test()
{
;
}
int main()
{
Test();
return 0;
}
Test函数的作用就是用来运行扫雷的,所以不需要返回值和传参数所以写void和()里不写参数。所以这个构造是:
void Test()
{
}
接着我们开始写菜单,构造一个Menu函数,里面负责打印菜单,所以不需要返回值和传参数。所以写void和()里不写参数
void Menu()
{
printf("-----------------------\n");
printf("---------1. play-------\n");
printf("---------0. exit -----\n");
printf("-----------------------\n");
}
现在在Test函数中调用Menu函数,并运行。代码与运行结果如下:
void Menu()
{
printf("-----------------------\n");
printf("---------1. play-------\n");
printf("---------0. exit -----\n");
printf("-----------------------\n");
}
void Test()
{
Menu();
}
int main()
{
Test();
return 0;
}
运行结果:
现在我们已经写好了菜单,接下来就是让玩家们输入1或者0,来选择玩与不玩。现在想一下,当我们选择1后开始游戏。游戏结束后我们如果还想玩要怎么办,难道要Ctrl+F5重新运行程序吗?这样显然比较麻烦,我们可以用循环来写。而在我们运行这个程序的时候显然都是现打印菜单随后才会开始选择进行是否开始游戏。所以这里我们可以选择do while循环,上来先打印菜单,然后再判断是否符合条件进行循环。写法如下:
void Test()
{
do
{
Menu();
}while()
}
看完这段代码,大家会想,这个do while()循环中的循环条件该写什么呢?我现在来告诉大家。前面我们写的菜单有两个选项,分别是1.play与0.exit。打印完菜单后就是让玩家来选择,我们可以创建一个变量input来存放玩家所需要选择的数字。当玩家输入1时,打印开始游戏。输入0时则打印退出游戏。但每个玩家都会选择1或者0吗?像我这样粗心的人偶尔会输错数字,那是不是应该让我们输入错误时要有提醒,让我们重新输入呀。这时候我们就可以用到switch case语句!!!写法如下:
void Test()
{
int input = 0;
do
{
Menu();
printf("请输入选项:\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请输入1或者0:\n");
}
} while (input);
}
眼尖的小伙伴们肯定发现了do while()循环这里的循环条件是input,为什么呢?其实这里有妙用哇。当玩家输入1时,程序会打印“开始游戏”,打印完后现在的input还是1,while(1)条件为真,继续循环,接着打印菜单,这样就可以再次选择1或者0.若选择0,则打印“退出游戏”,此时input为0。while(0),条件为假,循环终止。当我们不小心填入其他数字时,则会打印“输入错误,请输入1或者0:”while(其他数字)则为真,继续循环打印菜单。继续选择。以下分别为选择1、0、5的代码运行结果:
为1:
为0:
为5:
现在一个游戏的基本逻辑已经完成啦,我现在把所有代码贴出来让大家方便观看:
void Menu()
{
printf("-----------------------\n");
printf("---------1. play-------\n");
printf("---------0. exit -----\n");
printf("-----------------------\n");
}
void Test()
{
int input = 0;
do
{
Menu();//打印菜单
printf("请输入选项:\n");
scanf("%d", &input);
switch (input)//判断是否玩游戏
{
case 1:
printf("开始游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请输入1或者0:\n");
}
} while (input);
}
int main()
{
Test();
return 0;
}
现在我们就可以开始写扫雷啦!!!
首先我们现写一个game函数,里面用来放扫雷的各个函数。
还是没有返回值和参数,所以写void和()里不写参数
void Game()
{
}
随后在Test()函数中调用Game,如下图:
当我们选择1时,进入case 1:就可以开始游戏了。现在就开始往Game()里写扫雷的代码了。
扫雷代码
游戏分析:
1.布置雷
2.扫雷(排查雷)
这个位置是雷就炸死,游戏结束
这个位置不是雷,显示这个位置周围有几个雷
3.把所有不是雷的位置找到,剩下的都是雷的时候,游戏结束。
代码实现:
用0表示没有雷,1表示有雷
1.我们要有一个9*9的地方(接下来我会把这个地方称为棋盘)并初始化棋盘
2.打印棋盘让我们能够看到
3.放置10个雷
4.进行扫雷
-------------------------------------------分割线--------------------------------------------------------
这里是扫雷网页版的链接,下面讲解的图片截图来自:https://www.minesweeper.cn/
-------------------------------------------分割线--------------------------------------------------------
1.要想有一个9x9的棋盘我们很容易可以想到用一个二维数组来当我们的棋盘。那我们要几行几列呢?有的小伙伴肯定会想9*9的棋盘当然是9行9列啊。其实不然,扫雷是点击一个格子然后若这个格子没有雷,那么就在这个格子里显示其周围8个格子的雷的个数。
如这张3*3的图,当我们点击中间的位置时,然后就显示了1,代表这个格子周围只有一个雷。
接下来看下面这张图:
当我们点击这张图的角(边界)时,按道理来说应该是会出现这个格子周围8个格子里雷的个数。
这时候代码就会在我所画的3x3的格子里开始查找是否有雷。但我们可以很明显的发现,红色部分的格子明显不在我们最开始所想的9*9二维数组里面。这时候若对红色部分进行查找雷就会对数组非法访问。
为了避免非法访问数组的问题,我们就可以把这个二维数组进行扩大至11*11(下面的图我就大致画一下,大家将就一下看吧)
这时候我们在对刚刚那个位置进行点击,这时候就不会出现非法访问的情况。到时候我们把棋盘11行11列全都都放0进行初始化,9 x9的地方随机放雷。当我们点击9x9的角(边界)的时候,就算查找到外面也没关系,外面是0,不会影响我们扫雷的结果。
现在我们就解决好了要设置多大的棋盘。现在我们就可以0表示没有雷1表示有雷。但其实这样的表示是有问题的
左边这个张图我们可以很轻松的发现2这里周围是有两个雷的,而右图其实我们就不好认出哪个是雷的信息(显示雷的个数),哪个是雷。这样就有歧义。这时候我们就可以创建两个棋盘。棋盘1用来放置雷,棋盘2用来放雷的信息
当我们点击2-2这个位置时,看到周围只有一个雷,然后把这个位置周围有多少个雷,放入右边这张图。这样就不会出现歧义了。我们可以把棋盘2全都初始化成✳(shift + 8,我在写的时候不知道为什么打不出来,就用那个符号代替了)符号。来代替扫雷页面里砖块格的形状。既然要把棋盘2全都初始化成’✳’,那么就要用char来创建数组,但是字符数组不能放数字呀,我们的0和1要怎么放进去呢?简单!我们可以用字符’0’和字符’1’,放入。
现在开始创建并初始化棋盘,现在我们自己创建的头文件game.h里创建变量
#define Row 9//表示行 Row = 9
#define Col 9//表示列 Col = 9
#define Rows Row + 2//表示Rows = 9 + 2 = 11
#define Cols Col + 2//表示Cols = 9 + 2 = 11
大家记住Row和Col表示的是9.Rows和Cols表示的是11,下面我讲的时候就不会特意说 “谁表示多少数了”。
然后在自己的test.c文件里引用自己的头文件game.h.注意,引用自己的头文件不能用<>,要用""。
#include "game.h"
创建二维数组
然后是初始化数组,我们可以写一个Init_board()函数来对数组初始化。对二维数组进行遍历。传的参数是数组和9*9的行和列,和要初始化的内容。在头文件进行声明,在Game.c文件里实现函数
传参数
这里传的是数组mine,Rows和Cols是把11行和11列传过去,这里之所以传的不是Row和Col是因为我们初始化的时候要把整个二维数组都初始化,我们定义的数组是11行11列的,所以这里传Rows和Cols。最后这里传的字符‘0’,是我们要初始化的内容
函数的声明
声明完后就是对函数的实现,代码如下:
首先不要忘记包含头文件game.h
1这里写 char board[Rows][Cols],
不要写成char board[Row][Col]
2与3是为了接收行和列
4这里char set是为了接收要初始化的内容
写完声明和函数的实现,我们就可以初始化数组了
void Game()
{
char mine[Rows][Cols];
char show[Rows][Cols];
Init_board(mine, Rows, Cols, '0');//调用函数
Init_board(show, Rows, Cols, '*');//注意,单个字符是单引号
}
运行代码后,mine数组就全是字符’0’,show数组全是字符’*'.
---------------------------------分界线------------------------------------------------------------------
当我们初始化后,我们写一个打印棋盘Print_board()函数来确认一下是否初始化成功。同样是进行声明(game.h),实现(game.c),调用(test.c)
我们要打印的内容是图片里的砖块格子所占的地方,所以等一下传行与列时传的是Row与Col
声明:
实现:
void Print_board(char board[Rows][Cols], int row, int col)
{
int i = 0;
for (i = 1; i <= row; i++)
{
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c", board[i][j]);
}
}
printf("\n");
}
调用:
然后运行一下看是否初始化成功:
如图,成功初始化了。为了好看,我现在把打印函数改一下,如下:
void Print_board(char board[Rows][Cols], int row, int col)
{
printf("------------扫雷------------\n");
int i = 0;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
运行后结果如下:
这样就好看多了,由于这个只是为了美观,就不具体说怎么写了,大家看一下代码应该也知道咋写。
在我们玩的时候,肯定是要把mine数组藏起来,show数组给大家看,所以这里要记得只打印show数组哦。
这时候运行代码就是这样的:
---------------------------------分界线------------------------------------------------------------------
接下来就是往棋盘里面放置雷啦(10个雷)
思路,随机生成1-9的横坐标X与纵坐标Y
比如生成X = 2,Y = 3.现在判断board[x][y]是否为字符’0’,若是,则把字符’1’,赋值给board[x][y]。若是字符’1’,则重新循环。我们到时候可以传一个参数count,代表雷的个数。使用while(count)循环,每设置一个雷就count–;当10个雷全都设置完成后,while(count)等价于while(0),为。循环结束。代码如下:
声明:
实现:
调用:
注意使用rand()生成随机数还需要srand()生成随机数种子
写法:
srand((unsigned)time(NULL));
这里就不过多讲解为什么这样写了,大家可以搜索随机数生成,有大佬讲的十分详细~~~
位置写在这就行:
记得引用头文件<time.h>和<stdlib.h>
我们现在验证一下是否放置好雷,我们在调用Set_board()函数后。再调用一下Print_board(),把mine数组打印出来
Set_mine(mine, Row, Col, count);
Print_board(mine, Row, Col);
然后运行代码:
一共10个雷,设置成功!!!大家要记得。看完是否设置成功后要把打印mine数组给注释或者删掉哦。
---------------------------------分界线--------------------------------------
终于到了最后一步!!!扫雷
思路:我们前面已经把10个雷给设置好了,现在要做的就是排除雷。我们可以输入横坐标和纵坐标来传到mine数组里。若这里不是雷,则计算周围有几个雷,最终把雷的个数传给show数组。当我们输入坐标时,首先要避免二维数组的非法访问。所以我们可以用if语句来判断输入的范围。若不在这个方位里,则重新输入。代码如下:
int x = 0;
int y = 0;
scanf ("%d %d", &x, &y);
if (x > 0 && x <=row && y > 0 && y<= col)
{
;//开始排查雷
}
else
{
printf("非法输入\n");
}
若我们输入的值满足条件,则开始排查雷
思路:现在我们已经输入了X 与 Y,开始判定mine[x][y] 是否为 ‘0’。
if (mine [x][y] == '0')//是的话,就把周围的雷找出来
{
;//找周围的雷,找完后把雷的个数传给show数组
}
else//若是雷
{
printf("很遗憾,你扫到雷了\n");//扫到雷后我们可以把有雷
//的数组打印出来给玩家看
Print_board(mine, Row, COl);
}
最后我们用一个循环重复这个过程
while ()
{
printf("请输入坐标:");
scnaf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (mine[x][y] == '0')
{
;//查找周围的雷
}
else
{
printf("很遗憾,游戏失败\n");
Print_board(mine, Row, Col);
break;
}
}
else
{
printf("非法输入");
}
}
}
现在我们想一下while循环的循环终止条件是什么。我们这是一个9*9的棋盘并且有10个雷。
9*9 = 81,一共有81个位置,现在把10个雷放进去,我们还剩下9 * 9 - 10 = 71个空白的位置。我们扫雷的本质就是在不点到雷的情况下把空白位置全部点完就游戏成功。所以我们这时候可以设置一个变量win,每一次点到空白处就win++;当win,直到循环71次后所有空白都点完了,游戏成功,所以while循环的条件可以这样写while(win < row * col - count);
(count是我们之前定义的雷的个数,为10)
代码如下:
while (win < row * col - count)
{
printf("请输入坐标:");
scnaf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (mine[x][y] == '0')
{
;//查找周围的雷
win++;
}
else
{
printf("很遗憾,游戏失败");
Print_board(mine, Row, Col);
break;
}
}
else
{
printf("非法输入");
}
}
}
我们把这段代码放到一个函数里Find_mine()
声明:
实现:
void Find_mine(char mine[Rows][Cols], char[Rows][Cols]int row, int col, int count)
{
int win = 0;
int x = 0;
int y = 0;
while (win < row * col - count)
{
printf("请输入坐标:");
scnaf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (mineF[x][y] == '0')
{
;//查找周围的雷
win++;
}
else
{
printf("很遗憾,游戏失败\n");
Print_board(mine, Row, Col);
break;
}
}
else
{
printf("非法输入\n");
}
}
}
调用:
---------------------------------分界线--------------------------------------
现在我们要做的就是写一个查找周围有几个雷的函数Find_around();我们已经输入了x 与 y。现在就是查找x 与 y周围的地方是否有雷,那周围地方的坐标是什么呢?我画一个图大家就知道了
这些就是(x,y)周围的坐标了。由于我们设置mine数组的时候刚开始全都初始化0,设置雷的时候部分格子就变为1。要计算周围有几个雷其实只要把(x,y)周围的值全都加起来就行。不是雷的地方是0,有雷的地方是1,这样都加起来最终就是雷的个数了。细心的小伙盘这时候应该发现了,我们这个是char 定义的数组啊,里面的元素是字符’0’和字符’1’呀,要怎么相加呢?
这是字符’0’到字符’9’之间的ASCII码表:
如图可知字符’0’的ASCII码值为48,字符’1’的ASCII码值为49。
可以理解成字符’0’代表48,字符’1’代表49。
字符’0’ - '0’可以看成48 - 48 = 0;
字符’1’ - '0’可以看成49 - 48 = 1;
所以我们可以先把mine[x][y]周围的8个字符全部相加,最后在减去(8 乘以 ‘0’)最后的结果就是数字也就是雷的个数,代码如下:
注意!!我写到后面发现,Find_mine()函数里少传了一个show数组,就只有下面这张图片,其他的我已经改好了
正确的写法如下:
void Find_mine(char mine[Rows][Cols], char show[Rows][Cols],int row, int col, int count)
函数的调用:
这里我们直接把Find_around()写在Find_mine()函数实现的上方:
这个函数需要找到雷的个数并放回,所以返回值为int
int Find_around(char mine[Rows][Cols], char show[Rows][Cols], int x, int y)
{
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] - 8 * '0';
}
现在我们就得到了mine[x][y]周围雷的个数,现在把这个雷的个数传给show数组。我们创建一个变量num把Find_around的返回值传给num。再把num的值赋值给show[x][y],要注意的是,我们得到的num是一个整型值,给show数组的时候记得加上字符’0’,让这个数字变成字符的数字
show = num + '0';
代码如下:
---------------------------------分界线---------------------------------------------------
最后这个代码还有一点点的问题,当我们踩到雷的时候,循环会结束,跳出循环,到达我打了很多”//“那里(直接看下面代码最后几行),当我们排除所有雷时,循环也会结束,也是到那个地方”///“。
void Find_mine(char mine[Rows][Cols], char show[Rows][Cols],int row, int col, int count)
{
int win = 0;
int x = 0;
int y = 0;
while (win < row * col - count)
{
printf("请输入坐标:");
scnaf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (mine[x][y] == '0')
{
int num = Find_around(mine, x, y);
show[Rows][Cols] = num + '0';
win++;
}
else
{
printf("很遗憾,游戏失败\n");
Print_board(mine, Row, Col);
break;
}
}
else
{
printf("非法输入\n");
}
}
这里这里,看见了吗
}
这时候我们需要在这里判断一下有没有赢
当win = Rol * Col - count的时候,就赢了。代码如下:
if (win == row * col - count)
{
printf("恭喜你,扫雷成功\n");
//最后可以打印show数字让大家看一下结果
Print_Board(show,Row,Col);
}
整体代码如下game.h:
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define Row 9
#define Col 9
#define Rows Row + 2
#define Cols Col + 2
//初始化棋盘声明
void Init_board(char board[Rows][Cols], int rows, int cols, char set);
//打印棋盘声明
void Print_board(char board[Rows][Cols], int row, int col);
//设置雷声明
void Set_mine(char board[Rows][Cols], int row, int col, int count);
//找雷函数的声明
void Find_mine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col, int count);
game.c :
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化棋盘
void Init_board(char board[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++)
{
board[i][j] = set;
}
}
}
//打印棋盘
void Print_board(char board[Rows][Cols], int row, int col)
{
printf("------------扫雷------------\n");
int i = 0;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
//放置雷
void Set_mine(char board[Rows][Cols], int row, int col, int count)
{
while (count)
{
int x = rand() % row + 1;//生成1-9的数字
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
int Find_around(char mine[Rows][Cols], int x, int y)
{
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] - 8 * '0';
}
//找雷
void Find_mine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col, int count)
{
int win = 0;
int x = 0;
int y = 0;
while (win < row * col - count)
{
printf("请输入坐标:");
scanf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (mine[x][y] == '0')
{
int num = Find_around(mine, x, y);
show[x][y] = num + '0';
Print_board(show, Row, Col);
win++;
}
else
{
printf("很遗憾,游戏失败\n");
Print_board(mine, Row, Col);
break;
}
}
else
{
printf("非法输入\n");
}
}
if (win == row * col - count)
{
printf("恭喜你,扫雷成功\n");
Print_board(show, Row, Col);
}
}
test.c :
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void Menu()
{
printf("-----------------------\n");
printf("---------1. play-------\n");
printf("---------0. exit-------\n");
printf("-----------------------\n");
}
void Game()
{
char mine[Rows][Cols];
char show[Rows][Cols];
//初始化数组
Init_board(mine, Rows, Cols, '0');
Init_board(show, Rows, Cols, '*');
//打印数组
//Print_board(mine, Row, Col);
Print_board(show, Row, Col);
//放置雷
int count = 10;//雷的个数
Set_mine(mine, Row, Col, count);
Print_board(mine, Row, Col);
//找雷
Find_mine(mine, show, Row, Col, count);
}
void Test()
{
srand((unsigned)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("输入错误,请输入1或者0:\n");
}
} while (input);
}
int main()
{
Test();
return 0;
}
最后验证一下大家有没有写错,我们把count的值赋值为80,这样就只有一个空白的地方,我们在设置完雷之后再打印mine数组,这样我们就可以看见哪里没有雷,点击那个地方,若打印”游戏成功“那就应该没有问题啦。验证完后记得把雷改成10个哦,也要记得把打印mine数组给注释掉
运行程序:
我这里只有1 5 不是雷,输入1 5
扫雷成功啦!!!!!!!!恭喜各位!!
注:这一篇博客是我目前写的最长的一篇,写了好几个小时,快些完的时候,发现前面有一些错误。就回头改呀改,应该是改完了,还请大家多多包涵~ 最后祝大家天天开心~