扫雷游戏增强版代码实现和详解(超详细~)
前言:想必屏幕前的你们,也许都玩过扫雷游戏吧,这款游戏玩法比较简单,也用很容易上手。当玩家通过单击鼠标左键、右键以及同时点击左右键的双击操作进行标记安全格子和地雷,当正确找出所有地雷时则视为胜利,反之如果玩家翻开的格子有地雷,则游戏结束。
1.扫雷游戏分析和设计
1.1 扫雷游戏的功能说明
-
使用控制台实现经典的扫雷游戏
-
游戏可以通过菜单实现继续玩或者退出游戏
-
可以选择游戏难度
。简单9*9棋盘,10个雷
。中等16*16棋盘,40个雷
。困难30*30棋盘,99个雷
-
可以排查雷
。如果位置不是雷,就显示周围有几个雷
。如果位置是雷,就炸死游戏结束
。把除(10,40,99)个雷之外的所有雷都找出来,那么排雷成功,游戏结束
-
1.2 游戏的分析和设计
1.2.1数据结构的分析
扫雷过程中,布置的雷和拍出的雷都需要存储。因此我们需要一定的数据结构来存储这些信息。
举个例子,我们需要在9x9布置雷的信息和排查雷,我们首先想到的是创建一个9x9的数组来存放信息。
如果这个位置布置雷,我们就存放1,没有的话就布置存放雷。
假设我们排查(2,5)这个坐标时,我们访问周围的一圈8个黄色位置,统计周围雷的个数是1。
假设我们排查(8,6)这个坐标时,我们访问周围一圈8个黄色位置,统计周围雷的个数时,最羡慕的坐标就会越界,所以我们设计的时候。给数组扩大一圈,雷还是布置在中间的9x9的坐标上,周围一圈不去布置雷就行啦,这样就很好地解决了越界的问题。所以,我们存放数据创建成11x11是比较合适的
另外,我们虽然在棋盘布置了雷,棋盘上雷的信息(1)和非雷的信息(0),那么假设我们排查某个坐标的时候,这个坐标处不是雷,那个坐标的周围有1个雷,那么我们需要将排出的雷的数量信息记录下来存储,并打印出来。作为排雷的重要 参考信息,那个这个雷的个数我们可以存放到哪里呢?如果存档在布置雷的数组中,这样雷的信息和雷的个数信息可能或产生混淆和打印上的困难。
我们可以采用一种方案,专门给一个棋盘(对应的数组mine)存放布置好雷的信息,再给另外一个棋盘(对应另外一个数组show)存放排出的雷的信息。这样就互不干扰了,把雷布置到mine数组,再mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
同时我们为了保持神秘,show数组开始时初始化为‘*’,为了保持两个数组的数据类型一致,我们可以使用一套函数进行处理,mine数组最开始也初始化为字符‘0’,布置雷改成‘1’。具体如下图所示:
对应的数组如下代码所示:
char mine[11][11]={0};//用来存放布置好雷的信息
char show[11][11]={0};//用来存放排查出的雷的个数信息
1.2.2 文件结构设计
一般我们写这种小型项目的时候,代码可能比较多,不会将所有的代码都放到一个文件中;我们往往根据程序的功能,将代码拆分放到多个文件中。
一般情况下,函数的声明放在头文(.h)中,函数的实现放在源文件(.c)中。所以我们之类可以创建三个文件
1.test.c //文件中写游戏的测试逻辑
2.game.c //文件中写游戏中函数的实现等
3.game.h //文件中写游戏需要的数据类型和函数声明等
2.扫雷游戏的代码实现
2.1 游戏程序菜单设置
我们可以通过菜单实现继续玩或者退出游戏,在开始游戏前让玩家选择游戏难度。
首先我们可以先封装一个menu1的函数,让玩家可以选择玩游戏还是退出游戏。
void menu1() {
printf("********************************\n");
printf("****** 0.Exit game *******\n");
printf("****** 1.Enter game *******\n");
printf("********************************\n");
}
接下来我们可以封装一个menu2的函数,让玩家选择游戏难度
void menu2() {
printf("**************************************\n");
printf("********** 请选择游戏难度 *********\n");
printf("********** 1.简单 *************\n");
printf("********** 2.中等 **************\n");
printf("********** 3.困难 *************\n");
printf("**************************************\n");
}
选择好之后我们就可以进入游戏了。
2.2定义宏变量
首先我们要先在game.h中定义这些符号,然后我们把函数的头文件全部写在game.h里面,然后在自己所写源文件的主函数最上面引入头文件game.h,这样我们就不需要在源文件引入**#incldue<stdio.h>#include<stdlib.h>**这些头文件了,引入头文件如下所示:
#include "game.h"
2.3游戏程序主函数
在编写这类游戏代码的时候,我们通常都是写上这个主函数。
并且我们可以通过玩家所选游戏难度等级,把参数传给game,这样可以打印出相对应的棋盘大小和雷的个数出来。
代码如下图所示:
2.4初始化棋盘和打印棋盘
首先,我们先在源文件test.c主函数中定义函数InitBoard和Displayboard
//棋盘初始化
InitBoard(Mine, ROWS, COLS, '0');
InitBoard(Show, ROWS, COLS, '*');
//打印棋盘
//Displayboard(Mine, ROW, COL);
Displayboard(Show, ROW, COL);
接下来我们在头文件Game.h中声明函数。
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void Displayboard(char board[ROWS][COLS], int row, int col);
接下来,我们再源文件game.c再实现这个函数。
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set) {
int i = 0;
for (i = 0; i <rows; i++) {
int j = 0;
for (j = 0; j < cols; j++) {
board[i][j] = set;
}
}
}
void Displayboard(char board[ROWS][COLS], int row, int col) {
int i = 0;
if(row==9)
puts("--------扫雷游戏--------");
else {
puts("---------------------------------------------------扫雷游戏--------------------------------------------------------");
}
for (i = 0; i <= col; i++) {
if (i <= 9)
printf("%-2d", i);
else if (i >= 10)
printf("%-3d", i);
} printf("\n");
for (i = 1; i <= row; i++) {
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++) {
if (j <= 9)printf("%-2c", board[i][j]);
else
printf("%-3c", board[i][j]);
}
printf("\n");
}
if(row==9)
puts("--------扫雷游戏--------");
else {
puts("---------------------------------------------------扫雷游戏--------------------------------------------------------");
}
}
打印棋盘其实很简单,用for循环就能实现我们的需求了,需要注意的是,我们打印棋盘是从i=1开始的,这样能够避免打印中间的空格区域,只打印中心的(9x9,16x16,30x30)空格,除此之外,我们还添加了行号和列号,这样玩家就知道他们输入的坐标了。
2.5布置雷
同样地,我们需要在源文件test.c主函数中定义这个函数SetMine
//布置雷
SetMine(Mine, ROW, COL);
接着咱们在头文件game.h中声明函数SetMine
void SetMine(char Mine[ROWS][COLS], int row, int col);
然后在头文件game.c中实现这个函数。
void SetMine(char Mine[ROWS][COLS], int row, int col) {
int count = 0;
if (row == 9) {
count = EAST_COUNT1;
}
else if (row == 16) {
count = EAST_COUNT2;
}
else {
count = EAST_COUNT3;
}
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (Mine[x][y] == '0') {
Mine[x][y] = '1';
count--;
}
同时呢,我们在头文件game.h中引入了EAST_COUNT的变量,根据玩家所选游戏难度来设置雷的个数,那为什么要在这里定义式宏呢,因为到时要改变布置雷数,后面的代码也要改,比较麻烦,因此我们在这里直接定义这三个变量的值。
#define EAST_COUNT1 10
#define EAST_COUNT2 40
#define EAST_COUNT3 99
这些值就代表我们的雷有多少个了。
2.6排查雷
同样地,我们现在源文件test.c中定义函数FineMine
FineMine(Mine, Show, ROW, COL);
然后我们在头文件game.h中声明函数FineMine
void FineMine(char Mine[ROWS][COLS], char Show[ROWS][COLS], int row, int col);
接下来我们就在源文件game.c实现函数FineMine
void FineMine(char Mine[ROWS][COLS], char Show[ROWS][COLS], int row, int col) {
int x = 0;
int y = 0;
int win = 0;
int count = 0;
if (row == 9) {
count = EAST_COUNT1;
}
else if (row == 16) {
count = EAST_COUNT2;
}
else {
count = EAST_COUNT3;
}
while (win<row*col-count) {
printf("请输入要排查的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (Show[x][y] != '*') {
printf("该坐标已排查过,请重新输入\n");
}
else if (Mine[x][y] == '1') {
printf("boom,你被炸死了!!!\n");
Displayboard(Mine, row, col);
break;
}
else {
int count = getminecount(Mine, x, y);
Show[x][y] = count + '0';
Displayboard(Show, row, col);
win++;
}
}
else {
printf("该坐标不在范围内,请重新输入坐标\n");
}
}
if (win == row * col -count) {
printf("恭喜您呀,排雷成功!芜湖~~\n");
Displayboard(Mine, row, col);
}
}
这里我们需要注意的是,我们要根据玩家所选择游戏难度,确定棋盘的大小,行数和列数,所以呢我们要添加一行代码来确定代码的合理性,咱们举个例子,比如玩家所选难度为“中等”,对应的坐标是16x16的,但是输入了一个(88,88)的坐标,显然不在坐标范围之类,因此我们要提醒他要重新输入坐标。
另外,你可能对**Show[x][y] = count + ‘0’;**这个函数有所疑惑,为何要在count后面加个’0’呢?
讲到这里,我们可以看一下ASCLL码值。
由上图,我们可以知道’0’和’1’的ASCLL码值分别为48和49,我们之前定义Mine数组传的是字符0和字符1,
不是数字0和1,仔细观察,可以发现里面的数字跟对应的数字字符都相差了48,而48恰恰是’0’,所以我们要在count后面加上’0’,这样才能知道这个坐标周围有几个雷。
2.6.1统计雷的个数
那么如何将这些雷依次输出出来呢?
我们可以把行和列分别设为x和y。然后把这些坐标全部输入到二维数组,这样我们就可以求出雷的个数是多少了。
统计雷的个数如下所示:
static int getminecount(char Mine[ROWS][COLS], int x, int y) {
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');
return count;
}
这里因为我们输入统计出来的也是’1’字符,对应的ASCLL码值为49,那么统计好这八个坐标后,把它们分别相加起来,再减去8 *‘0’, 我们就能得到雷个数的数字。然后用getminecount这个函数来接收这个返回雷的个数,再通过
count+'0'=show[x][y];
从而得出该坐标附近有几个雷,并打印出相对应的数字字符。
除此之外,我们为什么要在上面代码中增加这个条件?
if (Show[x][y] != '*') {
printf("该坐标已排查过,请重新输入\n");
}
因为当玩家第一次输入排查的坐标后,显示show数组附近有几个雷,按理不应让他再次输入那个点的坐标,这样不符合游戏的逻辑。因此我们加上这个判断条件,可以避免玩家输入相同的坐标。具体运行如下所示:
3.总代码
下面是博主这次设计扫雷游戏的总代码,仅供参考~
test.c:文件中的游戏测试逻辑
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//
void menu1() {
printf("********************************\n");
printf("****** 0.Exit game *******\n");
printf("****** 1.Enter game *******\n");
printf("********************************\n");
}
void menu2() {
printf("**************************************\n");
printf("******** 请选择游戏难度 ******\n");
printf("********** 1.简单 *************\n");
printf("********** 2.中等 **************\n");
printf("********** 3.困难 *************\n");
printf("**************************************\n");
}
void game(int choice) {
if (choice == 1) {
//数组的初始化
char Mine[ROWS][COLS];
char Show[ROWS][COLS];
//先把棋盘初始化
InitBoard(Mine, ROWS, COLS, '0');
InitBoard(Show, ROWS, COLS, '*');
//打印棋盘
//Displayboard(Mine, ROW, COL);
Displayboard(Show, ROW, COL);
//布置雷
SetMine(Mine, ROW, COL);
//排查雷
FineMine(Mine, Show, ROW, COL);
}
else if (choice == 2) {
char Mine[ROWS2][COLS2];
char Show[ROWS2][COLS2];
InitBoard(Mine, ROWS2, COLS2, '0');
InitBoard(Show, ROWS2, COLS2, '*');
//Displayboard(Mine, ROW2, COL2);
Displayboard(Show, ROW2, COL2);
SetMine(Mine, ROW2, COL2);
FineMine(Mine, Show, ROW2, COL2);
}
else {
char Mine[ROWS3][COLS3];
char Show[ROWS3][COLS3];
InitBoard(Mine, ROWS3, COLS3, '0');
InitBoard(Show, ROWS3, COLS3, '*');
//Displayboard(Mine, ROW3, COL3);
Displayboard(Show, ROW3, COL3);
SetMine(Mine, ROW3, COL3);
FineMine(Mine, Show, ROW3, COL3);
}
}
int main() {
int input = 0;
int choice = 0;
srand((unsigned int)time(NULL));
do {
menu1();
printf("请选择>:");
scanf("%d", &input);
switch (input) {
case 0:
break;
case 1:
menu2();
scanf("%d", &choice);
while (choice < 1 || choice>3) {
printf("请重新选择:");
scanf("%d", &choice);
}
game(choice);
break;
default: {
printf("输入错误,请重新输入数字。\n");
}
}
} while (input);
return 0;
}
Game.h:文件中游戏的数据类型以及函数的声明
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//.
#define ROW 9
#define COL 9
#define ROW2 16
#define COL2 16
#define ROW3 30
#define COL3 30
#define ROWS ROW+2
#define COLS COL+2
#define ROWS2 ROW2+2
#define COLS2 COL2+2
#define ROWS3 ROW3+2
#define COLS3 COL3+2
#define EAST_COUNT1 10
#define EAST_COUNT2 40
#define EAST_COUNT3 99
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 Mine[ROWS][COLS], int row, int col);
void FineMine(char Mine[ROWS][COLS], char Show[ROWS][COLS], int row, int col);
Game.c:游戏程序中函数的实现
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//.game.c文件中是游戏中函数的实现
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set) {
int i = 0;
for (i = 0; i <rows; i++) {
int j = 0;
for (j = 0; j < cols; j++) {
board[i][j] = set;
}
}
}
void Displayboard(char board[ROWS][COLS], int row, int col) {
int i = 0;
if(row==9)
puts("--------扫雷游戏--------");
else {
puts("---------------------------------------------------扫雷游戏--------------------------------------------------------");
}
for (i = 0; i <= col; i++) {
if (i <= 9)
printf("%-2d", i);
else if (i >= 10)
printf("%-3d", i);
} printf("\n");
for (i = 1; i <= row; i++) {
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++) {
if (j <= 9)printf("%-2c", board[i][j]);
else
printf("%-3c", board[i][j]);
}
printf("\n");
}
if(row==9)
puts("--------扫雷游戏--------");
else {
puts("---------------------------------------------------扫雷游戏--------------------------------------------------------");
}
}
void SetMine(char Mine[ROWS][COLS], int row, int col) {
int count = 0;
if (row == 9) {
count = EAST_COUNT1;
}
else if (row == 16) {
count = EAST_COUNT2;
}
else {
count = EAST_COUNT3;
}
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (Mine[x][y] == '0') {
Mine[x][y] = '1';
count--;
}
}
}
static int getminecount(char Mine[ROWS][COLS], int x, int y) {
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');
return count;
}
void FineMine(char Mine[ROWS][COLS], char Show[ROWS][COLS], int row, int col) {
int x = 0;
int y = 0;
int win = 0;
int count = 0;
if (row == 9) {
count = EAST_COUNT1;
}
else if (row == 16) {
count = EAST_COUNT2;
}
else {
count = EAST_COUNT3;
}
while (win<row*col-count) {
printf("请输入要排查的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (Show[x][y] != '*') {
printf("该坐标已排查过,请重新输入\n");
}
else if (Mine[x][y] == '1') {
printf("boom,你被炸死了!!!\n");
Displayboard(Mine, row, col);
break;
}
else {
int count = getminecount(Mine, x, y);
Show[x][y] = count + '0';
Displayboard(Show, row, col);
win++;
}
}
else {
printf("该坐标不在范围内,请重新输入坐标\n");
}
}
if (win == row * col -count) {
printf("恭喜您呀,排雷成功!芜湖~~\n");
Displayboard(Mine, row, col);
}
}
3.1 效果演示
好啦,今天的扫雷游戏分享就到此结束啦,如果觉得博主讲得不错的,欢迎大家一键三连支持一下,谢谢!!