这篇博客是为了记录扫雷游戏的全部C语言代码。与前面记录三子棋游戏的博客一样,我会尽最大努力将扫雷游戏的所有细节都梳理出来(在代码中详细注释)。
游戏介绍
总体来说: 扫雷游戏一开始会初始化一个棋盘,在棋盘中任意的位置埋好雷,然后玩家开始排雷,如果玩家将所有的雷的位置都找到,那么玩家胜利。
具体细节:
- 游戏开始,初始化一个棋盘,将雷埋好
- 游戏第一步,玩家随机选个位置,开始排雷
- 玩家排雷时有两个选择:直接排雷或者标记该位置为雷
- 直接排雷时:如果该位置是雷,那么游戏结束,玩家失败;如果该位置不是雷,那么将这个位置周围雷的个数显示在该位置,如果周围雷的个数为零,那么遍历该位置周围其他位置,将其他位置周围雷的个数为零的自动排掉
- 标记雷:将某个位置标记为雷
- 重复执行3-5,直到游戏结束
代码逻辑
为了方便代码编写,也为了更清晰的逻辑,扫雷游戏也是用多文件的方式来实现。总共有三个文件:
game.h
包括所有的头文件引用、宏定义和功能函数的声明game.c
包括所有的功能函数的实现saolei.c
游戏的主体框架以及非功能函数
实现思路
首先需要定义两个二维数组mine[ROWS][COLS]
和show[ROWS][COLS]
来存储棋盘中的内容,show[ROWS][COLS]
存放要展示给玩家的信息,初始时里面全放成'*'
;mine[ROWS][COLS]
用来存储雷的信息,有雷的位置为'1'
,没雷的位置为'0'
。
注意:数组的行和列要比棋盘大2,举个例子来看,如下图所示:
红色框中是我们要展示给玩家的 9*9
的棋盘,总共的格子大小是11*11
的。玩家选择某个位置排雷时,需要检查以这个位置周围的位置有没有雷,当玩家选择边界位置时(如上图中绿色方格),方便起见,我们还是要统计周围位置雷的个数(蓝色方格),这时候如果数组的大小跟棋盘大小一样,那么就会造成数组的越界访问,为避免这个问题,我们就设置数组比棋盘大一圈。
然后在排雷过程中,把玩家标记为雷的位置显示'#'
,把玩家选择排雷的位置显示周围雷的个数。这两个操作修改的都是show数组中的内容。
最后如何判断玩家是否胜利,定义一个变量win
,每确定一个不是雷的位置,win
就增加1,如果win=棋盘格子个数-雷的个数
,那说明玩家已经找到所有雷了,所以胜利。
全部代码
game.h
// game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define EASY_COUNT 10 // 雷的个数,可修改
#define ROW 9 // 棋盘的行数
#define COL 9 // 棋盘的列数
#define ROWS ROW+2 // 数组的行
#define COLS COL+2 // 数组的列
// AutoFind()和GetMineCount()写在game.c里面了
// AutoFind是自动展开函数,GetMineCount是计算周围雷函数
// 初始化
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);
// 标记雷
void MarkShow(char show[ROWS][COLS], int x, int y, int row, int col);
game.c
// game.c
#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;
printf("-------------------------------------\n");
// 列号的打印
for (i = 0; i <= col; 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");
}
// 布置雷的函数 - 生成随机坐标,将该坐标位置设置成雷
// 注意这个函数名称,Mine代表着mine数组,是存储雷的信息的数组,不能给玩家展示的数组,只能自己看
void SetMine(char board[ROWS][COLS], int row, int col)
{
// 1.随机找坐标布置雷
int count = EASY_COUNT; // EASY_COUNT是前面宏定义的数,代表雷的个数
while (count)
{
// 成功布置一个雷,count--
// 1. 生成随机坐标
int x = rand() % row + 1; // 生成的随机数+1之后的范围是(1,row)
int y = rand() % col + 1;
// 2. 布置雷
if (board[x][y] == '0') // 如果当前位置是空的,那么可以设置为雷
{
board[x][y] = '1'; // 用字符'1'来代表雷
count--;
}
}
}
// 统计周围雷个数的函数
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
// '0'字符和'1'字符的ASCII码值分别为48、49,
// 把所有位置字符的值加起来,减去8*'0',就能得到周围雷的个数
return 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';
}
// 扫雷游戏是怎么结束的?
// 1. 炸死了
// 2. 排查出来所有雷
// 自动展开函数 - 递归判断周围雷的个数为0的位置,将该位置置为'0',再遍历该位置的周围位置
// 该函数涉及到mine和show两个数组
int AutoFind(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int row, int col)
{
int win = 0;
if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='*') // 如果坐标正确才可继续
{
int count = GetMineCount(mine, x, y); // 统计(x,y)周围雷的个数
if (0 == count) // 如果(x,y)周围雷的个数为0,那么遍历周围位置
{
win++; // 每找到一个周围雷个数为0的位置,那么说明排掉了一个位置
show[x][y] = '0'; // 将这个位置显示为雷的个数
// 下面就是遍历周围位置
win += AutoFind(mine, show, x, y - 1, ROW, COL);
win += AutoFind(mine, show, x, y + 1, ROW, COL);
win += AutoFind(mine, show, x - 1, y, ROW, COL);
win += AutoFind(mine, show, x + 1, y, ROW, COL);
win += AutoFind(mine, show, x - 1, y - 1, ROW, COL);
win += AutoFind(mine, show, x + 1, y - 1, ROW, COL);
win += AutoFind(mine, show, x - 1, y + 1, ROW, COL);
win += AutoFind(mine, show, x + 1, y + 1, ROW, COL);
}
}
return win; // win非常重要,涉及到后面判断是否胜利
}
// 标记函数 — 将玩家选中的位置用'#'表示
// 注意这个函数修改的也是show数组
void MarkShow(char show[ROWS][COLS], int x, int y, int row, int col)
{
if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '*')
{
show[x][y] = '#';
}
}
// 检查是否踩到雷、是否胜利
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
/*
win代表着棋盘上除了雷以外的所有位置的个数,如果玩家每找到一个除雷以外的位置,那么win就 + 1,直到找到所有
除雷以外的位置,那么玩家就胜利了
*/
while (win < ROW * COL - EASY_COUNT)
{
int input = 0;
printf("请选择你的操作:1.排雷 2.标记雷\n");
scanf("%d", &input);
switch (input)
{
case 1:
{
printf("请输入要排查的坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
// 判断x,y坐标处是否是雷
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, row, col);
exit(0);
break;
}
else
{
// 如果x,y坐标不是雷,就统计周围有几个雷
int count = GetMineCount(mine, x, y);
if (0 == count)
{
win += AutoFind(mine, show, x, y, ROW, COL);
// win加上自动展开的位置的个数
DisplayBoard(show, ROW, COL); // 给玩家展示一下
}
else
{
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
}
else
{
printf("坐标非法\n");
}
if (win == ROW * COL - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
break;
case 2:
{
int p = 0;
int q = 0;
printf("请输入要标记的坐标:>");
scanf("%d%d", &p, &q);
MarkShow(show, p, q, ROW, COL);
DisplayBoard(show, ROW, COL);
}
break;
default:
printf("输入有误,请重新选择:>");
}
}
}
saolei.c
// saolei.c
#include "game.h"
void menu()
{
printf("********************************\n");
printf("******** 1.paly ***********\n");
printf("******** 0.exit ***********\n");
printf("********************************\n");
}
void game()
{
// 真正的扫雷过程
// 创建两个数组
// 存放布置好的雷
char mine[ROWS][COLS] = {0}; // '0'
// 存放排查出来的雷
char show[ROWS][COLS] = {0}; // '*'
// 初始化数组
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
DisplayBoard(show, ROW, COL);
// 1.布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL); // 布置好的雷的信息
// 2.扫雷
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL)); // 设置随机数起点, 时间戳time(NULL)返回值是time_t整型
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
exit(0);
break;
default:
printf("输入错误!\n");
break;
}
} while (input);
return 0;
}
运行效果
为了方便测试,我将埋雷的个数设置成了1
初始界面
初始棋盘
开始排雷
因为只有1个雷,所以周围位置雷个数为0的位置全部自动展开了
排雷成功
标记雷
将(5,5)这个位置标记为雷,显示'#'
.
总结
总体来说上述代码实现了扫雷游戏的主要功能,后续考虑可以做个简单的UI界面,将输入坐标的方式改掉,一个个位置输坐标太麻烦了,假期再说吧。上面的代码我简单测试过很多次,应该没有大的问题,如果有的话,麻烦看到的人给我反馈一下,感激不尽!