1. 游戏概述
1.1 扫雷游戏的基本规则与目标
在扫雷游戏中,玩家要在格子中找雷,可以根据提示标记雷的位置。当排查的位置是雷,则玩家被炸死,玩家失败;当玩家排查完所有不是雷的位置,则玩家获胜。
1.2 历史背景与流行版本
《扫雷》于1992年由微软发行的小游戏,在大多版本的Windows有预装,其Windows XP的扫雷最为经典。
2. 游戏设计和分析
2.1文件封装
2.1.1 game.h
游戏需要的数据类型和头文件等,如stdio,define...在这声明,在其他文件声明该头文件,减少重复的声明
2.1.2 game.c
游戏的函数实现等
2.1.3 test.c
游戏的测试逻辑
2.2 设计游戏
2.2.1 游戏菜单
首先,我们要为玩家提供一个菜单(printf 打印),要让玩家做出选择是否玩游戏。 给玩家选择 1. 玩游戏 0.退出
该过程至少有一次,采用do-while循环。
我们要记录玩家的选择(scanf 记录),并且要对选择做出判断(比如输入2(不合规))。
进入不同的事件。使用swtich,case和default的组合。
代码如下
game.h
#include <stdio.h> //使用printf , scanf
test.c
#include "game.h"
void game()
{
;
}
void menu()//菜单
{
printf("###########扫雷游戏##########\n");
printf("#############################\n");
printf("########## 1. play ##########\n");
printf("########## 0. exit ##########\n");
printf("#############################\n");
}
int main()
{
int input = 0;
do
{
menu();//菜单
printf("请选择\n");
scanf("%d", &input);//记录玩家输入
switch (input)
{
case 1://玩游戏
game();
break;
case 0://退出
printf("退出\n");
break;//跳出do-while循环
default://无效输入
printf("无效输入,请重新输入\n");//提示
break;
}
} while (input);//input == 0 循环结束
return 0;
}
2.2.2 测试1.0

2.2.3 棋盘初始化
棋盘选择11行11列,最后打印9行9列,行和列用 define 定义,用二维数组(arr[][])。要注意的是初始化棋盘要创建雷棋盘和展示棋盘,它们的行列最好相同,方便使用同一个函数。
代码如下
game.h
#define ROW 9 //用宏定义行
#define COL 9 //用宏定义列
#define ROWS ROW + 2
#define COLS COL + 2
void initboard(char board[ROWS][COLS], int rows, int cols);//初始化棋盘函数声明
game.c
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';
}
}
}
test.c
void game()
{
char board[ROWS][COLS] = { 0 };
char mine[ROWS][COLS] = { 0 };
initboard(board, ROWS, COLS);//初始化展示棋盘
initboard(mine, ROWS, COLS);//初始化雷棋盘
}
###为什么棋盘选择11行11列,最后却打印9行9列?
当玩家选择一个坐标,就要统计周围8个坐标雷的个数。如果,选择边缘的坐标,就会越界统计,导致程序出bug。

2.2.4 打印棋盘
打印棋盘用双for循环结构;注意打印不是从下标0开始,而是1;要打印坐标,方便玩家操作。
当打印大于10行或10列,会产生不对齐的情况,我们可这样解决
printf("%-nd",...);//向左对齐n个位置,同理%nd向右对齐...
game.h
void display(char arr[ROWS][COLS], int row, int col);//打印棋盘函数声明
game.c
void game()
{
char board[ROWS][COLS] = { 0 };
char mine[ROWS][COLS] = { 0 };
initboard(board, ROWS, COLS, '*');//初始化展示棋盘
initboard(mine, ROWS, COLS, '0');//初始化雷棋盘
display(board, ROW, COL);//打印展示棋盘
display(mine, ROW, COL);//打印雷棋盘
}
test.c
void display(char board[ROWS][COLS], int row, int col)//打印棋盘函数
{
int i = 0;
int j = 0;
for (i = 0; i <= row; i++)//打印列下标
printf("%d ", i);
printf("\n");
for (i = 1; i <= row; i++)//行
{
printf("%d ", i);//打印行下标
for (j = 1; j <= col; j++)//列
{
printf("%c ", board[i][j]);
}
printf("\n");//末尾换行
}
printf("\n");
}
2.2.5 测试2.0

