C语言练习(3)——扫雷

前言

使用C语言实现扫雷的基本功能,包括埋雷,扫雷,展开,标记等。


一、扫雷游戏

扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
我们重点实现9*9棋盘埋有10个雷的小型扫雷游戏
在这里插入图片描述

二、主要思路

先上成品图:
在这里插入图片描述

1.设置两个二维数组

我们可以通过设置二维数组来模拟棋盘,为了实现扫雷棋盘和埋雷棋盘的分离,应当设置两个同样大小的二维数组,hide数组代表埋雷图,show数组代表扫雷图。然后分别以0,(星号)*进行完全初始化,其中0用于埋雷图中代表该处位置没有雷,而星号用于扫雷图中,代表未排查的位置。

2.随机埋雷

通过使用随机数在埋雷图中设置10个雷,注意每个雷的位置不同。

3.玩家扫雷

玩家在扫雷图上进行扫雷,注意限制输入的坐标。对扫雷结果进行判断,失败则显示埋雷情况,退出游戏;成功,就进行信息探测,并在扫雷图上反馈。

4.信息探测

以玩家输入的坐标为中心,探测周围八个位置有多少雷,并把数目反馈到扫雷图上。为了实现扫雷效率应当运用递归,分别以该点周围的八个点为中心继续探测信息,直到有一个点的周围八个点都没有雷才停止 。

5.标记功能

设置标记函数,方便玩家在扫雷图上任意未知位置进行标记

三、代码实现

1.game.h

(1)引入函数库

代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdlib.h>
#include<windows.h>
#include<stdbool.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define COUNT_MINE 10

(2)接口函数

代码如下:

//初始化数组雷图
void InitBoard(char board[ROWS][COLS], int row, int col, char set);
//打印数组雷图
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//随机设置雷
SetMine(char board[ROWS][COLS], int row, int col);
//玩家扫雷
void PlayerChoose(char show[ROWS][COLS],char hide[ROWS][COLS], int row, int col);
//所选坐标周围炸弹数量
int check_hide(char hide[ROWS][COLS], int x, int y); 
//标记功能
void Mark(char show[ROWS][COLS],int row, int col);
//展开雷图
void Spread(char show[ROWS][COLS], char hide[ROWS][COLS],int x, int y);
//检测是否胜利
bool Win(char show[ROWS][COLS], char hide[ROWS][COLS], int row, int col);
//将雷暴露在扫雷图中
void PrintMineInShow(char show[ROWS][COLS], char hide[ROWS][COLS], int row, int col);

2.game.c

(1)初始化数组雷图

注意我们实现99大小的扫雷游戏,数组却不能设置为99大小。
因为我们需要以数组中的每一项为中心进行信息探测,如果只设置9乘9大小的数组,则边界上的元素进行信息探测时会发生数组越界所以为了满足边界元素的探测需求,应当设置11乘11大小的数组
代码如下:

void InitBoard(char board[ROWS][COLS], int row, int col, char set)
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = set;
		}
	}
}

(2)打印数组雷图

为了雷图的直观和美观,我在打印时加入了一些装饰元素,导致代码十分臃肿,但在体验有无边框的扫雷图后,就明白很值得!
效果图:
扫雷图样式

代码如下:

//打印雷图
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;
	for (j = 0; j <= col; j++)
	{
		if (j > 0)
		{
			printf(" %d  ", j);
		}
		else
		{
			printf(" %d  ", j);
		}
	}
	printf("\n");
	for (j = 0; j <= col; j++)
	{
		if (j > 0)
		{
			printf("    ");
		}
		else
		{
			printf("    ");
		}
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf(" %d |", i);
		for (j = 1; j <= col; j++)
		{
			printf(" %c", board[i][j]);
			if (j <= col)
				printf(" |");
		}
		printf("\n");
		if (i < row )
		{
			for (j = 1; j <= col+1; j++)
			{
				if (j > 1)
				{
					printf("----");
				}
				else
				{
					printf("    ");
				}
			}
			printf("\n");
		}
	}
	for (j = 0; j <= col; j++)
	{
		if (j > 0)
		{
			printf("    ");
		}
		else
		{
			printf("    ");
		}
	}
	printf("\n");
}

