业精于勤,荒于嬉;行成于思,毁于随。
2021农历辛丑牛年,祝所有人心想事成,万事胜意!
二维数组练习第二弹,实现扫雷小游戏。
文章目录
一、扫雷游戏规则
把所有非地雷的格子揭开即胜利,踩到地雷格子即失败。
二、游戏流程
电脑打印游戏菜单(1.开始游戏 0.退出游戏)——>玩家选择(开始游戏——>进入游戏函数)——>电脑打印出雷盘——>玩家输入需要排雷的坐标——>可能性1:被炸死 可能性2:坐标安全,系统显示出周围八个格子内的地雷总数——>雷盘更新——>玩家输入需要排雷的坐标,继续排雷——>游戏胜利
三、扫雷游戏的双层数组
前文有写到三(多)子棋小游戏,三子棋游戏只需要一个数组就够了,但是对于扫雷游戏的实现,一个数组是不够的,需要创建两个数组。
两个二维数组
第一个数组,存放雷的分布信息,面向游戏设计者创建,暂且称之为雷盘布置数组,简称布雷数组,如下所示。
0 1 2 3 4 5 6 7 8 9
1 0 0 0 0 0 1 0 1 0
2 0 0 0 0 0 0 1 0 0
3 0 1 0 0 0 0 1 1 0
4 0 0 0 0 0 0 0 0 0
5 0 0 0 0 1 0 0 0 0
6 0 0 0 0 0 0 0 0 0
7 0 0 0 0 1 0 0 0 0
8 0 1 0 0 0 0 1 0 0
9 0 0 0 0 0 0 0 0 0
第二个数组,存放排雷后的雷盘中雷的分布个数信息,面向玩家创建,暂且称之为玩家数组,如下所示。
0 1 2 3 4 5 6 7 8 9
1 * * * * * * * * *
2 * * * * * * * * *
3 * * 1 * * * * * *
4 * * * * * * * * *
5 * * * * * * * * *
6 * * * * * * * * *
7 * * * * * * * * *
8 * * * * * * * * *
9 * * * * * * * * *
布雷数组巧妙的设计
程序设计的时候,需要判断玩家坐标周围八个格子中雷的总个数,那么这就带来一个问题:
玩家选择边角最外环的一层坐标
和玩家选择内环坐标
判断方法不同。目测有两种解决方案。
第一种:加if判断
如果是最外面的一圈的就单独拉出来判断其周围的雷的总数,但是这又导致了一个问题,四个角和每条棱的判断方法各不相同,所以这个解决方案很繁琐。
第二种:巧妙地让布雷数组膨胀一圈
假设让布置雷的雷盘是11X11规格的,但是只在9X9的格子里布雷,那么对于每个9X9格子里的坐标,判断周围格子里雷的总数的算法都是一样的。(把巧妙打在公屏上!!!)
四、程序设计
创建三个文件,game.h
游戏的头文件functions.c
游戏所用到功能函数 game.c
游戏框架 三个文件。
头文件
头文件的书写是一步一步需要什么写什么建立而来的,这里先把所有的宏定义、库函数引用、函数声明放这里,后面用到会有解释。
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void test(void);
void menu(void);
void game(void);
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[ROW][COL], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
游戏实现框架
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"
int main(void)
{
test();
return 0;
}
//游戏执行测试框架
void test(void)
{
int input = 0;
do {
menu();//系统打印游戏菜单,调用menu函数
printf("请选择:>>>");
scanf("%d", &input);
switch (input)//switch开关语句,作用显而易见
{
case 1:
game();//调用game函数
break;
case 2:
printf("退出游戏\n");
input = 0;
break;
default:
printf("输入值无效\n");
break;
}
} while (input);
}
//菜单打印函数
void menu(void)
{
printf("************************************\n");
printf("*****1.开始游戏 0.退出游戏******\n");
printf("************************************\n");
}
//游戏主函数
void game(void)
{
//第一步,创建两个数组,一个是布盘数组,一个是用户数组
//两个数组大小相同,属于叠加的双层结构。
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//第二步,初始化
InitBoard(mine, ROWS, COLS, '0');//初始化两个数组。
InitBoard(show, ROWS, COLS, '*');
//第三步,布置雷
SetMine(mine, ROW, COL);//布置雷
DisplayBoard(mine, ROW, COL);//打印雷盘分布,上帝视角,测试需要。
DisplayBoard(show, ROW, COL);//更新打印好后的雷盘
//第四步,扫雷
FindMine(mine, show, ROW, COL);
}
功能函数
游戏主函数中除游戏框架外的所有函数,习惯单独写在一个文件里。
InitBoard
数组创建好,需要对两个数组进行初始化,但是因为两个数组初始化的分辨元素不一样,所以这里加入一个调用参数set。
//初始化函数
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;//让布雷数组全部元素为字符'0',玩家数组全部为字符'*'
}
}
}
DisplayBoard
初始化函数写好之后可以先写雷盘打印函数进行核验,所以先介绍DisplayBoard函数。
//雷盘打印
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("\n");//打印个换行符分割下,不是必要
//打印列号
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");//打印个换行符分割下,不是必要
}
SetMine
数组初始化好就要布置雷了,采用随机值的方法进行布雷,传递整个数组,但需要控制雷的分布只出现在内环9*9的网格里。
//布置雷盘
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
srand((unsigned int)time(NULL));//使用系统时间作为随机值的种子
while (count)//控制雷的个数
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
FindMine
进行扫雷,如果玩家踩雷就被炸死,未踩雷,系统报告周围雷的个数。
//扫雷
void FindMine(char mine[ROWS][COLS], char show[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啊哦,你被炸死了!!!\n");
DisplayBoard(mine, row, col);
break;
}
else
{//计算周围8个方块中雷的总数
int count = (mine[x - 1][y] + mine[x - 1][y - 1] +
mine[x][y - 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1] +
mine[x][y + 1] + mine[x - 1][y + 1]) - 8 * '0';
show[x][y] = count + '0';
DisplayBoard(show, row, col);
win++;//玩家输入一次win加一次,等到棋盘上只有雷就胜利了
}
}
else
{
printf("非法输入!!!\n");
}
}
}
五、完整程序
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void test(void);
void menu(void);
void game(void);
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 show[ROWS][COLS], int row, int col);
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"
int main(void)
{
test();
return 0;
}
//游戏执行测试框架
void test(void)
{
int input = 0;
do {
menu();//系统打印游戏菜单,调用menu函数
printf("请选择:>>>");
scanf("%d", &input);
switch (input)//switch开关语句,作用显而易见
{
case 1:
game();//调用game函数
break;
case 2:
printf("退出游戏\n");
input = 0;
break;
default:
printf("输入值无效\n");
break;
}
} while (input);
}
//菜单打印函数
void menu(void)
{
printf("************************************\n");
printf("*****1.开始游戏 0.退出游戏******\n");
printf("************************************\n");
}
//游戏主函数
void game(void)
{
//第一步,创建两个数组,一个是布盘数组,一个是用户数组,两个数组大小相同,属于叠加的双层结构。
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//第二步,初始化
InitBoard(mine, ROWS, COLS, '0');//初始化两个数组。
InitBoard(show, ROWS, COLS, '*');
//第三步,布置雷
SetMine(mine, ROW, COL);//布置雷
DisplayBoard(mine, ROW, COL);//打印雷盘分布,上帝视角,测试需要。
DisplayBoard(show, ROW, COL);//更新打印好后的雷盘
//第四步,扫雷
FindMine(mine, show, ROW, COL);
}
#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;//让布雷数组全部元素为字符'0',玩家数组全部为字符'*'
}
}
}
//打印雷盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("\n");//打印个换行符分割下,不是必要
//打印列号
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 SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
srand((unsigned int)time(NULL));//使用系统时间作为随机值的种子
while (count)//控制雷的个数
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
//扫雷
void FindMine(char mine[ROWS][COLS], char show[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啊哦,你被炸死了!!!\n");
DisplayBoard(mine, row, col);
break;
}
else
{//计算周围8个方块中雷的总数
int count = (mine[x - 1][y] + mine[x - 1][y - 1] +
mine[x][y - 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1] +
mine[x][y + 1] + mine[x - 1][y + 1]) - 8 * '0';
show[x][y] = count + '0';
DisplayBoard(show, row, col);
win++;//玩家输入一次win加一次,等到棋盘上只有雷就胜利了
}
}
else
{
printf("非法输入!!!\n");
}
}
}
六、游戏测试
这里将ROW
和COL
更改成3
,EASY_COUNT
改成了5
进行测试。
************************************
*****1.开始游戏 0.退出游戏******
************************************
请选择:>>>1
0 1 2 3
1 1 1 0
2 1 0 1
3 0 0 1
0 1 2 3
1 * * *
2 * * *
3 * * *
请输入排查雷的坐标:>>>1 1
啊哦,你被炸死了!!!
0 1 2 3
1 1 1 0
2 1 0 1
3 0 0 1
************************************
*****1.开始游戏 0.退出游戏******
************************************
请选择:>>>1
0 1 2 3
1 0 1 1
2 1 0 1
3 0 1 0
0 1 2 3
1 * * *
2 * * *
3 * * *
请输入排查雷的坐标:>>>2 2
0 1 2 3
1 * * *
2 * 5 *
3 * * *
请输入排查雷的坐标:>>>1 1
0 1 2 3
1 2 * *
2 * 5 *
3 * * *
请输入排查雷的坐标:>>>3 1
0 1 2 3
1 2 * *
2 * 5 *
3 2 * *
请输入排查雷的坐标:>>>3 3
0 1 2 3
1 2 * *
2 * 5 *
3 2 * 2
恭喜你,历经千辛万苦排雷成功!
************************************
*****1.开始游戏 0.退出游戏******
************************************
请选择:>>>
七、写在后面
总的来说,这个程序只用了四个功能函数(处游戏框架外)实现了基本的扫雷程序,但是游戏还是非常鸡肋
的,在真正的扫雷游戏中,当玩家选择的坐标周围八个坐标均没有雷时雷盘会之间展开,这还是很必要的,因为当扫雷的棋盘很大时,一个一个的选择坐标实属太没有游戏体验了,这个需要加入一个递归展开函数。
加入递归展开后,游戏胜利判断条件也需要改变,相对复杂,所以写在了下一篇博客
,欢迎各位大佬进行检阅、批评和指正,非常感谢!!!非常感谢!!!