2.2.6 设置雷
2.2.6.1 分析
在设置雷时要注意以下情况
(1)将要设置雷的位置是否已有雷,有则重新设置
(2)设置雷的坐标要在符合范围
2.2.6.2 如何设置随机数
用 rand 函数,该函数的的使用要声明头文件 stdlib.h 。但 rand 函数生成的是伪随机数单独使用,每次生成的随机数的值不会改变,因为它的种子(srand()函数,括号内初始值为1)不变,如以下代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}
值始终为
1804289383
846930886
1681692777
1714636915
1957747793
要实现随机数我们要使用 time 函数,该函数的的使用要声明头文件 time.h 。time() 的值是1970年1月1日00:00:00至今的秒数(也称为时间戳),传入NULL,则返回时间值类型为 time_t 使用时要强转为 unsigined int 类型,该值是随机的,但是如果 srand 和 rand 的位置近,快速运行两次代码时,会出现生成随机数的不变的情况
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand((unsigned int)time(NULL));
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}
既然随机数有,但对于随机数的取值是有要求的范围是1-9
我们可以这样实现
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand((unsigned int)time(NULL));
int i = rand() % 9 + 1;
int j = rand() % 9 + 1;
printf("%d %d", i, j);
return 0;
}
我们可以这样写
game.h
#include <stdlib.h> //使用srand, rand函数
#include <time.h> //使用time函数
#define MINE_COUNT 10 //雷的个数
void putmine(char mine[ROWS][COLS], int row, int col, int n);//设置雷函数声明
game.h
void putmine(char mine[ROWS][COLS], int row, int col, int n)//设置雷函数
{
while (n)
{
int i = rand() % row + 1;//设置行的随机值 1-9
int j = rand() % col + 1;//设置列的随机值 1-9
if (mine[i][j] == '0')//判断该坐标是否有雷
{
mine[i][j] = '1';//设置为雷
n--;
}
}
}
test.c
void game()
{
char board[ROWS][COLS] = { 0 };
char mine[ROWS][COLS] = { 0 };
initboard(board, ROWS, COLS, '*');//初始化展示棋盘
initboard(mine, ROWS, COLS, '0');//初始化雷棋盘
display(board, ROW, COL);//打印展示棋盘
display(mine, ROW, COL);//打印雷棋盘
putmine(mine, ROW, COL, MINE_COUNT);//设置雷
}
2.2.7 测试3.0
注意!要想看到雷的分布,就要在 putmine(mine, ROW, COL, MINE_COUNT); 后再加上 display(mine, ROW, COL); 打印雷棋盘

2.2.8 排查雷
2.2.8.1 分析
当我们写排查雷函数时要对玩家输入的坐标做出以下判断
(1) 坐标是否合规(1. 是否已排查过,2. 是否超出范围)
否,则提醒并要求重新输入;是则考虑(2)
(2)在雷棋盘中对应的该坐标是否有雷
否,则考虑(3);是,则宣布玩家失败,展示雷棋盘并跳出排查雷函数
(3)是否达到胜利条件(变量 win == 0)
否,则,输出周围雷的个数并使win自减1;是,则宣布胜利
通过以上分析我们可以用下结构设计函数
while (win)
{
if (1)//坐标是否合规
{
if (2)//在雷棋盘中对应的该坐标是否有雷
{
...
}
else
{
if (3)//是否达到胜利条件(变量 win == 0)
{
...
}
}
}
else
{
...
}
}
...//win==0 或 win > 0
2.2.8.2 统计雷的个数
如图

