C语言扫雷游戏
野猪佩奇` 人气:0大家好,今天我们将一起用C语言实现一个经典小游戏 – 扫雷,Let is go !
游戏介绍
扫雷游戏相信大家都玩过,上图就是一个网页版的扫雷,它的规则是玩家选择一个方格,若此方格没有地雷,那么该方格会显示与它相邻的八个方格中雷的个数,若此方格有地雷,那么游戏失败,当玩家把除了有地雷的方格外的其他方格都成功翻开时,游戏胜利。
游戏整体框架
对于一个代码量还算可以的小游戏我们还是利用多文件来进行编程,养成良好习惯,为以后在公司团队合作编程打下基础,因此我们把扫雷游戏分成三个文件来编写:
test.c:游戏逻辑的测试,包含游戏菜单的打印,游戏设计的基本逻辑的展示。
game.c:游戏功能的具体实现,这部分是整个游戏的核心代码,一般不会展示给用户。
game.h:相关头文件的包含、符号的声明以及函数的声明。
游戏具体功能及实现
1、雷盘的定义
对于扫雷游戏,我们遇到的第一个问题就是:应该如何表示扫雷的雷盘及如何存放布雷、排雷的数据;我们发现,二维数组可以很好的解决这个问题。
如上图:我们定义了两个棋盘,分别用来保存布置雷的信息和排查雷的信息,这样就可以避免二者相互干扰或者相互覆盖;
同时,我们使用宏来定义雷盘的大小以及雷的个数,这样做的好处是当我们以后想使用更大的雷盘或者想增加扫雷的难度的时候,我们只需要改动这里一次即可,增加了代码的可维护性。
另外,很多小伙伴可能会疑惑为什么我这里会定义两个不同ROW和COL,这其实是为后面的排雷做铺垫:
如图:当我们排查1位置时,如果1处不是雷,那么我们就会依次检查1周围8个坐标是否有地雷,如果有,就会把地雷的数量显示在1位置处;但是当我们排查2位置时,我们发现, 数组排查雷时会发生越界,所以为了避免数组越界,我们就需要增加一系列限制条件,这样做无疑是比较麻烦的,所以有的大佬就想出了这样一种办法:在定义数组长度时我们直接在上下左右四个方向各多给一行的空间,并把这些空间中的数据初始化为非雷,这样,就轻松解决了数组越界的问题,不得不说,这种方法实在巧妙!
2、雷盘的初始化
最开始的时候我们把mine数组元素全部初始化为字符0,把show数组元素全部初始化为字符*(给用户一种神秘的感觉)。
3、布置雷
对于布置雷我们有两个需要注意的地方:
第一是用于随机生成坐标的rand函数的种子srand函数只需要在main函数中声明一次即可。
第二是我们在布置雷的时候需要检查该位置是否已经有雷,避免重复布置。
4、排查雷
排查雷的时候我们首先需要让用户输入需要排查的坐标,然后判断坐标的合法性及该坐标是否已被排查,其次再判断该坐标是否有雷,如果没有,就递归检查它周围的坐标,直到遇到有雷的坐标才停止递归,再让用户选择是否需要标记雷的信息,最后检查是否满足游戏胜利的条件。
5、递归式展开一片
观察网页版的扫雷我们可以发现,当用户点击一个坐标,如果该坐标及其周围的坐标都没有雷,那么雷盘就会一次性展开一片,而这样设计也是比较合理的,因为如果每一个非雷坐标都需要玩家排查的话十分影响游戏体验;所以,这里我们就利用递归的实现模拟实现了这个功能。
6、获取周围雷的个数
7、标记特定位置
同样:在网页版的扫雷中,如果我们确定某一位置一定是雷时,我们可以利用标记功能来标识该坐标,方便我们后面的判断。
本代码中,我们用字符 ! 来标识雷。
8、打印雷盘
游戏完整代码
1、test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void menu() { printf("*****************************************\n"); printf("********* 1.play 0.exit *********\n"); printf("*****************************************\n"); } void game() { //定义用于存放雷和显示雷的数组 char mine[ROWS][COLS]; char show[ROWS][COLS]; //数组初始化 BoardInit(mine, ROWS, COLS, '0'); BoardInit(show, ROWS, COLS, '*'); //埋雷 SetMine(mine, ROW, COL); system("cls"); //清除菜单,美观整洁 //打印雷盘 //BoardPrint(mine, ROW, COL); //用于自己调试观察,在发布时注释掉 BoardPrint(show, ROW, COL); //排雷 FindMine(mine, show, ROW, COL); } int main() { //设置随机数的种子 srand((unsigned int)time(NULL)); int input = 0; do { menu();//菜单 printf("请选择:"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏!\n"); break; default: printf("输入错误,请重新输入!\n"); break; } } while (input); return 0; }
2、game.h
#pragma once #include<stdio.h> #include<windows.h> #include<time.h> #include<stdlib.h> #define ROW 9 #define COL 9 #define ROWS ROW + 2 #define COLS COL + 2 #define MINE_COUNT 10 //数组初始化 void BoardInit(char board[ROWS][COLS], int rows, int cols, char set); //埋雷 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 BoardPrint(char board[ROWS][COLS], int row, int col);
3、game.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //数组初始化 void BoardInit(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; //set表示要初识化的字符 } } } //埋雷 void SetMine(char board[ROWS][COLS], int row, int col) { int count = MINE_COUNT; while (count) { int x = rand() % row + 1; //随机生成雷的坐标 int y = rand() % col + 1; if (board[x][y] == '0') //检查该位置是否已经有雷 { board[x][y] = '1'; count--; } } } //打印雷盘 void BoardPrint(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 MarkMine(char board[ROWS][COLS], int row, int col) { int x = 0; int y = 0; while (1) { printf("请输入你想要标记位置的坐标->"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) //判断该坐标是否合法 { if (board[x][y] == '*') //判断该坐标是否被排查 { board[x][y] = '!'; break; } else { printf("该位置不能被标记,请重新输入!\n"); } } else { printf("输入错误,请重新输入!\n"); } } } //获取坐标周围雷的个数 int GetMineCount(char board[ROWS][COLS], int x, int y) { int i = 0; int j = 0; int count = 0; for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { if (board[i][j] == '1') { count++; } } } return count; } //递归爆炸式展开一片 void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* pw) { if (x >= 1 && x <= row && y >= 1 && y <= col) //判断坐标是否为排查范围内 { int num = GetMineCount(mine, x, y); //获取坐标周围雷的个数 if (num == 0) { (*pw)++; show[x][y] = ' '; //如果该坐标周围没有雷,就把该坐标置成空格,并向周围八个坐标展开 int i = 0; int j = 0; for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { if (show[i][j] == '*') //限制递归条件,防止已经排查过的坐标再次递归,从而造成死递归 ExplosionSpread(mine, show, row, col, i, j, pw); } } } else { (*pw)++; show[x][y] = num + '0'; } } } //排雷 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; int win = 0; //用来标记是否取得胜利 int* pw = &win; char ch = 0; //用来接受是否需要标记雷 while (win < row * col - MINE_COUNT) { 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); //被炸死了就打印mine数组,让用户知道自己怎么死的 break; } else { if (show[x][y] != '*') //判断是否重复排查 { printf("该坐标已被排查,请重新输入!\n"); continue; //直接进入下一次循环 } else { ExplosionSpread(mine, show, row, col, x, y, pw); //爆炸展开一片 system("cls"); //清空屏幕 BoardPrint(show, row, col); //打印棋盘 printf("需要标记雷的位置请输入y/Y,否则请按任意键->"); while ((ch = getchar()) != '\n'); //清理缓冲区 scanf("%c", &ch); if (ch == 'Y' || ch == 'y') { MarkMine(show, row, col); //标记雷 system("cls"); BoardPrint(show, row, col); } else { continue; } } } } else { printf("输入错误,请重新输入!\n"); } } if (win == row * col - MINE_COUNT) { system("cls"); printf("恭喜你,排雷成功!\n"); BoardPrint(show, row, col); return; } }
游戏效果展示
加载全部内容