C语言扫雷游戏的优化【一次扫雷操作可以展开一片区域、可以对雷区进行标记】

目录

一. 前言

二. 扫雷游戏的代码优化 

2.1 加入标记功能

2.1.1 优化方法和优化代码

2.1.2 优化后的代码运行情况检验

2.2 若输入的排查点(x,y)周围8个区域均不是雷,则周围8个区域都进行排查,并显示周边雷的数量

2.2.1 优化方法和优化代码 

2.2.2 优化代码检验

三. 扫雷优化版的完整项目代码

3.1 头文件(game.h)代码

3.2 源文件代码

3.2.1 test.c 文件的代码

3.2.2 game.c文件的代码

3.2.3 InitialBoard.c 文件代码

3.2.4 SetMine.c 文件代码

3.2.5 DisplayBoard.c 文件代码

3.2.6 FindMine.c 文件代码

3.2.7 ShowMine.c 文件代码

3.2.8 CountMineNum.c 文件


一. 前言

在我之前的博客【C语言】扫雷游戏_【Shine】光芒的博客-CSDN博客DN博客中 详细介绍了如何通过C语言代码实现简单的扫雷游戏。并根据网页版扫雷游戏的界面,在基础版扫雷游戏的基础上提出了两点改进优化措施:

  • 若输入的排查点(x,y)周围8个区域均不是雷,则周围8个区域都进行排查,并显示周边雷的数量。
  • 网页版游戏中可以用小红旗进行标记,表明玩家已经认为该区域为雷,提醒不要去翻开。

本文在之前博客代码的基础之上,对代码进行改进优化,以实现贴合网页版的扫雷游戏。

二. 扫雷游戏的代码优化 

根据上述两个优化方案,可以推断,我们只需要对扫雷函数FindMine进行优化即可,因为统计一个坐标周围有几个雷是在FindMine函数中调用统计雷个数函数CountMineNum来实现的、加入标记功能也应加到扫雷函数中去。

2.1 加入标记功能

2.1.1 优化方法和优化代码

如图2.1所示为网页版扫雷游戏提供的标记功能,如果玩家认为某个点是雷,则在这个点上插小红旗,以防止玩家踩雷。

图2.1 用小红旗标记玩家认为的雷区

为了用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,开始游戏,系统提示玩家选择是标记还是排雷。

图2.2 运行检验STEP1

 

STEP2: 输入m,选择进行标记,在输入排查坐标,这里以(2,5)为例,编译器打印出标记后的显示数组show,可以看到show[2][5]='m',表示此处已被标记。

图2.3 标记检验STEP2

STEP3:观察完成一次标记后能否顺利进行下一次标记或扫雷操作。 如图2.4所示,标记完一次后,既不影响下一次标记操作,也不影响接下来的扫雷操作。

图2.4  标记检验STEP3

 经检验,程序可以顺利对雷区进行标记,且不影响下面的操作,标记优化程序合理可行。

2.2 若输入的排查点(x,y)周围8个区域均不是雷,则周围8个区域都进行排查,并显示周边雷的数量

在网页版扫雷游戏上,对某个点进行排查,若这个点周围没有雷,则展开一片区域。如图2.5所示,一个点周围没有雷则对它周围所有点进行排查,直到所有点周围均至少有一个雷为止。

图2.5 网页版扫雷游戏 -- 展开一片

2.2.1 优化方法和优化代码 

实现这一优化的最基本思想是递归调用函数。这里,相对于初级版扫雷游戏代码,我引入了一个新的函数:ShowMine(扫雷显示函数)。在ShowMine函数包括7个输入参数,其意义分别为:

  1. mine:雷区数组
  2. show:扫雷过程显示数组
  3. x:排查点的横坐标
  4. y:排查点的纵坐标
  5. row:可排查的行数(雷区纵向尺寸)
  6. col:可排查的列数 (雷区横向尺寸)
  7. count:指针变量,用于统计总共排查了多少个坐标(这里使用指针变量的是为了建立函数内外的联系,在函数内部可以操纵外部的变量)

在ShowMine中,首先调用雷个数统计函数CountMineNum,统计坐标(x,y)周围有多少个雷,并将CountMine函数的返回值赋给变量n。

如果n\neq 0,这表示排查坐标(x,y)的周围有雷,直接将(x,y)周围雷的个数赋给显示数组show对应的位置,函数调用结束。

如果n=0,表示(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)为例,看到显示数组可以顺利展开一片。

图2.5  代码检验STEP2

 STEP3:检查是否影响后面的操作。后续扫雷操作顺利进行,优化代码正确代码。

图2.6 代码检验STEP3

三. 扫雷优化版的完整项目代码

在优化版扫雷项目中,包含一个头文件和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';
}

全文结束,感谢大家的阅读,敬请批评指正

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值