(3)随机埋雷

将埋雷图(hide)中任意十个位置赋值为1,代表有雷。
只在9*9的区域内埋雷,但形参仍要写成board【ROWS】【COLS】!

埋雷图:
随机埋雷后的埋雷图

代码如下:

//随机设置雷
SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = COUNT_MINE;
	while (count)
	{
		int i = rand() % row + 1;
		int j = rand() % col + 1;
		if (board[i][j] != '1')
		{
			board[i][j] = '1';
			count--;
		}
	}
}

(4)玩家扫雷

玩家进行扫雷,注意输入成败和游戏成败
代码如下:

//玩家扫雷
void PlayerChoose(char show[ROWS][COLS], char hide[ROWS][COLS], int row, int col)
{
	int x, y;
	while (1)
	{
		printf("请输入要排查的坐标:>\n");
		scanf("%d %d", &x, &y);
		//判断坐标的合法性
		if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
		{
			if (show[x][y] != '*')
			{
				printf("该处已用!\n");
			}
			else
			{
				//踩到雷
				if (hide[x][y] == '1')
				{
					system("cls");
					show[x][y] = '1';
					printf("-----------------扫雷图-----------------\n");
					DisplayBoard(show, row, col);
					printf("很遗憾,您被炸死了!\n");
					Sleep(1000);
					printf("-----------------埋雷图-----------------\n");
					PrintMineInShow(show, hide, row, col);
					DisplayBoard(show, row, col);
					return;
				}
				//未踩到雷
				else
				{
					system("cls");
					//展开所选位置
					Spread(show, hide, x, y);
					printf("-----------------扫雷图-----------------\n");
					DisplayBoard(show, row, col);
					//检测是否胜利
					bool ret = Win(show, hide, row, col);
					if (ret)
					{
						printf("恭喜你获得了胜利!\n");
						//胜利时,显示埋雷图
						printf("-----------------埋雷图-----------------\n");
						PrintMineInShow(show, hide, row, col);
						DisplayBoard(show, row, col);
						return;
					}
					Mark(show, row, col);
				}
			}
		}
		else
		{
			printf("输入错误!\n");
		}
	}
}

(5)探测所选位置周边雷的信息

因为埋雷数组(hide)中的值是字符0和字符1,分别表示无雷和有雷,我们可以通过累加算选位置周边八个位置及其本身共九个元素,再减去字符0乘以9,就获得了所选位置周边雷的个数!
代码如下:

//所选坐标周围炸弹数量
int check_hide(char hide[ROWS][COLS], int x, int y)
{
	return
		hide[x - 1][y - 1] + hide[x - 1][y] + hide[x - 1][y + 1]
		+ hide[x][y - 1] + hide[x][y] + hide[x][y + 1]
		+ hide[x + 1][y - 1] + hide[x + 1][y] + hide[x + 1][y + 1] - 9 * '0';
}

(6)展开雷图

我们可以通过递归实现用一个点展开周边所有探测信息为0的点。
需要解决死递归(栈溢出)
我们递归的限制条件是某点周围(围绕该点的八个点)存在探测信息为0的点并且这个点在show(扫雷数组)中的值为星号(*,未知位置)或者@(标记位置),如果不加入这个限制条件,就会死递归。因为两个关联的九宫展开的格子有共同的数个探测信息为0的点,会使得两个格子围绕这个点不断进行展开。如图:在这里插入图片描述
代码如下:

