阿林说:点赞,观看,养成好习惯!
1.扫雷游戏的分析
敲出一个简单的扫雷游戏,要先对游戏的设计和实现进行思考
1.以9*9的扫雷棋盘为例子,我们需要随机布置雷、排查雷 ,那就需要存储这些信息,而棋盘是个二维的形状,,我们很容易想到使用二维数组来存储这些信息,棋盘在没有排查的时候,我们用*这个符号指代每一个格子,那么,棋盘就用字符二维数组进行存储,如图:
这样的棋盘没有标坐标,不方便扫雷,我们给他标上坐标
2.棋盘需要实现展示功能,还要储存随机布置雷的位置,那我们就用两个棋盘来存储这些信息,一个用来展示棋盘,分辨扫过雷的位置和没有扫雷的位置,另一个棋盘存储雷的排布,为了展示雷的棋盘和储存雷的棋盘的类型保持一致,那也用字符类型的数组来存储,并且这个棋盘不会展示出来
判断有没有放置雷就像真假一般,我们很自然的使用 0为假,1为真,而因为是字符数组,我们就用'0'和'1'来代替无雷和有雷
3.在玩游戏的时候,我们都知道,我们扫了雷的地方如果没有雷,就统计这个格子周围有几个雷,如果设置成9*9的棋盘,在边缘的格子不是雷,需要统计它周围有几个雷的时候,显然会越界访问,那我们就将棋盘给它加一条“边”,这条边不布置雷,也不显示出来,这样就可以简单地解决问题了
因此,我们在设置棋盘的时候,设置11行11列,又因为每个棋盘都是11行11列,11这个数字多次出现,这个数字又可以改变棋盘的大小,我们定义它为一个数,后续需要修改时也很方便
#define ROW 9 //行(用于展示)
#define COL 9 //列#define ROWS ROW + 2 (用于布置)
#define COLS COL + 2
我们也将9这个数字定义一下,后续需要修改棋盘大小时方便修改
而给棋盘加条边,相当于行和列各加2,
所以定义ROWS和COLS分别为ROW+2,COL+2
4.最后一个稍微难的点就是统计一个格子周围雷的数量了,我们给排雷的坐标设为(a,b),则用
a-1,b-1,a,b,a+1,b+1这几个数就可以描述周围的坐标了
用于统计的代码如下
arr[a - 1][b - 1] + arr[a - 1][b] + arr[a - 1][b + 1]
+ arr[a][b - 1] + arr[a][b + 1]
+ arr[a + 1][b - 1] + arr[a + 1][b] + arr[a + 1][b + 1] - 7 * '0';
因为我用的是字符1和字符0去描述雷的信息的,所以要统计雷的数量,还要减去7个字符0
即-7 * ' 0 '
5.判定游戏结束
5.1第一种游戏结束的情况就是踩雷了
if (mine[a][b] == '1')
{
printf("雷的位置(1为雷):\n");
_printf(mine);//踩雷后展示所有雷的位置,让玩家“死”而无憾
printf("你被炸死了,游戏结束\n\n"); //这里可以写上当玩家扫雷失败时,对玩家说的话//还可以对玩家进行惩罚~(手动狗头)
return 1;
}
这里的(a,b)是玩家要扫雷的地方,_printf()这个函数是用来打印棋盘的函数,参数是哪个棋盘,(二维数组)
(下面的代码在文章最后有)
void _printf(char arr[ROWS][COLS])
{
for (int i = 0; i <= ROW; i++)
{
printf("%d ", i);//用来给棋盘加上坐标
}
printf("\n");
for (int i = 1; i <= ROW; i++)
{
printf("%d ", i);//用来给棋盘加上坐标
for (int j = 1; j <= COL; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("\n");
}
5.2第二种情况就是玩家将雷以外的空格全扫光了
我们可以用棋盘格子总数减去雷的数量来得到雷以外的所有空格数cot,用while(cot)
判断游戏的进行还是结束
当玩家每扫出一个空格这个数就加减1,直到减少到0,退出循环,玩家胜利,游戏结束
(下面的代码在文章最后有)
while (cot)
{
printf("请输入你要猜的位置>");
scanf("%d%d", &a, &b);
if (a > 0 && a <= ROW && b > 0 && b <= ROW)
{
if (mine[a][b] == '1')
{
printf("雷的位置(1为雷):\n");
_printf(mine);//踩雷后展示所有雷的位置,让玩家“死”而无憾
printf("你被炸死了,游戏结束\n\n");
return 1;
}
else
{
show[a][b] = getnum(mine, a, b);
system("cls");
_printf(show);
cot--;
}
}
else
printf("输入错误,请重新输入\n");//只对数字有效,使用符号会进入死循环
}
printf("恭喜你,扫雷成功!\n");
}
2.游戏逻辑设计想象
1.我们要打印出一个简易菜单,说明进入和退出游戏的操作是什么,为了游戏逻辑简单易读,定义一个菜单函数来负责打印菜单
void menu1()
{
printf("****************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("****************\n");
}
2.用户输入一个数,选择1:进入游戏,选择0:退出游戏,选择0和1以外的其他内容时,提示选择错误,请重新输入,并且用户可再次选择
那就意味着不仅要输入,还要判断这个数并提供相应的作用,并且可以再次输入
可以多次输入就用do - while()循环来实现,条件为scanf(),不仅可以多次输入,还可使得游戏不断进行,里面再嵌套一个switch函数用来使不同的输入值,实现不同的功能
(下面的代码在文章最后有)
int main()
{
srand((unsigned)time(NULL));//设置时间种子,使得随机布雷
int input = 0;
do
{
menu1();
printf("请选择>");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出\n");
break;
case 1:
printf("扫雷开始\n");
int ret=game();
break;
default :
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
3.代码实现分析
在上面的代码中,我们为了游戏逻辑的清晰,将游戏主体的实现放在game()这个函数里面,接下来,我们给他写上代码,完成游戏的设计
1.显示游戏菜单
提示玩家输入何值进行游戏或退出
void menu1()
{
printf("****************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("****************\n");
}
因为对于玩家而言,选择play就是进入游戏,选择play时要向玩家再次提示输入内容的限制
因为我们的棋盘是有限的,坐标的范围也是有限的,玩家有可能输入的不是坐标,而是符号,
会令玩家无法正常游戏,因此有必要提示
printf("注意:\n
1.不可输入字母\n
2.每输入一个数要用空格隔开\n
3.每次必须输入两个数\n");
// 这里为了方便阅读而分成三行,实际上不可分成三行
2.布置棋盘
char mine[ROWS][COLS] = { 0 }; //放置雷的棋盘
char show[ROWS][COLS] = { 0 }; //用于展示的棋盘
3.棋盘初始化
展示的棋盘全用 * 初始化
放雷的棋盘先不放置雷,即用 '0'进行初始化
我们用一个函数来完成棋盘的初始化,则:
函数的第一个参数为哪个棋盘(二维数组),第二个参数为初始化的内容(一个是*,一个是’0‘)
void set(char arr[ROWS][COLS], char s) //函数声明
{
for (int i = 0; i < ROWS; i++)
{
for (int j = 0; j < COLS; j++)
{
arr[i][j] = s;
}
}
}set(mine, '0'); //给雷的棋盘初始化
set(show, '*'); //给展示的棋盘初始化
4. 布置雷
使用随机数随机一个坐标,用一个数设置要放雷的数count,循环放置
如果这个位置没有雷,放一个雷,count-1
如果这个地方已经有雷,count不变,再次进入布置雷的循环,直到count为0
void setmine(char arr[ROWS][COLS], int count)//放置雷的函数
{
while (count)
{
int a = rand() % ROW + 1;
int b = rand() % COL + 1;
if (arr[a][b] != '1')
{
arr[a][b] = '1';
count--;
}
}
}
5.屏幕上打印展示的棋盘
_printf(show);
6.待玩家输入一个坐标,进行排雷
需要注意的是如果输入两个错误的数字,会提示错误,输入一个正确数字、一个错误数字
会使得坐标不按玩家想要的坐标进行排雷,因为上一次的一个坐标合法,这次再获取一个合法数字
即可组成一个坐标。遇到这种情况,只需要输入一个合法数就恢复正常了,但是不能保证这个坐标底下不是雷(合法数就是符合棋盘范围的数)
scanf("%d%d", &a, &b);
7.打印棋盘
1.先判断这个位置是不是雷,是雷,游戏结束,不是雷,统计周围的雷
2.因为棋盘是一个一个打印出来的,排查的位置越多 ,打印的棋盘也越多,为了界面美观,
使用函数system("cls")进行清屏,先清屏,再打印
show[a][b] = getnum(mine, a, b);//统计周围的雷的数量
system("cls");//清屏
_printf(show);//打印棋盘
cot--;
8.游戏胜利
在不断获取非雷位置,最终会获胜,需要使这局游戏停下
while (cot)
{xxxxxxxx //省略这里的代码,看整体结构
xxxxxxxx //省略这里的代码,看整体结构
printf("恭喜你,扫雷成功!\n");
}
9.落入循环,玩家进行下一 次的选择
4.所有代码
在一个工程里,往往会用多个位置来写代码,将头文件、游戏实现、游戏逻辑,分别放置
game.h
头文件
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<windows.h>
#define ROW 9 //行
#define COL 9 //列
#define num 15 //雷的数量
#define ROWS ROW + 2
#define COLS COL + 2
void set(char arr[ROWS][COLS], char s);
void setmine(char arr[ROWS][COLS], int count);
void _printf(char arr[ROWS][COLS]);
char getnum(char arr[ROWS][COLS], int a, int b);
game.c
实现游戏的各个函数主体
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void set(char arr[ROWS][COLS], char s)//初始化函数声明
{
for (int i = 0; i < ROWS; i++)
{
for (int j = 0; j < COLS; j++)
{
arr[i][j] = s;
}
}
}
void setmine(char arr[ROWS][COLS], int count)//放置雷的函数
{
while (count)
{
int a = rand() % ROW + 1;
int b = rand() % COL + 1;
if (arr[a][b] != '1')
{
arr[a][b] = '1';
count--;
}
}
}
void _printf(char arr[ROWS][COLS])
{
for (int i = 0; i <= ROW; i++)
{
printf("%d ", i);//用来给棋盘加上坐标
}
printf("\n");
for (int i = 1; i <= ROW; i++)
{
printf("%d ", i);//用来给棋盘加上坐标
for (int j = 1; j <= COL; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("\n");
}
char getnum(char arr[ROWS][COLS], int a, int b)
{
return arr[a - 1][b - 1] + arr[a - 1][b] + arr[a - 1][b + 1]
+ arr[a][b - 1] + arr[a][b + 1]
+ arr[a + 1][b - 1] + arr[a + 1][b] + arr[a + 1][b + 1] - 7 * '0';
}
test.c
游戏逻辑、游戏实现
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu1()
{
printf("****************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("****************\n");
}
int game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
set(mine, '0');//给雷的棋盘初始化
set(show, '*');//给展示的棋盘初始化
int count = num;
int c = ROW - 1;
setmine(mine, count);
int cot = c * c - count;
printf("注意:\n 1.不可输入字母\n 2.每输入一个数要用空格隔开\n 3.每次必须输入两个数\n");
int a = 0;
int b = 0;
_printf(show);
while (cot)
{
printf("请输入你要猜的位置>");
scanf("%d%d", &a, &b);
if (a > 0 && a <= ROW && b > 0 && b <= ROW)
{
if (mine[a][b] == '1')
{
printf("雷的位置(1为雷):\n");
_printf(mine);//踩雷后展示所有雷的位置,让玩家“死”而无憾
printf("你被炸死了,游戏结束\n\n");
return 1;
}
else
{
show[a][b] = getnum(mine, a, b);//统计周围的雷的数量
system("cls");//清屏
_printf(show);//打印棋盘
cot--;
}
}
else
printf("输入错误,请重新输入\n");//只对数字有效,使用符号会进入死循环
}
printf("恭喜你,扫雷成功!\n");
}
int main()
{
srand((unsigned)time(NULL));//设置时间种子,使得随机布雷
int input = 0;
do
{
menu1();
printf("请选择>");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出\n");
break;
case 1:
printf("扫雷开始\n");
int ret=game();
break;
default :
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
最后再提示:
需要注意的是如果输入两个错误的数字,会提示错误,输入一个正确数字、一个错误数字
会使得坐标不按玩家想要的坐标进行排雷,因为上一次的一个坐标合法,这次再获取一个合法数字
即可组成一个坐标。遇到这种情况,只需要输入一个合法数就恢复正常了,但是不能保证这个坐标底下不是雷(合法数就是符合棋盘范围的数)
阿林说:都看到这里了,点点关注点点赞呗!
“谢谢您的观看!”