开门见山
给出我在写这篇代码时的思维导图,这篇博客也是根据思维导图写的
游戏结果展示
//因为本人有点小菜,所以就设置4*4 -->4的规格,即16格子找出4个雷以演示游戏代码,想挑战高难度的小伙伴可自行设置.那么就见笑了
游戏思路分析
先展示本次编写代码所用到的头文件和源文件展示,方便后续阅读
test.c | 用于测试编写的扫雷代码相关功能即对game.c中的函数进行调用 |
game.h | 用于对扫雷代码所涉及的主要函数及参数的声明 |
game.c | 代码的主体,对game.h中声明的函数进行定义 |
和三子棋(见前篇)一样,应该有个菜单,你觉得怎么好看就怎么设置----test.c
void menu()
{
printf("****************************\n");
printf("************扫雷************\n");
printf("********** 1. play *********\n");
printf("********** 0. exit *********\n");
printf("****************************\n");
}
本来menu()应该属于game.c但是由于menu()本来就很简单,就没必要和game.c中的大头"争宠"了
布置雷
先看一张图
两个问题1.雷该放到哪呢?2.雷该用什么表示呢?
答:1.雷应该放到一个字符数组,不妨设为boom[ROWS][COLS]
2.雷用'1'表示,非雷用'0'表示,注意这里的'1'和'0'是字符1和0. 为什么选择用'1'和'0'?文末总结部分会详细给出
ps:这里的ROWS和COLS对应上图的11行11列,即橙色框.有的小伙伴就会问了我的棋盘不是9*9的嘛,设置成11*11岂不是浪费空间!实则不然,这样设置就可以避免产生数组越界的问题.何出此言?
我们看上面的绿色框,它的作用是去排查(x,y)周围八个格子的字符1(雷)的个数,排查(8,8)当然不会有数组越界,那我要是去排查(1,1) (1,2)...(9,9)也就是最外围的那32个格子,会出现什么情况?绿色框会访问不属于红色框的内容,(不恰当的比喻:你去拿了不该属于你的东西)这当然是不行的.计算机会无情的告诉你error.所以呢,我们应该设置为11*11的,最外围那一圈的元素可以不用,但不能没有!我们只操作中间的9*9就行了.
排查雷
像上图的绿色框,排查了(8,8),周围有3个雷,排查出来了该怎么办呢?是不是该把这个"3"存起来呀.怎么存呢?当然也是用数组咯.
这时候就有小伙伴跃跃欲试了:int show[ROWS][COLS],创建了一个整形数组;
那我只能说:你这波啊,还在第一层.且往下看
我打算用char show[ROWS][COLS],一个字符类型的数组.为什么要这么设计?这里先卖个关子,在函数定义部分会给出原因.
ok,以上便是我们的思路分析了,真正的大头来了
思路程序化
第一步当然是编写我们的test.c了
//游戏测试模块
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("****************************\n");
printf("************扫雷************\n");
printf("********** 1. play *********\n");
printf("********** 0. exit *********\n");
printf("****************************\n");
}
void game()
{
//关于雷的信息存储
//1. 创建一个数组来存放雷
char boom[ROWS][COLS] = { 0 };
//2. 创建一个数组来显示排查出的雷
char show[ROWS][COLS] = { 0 };
//初始化两个数组
InitBoard(boom, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
//DisplayBoard(boom, ROW, COL);
DisplayBoard(show, ROW, COL);//
//布置雷
Setboom(boom, ROW, COL);
//DisplayBoard(boom, ROW, COL);
//扫雷
Findboom(boom, show, ROW, COL);
}
void test()
{
int choice = 0;
srand((unsigned int)time(NULL));//时间戳
do
{
menu();
printf("请选择:>");
scanf("%d", &choice);
switch (choice)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择!\n");
break;
}
} while (choice);
}
int main()
{
test();
return 0;
}
可以发现是不是很精简且简单
main()调用了test(),test()调用了menu()如果输入1,test()又调用了game(),game()又调用了一系列的函数.然后我们才能玩到扫雷游戏.
细心的小伙伴会发现里面有个 #include "game.h" 这其实是头文件名的一种形式,只不过这个是我们自己定义的罢了,不像 #include<stdio.h> #include<math.h> 这类是vs自带的.你只需要记住:自己定义的头文件要引用的话要加 " "
函数要"先声明后使用"
我们来看看game.h里面有什么
1 //函数声明模块
2 //一些数据的全局定义,方便后续修改
3
4
5
6 #define ROW 9
7 #define COL 9
8
9 #define ROWS ROW+2
10 #define COLS COL+2
11 //雷的数量
12 #define BOOM_COUNT 10
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <time.h>
17 //函数返回类型 函数名 函数参数
18 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
19 void DisplayBoard(char board[ROWS][COLS], int row, int col);
20 void Setboom(char board[ROWS][COLS], int row, int col);
21 void Findboom(char boom[ROWS][COLS], char show[ROWS][COLS], int row, int col);
18-21行这些函数是game()反复调用的,声明已经有了,还差定义,我们把他们定义在game.c里面
下面一一介绍
InitBoard()
将数组初始化为你想要的内容 InitBoard() 返回类型 viod 参数 一个char类型数组,两个整形数,一个字符数 还记得上面卖的关子吗,我把show[][]也定义为char类型,现在来告诉你答案,我把show[][]定义为和boom[][]一样的char类型,就是为了,让InitBoard()一个函数操作两个数组,使代码更精简,避免了我要因为初始化show[][]去重新定义另一个函数 下面的DisplayBoard()也是同样的道理
|
DisplayBoard()
打印数组内容
void DisplayBoard(char board[ROWS][COLS], int row, int col) { int i = 0; int j = 0; //打印列号 for (i = 0; i <= col; i++) { printf("%d ", i); } printf("\n"); for (i = 0; i <= col; i++) { printf("--"); } printf("\n"); //打印行号 for (i = 1; i <= row; i++) { printf("%d", i); printf("|");//紧接着打印数组内容 for (j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } } |
Setboom()
布置雷->把boom[][]里对应坐标换成字符1 '1'
随机设置雷的位置
void Setboom(char board[ROWS][COLS], int row, int col) { int count = BOOM_COUNT; while (count) { int x = rand() % row + 1;//让x范围是1-9 int y = rand() % col + 1;//让y范围是1-9 if (board[x][y] == '0') { board[x][y] = '1'; count--; } } } |
Findboom()
找出boom[x][y]周围的字符1 '1' 并把找到的'1'的个数存到show[x][y]
//去找我们想排查的那个坐标周围的8个格子中雷的数量 int get_boom_count(char boom[ROWS][COLS], int x, int y) {
return boom[x - 1][y] + boom[x - 1][y - 1] + boom[x][y - 1] + boom[x + 1][y - 1] + boom[x + 1][y] + boom[x + 1][y + 1] + boom[x][y + 1] + boom[x - 1][y + 1] - 8 * '0'; } void Findboom(char boom[ROWS][COLS], char show[ROWS][COLS], int row, int col)//两个数组,相同的坐标,一个表示雷,一个表示坐标周围的雷的个数 { int x = 0; int y = 0; int rest_blank = 0;//用来表示剩余的不是雷的格子的个数
while (rest_blank < row * col - BOOM_COUNT) { printf("请输入要排查的坐标:>"); scanf("%d%d", &x, &y); //这个是时候应该先判断坐标合法性,即要排查的坐标应该在9*9的格子的范围 if (x >= 1 && x <= row && y >= 1 && y <= col) { //坐标合法 //1. 踩雷 if (boom[x][y] == '1') { printf("很遗憾,你被炸死了\n"); //起码要让玩家"死得明白" DisplayBoard(boom, row, col);//展示一下雷的分布 break; } else //不是雷 { //计算x,y坐标周围有几个雷 int count = get_boom_count(boom, x, y); //把找到的雷的个数存到show数组里 show[x][y] = count + '0';//一个整数转换为对应的字符加上'0' 如 3+'0'=='3' DisplayBoard(show, row, col); //找到一个雷rest_blank就自增 rest_blank++; } } else { printf("输入坐标非法,请重新输入!\n"); } } //判断一下是否等于所有非雷的格子的数量 if (rest_blank == row * col - BOOM_COUNT) { printf("恭喜你,排雷成功\n"); DisplayBoard(boom, row, col); } } |
最后来看一下game.c的内容其实就是上面几个函数的汇总
//函数定义模块
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//打印列号
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 0; i <= col; i++)
{
printf("--");
}
printf("\n");
//打印行号
for (i = 1; i <= row; i++)
{
printf("%d", i);
printf("|");//紧接着打印数组内容
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void Setboom(char board[ROWS][COLS], int row, int col)
{
int count = BOOM_COUNT;
while (count)
{
int x = rand() % row + 1;//让x范围是1-9
int y = rand() % col + 1;//让y范围是1-9
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
//去找我们想看的那个坐标周围的8个格子中雷的数量
int get_boom_count(char boom[ROWS][COLS], int x, int y)
{
return boom[x - 1][y] +
boom[x - 1][y - 1] +
boom[x][y - 1] +
boom[x + 1][y - 1] +
boom[x + 1][y] +
boom[x + 1][y + 1] +
boom[x][y + 1] +
boom[x - 1][y + 1] - 8 * '0';
}
//
void Findboom(char boom[ROWS][COLS], char show[ROWS][COLS], int row, int col)//两个数组,相同的坐标,一个表示雷,一个表示坐标周围的雷的个数
{
int x = 0;
int y = 0;
int rest_blank = 0;//用来表示剩余的不是雷的格子的个数
while (rest_blank < row * col - BOOM_COUNT)
{
printf("请输入要排查的坐标:>");
scanf("%d%d", &x, &y);
//这个是时候应该先判断坐标合法性,即要排查的坐标应该在9*9的格子的范围
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//坐标合法
//1. 踩雷
if (boom[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
//起码要让玩家"死得明白"
DisplayBoard(boom, row, col);//展示一下雷的分布
break;
}
else //不是雷
{
//计算x,y坐标周围有几个雷
int count = get_boom_count(boom, x, y);
//把找到的雷的个数存到show数组里
show[x][y] = count + '0';
DisplayBoard(show, row, col);
//找到一个雷rest_blank就自增
rest_blank++;
}
}
else
{
printf("输入坐标非法,请重新输入!\n");
}
}
//判断一下是否等于所有非雷的格子的数量
if (rest_blank == row * col - BOOM_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(boom, row, col);
}
}
ok,到这大头部分就讲完了
总结注意事项
#define _CRT_SECURE_NO_WARNINGS 1
这其实是为了让编译器在编译scanf时不报错,因为一些编译器(比如我自用的VS2019)会认为scanf不安全,建议你用scanf_s.但是我不听它的建议,我就要用scanf,就在代码前面加上上面那句就行了
关于雷的设置问题
充分利用数字的属性, '1'表示雷, 2表示周围有2个雷 这是实实在在的2,
'2'可以作为数组元素被打印出来,让我们在视觉上认为周围有两个雷,这是字符'2',却有了数字的属性.
可能有点绕,哈哈哈,但应该理解的过来
关于数组类型设置的问题
其实一开始我也没想到把show[][]设置为char类型,我是在编写InitBoard()的时候,突然想到的.我去初始化两个数组,能不能用一个函数啊,但是一个函数只能初始化一个类型的数组,且初始化对象和初始化内容必须是同一种类型(我不想强制类型转换嗷)
初始化 char 类型的boom[][]为 '0' (开始的时候应该没有雷) 要一个函数
初始话 int 类型的show[][] 为 '*' (一开始展示给我们看的应该全部是*,要把雷的位置遮起来,就不让你看,哼) 又有点行不通
所以就干脆把show[][]也定义为char类型,这样一来,数组类型和初始化内容的类型不就统一了嘛.
而且DiaplayBoard()也可以不用为类型发愁
游戏中的不足之处
1.在数周围8个格子的时候,get_boom_count()可以用循环,我为什么不用呢,因为直观,小伙伴们更容易理解
2.在排查雷的时候,不能展开一片.感兴趣的小伙伴可以用递归
排查boom[x][y]周围8个格子--> 1.是雷,就计数存到show[x][y] 2.不是雷,排查它周围的八个格子
到外围的一圈都是雷为止(其实我也不太懂扫雷的游戏规则).为了避免一些格子的重复排查,可以定义一个数组专门来表示某个格子是否被排查过.数组初始化为0,只要排查过,对应坐标就赋值为1.
3.用递归求斐波那契额数列,体现了上述思想,可以看我写的另一篇博客(算是波小广告啦)https://blog.csdn.net/HandsomeDog_L/article/details/120256097
小tips
建议小伙伴在写这种类型的代码时能有一个清晰的思路,怎么才能办到呢-------思维导图
当然大佬除外
上一篇写的三子棋也是有思维导图的,忘发出来了,我把它放到三子棋评论区吧,感兴趣的小伙伴可以去看看
由于编者水平实在有限,有什么错误之处望指正,非常感谢您的反馈
回见啦!