//展开雷图
void Spread(char show[ROWS][COLS], char hide[ROWS][COLS],int x, int y)
{
	int i = 0, j = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
		//防止越界
			if ((i >= 1 && i <= 9) && (j >= 1 && j <= 9))
			{
				if (hide[i][j] != '1'&&(show[i][j] == '*'||show[i][j]=='@'))
				{
					//记录该点展开后周边雷的个数
					char ret = check_hide(hide, i, j) + '0';
					//如果周边无雷,则在show中为空格,并且递归
					if (ret == '0')
					{
						show[i][j] = ' ';
						if ((i >= 1 && i <= 9) && (j >= 1 && j <= 9))
						{
							//递归将周边没有雷的边界位置展开
							Spread(show, hide, i, j);
						}
					}
					else
					{
						show[i][j] = ret;
					}
				}
			}
		}
	}
}

(7)标记功能

在玩家选定的位置上给show数组赋值@,用于标记作用,只可以标记未知位置(星号的位置)。
效果图:
在这里插入图片描述

代码如下:

//标记功能
void Mark(char show[ROWS][COLS], int row, int col)
{
	int input = 0;
	while (1)
	{
		begain:
		printf("是否需要标记雷? 是(1) : 否(0) \n");
		scanf("%d", &input);
		//标记
		if (input == 1)
		{
			int x = 0, y = 0;
		Here:
			printf("请输入要标记的坐标:>\n");
			scanf("%d %d", &x, &y);
			//坐标合法
			if ((x >= 1 && x <= 9) && (y >= 1 && y <= 9))
			{
				//只可以标记未知位置
				if (show[x][y] == '*')
				{
					show[x][y] = '@';
					system("cls");
					printf("-----------------扫雷图-----------------\n");
					DisplayBoard(show, row, col);
					goto begain;
				}
				else
				{
					printf("该处不可标记,只可以标记未知位置\n");
					goto Here;
				}
			}
			//坐标不合法
			else
			{
				printf("输入错误!\n");
				goto Here;
			}
		}
		//不标记
		else if (input == 0)
		{
			break;
		}
		//输入违法
		else
		{
			printf("输入错误!\n");
		}
	}
}

(8)检测是否胜利

胜利的条件是show雷图中所有星号或者@的位置在hide雷图中值为1(即代表有雷)
代码如下:

//检测是否胜利
bool Win(char show[ROWS][COLS], char hide[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;
	bool flag = true;
	for (i =1; i <= row ; i++)
	{
		for (j =1; j <= col ; j++)
		{
			if ((show[i][j] == '*'||show[i][j]=='@') && hide[i][j] != '1')
			{
				flag = false;
				break;
			}
		}
	}
	return flag;
}

(9)将雷显示到show扫雷图中

游戏结束时,应该显示雷的位置,让玩家输的明明白白!
效果图:
在这里插入图片描述

代码如下:

//将雷暴露在扫雷图中
void PrintMineInShow(char show[ROWS][COLS], char hide[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			//将埋有雷的位置凸显出来
			if (hide[i][j] == '1')
			{
				show[i][j] = '!';
			}
			else
			{
				show[i][j] = ' ';
			}
		}
	}
}

3.test.c

主要实现菜单功能
代码如下:

#include"game.h"
void menu()
{
	printf("#####################################\n");
	printf("#########   1.game   0.exit  ########\n");
	printf("#####################################\n");
}
void game()
{
	//创建两个数组,分别是 埋雷图  扫雷图
	char hide[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//初始化两个数组
	InitBoard(hide, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	//随机埋雷
	SetMine(hide, ROW, COL);
	//展示扫雷图
	printf("-----------------扫雷图-----------------\n");
	DisplayBoard(show, ROW, COL);
	//玩家扫雷
	PlayerChoose(show,hide, ROW, COL);
}
int main()
{
	srand((unsigned int)time(NULL));
	int input;
	do {
		menu();
		printf("请选择:>\n");
		scanf("%d", &input);
		system("cls");
		switch (input)
		{
		case 1:
			printf("扫雷游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误\n");
			printf("请重新输入:>\n");
			break;
		}
	} while (input);
	return 0;
}

总结

该扫雷程序有非常大的完善空间。
简单完善:将玩家排查某位置和玩家标记某位置做成选择,这样便不需要在排查后才能标记、防止玩家第一次排查被炸死的功能等;
复杂完善:加入图形界面,实现鼠标操作等。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值