前言
相信大多数人都玩过扫雷游戏–微软自带扫雷游戏。
在一个9×9(初级)、16×16(中级)、16×30(高级)或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个),再由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。
玩家根据数字(数字代表周围8个格子有多少个雷)来依次排除雷;当点击到空白位置时,区域就会自动展开;你知道雷位置之后,也可以标记这个雷;当然,老玩家都知道,如果标记正确的话,点击已经开过的位置时,它还会自动展开。
下面,我们就来实现一个9*9扫雷游戏的主要特性,包括:标记雷和空白展开,当然扫雷的基本特性也会实现。实现标记和排查用坐标方式来进行。
非常清晰的思路布局
实现游戏界面
我们先对简单部分下手。对于一个游戏,首先映入眼帘的就是界面显示,这个界面拥有一些选择功能。所以我们先写一个打印菜单,然后给玩家选择的权利。
打印菜单嘛,只需用到printf即可;选择部分我们就要用到switch来进行选择;
在switch中我们根据菜单给出的选项,给出相应的选择项目,当然,还需要考虑到玩家可能会输入错误,所以我们就要用到default识别。无论玩家是进入游戏还是退出游戏,决定权永远在玩家手里,所以,即使进入游戏之后,当一局游戏结束之后,也要给玩家继续选择。所以,我们要在这个switch外面写一个循环封装起来。对于循环,只要选择退出游戏就是终止循环,所以,我们可以用0代表退出游戏,对于先做后判断循环是否继续的,直接使用do while会更加完美。
(为了避免文章冗余,这里先给出思路图,代码会放在最后面)
大体思路的布局
接着就需要考虑进入游戏的情况了。我们要先理一下总体思路。首先就是展示一个棋盘给玩家,然后玩家进行排查雷;要有雷,我们就得埋雷。在这里我们用**‘1’来标识雷(原因后面会知),非雷就用‘0’标记**;在一个棋盘上我们既要埋雷,又得在棋盘上对其掩盖起来,对玩家进行隐藏,在同一个棋盘上我们有点不知所措;那么我们不妨创建两个棋盘,一个来进行埋雷,一个进行显示掩盖效果。那么向玩家展示的,就是这个显示效果的了。之后我们要简单模拟一下棋盘排除雷时的效果,我们会发现,在9*9的棋盘上,==当位置处于边缘时,对它进行周围雷的统计时,会发生越界,==为了防止越界,我们实际创建棋盘时就要创建一个大一点的棋盘,给玩家展示就展示小一点的即可。
简简单单的初始化和打印
我们首先需要弄一个棋盘出来,也就是打印。而在打印出来之前,我们打印什么就需要进行初始化。棋盘是一个面,所以我们用一个二维数组创建棋盘。因此,我们可以用两个for循环来进行遍历初始化。由于我们有两个棋盘,所以要进行不同的初始化,所以可以在形参中添加一个变量进行不同的初始化的选择。
void BoardInit(char board[ROWS][COLS], int row, int col,char init);
init就是进行不同初始化的选择。
打印也是用两个for循环来进行。当然,我这里为了方便最终会给出坐标来对应行列,便于玩家来选择坐标。
理所应当的埋雷
接着就是实现埋雷了,这里我们会埋10颗雷。我们想要的效果肯定是电脑能给我们随机埋雷,这时就要想要随机数函数rand(),在对它进行取模即可,但只对它取模的话,只会是0—8,所以我们只需要简单的+1就行。最后用while来循环10次埋雷。要实现不同地方埋雷,还需要判断是否出现同个地方已经埋雷的情况,所以成功埋一颗雷就减少一颗。
while (count)//全部10颗雷,没有雷就停止 { x = rand() % 9 + 1;//随机数 y = rand() % 9 + 1; if (board[x][y] == ‘0’)//进行判断 { board[x][y] = ‘1’; count–;//成功减少一颗 }
有点难的排雷
最后就是排查雷,这是最关键的一步,相对的,也比较难以实现。
对于较为复杂的情况,我们就要分情况。思考片刻,不就是分这个地方有雷没雷的情况吗?当然我们还要考虑玩家可能会输入错误的坐标,那我们就大体分为三种情况即可。
这里先对有雷的先说,没雷会展开细说。上面我们埋雷知道,雷就是‘1’,所以踩到雷之后,就把实际雷的情况展示给玩家看即可。
没雷时,我们还需要判断该位置是否为空白,如果是空白就进行展开,非空白那就停止。
是否空白我们还需要进行判断;我们要对该位置周围进行判断雷的个数;
我们上面引用了‘1’来标识雷,周围的雷数我们将这些‘1’加起来即可。这就是利用‘1’的方便之处。由于周围要访问8个格子,我们不妨对这些位移下标用一个全局变量一维数组进行记录,引用时只需要一个循环即可,这样写你会发现思路明朗不少,而且代码看起来比较干净。
//创建下标位移数组
int dx[8] = { -1,-1,-1,0,0,1,1,1 };
int dy[8] = { -1,0,1,-1,1,-1,0,1 };
引用时,只需对当前下标再加上位移下标数组的位置即可。
for (int i = 0; i < 8; i++) { int nx = x + dx[i]; int ny = y + dy[i]; count = count + (mine[nx][ny] - ‘0’);//由于打印时用的是字符,所以需要进行简单的转换,也就是减去‘0’ //count是统计雷数的。 }
非空白情况很容易,只需要显示当前周围雷数就行。空白该如何展开才是一个难点。
假设我们对一个空白位置上,我们需要对它周围判断是否为为空白,如果是空白,继续展开,遇到数字了,就停下来。如:
这个数的正上方为空白,那么就要对这个正上方的空白位置的周围位置继续遍历。这种有种“以此类推”的思想,我们会想到循环和递归,但是循环在这里明显行不通,如果看过我前面一些文章的话,我们知道递归还有另一种说法:深度优先探索,能对周围一直探索下去。所以这种递归的思想是符合我们想要”以此类推“的思想的。符合递归思想后,我们就要想出递归的终止条件。
由图可看出,还没有限制条件的递归会对同一个地方进行多次访问,我们就要排除已经遍历过的。再给它一个框架限制在9*9棋盘内即可。而遇到非空白的就停止对周围进行遍历访问。那么我们可以将是否空白结合起来,只要遇到空白就只给出相应雷数数字就行。我们到这里还要用一个变量进行对非雷进行统计,因为判断正确时,我们还要继续排查雷,所以加上一个循环,这个变量可以用来判断循环条件,且可以判断输赢。
到这里,扫雷小游戏已经基本实现完了,但当我在测试时,如果没有对已知雷进行标记,还是有点眼花缭乱,所以最后,我还多加了一个标记雷的功能。标记雷也很容易,跟埋雷差不多,用两个for循环,再判断该位置是否已经被展开即可。
由于我们在中途想出来的,所以最后是在排除雷的函数中进行实现的。也是分情况进行判断。
最后,对以上实现不同功能的扫雷函数进行封装,就完成了。
这里,还是用了分文件进行代码实现。
原代码
//test.c #include"game.h" void menu() { printf("*************************\n"); printf("*************************\n"); printf("****** 1.PLAY *******\n"); printf("****** 0.EXIT *******\n"); printf("*************************\n"); printf("*************************\n"); } void game() { char mine[ROWS][COLS];//埋雷的 char show[ROWS][COLS];//操作的 //初始化 BoardInit(mine, ROWS, COLS,'0'); BoardInit(show, ROWS, COLS,'*'); //打印 BoardPrint(show, ROW, COL); //埋雷 SetMine(mine, ROW, COL); //排查雷(包含标记雷) FineMine(mine, show, ROW, COL); } int main() { int input = 0; srand((unsigned int)time(NULL));//随机种子 do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: system("cls");//表示清屏,为了使显示效果更加简洁 printf("已进入游戏。\n"); game(); break; case 0: system("cls"); printf("已退出游戏。\n"); break; default: printf("输入错误,请重新输入。\n"); break; } } while (input); return 0; }
//game.c #define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //初始化 void BoardInit(char board[ROWS][COLS], int row, int col,char init) { for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { board[i][j] = init; } } } //打印 void BoardPrint(char board[ROWS][COLS], int row, int col) { 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 ", board[i][j]); } printf("\n"); } } //埋雷 void SetMine(char board[ROWS][COLS], int row, int col) { int x, y; int count = COUNT; while (count) { x = rand() % 9 + 1; y = rand() % 9 + 1; if (board[x][y] == '0') { board[x][y] = '1'; count--; } } } //选择菜单 void menu_() { printf("选择标记雷或者排查雷\n"); printf("**** 1.标记雷 ****\n"); printf("**** 2.排查雷 ****\n"); } //标记雷 void Mark(char show[ROWS][COLS],int row,int col) { int x, y; while (1) { printf("请输入你要标记雷的坐标:"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show[x][y] == '*') { system("cls"); show[x][y] = '#'; BoardPrint(show, row, col); break; } else { printf("该位置已经是非雷了,请重新输入\n"); } } else { printf("输入坐标超出坐标范围,请重新输入。\n"); } } } //创建下标位移数组 int dx[8] = { -1,-1,-1,0,0,1,1,1 }; int dy[8] = { -1,0,1,-1,1,-1,0,1 }; //查找周围有没有雷 int GetMineCount(char mine[ROWS][COLS], int x, int y) { int count = 0; for (int i = 0; i < 8; i++) { int nx = x + dx[i]; int ny = y + dy[i]; count = count + (mine[nx][ny] - '0'); } return count; } //展开 void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,int* sum)//这里要对sum进行保存,所以用了指针取实参的地址 { if (x >= 1 && x <= ROW && y >= 1 && y <= COL&& show[x][y] == '*')//符合递归条件 { (*sum)++;//记录符合个数 int count = GetMineCount(mine, x, y); if (count == 0)//0继续递归,非0显示并停止 { for (int i = 0; i < 8; i++)//依次向周围进行递归探索 { show[x][y] = ' '; //nx,ny表示进入下个位置的下标 int nx = x + dx[i]; int ny = y + dy[i]; expand(mine, show, nx, ny, sum); } } else { show[x][y] = count + '0'; } } } //排查雷 void FineMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row, int col) { //sum为统计非雷个数 int x, y,sum=0; while (sum < row * col - COUNT) { int input = 0; //选择标记雷或者排查雷 menu_(); printf("请输入:"); scanf("%d", &input); if (input == 1) { Mark(show, row, col); } else if (input == 2) { printf("请输入坐标:"); scanf("%d %d", &x, &y); //在给定范围内 if (x >= 1 && x <= row && y >= 1 && y <= col) { if (mine[x][y] == '1') { system("cls"); printf("恭喜你中奖了,请重新开始。\n"); BoardPrint(mine, row, col); break; } else { expand(mine, show, x, y, &sum); system("cls"); BoardPrint(show, row, col); } } else { printf("超出输入范围,请重新输入。\n"); } } else { printf("输入错误,请重新输入。\n"); } } if (sum == col * row - COUNT) { system("cls"); printf("恭喜你,游戏胜利。\n"); BoardPrint(mine, row, col); } }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #include<time.h> #include<windows.h> //ROW和COL表示棋盘可执行大小,ROWS和COLS表示实际棋盘大小 #define ROW 9 #define COL 9 #define ROWS (ROW+2) #define COLS (COL+2) //表示埋雷个数 #define COUNT 10 //初始化 void BoardInit(char board[ROWS][COLS], int row, int col,char init); //打印 void BoardPrint(char board[ROWS][COLS], int row, int col); //开始埋雷 void SetMine(char board[ROWS][COLS], int row, int col); //排查雷 void FineMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row, int col);
没有什么的结尾
在这个代码里面,还是能实现选择不同难度的扫雷,只不过决定权没有给到玩家手上,因为实现该小游戏的目的在于对我们C语言能力的验证,并且我们只是打印了出来,看起来还是有点辛苦的。所以就没有实现。
好了,这篇文章就到这里吧,如果你对这篇文章感兴趣的话,那就点给赞吧。