文章目录
- 一、扫雷游戏分析和设计
- 1.1 扫雷游戏的功能说明
- 1.2 游戏的分析和设计
- 1.2.1 数据结构的分析
- 1.2.2 ⽂件结构设计
- 二、扫雷游戏代码实现
- 总结
一、扫雷游戏分析和设计
1.1 扫雷游戏的功能说明
• 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9
的格⼦
• 默认随机布置10
个雷
• 可以排查雷
◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
游戏的界⾯:
初始游戏画面:
排雷画面:
排雷失败画面:
1.2 游戏的分析和设计
1.2.1 数据结构的分析
◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。
因为我们需要在9*9
的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9
的数组来存放信息。
那如果这个位置布置雷,我们就存放1,没有雷我们就存放0
假如排查的是(3,2)
这个坐标,我们就访问绿色一圈里面,除黄色方格的八个方位的雷的个数,然后统计周围的雷的个数。
但是问题来了,但当我们如果要排查
(8,1)
这个坐标的时候,当我们开始访问周围一圈8个位置,统计雷的个数时,最下面的三个位置由于没有开辟空间,会导致数组越界访问,越界访问,代码就执行不起来了呀?这怎么办。
既然他会数组越界,那我们不让他越界就好了呀!既然99访问四周边缘的时候会多访问一层空间,那我们就再包裹一圈,也就是创建1111的数组,我们在外层里面不放雷,到时候访问坐标(8,1)
的时候,直接就可以访问了,这样就解决了数组越界的问题。
此时此刻我们的雷阵大小的设计是不是创建好了呢,雷是1
,非雷是0
,如果我们要排查一个某一位置,这个坐标是不是雷,这个坐标周围有一个雷,我们将统计周围的雷数量打印在棋盘上的时候,这个雷如果打印在棋盘上面,那到底是雷的个数信息,还是雷的信息,这就产生了混淆和打印上的困难,怎么办呢?
如果我们加多一个棋盘,玩家点击该棋盘上的信息对应到埋雷的棋盘上的信息。将雷的信息放在一个棋盘上,而雷的个数信息通过专门给玩家猜雷的棋盘,而玩家猜雷的棋盘统计雷的棋盘并打印在玩家的棋盘上,而我们的埋雷的棋盘上的信息并没有发生相应的改变。
读到这里,哎,你可能会想到,我一个棋盘也可以搞定呀!非雷用#
,雷用&
,然后用数字1,2,3...
来统计周围雷的数量就可以了,谁说过一定要用0
和1
来代表非雷与雷。没错,这种办法也可以,博主也期待你们去尝试一下,但是这样做棋盘上既有雷和非雷的信息,还有排查出雷的个数信息,就比较混杂,但不够方便!
1.2.2 ⽂件结构设计
当我们去写一个游戏,设计一个游戏时,我们可以采用分文件的形式来写:
1 test.c //⽂件中写游戏的测试逻辑
2 game.c //⽂件中写游戏中函数的实现等
3 game.h //⽂件中写游戏需要的数据类型和函数声明等
举个例子:
当然这个代码也可以执行起来:
二、扫雷游戏代码实现
通过上面的形式,我们把文件分成三个文件管理
1. removal_of_mines.c
2. game.c
3. game.h
首先,我们一上来就要打印菜单给玩家看,我们可以选择do...while
循环,而菜单通过函数封装,用menu()
打印菜单,然后玩家输入,switch语句来辨认玩家的选择,玩与不玩,接下来,巧妙的地方来了,我们把玩家输入的选择作为do....while(intput)
的循环条件,因为这样可以让玩家选择继续玩,或者退出!点击(猜数字小游戏的一步一步实现)的步骤一样
# define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("******************************************\n");
printf("******************************************\n");
printf("*********** 1. 扫雷游戏启动!! *******\n");
printf("************ 0. 不玩了,启动不了 ********\n");
printf("******************************************\n");
printf("******************************************\n");
}
int main()
{
int input = 0;
do //因为我们一上来就是要打印菜单,因此我们选择do.....while语句
{
menu();//通过函数封装,用menu()打印菜单
printf("请选择--->");
scanf("%d", &input);//然后玩家输入
switch (input) //用switch分支选择选择玩与不玩
{
case 1:
printf("扫雷游戏启动!!\n");
game();
break;
case 0:
printf("不玩了,启动不了!\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);//选择1继续循环,0退出,这也是采用do.....while语句的好处
return 0;
}
接下来,我们要实现game(),我们刚才分析得出,创建两个数组,一个埋雷的棋盘,一个用来给用户看和猜的棋盘;
埋雷的棋盘:char mine[][]={0};
猜雷的棋盘:char player[][]={0};
到这里,我们是不是只要把9放进去就好了呢?但是这样不利于后面代码的维护,如果要改为16*16的棋盘呢?就要把整个程序的char mine[9][9]里的9
改为16
,一个一个的改,也可以,不过有点费力。如果代码很长要改的数量很多,有几个漏改的,代码也就会出错,因此为了以后代码的修改和长远的考虑,我们可以#define定义ROW(行)和COL(列),
也就是
#define ROW 9
#define COL 9
char mine[ROW][COL]={0};
char palyer[ROWS][COLS]={0};
但是这样九行就列真的可以了吗?哎,显然还不行,因为,前面我们分析了数组会越界,因此这里我们要定义11*11数组,那我们是可以加2:
#define ROWS ROW+2
#define COLS COL+2
因此数组创建为:
char mine[ROWS][COLS]={0};
char player[ROW]S[COLS]={0};
这个时候,数组的大小创建好了,接下来就是对数组初始化了,也就是给我们两个棋盘放我们的初始值,那我们就创建一个初始化函数吧!
InitBoard(player, ROWS, COLS);
void InitBoard(char board[ROWS][COLS], int rows, int cols)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = ‘*’;
}
}
}
这样写也可以,但是我们要放字符0
和*
,这样我们就要调用两个相同的函数,这样的话,就有点太繁琐了,如果我们调用一个函数,能同时打印;那就更好了,让代码来:
void InitBoard(char board[ROWS][COLS], int rows, int cols, int ret)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = ret;
}
}
}
InitBoard(mine, ROWS, COLS, '0');
InitBoard(player, ROWS, COLS, '*');
把0
和*
一起传过去字符‘0’代表该位置没有雷,字符*
代表隐藏,让玩家来猜,初始化完了,我们得看看我们放的值有没有实现我们想要的效果,那我们来打印一下吧,打印我们得要printf函数吧!
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
printf("******************************************\n");
int i = 0;
for (i = 0; i <= col; i++)
{
printf(" %d ", i);
}
printf("\n");
for (i = 1; i <= col; i++)
{
printf(" %d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf(" %c ", board[i][j]);
}
printf("\n");
}
printf("******************************************\n");
}
接下来就是埋地雷了;使用rand()函数(猜数字小游戏的一步一步实现有讲解),随机生成坐标,但是我们的坐标要合法,x 的范围必须在row的范围内,y也同样如此,为了避免重复埋雷,我们要加上if判断语句;
void SetMine(char board[ROWS][COLS], int row, int col)
{
//布置10个雷
//⽣成随机的坐标,布置雷
int count = EASY_COUNT;//EASY_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--;
}
}
}
打印雷盘看看是否埋好雷了:
此时此刻,我们的雷布置好了,接下来我们要排查雷了;排查雷我们要排查的位置是里面9*9的位置,也就是(x >= 1 && x <= row && y >= 1 && y <= col)
在这些合法的位置排查雷,
(一共10个雷,没排完了71个位置,继续循环,排完了71个位置,也排赢了)
如果mine[x][y] == '1'
,也就是点中雷了,游戏结束,然后打印雷的地图给玩家看。
如果该位置不是雷,那么就统计该坐标周围的雷,也就是八个方位。我们用
GetMineCount来统计周围雷的个数
void FindMine(char mine[ROWS][COLS], char player[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)//一共10个雷,没排完了71个位置,继续循环
{
printf("请输入要排查的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//该位置不是雷,就统计这个坐标周围有⼏个雷
int count = GetMineCount(mine, x, y);
player[x][y] = count + '0';
DisplayBoard(player, ROW, COL);
win++;
}
}
else
{
printf("坐标⾮法,重新输⼊\n");
}
}
if (win == row * col - EASY_COUNT)//一共10个雷,排完了71个位置,也排赢了
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
利用GetMineCount统计雷的个数,怎么统计呢?由于我们用的是字符‘0’和‘1’来存储,怎么把字符统计起来,转换成整型数字呢?
小知识来啦:
字符:‘1’
-1==‘0’
,‘2’
-1==‘1’
,‘3’
-2==’1‘
字符1
的ASCII是49,字符0
的ASCII是48,两相减就是数字1了,在C语言中,当字符‘0’
以%c字符的形式打印,打印出来的字符0和数字0一样的,以整形的形式打印出来的是48
,也就是他的ASCII值。
我们将八个方位的字符统计起来再减去字符’0‘
,算出来的值就是我们的雷的个数。
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
return (mine[x - 1][y] + mine[x - 1][y - 1] +
mine[x - 1][y + 1] + mine[x + 1][y] +
mine[x + 1][y - 1] + mine[x + 1][y + 1]+
mine[x][y - 1] + mine[x][y + 1] - 8 * '0');
}
源码如下:
removal_of_mines.c
# define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void game()
{
char mine[ROWS][COLS];
char player[ROWS][COLS];
InitBoard(mine, ROWS, COLS, '0');
InitBoard(player, ROWS, COLS, '*');
SetMine(mine, ROW, COL);
DisplayBoard(player, ROW, COL);
FindMine(mine, player, ROW, COL);
}
void menu()
{
printf("******************************************\n");
printf("******************************************\n");
printf("*********** 1. 扫雷游戏启动!! *******\n");
printf("************ 0. 不玩了,启动不了 ********\n");
printf("******************************************\n");
printf("******************************************\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do //因为我们一上来就是要打印菜单,因此我们选择do.....while语句
{
menu();//通过函数封装,用menu()打印菜单
printf("请选择--->");
scanf("%d", &input);//然后玩家输入
switch (input) //用switch分支选择选择玩与不玩
{
case 1:
printf("扫雷游戏启动!!\n");
game();
break;
case 0:
printf("不玩了,启动不了!\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);//选择1继续循环,0退出,这也是采用do.....while语句的好处
return 0;
game.c
# define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, int ret)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = ret;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
printf("******************************************\n");
int i = 0;
for (i = 0; i <= col; i++)
{
printf(" %d ", i);
}
printf("\n");
for (i = 1; i <= col; i++)
{
printf(" %d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf(" %c ", board[i][j]);
}
printf("\n");
}
printf("******************************************\n");
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
//布置10个雷
//⽣成随机的坐标,布置雷
int count = EASY_COUNT;//EASY_COUNT是设置雷的个数
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
return (mine[x - 1][y] + mine[x - 1][y - 1] +
mine[x - 1][y + 1] + mine[x + 1][y] +
mine[x + 1][y - 1] + mine[x + 1][y + 1]+
mine[x][y - 1] + mine[x][y + 1] - 8 * '0');
}
void FindMine(char mine[ROWS][COLS], char player[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//该位置不是雷,就统计这个坐标周围有⼏个雷
int count = GetMineCount(mine, x, y);
player[x][y] = count + '0';
DisplayBoard(player, ROW, COL);
win++;
}
}
else
{
printf("坐标⾮法,重新输⼊\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
game.h
# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+9
#define COLS COL+9
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char player[ROWS][COLS], int row, int col);
总结
好啦!扫雷简易版的小游戏到这里就完了,注:永远不要害怕每一次失败,要勇于尝试,看过千遍万遍,不过于自己动手做几遍!
感谢您的观看,如果喜欢的话,请点个赞。如果文章有错误,我不胜感激,你可以指出,我不胜感激,让我们共同学习和交流!