扫雷游戏

扫雷游戏分析和设计
扫雷游戏的代码实现
扫雷游戏的展开一片
一.扫雷游戏分析和设计
扫雷游戏分析及其功能
使⽤控制台实现经典的扫雷游戏
游戏可以通过菜单实现继续玩或者退出游戏
打印扫雷的棋盘是9*9的格⼦
随机布置10个雷
玩家可以排查雷
在排查过程中:
如果位置不是雷,就显⽰周围有⼏个雷
如果位置是雷,就炸死游戏结束
排查结束:把所有雷全部找到
进入游戏界面
排雷失败界面
失败了把布置雷的棋盘打印出来,自己知道死得其所
扫雷游戏的设计
我们要将布置雷的信息和排查雷的信息存储在哪呢,我们很简单发现可以用前面所学的数组,每一个都有自己的坐标,那么我们就将信息放进二维数组中,因此我们需要9 * 9的二维数组
最初的棋盘
那如果随机布置10个雷,我把这个棋盘初始化为0,把布置雷的位置放1
数组越界问题
我们把10个雷放好了,但我们下面不是要进行排查雷吗,排查一个坐标后,这个坐标不是雷的话,我们要显示周围有几个雷,但这时候有问题了
图一中假如我们选择(3,2)这个位置,周围黄色部分有1个雷,排查没问题
但如果排查(8,8)这个位置呢,明显数组越界访问了,因此我们这时候难道要增加判断越界部分的吗,其实并不需要,我们把二位数组扩大一圈变成11 *11的数组,虽然浪费了一点空间,但给我们编写带来了便利
图一
改进后扩大的数组
布置雷和排查雷之间的“1”的歧义
我们不难发现,我们在排查一个非雷坐标后,需要要在其位置上显示周围雷的信息
那我们将这个信息海储存在我们第一开始mine数组(这个来存放布置雷的信息),例如上图中我排查了(4,3)这个位置后,它的周围有1个雷,我们要在这个位置显示出来周围有1个雷,那我如果还将这个信息存储在这个mine数组中的话,我们不知道这个‘1’是布置雷的信息还是排查雷的信息呢?难道我们还要进行判断这个1表什么吗?
实则不然,我们这时候可以创建两个11*11的数组呢,一个mine数组放布置雷的信息,一个show数组放排查雷的信息。既然上面的mine数组初始化为‘0’,雷的信息为‘1’,那我的show数组初始化为‘ * ’。
//下面时这两个数组
char mine[11][11];//存放布置雷的信息
char show[11][11];//存放排查雷的信息
mine数组在初始化时全部初始化为字符‘0’,布置雷的位置,雷为字符‘1’
show数组在初始化时全部初始化为‘ * ’
到这里游戏分析完了,下面让我们来用代码实现吧
二. 扫雷游戏代码实现(逐步瓦解)
首先我们这个代码肯定有好多功能,我们把它们全部放在一个文件中没有条理,我们使用三个文件
test.c //源⽂件中写游戏的主要的运行逻辑
game.c //源⽂件中写游戏中函数的实现等
game.h //头文件中写游戏需要函数声明和所需数据等
就类似这样的样式
1.源文件test.c(游戏菜单)
首先我们要打印进入游戏菜单,1进入游戏,0退出游戏,并且可以玩多次,这个前面的猜数字游戏也讲了,这里就不展开陈述了
//源文件test.c 文件中
void menu()
{
printf("*******************\n");
printf("*******1.play******\n");
printf("*******0.exit******\n");
printf("*******************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("进入扫雷游戏\n");
game();//进入游戏的函数
break;
case 0:
printf("退出游戏\n");
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
2.头文件game.h(游戏所需的数据和头文件等)
由于我们后期如果想要不知仅仅玩一个9 * 9的棋盘的话,我们可以将我们需要的数据放在开头用 define来定义,这样我们如果想修改数据的话没必要一个一个修改
我们把这些数据放在头文件 game.h这个文件中,这样我们的代码就是非常的灵活
//这里的信息存放在头文件game.h中
//我们把所有都要用到的东西(头文件,数据等)放到game.h中
//这样其他文件只需要引用 “game.h"这个头文件就行,注意是双引号
#pragma once
#include<stdio.h>
#include<stdlib.h>//rand的头文件
#include<time.h>//time函数的头文件
#define ROW 9//定义棋盘行数
#define COL 9//定义棋盘列数
//因为在盘查雷的时候,我们需要周围的判断,所以我们创建大一圈的二维数组,以便盘查雷
#define ROWS ROW+2
#define COLS COL+2
//便于后期改变雷的个数
#define EASY_COUNT 10
3. 源文件game.c(游戏实现)
初始化棋盘
IintBoard(mine, ROWS, COLS, '0');//用来布置雷的信息
IintBoard(show, ROWS, COLS, '*');//用来盘查雷的信息c
//初始化棋盘
//这里我们因为创建了两个棋盘一个是存放布置雷的信息一个是放排查雷的信息
//初始化的符号也不一样,所以我们用set 来接收初始化的符号,
//这里我们初始化的是11* 11的数组,但后面我们只使用9*9的数组
void IintBoard(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;
}
}
}
打印棋盘
Displayboard(show, ROW, COL);
虽然我们创建11 * 11的数组,但我们打印的化,只打印9 * 9的数组
打印时把行和列号全部都打印出来,这样有利与坐标的观察
//打印棋盘
void Displayboard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
//打印列号
printf("--------扫雷--------\n");
for (i = 0;i <= col;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数组中,雷为‘1’不是雷为‘0’
Setmine(mine, ROW, COL);
在布置雷时要注意坐标合法,我么布置雷的时候只布置在99的数组中,(1111的数组只是用来显示显示排查雷的信息),并且坐标未被布置过,这里我们用[rand]()的生成随机坐标
这里rand的如何使用,在前面的**猜数字游戏中随机数**生成具体讲了
//布置雷
//先要生成合法坐标
void Setmine(char mine[ROWS][COLS], int row, int col)
{
//将需要雷的个数放在game.h中定义,以便后期修改
int count = EASY_COUNT;//便于以后改变布置雷的个数
//随机布置10个雷,行坐标是1-ROW,列坐标是1 - COL
while (count)
{
//注意要把坐标生成放在循环里,直到布置好10个雷后,跳出循环
int x = rand() % row + 1;//生成行坐标在[1,row]
int y = rand() % col + 1;//生成列坐标在[1,col]
if (mine[x][y] == '0')
{
mine[x][y] = '1';//字符1,以便于后面显示排查雷的信息
count--;
}
}
}
这里的main函数就要加上srand帮助生成随机数,及其头文件,具体如何使用请看猜数字游戏
#include<stdio.h>
#include<stdlib.h>//rand的头文件
#include<time.h>//time函数的头文件
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//帮助生成随机数
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("进入扫雷游戏\n");
game();//进入游戏的函数
break;
case 0:
printf("退出游戏\n");
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
从图片中可以看出雷布置成功了,并且是随机的
排查雷
Findmine(mine, show, ROW, COL);
这里我们要
1.判断自己排查的坐标是否合法
2.要判断这个位置是否被排查过
3.要判断这个位置是不是雷,是雷的话被炸死,跳出游戏,如果不是该坐标显示周围雷的信息
4. 何时排查成功,当我们把所有不是雷的坐标全部排查出来后,就赢了
判断周围雷的个数,显示周围雷的信息,我们借助mine数组(因为其数组中字符‘0’不是雷,字符‘1’是雷,我们只需要判断有多少字符‘1’就行)但我们要知道数字在存储的时候存的是ASCLL码值。‘0’对应的ASCLL码值是48 ‘1’对应的是ASCLL码值是49以此类推,如果所以我们把这个坐标周围的八个坐标(如图绿色部分坐标)全部都想加然后减去 8 个字符 ‘0’就得到周围雷的信息,字符‘1’减去字符‘0’是整型值1
//显示排查过雷的信息(周围有几个雷)
int Get_count(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int sum = 0;
for (i = -1;i <= 1;i++)
{
int j = 0;
for (j = -1;j <= 1;j++)
{
sum += mine[x + i][y + i];//但这样多加入了一个board[x][y],既然能被排查肯定不是雷了
}
}
return sum - mine[x][y] - 8 * '0';
//或者 return sum-9*'0'既然mine[x][y]能被排查说明它肯定不是雷了
}
//排查雷
//坐标要和法,并且为被排查过
//什么时候排查成功
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//什么时候排查成功
int win = 0;
while (win < row * col - EASY_COUNT)//排查出所有不是雷的坐标
{
//排查雷
int x = 0;
int y = 0;
printf("请输入要排查的坐标:");
scanf("%d %d", &x, &y);
//坐标合法
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
//位置未被排查
if (show[x][y] == '*')
{
if (mine[x][y] == '0')//显示周围有几个雷
{
int c = Get_count(mine, x, y);
//接收的是整形值,存放的时候存的仍然是对应ASCLL码值对应的值
//'0'加上一个整形,对应的ASCLL码值就是自己想要的
show[x][y] = c + '0';
win++;
Displayboard(show, ROW, COL);
}
else
{
printf("你被炸死了,游戏结束\n");
//结束了,让你看看是不是真的,看下布置雷的信息
Displayboard(mine, ROW, COL);
break;
}
}
else
{
printf("该位置已被排查过了,请重新输入\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
if (win == row * col - EASY_COUNT)//排查出所有不是雷的坐标
{
printf("扫雷成功\n");
Displayboard(mine, ROW, COL);
break;
}
}
}
三.扫雷游戏的扩展 展开一片
大大提高我们扫雷的效率
大家想想这个是不是可以实现展开一片的功能
1.这个坐标不是雷
2.这个坐标周围没有雷
3.这个坐标没有被排查过
这需要用函数递归来写
首先我们要进行展开,如果这个坐标不是雷,并且周围没有雷那么可以将该位置直接赋值为‘0’,然后在进行其周围的八个坐标进行重复操作,这八个坐标每个坐标又会对应八个坐标,一直进行下去,直到我们展开周围都有雷,但要注意如果这里已经被排查过了,跳过这个,进行下一个防止死递归
//展开一片
spread(mine, show, x ,y);
其次,这个我们怎么判断输赢呢:?
这里的话,我们不断展开,不断排查,当我们show数组剩余未被排查的‘*’的个数等于雷的个数说明,我们排查成功
//计算show数组中剩余‘*’的个数
CountShow(char show[ROWS][COLS], int row, int col)
此功能只是对排查雷代码有一定的改变,其他代码一样
//计算周围雷的个数
int Get_count(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int sum = 0;
for (i = -1;i <= 1;i++)
{
for (j = -1;j <= 1;j++)
{
sum += mine[x + i][y + j];
}
}
return sum - mine[x][y] - 8 * '0';
}
//函数递归展开一片
//展开的坐标我们将它显示为‘0’
void spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x,int y)
{
int i = 0;
int j = 0;
//1.首先这个坐标和法,并且不是雷,才可能进行展开
//反之则该坐标不符合条件,跳出
if (x >= 1 && x <= ROW && y >= 1 && y <= COL && mine[x][y]!= '1')
{
//并且如果这个位置周围没有雷
if (Get_count(mine ,x,y) == 0)//前面所讲的求周围雷的个数,这个计算方法不变
{
//因为第一个条件时是判断该坐标是不是雷
//过了第二个条件说明这个坐标周围没有雷
//所以可以直接为‘0’
show[x][y] = '0';
int i = 0;
int j = 0;
for (i = x - 1;i <= x + 1;i++)
{
for (j = y - 1;j <= y + 1;j++)
{
//如果这个已经被展开过了,那这个不需要展开,防止死递归
if (show[i][j] != '*')
{
continue;
}
else
{
//继续进行递归,直到周围全部都是有雷时截止
spread(mine, show, i,j);
}
}
}
}
//如果这个位置周围有雷的话,该位置直接显示周围雷的个数
else
{
show[x][y] = Get_count(mine, x, y) + '0';
}
}
else
{
return;
}
}
//统计show数组中*的个数
int CountShow(char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int count = 0;
for (i = 1;i <= row;i++)
{
for (j = 1;j <= col;j++)
{
if (show[i][j] == '*')
{
count++;
}
}
}
return count;
}
//展开一片
//排查雷
//坐标要和法,并且为被排查过
//什么时候排查成功,当show数组中*个数等于雷的个数就赢了
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//什么时候排查成功
int win = 0;
while (1)
{
//排查雷
int x = 0;
int y = 0;
printf("请输入要排查的坐标:");
scanf("%d %d", &x, &y);
//坐标合法
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
//位置未被排查
if (show[x][y] == '*')
{
//这个坐标不是雷
if (mine[x][y] == '0')
{
spread(mine, show, x ,y);//展开一片
win = CountShow(show, row, col);//统计show数组中*的个数
if (win == EASY_COUNT)//如果show数组中*的个数等于雷的个数,扫雷成功
{
printf("扫雷成功\n");
Displayboard(mine, ROW, COL);
break;
}
Displayboard(show, ROW, COL);
}
else
{
printf("你被炸死了,游戏结束\n");
//结束了,让你看看是不是真的,看下布置雷的信息
Displayboard(mine, ROW, COL);
break;
}
}
else
{
printf("该位置已被排查过了,请重新输入\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
if (win == row * col - EASY_COUNT)//排查出所有不是雷的坐标
{
printf("扫雷成功\n");
Displayboard(mine, ROW, COL);
break;
}
}
}
扫雷游戏完整源代码
这里要注意自己引用自己创建代码仓的头文件,用双引号引用,例如这次game.c和test.c引用game.h的话
#include"game.h",这样写,这里面有展开一片的代码
1.game.h头文件
#pragma once
#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 IintBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void Displayboard(char board[ROWS][COLS], int row, int col);
//布置雷
void Setmine(char mine[ROWS][COLS], int row, int col);
//排查雷
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
2.game.c源文件
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
//初始化棋盘
void IintBoard(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 Displayboard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
//打印列号
printf("--------扫雷--------\n");
for (i = 0;i <= col;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 Setmine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;//便于以后改变布置雷的个数
//随机布置10个雷,行坐标是1-ROW,列坐标是1 - COL
while (count)
{
//注意要把坐标生成放在循环里,直到布置好10个雷后,跳出循环
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';//字符1,以便于后面显示排查雷的信息
count--;
}
}
}
//因为数字在存储的时候存的是ASCL码值,‘0’对应的是48 ‘1’对应的是49
//所以我们把这个坐标周围的八个坐标全部都想加,这时减去'0',因为‘1’-‘0’为1,以这样我们看出总共有多少雷周围
int Get_count(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int sum = 0;
for (i = -1;i <= 1;i++)
{
int j = 0;
for (j = -1;j <= 1;j++)
{
sum += mine[x + i][y + i];//但这样多加入了一个board[x][y],既然能被排查肯定不是雷了
}
}
return sum - mine[x][y] - 8 * '0';
//或者 return sum-9*'0'既然mine[x][y]能被排查肯定不是雷了
}
//排查雷
//坐标要和法,并且为被排查过
//什么时候排查成功
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
//{
// //什么时候排查成功
// int win = 0;
// while (win < row * col - EASY_COUNT)//排查出所有不是雷的坐标
// {
// //排查雷
// int x = 0;
// int y = 0;
// printf("请输入要排查的坐标:");
// scanf("%d %d", &x, &y);
// //坐标合法
// if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
// {
// //位置未被排查
// if (show[x][y] == '*')
// {
// if (mine[x][y] == '0')//显示周围有几个雷
// {
// int c = Get_count(mine, x, y);
// show[x][y] = c + '0';//存放的时候存的仍然是ASCLL码值,所以将他转化为
//
// win++;
// Displayboard(show, ROW, COL);
// }
// else
// {
// printf("你被炸死了,游戏结束\n");
// //结束了,让你看看是不是真的,看下布置雷的信息
// Displayboard(mine, ROW, COL);
// break;
// }
// }
// else
// {
// printf("该位置已被排查过了,请重新输入\n");
// }
// }
// else
// {
// printf("坐标非法,请重新输入\n");
// }
// if (win == row * col - EASY_COUNT)//排查出所有不是雷的坐标
// {
// printf("扫雷成功\n");
// Displayboard(mine, ROW, COL);
// break;
// }
// }
//}
//展开一片排查雷
void spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x,int y)
{
int i = 0;
int j = 0;
//首先这个坐标和法,并且不是雷,才可能进行展开
if (x >= 1 && x <= ROW && y >= 1 && y <= COL && mine[x][y]!= '1')
{
//如果这个位置不是雷的话,直接开始进行展开
if (Get_count(mine ,x,y) == 0)//前面所讲的求周围雷的个数
{
show[x][y] = '0';
int i = 0;
int j = 0;
for (i = x - 1;i <= x + 1;i++)
{
for (j = y - 1;j <= y + 1;j++)
{
//如果这个已经被展开过了,那这个不需要展开,防止死递归
if (show[i][j] != '*')
{
continue;
}
else
{
//继续进行递归,知道周围全部都是有雷时截止
spread(mine, show, i,j);
}
}
}
}
//如果这个位置周围有雷的话,直接显示周围雷的个数
else
{
show[x][y] = Get_count(mine, x, y) + '0';
}
}
else
{
return;
}
}
//统计show数组中*的个数
int CountShow(char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int count = 0;
for (i = 1;i <= row;i++)
{
for (j = 1;j <= col;j++)
{
if (show[i][j] == '*')
{
count++;
}
}
}
return count;
}
//排查雷
//坐标要和法,并且为被排查过
//什么时候排查成功,当show数组中*个数等于雷的个数就赢了
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//什么时候排查成功
int win = 0;
while (1)
{
//排查雷
int x = 0;
int y = 0;
printf("请输入要排查的坐标:");
scanf("%d %d", &x, &y);
//坐标合法
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
//位置未被排查
if (show[x][y] == '*')
{
//这个坐标不是雷
if (mine[x][y] == '0')
{
spread(mine, show, x ,y);//展开一片
win = CountShow(show, row, col);//统计show数组中*的个数
if (win == EASY_COUNT)//如果show数组中*的个数等于雷的个数,扫雷成功
{
printf("扫雷成功\n");
Displayboard(mine, ROW, COL);
break;
}
Displayboard(show, ROW, COL);
}
else
{
printf("你被炸死了,游戏结束\n");
//结束了,让你看看是不是真的,看下布置雷的信息
Displayboard(mine, ROW, COL);
break;
}
}
else
{
printf("该位置已被排查过了,请重新输入\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
if (win == row * col - EASY_COUNT)//排查出所有不是雷的坐标
{
printf("扫雷成功\n");
Displayboard(mine, ROW, COL);
break;
}
}
}
3.test.c源文件
#define _CRT_SECURE_NO_WARNINGS
#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];
//初始化棋盘
IintBoard(mine, ROWS, COLS, '0');//用来布置雷的信息
IintBoard(show, ROWS, COLS, '*');//用来盘查雷的信息
//布置雷,在mine数组中,雷为‘1’不是雷为‘0’
Setmine(mine, ROW, COL);
//打印棋盘
Displayboard(show, ROW, COL);
//对应雷的信息在自己写代码时用来检验自己
//雷是否布置好
//Displayboard(mine, ROW, COL);
//排查雷
Findmine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("进入扫雷游戏\n");
game();//进入游戏
break;
case 0:
printf("退出游戏\n");
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}