当玩家排查坐标(x,y)合规且未踩雷,我们要在雷棋盘统计(x,y)周围雷的个数,并把个数放入展示棋盘(x,y)中。
那么,如何统计,在雷棋盘中,我们放入的都是字符,它们都有对应的ASCII码值,且字符0和字符1的ASCII码值相差1,因此我们可以遍历图中所有坐标的字符,并都减去字符0,在最后加上字符0,还原为对应的字符。
void mine_count(char board[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
board[x][y] = mine[i][j] - '0';
}
}
board[x][y] = board[x][y] + '0';
}
game.h
void findmine(char board[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int le);//排查雷函数声明
game.c
void mine_count(char board[ROWS][COLS], char mine[ROWS][COLS], int x, int y)//统计雷的个数函数
{
int i = 0;
int j = 0;
int count = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
count = mine[i][j] - '0';
}
}
board[x][y] = count + '0';//转换为对应字符
}
void findmine(char board[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int le)//排查雷函数
{
int x = 0;
int y = 0;
int win = row * col - le;//统计无雷坐标的个数
while (win)
{
printf("请输入你要排查的坐标>");//提示
scanf("%d %d", &x, &y);//获取玩家要排查的坐标
if (0 < x && x <= row && 0 < y && y <= col && board[x][y] == '*')//判断坐标是否规
{
if (mine[x][y] == '1')//排查坐标是雷
{
printf("你踩雷了,游戏结束\n");//失败
display(mine, ROW, COL);//打印雷棋盘
break;//跳出while循环
}
else//不是雷
{
if (win)
{
mine_count(board, mine, x, y);//统计周围雷的个数
win--;
display(board, ROW, COL);//打印展示棋盘
}
}
}
else//不合规
{
printf("输入非法,请重新输入\n");//坐标非法
}
}
printf("恭喜!排雷成功\n");//获胜
}
test.c
void game()
{
char board[ROWS][COLS] = { 0 };
char mine[ROWS][COLS] = { 0 };
initboard(board, ROWS, COLS, '*');//初始化展示棋盘
initboard(mine, ROWS, COLS, '0');//初始化雷棋盘
display(board, ROW, COL);//打印展示棋盘
//display(mine, ROW, COL);//打印雷棋盘
putmine(mine, ROW, COL, MINE_COUNT);//设置雷
//display(mine, ROW, COL);//打印雷棋盘
findmine(board, mine, ROW, COL, MINE_COUNT);//排查雷
}
2.2.9 最终测试
非法输入

踩雷

成功
\
3. 源代码
game.h
#include <stdio.h> //使用printf , scanf函数
#include <stdlib.h> //使用srand, rand函数
#include <time.h> //使用time函数
#define ROW 9 //用宏定义行
#define COL 9 //用宏定义列
#define ROWS ROW + 2
#define COLS COL + 2
#define MINE_COUNT 10 //雷的个数
void initboard(char board[ROWS][COLS], int rows, int cols, char flag);//初始化棋盘函数声明
void display(char arr[ROWS][COLS], int row, int col);//打印棋盘函数声明
void putmine(char mine[ROWS][COLS], int row, int col, int n);//设置雷函数声明
void findmine(char board[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int le);//排查雷函数声明
game.c
#include "game.h"
void initboard(char board[ROWS][COLS], int rows, int cols, char flag)//初始化棋盘函数
{
int i = 0;
for (i = 0; i < rows; i++)//行
{
int j = 0;
for (j = 0; j < cols; j++)//列
{
board[i][j] = flag;
}
}
}
void display(char board[ROWS][COLS], int row, int col)//打印棋盘函数
{
int i = 0;
int j = 0;
for (i = 0; i <= row; i++)//打印列下标
printf("%d ", i);
printf("\n");
for (i = 1; i <= row; i++)//行
{
printf("%d ", i);//打印行下标
for (j = 1; j <= col; j++)//列
{
printf("%c ", board[i][j]);
}
printf("\n");//末尾换行
}
printf("\n");
}
void putmine(char mine[ROWS][COLS], int row, int col, int n)//设置雷函数
{
while (n)
{
int i = rand() % row + 1;//设置行的随机值 1-9
int j = rand() % col + 1;//设置列的随机值 1-9
if (mine[i][j] == '0')//判断该坐标是否有雷
{
mine[i][j] = '1';//设置为雷
n--;
}
}
}
void mine_count(char board[ROWS][COLS], char mine[ROWS][COLS], int x, int y)//统计雷的个数函数
{
int i = 0;
int j = 0;
int count = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
count += mine[i][j] - '0';
}
}
board[x][y] = count + '0';//转换为对应字符
}
void findmine(char board[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int le)//排查雷函数
{
int x = 0;
int y = 0;
int win = row * col - le;//统计无雷坐标的个数
while (win)
{
printf("请输入你要排查的坐标>");//提示
scanf("%d %d", &x, &y);//获取玩家要排查的坐标
if (0 < x && x <= row && 0 < y && y <= col && board[x][y] == '*')//判断坐标是否规
{
if (mine[x][y] == '1')//排查坐标是雷
{
printf("你踩雷了,游戏结束\n");//失败
display(mine, ROW, COL);//打印雷棋盘
break;//跳出while循环
}
else//不是雷
{
if (win)
{
mine_count(board, mine, x, y);//统计周围雷的个数
win--;
display(board, ROW, COL);//打印展示棋盘
}
}
}
else//不合规
{
printf("输入非法,请重新输入\n");//坐标非法
}
}
if(win == 0)
printf("恭喜!排雷成功\n");//获胜
}
test.c
#include "game.h"
void game()
{
char board[ROWS][COLS] = { 0 };
char mine[ROWS][COLS] = { 0 };
initboard(board, ROWS, COLS, '*');//初始化展示棋盘
initboard(mine, ROWS, COLS, '0');//初始化雷棋盘
display(board, ROW, COL);//打印展示棋盘
//display(mine, ROW, COL);//打印雷棋盘
putmine(mine, ROW, COL, MINE_COUNT);//设置雷
display(mine, ROW, COL);//打印雷棋盘
findmine(board, mine, ROW, COL, MINE_COUNT);//排查雷
}
void menu()//菜单
{
printf("###########扫雷游戏##########\n");
printf("#############################\n");
printf("########## 1. play ##########\n");
printf("########## 0. exit ##########\n");
printf("#############################\n");
}
int main()
{
int input = 0;
do
{
srand((unsigned int)time(NULL)); //设置种子的随机值
menu();//菜单
printf("请选择\n");
scanf("%d", &input);//记录玩家输入
switch (input)
{
case 1://玩游戏
game();
break;
case 0://退出
printf("退出\n");
break;//跳出do-while循环
default://无效输入
printf("无效输入,请重新输入\n");//提示
break;
}
} while (input);//input == 0 循环结束
return 0;
}
4. 扫雷扩展建议
扫雷游戏的基本功能已实现,但与真正的扫雷游戏还有差距,我们在可以源代码的基础上减小它们的差距,来提高玩家体验。有以下方面可以扩展:
1. 难度选择 2. 优化坐标轴 3. 改变雷的字符 4. 计时
5. 标记 6. 自动清屏 7. 递归展开 8. 字符颜色
大家可以自行完善扫雷,如有错误欢迎大家在评论区指正,谢谢!!!

被折叠的 条评论
为什么被折叠?



