C语言--从零开始的扫雷游戏
- 1. 游戏说明
- 2. 总体代码
- 3. 详细讲解
- 3.1 菜单部分
- 3.2 游戏主体部分
- 3.2.1 总体分析
- 3.2.2 棋盘初始化
- 3.2.3 棋盘展示
- 3.2.4 设置地雷
- 3.2.5 扫雷阶段
- 3.2.6 统计雷个数的代码
- 3.2.7 使用迭代的方式进行展开:
- 3.2.8 扫雷部分主体代码
- 4. 总结
1. 游戏说明
扫雷游戏的功能说明:
• 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9的格⼦
• 默认随机布置10个雷
• 可以排查雷
◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
2. 总体代码
//头文件部分
#define _CRT_SECURE_NO_WARNINGS
#pragma once#include <stdio.h>
#include <time.h>
#include <stdlib.h>//棋盘长宽
#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2#define EASY_COUNT 10 //地雷个数//初始化棋盘
void InitBoard(char arr[ROWS][COLS], int rows, int cols,char ch);//打印棋盘
void PrintBoard(char arr[ROWS][COLS], int row, int col);//设置地雷
void MineSet(char mine[ROWS][COLS], int row, int col);//找雷
void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS],int row,int col);//以上为头文件部分
//测试部分
#define _CRT_SECURE_NO_WARNINGS#include "game.h"void menu()
{printf("******************\n");printf("**** 1.play ****\n");printf("**** 0.exit ****\n");printf("******************\n");
}void game()
{char show[ROWS][COLS];// '*'char mine[ROWS][COLS];// '数字'//初始化InitBoard(show, ROWS, COLS, '*');InitBoard(mine, ROWS, COLS, '0');//打印棋盘//PrintBoard(mine, ROW, COL);PrintBoard (show, ROW, COL);//设置地雷MineSet (mine, ROW, COL);//PrintBoard (mine, ROW, COL);//找地雷FindMine(mine,show, ROW, COL);}int main()
{int input=0;srand((unsigned int)time(NULL));do{menu();printf("请输入你的选择:> ");scanf("%d", &input);switch (input){case 1:printf("< 请注意,本轮一共有%d颗雷 >\n", EASY_COUNT);game();break;case 0:printf("游戏结束\n");break;default:printf("输入错误,请重新输入!\n");break;}} while (input);return 0;
}//以上为测试部分
//主体游戏部分
#define _CRT_SECURE_NO_WARNINGS#include "game.h"void InitBoard(char arr[ROWS][COLS], int rows, int cols, char ch)
{int i = 0;int j = 0;for (i = 0; i < ROWS; i++){for (j = 0; j < COLS; j++){arr[i][j] = ch;}}
}void PrintBoard(char arr[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 ", arr[i][j]);}printf("\n");}printf("********************\n");
}//设置地雷
void MineSet(char mine[ROWS][COLS], int row, int col)
{int count = 0;while (count < EASY_COUNT){int x = rand() % row + 1;int y = rand() % col + 1;if (mine[x][y] == '0'){mine[x][y] = '1';count++;}}
}//统计该点附近雷的个数
int MineCount(char mine[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++){count =count + (mine[i][j] - '0');}}return count;
}//如果附近没有雷,直接展开
void BlankExpansion(char mine[ROWS][COLS],char show[ROWS][COLS],int x,int y)
{int i = 0;int j = 0;int count = MineCount(mine, x, y);if (count == 0){show[x][y] = ' ';for (i = x - 1; i <= x + 1; i++){for (j = y - 1; j <= y + 1; j++){if (show[i][j] == '*'){BlankExpansion(mine, show, i, j);}}}}else{show[x][y] = count + '0';}}//统计剩余‘*’的个数
int LeftCount(char show[ROWS][COLS], int row, int col)
{int i = 0;int j = 0;int count = 0;for (i = 1; i <= row; i++){for (j = 1; j <= col; j++){if (show[i][j] == '*')count++;}}return count;
}//扫雷
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{int count = row * col;int x = 0;int y = 0;while (count > EASY_COUNT){ printf("请输入坐标:> ");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){if (mine[x][y] == '1'){printf("好可惜,你被雷炸死了\n");PrintBoard(mine, ROW, COL);break;}else{BlankExpansion(mine, show, x, y);count = LeftCount(show, ROW, COL);PrintBoard(show, ROW, COL);printf("剩余未知点数: %d\n", count);}}else{printf("输入坐标错误,请重新输入\n");}}if (count == EASY_COUNT){printf("恭喜你,你赢了!\n");}}//主体游戏部分
3. 详细讲解
3.1 菜单部分
打开游戏程序,第一步是展示菜单,提醒玩家进行选择,菜单与代码如下:
void menu()
{printf("******************\n");printf("**** 1.play ****\n");printf("**** 0.exit ****\n");printf("******************\n");
}
根据玩家的选择,进入不同的程序当中,当玩家选择 1 的时候,进入游戏程序,当玩家选择 0 的时候,结束游戏。考虑到玩家玩游戏不可能只是玩一次,因而需要在开头加入一个循环部分。代码实现如下:
int main()
{srand((unsigned int)time(NULL));//生成随机数int input=0;do{menu();printf("请输入你的选择:> ");scanf("%d", &input);switch (input){case 1:printf("< 请注意,本轮一共有%d颗雷 >\n", EASY_COUNT);game();break;case 0:printf("游戏结束\n");break;default:printf("输入错误,请重新输入!\n");break;}} while (input);return 0;
}
3.2 游戏主体部分
3.2.1 总体分析
扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。
因为我们需要在 9 * 9 的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个 9 * 9 的数组来存放信息。
那如果这个位置布置雷,我们就存放1,没有布置雷就存放0.
假设我们排查 ( 2 , 5 ) 这个坐标时,我们访问周围的⼀圈 8 个⻩⾊位置,统计周围雷的个数是 1.
假设我们排查 ( 8 , 6 ) 这个坐标时,我们访问周围的⼀圈 8 个⻩⾊位置,统计周围雷的个数时,最下⾯的三个坐标就会越界,为了防⽌越界,我们在设计的时候,给数组扩⼤⼀圈,雷还是布置在中间的 9 * 9的坐标上,周围⼀圈不去布置雷就⾏,这样就解决了越界的问题。所以我们将存放数据的数组创建成 11 * 11 是⽐较合适。
我们在棋盘上布置了雷,棋盘上雷的信息(1)和⾮雷的信息(0),假设我们排查了某⼀个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪⾥呢?如果存放在布置雷的数组中,这样雷的信息和雷的个数信息就可能或产⽣混淆和打印上的困难。
这⾥我们肯定有办法解决,⽐如:雷和⾮雷的信息不要使⽤数字,使⽤某些字符就⾏,这样就避免冲突了,但是这样做棋盘上有雷和⾮雷的信息,还有排查出的雷的个数信息,就⽐较混杂,不够⽅便。
这⾥我们采⽤另外⼀种⽅案,我们专⻔给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不⼲扰了,把雷布置到
mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
同时为了保持神秘,show数组开始时初始化为字符’*‘,为了保持两个数组的类型⼀致,可以使⽤同⼀套函数处理,mine数组最开始也初始化为字符’0’,布置雷改成’1’。如下如:
mine数组布置雷后的状态
show输出初始化的状态
对应的数组应该是:
char mine[11][11] = {0};//⽤来存放布置好的雷的信息 char show[11][11] = {0};//⽤来存放排查出的雷的个数信息
为方便代码管理,将文件内容分为以下三个方面
test.c //⽂件中写游戏的测试逻辑
game.c //⽂件中写游戏中函数的实现等
game.h //⽂件中写游戏需要的数据类型和函数声明等
为了方便后期对难易程度的修正,可以将行和列的信息用宏的方式进行修正,如下所示:
//棋盘长宽
#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2//地雷个数
#define EASY_COUNT 10//修改后的数组
char show[ROWS][COLS];// '*'
char mine[ROWS][COLS];// '数字'
3.2.2 棋盘初始化
在布雷之前,需要将两个棋盘进行初始化。为了后面再计数的时候不影响判断,此时应该将 ROWS 与 COLS 传入函数当中。为了使初始化函数的能对两个数组都起作用,因而还要将要初始化的字符传进去。代码如下:
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char ch)
{int i = 0;int j = 0;for (i = 0; i < ROWS; i++){for (j = 0; j < COLS; j++){arr[i][j] = ch;}}
}
3.2.3 棋盘展示
初始化之后,需要写一个棋盘显示函数来显示棋盘的初始化结果以及后面不同结果的展示,代码如下:
void PrintBoard(char arr[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 ", arr[i][j]);}printf("\n");}printf("********************\n");
}
此处需要注意,传入数组的时候,要把整体 11 * 11 的数一起传进去,目的使方便后面对整体把控,但是在进行显示的时候,只需要显示 9 * 9 的范围即可,第一行和最后一行只是我们为了方便之后对地雷的统计而加进去的,给玩家展时的界面里面不应该存在第一行与最后一行的内容。
3.2.4 设置地雷
在 mine 数组里面随机放置预选设置EASY_COUBT好的地雷数目,即EASY_COUBT代表的数。使用随机数生成函数生成随机坐标,mine 数组里面这个坐标的符号如果是 ’ 0 ’ ,则将 ’ 1 ’ 放入里面,用 count 进行计数,使布置的地雷数小于EASY_COUBT代表的数。代码如下:
void MineSet(char mine[ROWS][COLS], int row, int col)
{int count = 0;while (count < EASY_COUNT){int x = rand() % row + 1;int y = rand() % col + 1;if (mine[x][y] == '0'){mine[x][y] = '1';count++;}}
}
在这里应该注意,布置地雷的时候,数组同样是将全部传递过来,但是进行布雷操作的时候,只对 1 ~ 9 行数组进行操作,对剩下的两组不做任何操作。此外,因为 rand() % row 的结果是 0 ~ 8 之间的数,需要在此基础上加一,才能保证其只对 1 ~ 9 进行操作。
3.2.5 扫雷阶段
玩家随意输入一个坐标,先进行判断其是否在棋盘之内,如果不是,则提醒其重新输入,如果是,则要判断该点是否的是雷,如果是,则直接结束游戏,如果不是,则要将其附近的雷的个数统计出来。
3.2.6 统计雷个数的代码
int MineCount(char mine[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++){count =count + (mine[i][j] - '0');}}return count;
}
如果统计的个数为非零,则直接在 show 数组相应的坐标上直接显示附近雷的个数,如下图:
如果为零,则要对其附近的点进行架空处理,即在 show 数组上把其附近全部为零的坐标全部变为空格,直至附近的数全部显示为非零值,如下图:
3.2.7 使用迭代的方式进行展开:
当玩家输入的坐标附近的雷统计数为零的时候,对该坐标附近的八个坐标都要进行判断其附近是否为零,如果不为零,直接显示其附近雷的个数,如果为零,又要调用该函数对其附近的八个坐标进行判断,一直往复循环,直至附近的坐标全部为非零数。代码如下:
void BlankExpansion(char mine[ROWS][COLS],char show[ROWS][COLS],int x,int y)
{int i = 0;int j = 0;int count = MineCount(mine, x, y);if (count == 0){show[x][y] = ' ';for (i = x - 1; i <= x + 1; i++){for (j = y - 1; j <= y + 1; j++){if (show[i][j] == '*'){BlankExpansion(mine, show, i, j);}}}}else{show[x][y] = count + '0';}}
3.2.8 扫雷部分主体代码
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{int count = row * col;int x = 0;int y = 0;while (count > EASY_COUNT){ printf("请输入坐标:> ");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){if (mine[x][y] == '1'){printf("好可惜,你被雷炸死了\n");PrintBoard(mine, ROW, COL);break;}else{BlankExpansion(mine, show, x, y);count = LeftCount(show, ROW, COL);PrintBoard(show, ROW, COL);printf("剩余未知点数: %d\n", count);}}else{printf("输入坐标错误,请重新输入\n");}}if (count == EASY_COUNT){printf("恭喜你,你赢了!\n");}}
4. 总结
在进行传参数的时候,一定要紧记,传数组时,要将整个数组全部传过去,但是对数组进行操作的时候,只对 1 ~ 9 行进行操作,因为玩家所能看到的展示界面只有 1 ~ 9 行,唯独初始化的时候是要对整个数组进行操作。
以上就是小编要分享的内容,如果有不对的地方,欢迎大家在留言区讨论。