目录
2.2 若输入的排查点(x,y)周围8个区域均不是雷,则周围8个区域都进行排查,并显示周边雷的数量
一. 前言
在我之前的博客【C语言】扫雷游戏_【Shine】光芒的博客-CSDN博客DN博客中 详细介绍了如何通过C语言代码实现简单的扫雷游戏。并根据网页版扫雷游戏的界面,在基础版扫雷游戏的基础上提出了两点改进优化措施:
- 若输入的排查点(x,y)周围8个区域均不是雷,则周围8个区域都进行排查,并显示周边雷的数量。
- 网页版游戏中可以用小红旗进行标记,表明玩家已经认为该区域为雷,提醒不要去翻开。
本文在之前博客代码的基础之上,对代码进行改进优化,以实现贴合网页版的扫雷游戏。
二. 扫雷游戏的代码优化
根据上述两个优化方案,可以推断,我们只需要对扫雷函数FindMine进行优化即可,因为统计一个坐标周围有几个雷是在FindMine函数中调用统计雷个数函数CountMineNum来实现的、加入标记功能也应加到扫雷函数中去。
2.1 加入标记功能
2.1.1 优化方法和优化代码
如图2.1所示为网页版扫雷游戏提供的标记功能,如果玩家认为某个点是雷,则在这个点上插小红旗,以防止玩家踩雷。
![](https://i-blog.csdnimg.cn/blog_migrate/8811d90af9b4429ae5a49ad30f112fdc.png)
为了用C语言实现标记功能,我在FindMine函数中加入字符变量mark来确实是进行标记还是要翻开雷区。这段代码中,我设计如果mark == 'm' 则表示玩家要进行标记操作,mark是其余任何字符均代表玩家想翻开雷区进行排查。
当玩家确定要进行标记操作和输入标记坐标后,如果玩家输入的坐标通过了合法性检验,给显示数组show对应的(x,y)处赋值为'm'。
优化后的扫雷函数FindMine的代码:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col)
{
int count_find = 0; //统计排了几个雷
int x = 0;
int y = 0;
char mark = '0';
while (1)
{
printf("请选择你要进行标记还是排雷(m是标记,其余均是排雷): > ");
mark = getchar();
getchar(); //清理缓冲区中的'\n'
printf("请选择你要排查的坐标: > ");
scanf("%d %d", &x, &y);
getchar(); // 清理缓冲区
//检查排查坐标的合法性
if (x < 1 || x > row || y < 1 || y > col) //坐标超出范围
{
printf("你输入的坐标超出范围!\n");
}
else //坐标在范围内(未越界)
{
//判断该坐标位置是否已经被排查
if (show[x][y] == '*' || show[x][y] == 'm') //还未被排查
{
if (mark == 'm') //输入排查标识m
{
show[x][y] = 'm'; //对应显示数组show的(x,y)坐标处改写为m
DisplayBoard(show, ROW, COL); //数组打印函数
}
else
{
if (mine[x][y] == '1') //踩雷
{
printf("很遗憾,你被炸死了!\n");
DisplayBoard(mine, ROW, COL); //打印雷区数组
break;
}
else
{
ShowMine(mine, show, x, y, ROW, COL, &count_find);
DisplayBoard(show, ROW, COL); //数组打印函数
}
}
}
else //输入的坐标已经被排查
{
printf("该坐标位置已经被排查!\n");
}
}
if (count_find == ROW * COL - CountMine) //判断是否完成排雷
{
printf("恭喜你,完成排雷任务!\n");
break;
}
}
}
2.1.2 优化后的代码运行情况检验
STEP1: 如图2.2所示,按下CTRL+F5运行代码,输入1,开始游戏,系统提示玩家选择是标记还是排雷。
![](https://i-blog.csdnimg.cn/blog_migrate/8e25f3131ecc03090acf5b529697357e.png)
STEP2: 输入m,选择进行标记,在输入排查坐标,这里以(2,5)为例,编译器打印出标记后的显示数组show,可以看到show[2][5]='m',表示此处已被标记。
![](https://i-blog.csdnimg.cn/blog_migrate/bbbaf9e839708d2bd572974b833697d1.png)
STEP3:观察完成一次标记后能否顺利进行下一次标记或扫雷操作。 如图2.4所示,标记完一次后,既不影响下一次标记操作,也不影响接下来的扫雷操作。
![](https://i-blog.csdnimg.cn/blog_migrate/66d555d038bfb6005cc5946bc240b0a8.png)
经检验,程序可以顺利对雷区进行标记,且不影响下面的操作,标记优化程序合理可行。
2.2 若输入的排查点(x,y)周围8个区域均不是雷,则周围8个区域都进行排查,并显示周边雷的数量
在网页版扫雷游戏上,对某个点进行排查,若这个点周围没有雷,则展开一片区域。如图2.5所示,一个点周围没有雷则对它周围所有点进行排查,直到所有点周围均至少有一个雷为止。
![](https://i-blog.csdnimg.cn/blog_migrate/e077ef98fa06e3392f46e361b7122bb8.png)
2.2.1 优化方法和优化代码
实现这一优化的最基本思想是递归调用函数。这里,相对于初级版扫雷游戏代码,我引入了一个新的函数:ShowMine(扫雷显示函数)。在ShowMine函数包括7个输入参数,其意义分别为:
- mine:雷区数组
- show:扫雷过程显示数组
- x:排查点的横坐标
- y:排查点的纵坐标
- row:可排查的行数(雷区纵向尺寸)
- col:可排查的列数 (雷区横向尺寸)
- count:指针变量,用于统计总共排查了多少个坐标(这里使用指针变量的是为了建立函数内外的联系,在函数内部可以操纵外部的变量)
在ShowMine中,首先调用雷个数统计函数CountMineNum,统计坐标(x,y)周围有多少个雷,并将CountMine函数的返回值赋给变量n。
如果,这表示排查坐标(x,y)的周围有雷,直接将(x,y)周围雷的个数赋给显示数组show对应的位置,函数调用结束。
如果,表示(x,y)周围没有雷,那么,这里就要涉及到递归调用。首先判断坐标(x,y)周围的点是否在雷区范围内,又是否已经被排查过。如果周围的点既没有超出雷区,又没有被排查,则以这个点的坐标来替代x和y,递归调用函数ShowMine,指到直到所有点周围均至少有一个雷为止。
此外,ShowMine函数还有一个指针参数count,这个参数是用来统计总共有多少个点被排查过了,当所有不是雷的坐标军被排查,则扫雷成功。
ShowMine函数的代码演示:
#include "game.h"
void ShowMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int row, int col, int* count)
{
int n = CountMineNum(mine, x, y); //统计(x,y)周围有几个雷
if (n == 0)
{
show[x][y] = '0'; //将显示数组show对应位置的元素置0
int i = 0;
int j = 0;
//外循环为行,内循环为列,遍历(x,y)周围的8个坐标
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
//if判断(x,y)周围点坐标(i,j)是否超出雷区范围,是否已经被排查
//若既没超出雷区范围,也没有被排查,则进行递归调用操作
if ((i >= 1 && j <= row && i >= 1 && j <= col) && (show[i][j] == '*' || show[i][j] == 'm'))
{
(*count)++; //被排查的点 +1
ShowMine(mine, show, i, j, ROW, COL, count); //递归调用
}
}
}
}
else
{
(*count)++; //被排查的次数+1
show[x][y] = n + '0';
}
}
2.2.2 优化代码检验
STEP1:开始运行代码,系统提示选择是否开始游戏,输入1,开始游戏。
STEP2:系统提示玩家选择进行标记还是排雷,输入f,表示选择排雷操作。之后输入排查坐标,这里以(4,8)为例,看到显示数组可以顺利展开一片。
![](https://i-blog.csdnimg.cn/blog_migrate/94e54ca23d6d084a12cf56ad0fd355cb.png)
STEP3:检查是否影响后面的操作。后续扫雷操作顺利进行,优化代码正确代码。
![](https://i-blog.csdnimg.cn/blog_migrate/f80149a0dd055a8ae4318a3a56dc8c61.png)
三. 扫雷优化版的完整项目代码
在优化版扫雷项目中,包含一个头文件和8个源文件。
一个头文件:
- game.h 进行函数的声明、头文件的包含、宏的定义
八个源文件:
- test.c 检测文件,选择是否开始游戏,是否结束游戏,是否继续进行游戏
- game.c 游戏大逻辑文件
- InitialBoard.c 初始化数组函数文件
- SetMine.c 布雷函数文件
- DisplayBoard.c 打印数组文件
- FindMine.c 扫雷函数文件
- ShowMine.c 排雷过程显示函数文件
- CountMineNum.c 统计雷个数函数
3.1 头文件(game.h)代码
#define _CRT_SECURE_NO_WARNINGS 1
//头文件的包含
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//宏的定义
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
#define CountMine 10
//函数声明
void game();
void InitialBoard(char board[ROWS][COLS], int row, int col, char set);
void SetMine(char board[ROWS][COLS], int row, int col);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void FindMine(char board[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int CountMineNum(char mine[ROWS][COLS], int x, int y);
3.2 源文件代码
3.2.1 test.c 文件的代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("**************************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("**************************\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择是否开始游戏: > ");
scanf("%d", &input);
getchar(); //清理缓冲区
switch (input)
{
case 1:
printf("扫雷游戏开始!\n");
game();
break;
case 0:
printf("游戏结束!\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
return 0;
}
3.2.2 game.c文件的代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void game()
{
char mine[ROWS][COLS]; //雷区数组
char show[ROWS][COLS]; //显示数组
//雷区数组,0表示安全区,1表示雷
InitialBoard(mine, ROWS, COLS, '0'); //初始化雷区数组,全部赋初值为字符'0'
InitialBoard(show, ROWS, COLS, '*'); //初始化显示数组,全部赋初值为字符'*'
SetMine(mine, ROW, COL); //布雷函数
DisplayBoard(show, ROW, COL); //调用函数打印显示数组
FindMine(mine, show, ROW, COL); //扫雷函数
}
3.2.3 InitialBoard.c 文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化二维数组函数
//board为待初始化的二维数组,row为数组行数,col为数组列数,set表示数组元素被初始化后是什么
void InitialBoard(char board[ROWS][COLS], int row, int col, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < ROWS; i++)
{
for (j = 0; j < COLS; j++)
{
board[i][j] = set;
}
}
}
3.2.4 SetMine.c 文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = 0; //统计布雷个数
while (1)
{
int x = rand() % row + 1; //随机生成布雷点坐标
int y = rand() % col + 1;
if (board[x][y] == '0') //判断(x,y)坐标处是否已经布雷
{
board[x][y] = '1';
count++; //布雷数+1
}
if (CountMine == count) //到了设定雷数后终止布雷操作
{
break;
}
}
}
3.2.5 DisplayBoard.c 文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//打印列号
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");
}
}
3.2.6 FindMine.c 文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col)
{
int count_find = 0; //统计排了几个雷
int x = 0;
int y = 0;
char mark = '0';
while (1)
{
printf("请选择你要进行标记还是排雷(m是标记,其余均是排雷): > ");
mark = getchar();
getchar(); //清理缓冲区中的'\n'
printf("请选择你要排查的坐标: > ");
scanf("%d %d", &x, &y);
getchar(); // 清理缓冲区
//检查排查坐标的合法性
if (x < 1 || x > row || y < 1 || y > col) //坐标超出范围
{
printf("你输入的坐标超出范围!\n");
}
else //坐标在范围内(未越界)
{
//判断该坐标位置是否已经被排查
if (show[x][y] == '*' || show[x][y] == 'm') //还未被排查
{
if (mark == 'm') //输入排查标识m
{
show[x][y] = 'm'; //对应显示数组show的(x,y)坐标处改写为m
DisplayBoard(show, ROW, COL); //数组打印函数
}
else
{
if (mine[x][y] == '1') //踩雷
{
printf("很遗憾,你被炸死了!\n");
DisplayBoard(mine, ROW, COL); //打印雷区数组
break;
}
else
{
ShowMine(mine, show, x, y, ROW, COL, &count_find);
DisplayBoard(show, ROW, COL); //数组打印函数
}
}
}
else //输入的坐标已经被排查
{
printf("该坐标位置已经被排查!\n");
}
}
if (count_find == ROW * COL - CountMine) //判断是否完成排雷
{
printf("恭喜你,完成排雷任务!\n");
break;
}
}
}
3.2.7 ShowMine.c 文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void ShowMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int row, int col, int* count)
{
int n = CountMineNum(mine, x, y); //统计(x,y)周围有几个雷
if (n == 0)
{
show[x][y] = '0'; //将显示数组show对应位置的元素置0
int i = 0;
int j = 0;
//外循环为行,内循环为列,遍历(x,y)周围的8个坐标
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
//if判断(x,y)周围点坐标(i,j)是否超出雷区范围,是否已经被排查
//若既没超出雷区范围,也没有被排查,则进行递归调用操作
if ((i >= 1 && j <= row && i >= 1 && j <= col) && (show[i][j] == '*' || show[i][j] == 'm'))
{
(*count)++; //被排查的点 +1
ShowMine(mine, show, i, j, ROW, COL, count); //递归调用
}
}
}
}
else
{
(*count)++; //被排查的次数+1
show[x][y] = n + '0';
}
}
3.2.8 CountMineNum.c 文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//统计坐标(x,y)周围有几个雷
int CountMineNum(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] +
mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
}
全文结束,感谢大家的阅读,敬请批评指正