超详细优化版C语言实现扫雷

1:对界面进行了优化

2:修复了会出现的一些bug,如下所示

2.1:通过递归实现排查的时候,排查的范围做了缩小,避免深层次的递归

2.2:还有对一些排查的对象进行了重复排查的(类似递归实现斐波那契数列那样)

2.3:对于用户输入的坐标,使用scanf输入的时候,如果出现了误输入,导致程序死循环的情况

2.4:对于用户对一些已经排查过的坐标,进行再次排查,并且误输入,导致程序死循环的情况

这个程序,还是一样的,我分了三个源文件来写

Mine_Clearance_Game.h

#pragma once
# include <stdio.h>
# include <windows.h>
# include <time.h>
# include <stdlib.h>

#define ROW 9 /*扫雷棋盘显示行*/
#define COL 9 /*扫雷棋盘显示列*/
#define ROWS ROW+2 /*扫雷棋盘实际行*/
#define COLS COL+2 /*扫雷棋盘实际列*/

#define MINE_NUM 10 /*地雷数量*/


/******************控制台函数声明*****************************/

void Gotoxy(int x, int y);/*自定义控制台光标位置*/
void SetWindowSize(void);/*设置控制台窗口标题 大小*/

/******************游戏逻辑函数声明**************************/

void StartGame(void);/*开始游戏*/
void SetMine(char board[][COLS], int row, int col);/*在埋雷棋盘上布置雷*/
void DisplayBoard(char board[][COLS], int row, int col);/*打印扫雷棋盘*/
void ScreenMine(char mine[][COLS], char show[][COLS], int row, int col);/*排查雷*/
static int CalSurroundMineNum(char mine[][COLS], int x, int y);/*统计坐标周边雷数量*/
void ExpandBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win);/*递归逐层次排查无雷坐标周边的坐标*/
/******************游戏界面函数声明**************************/

void Menu(void);/*打印游戏开始界面菜单*/
void ShowGameOver(void);/*打印游戏结束画面*/
void ShowSelectError(void);/*选择错误画面*/
void MineBoom(char mine[][COLS], char show[][COLS], int row, int col);/*踩到地雷画面*/
void FindAllMine(char show[][COLS], int row, int col);/*排雷成功画面*/

/***********************************************************/

mine_clearance_game.c

# include "Mine_Clearance_Game.h"

/*
函数名称:SetWindowSize
函数功能:设置控制台窗口标题 大小 
编写时间:2022-07-14 19:18:40
*/
void SetWindowSize(void)
{
	/*这里如果不行的一个原因可以把工具->选项->调试->常规->调试停止时自动关闭控制台打勾*/
	system("title 扫雷");/*设置控制台标题*/
	system("mode con cols=70 lines=30");/*设置控制台大小*/
}

/*
函数名称:Gotoxy
函数功能:自定义控制台光标位置
编写时间:2022-07-14 19:13:58
*/
void Gotoxy(int x, int y)
{
	HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);/*获取控制台句柄*/
	COORD pos;
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(hout, pos);/*设置光标坐标*/
}

/*
函数名称:DisplayBoard
函数功能:打印扫雷棋盘
编写时间:2022-07-14 20:53:32
*/
void DisplayBoard(char board[][COLS], int row, int col)
{
	/* 定义的时候,我们定义的是11*11的数组,但是显示的时候,实际上只要显示9*9的数组,因为实际使用到的就是9*9棋盘 */
	int i = 0, j = 0;
	for (i = 1; i <= row; i++)/* 行坐标从1 ~ 9 */
	{
		Gotoxy(18, 5+i*2-1);
		printf(" %d ", i);/* 打印每一行的坐标 */
		for (j = 1; j <= col; j++)/* 列坐标从1 ~ 9 */
		{
			printf(" %c ", board[i][j]);
		}
		printf("\n");
	}

	/*打印每一列的坐标*/
	Gotoxy(18, 5 + i * 2 - 1);
	for (int k = 0; k <= row; k++)
	{
		printf(" %d ", k);
	}
}

/*
函数名称:SetMine
函数功能:在埋雷棋盘上布置雷
编写时间:2022-07-14 21:20:25
*/
void SetMine(char board[][COLS], int row, int col)
{
	int mineRow = 0, mineCol = 0, countMine = 0;

	while (countMine < MINE_NUM)
	{
		mineRow = (rand() % row) + 1;/*地雷的行坐标*/
		mineCol = (rand() % col) + 1;/*地雷的列坐标*/

		/* 判断是为了避免重复放雷 */
		if (board[mineRow][mineCol] == '0')
		{
			board[mineRow][mineCol] = '1';/*埋雷*/
			countMine++;/*统计已经埋下的地雷数量*/
		}
	}
}

/*
函数名称:CalSurroundMineNum
函数功能:统计坐标周边雷数量
编写时间:2022-07-14 22:39:47
*/
static int CalSurroundMineNum(char mine[][COLS], int x, int y)
{
	int count = 0;
	//循环方式
	/*for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			if (mine[i][j] == '1')
			{
				count++;
			}
		}
	}*/
	//非循环方式  因为布置雷的数组被初始化成了由'0'和'1'所组成的,所以将周边八个坐标相加减去8个字符0,就可以得到周边有多少个雷
	//相较于循环方式时间复杂度减少
	count = 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');
	return count;
}

/*
函数名称:ExpandBoard
函数功能:统计坐标周边雷数量
编写时间:2022-07-14 22:39:47
*/
void ExpandBoard(char mine[][COLS], char show[][COLS], int x, int y, int* calNoMineNum)
{
	int count = CalSurroundMineNum(mine, x, y);
	/*如果该坐标周围坐标都不是雷 就继续递归*/
	if (count == 0)
	{
		(*calNoMineNum)--;/*将除了雷以外的待排查坐标减1 */
		show[x][y] = ' ';//没有雷的坐标赋值为空格
		//递归周围的八个格子  每个排查的坐标必须满足在8*8的棋盘内 也就是不需要考虑9*9棋盘周围一圈的 因为无法扩展 容易数组越界
		if ((show[x - 1][y - 1] == '*') && (x - 1 > 0 && x - 1 < ROWS) && (y - 1 > 0 && y - 1 < COLS))/*排查坐标左上角坐标*/
			ExpandBoard(mine, show, x - 1, y - 1, calNoMineNum);
		if ((show[x - 1][y] == '*') && (x - 1 > 0 && x - 1 < ROWS) && (y > 0 && y < COLS))/*排查坐标上方坐标*/
			ExpandBoard(mine, show, x - 1, y, calNoMineNum);
		if ((show[x - 1][y + 1] == '*') && (x - 1 > 0 && x - 1 < ROWS) && (y + 1 > 0 && y + 1 < COLS - 1))/*排查坐标右上角坐标*/
			ExpandBoard(mine, show, x - 1, y + 1, calNoMineNum);
		if ((show[x][y - 1] == '*') && (x > 0 && x < ROWS) && (y - 1 > 0 && y - 1 < COLS))/*排查坐标左方坐标*/
			ExpandBoard(mine, show, x, y - 1, calNoMineNum);
		if ((show[x][y + 1] == '*') && (x > 0 && x < ROWS) && (y + 1 > 0 && y + 1 < COLS - 1))/*排查坐标右方坐标*/
			ExpandBoard(mine, show, x, y + 1, calNoMineNum);
		if ((show[x + 1][y - 1] == '*') && (x + 1 > 0 && x + 1 < ROWS - 1) && (y - 1 > 0 && y - 1 < COLS))/*排查坐标左下角坐标*/
			ExpandBoard(mine, show, x + 1, y - 1, calNoMineNum);
		if ((show[x + 1][y] == '*') && (x + 1 > 0 && x + 1 < ROWS - 1) && (y > 0 && y < COLS))/*排查坐标下方坐标*/
			ExpandBoard(mine, show, x + 1, y, calNoMineNum);
		if ((show[x + 1][y + 1] == '*') && (x + 1 > 0 && x + 1 < ROWS - 1) && (y + 1 > 0 && y + 1 < COLS - 1))/*排查坐标右下角坐标*/
			ExpandBoard(mine, show, x + 1, y + 1, calNoMineNum);

	}
	else/*如果该坐标周围坐标存在雷 就停止该坐标的逐层递归*/
	{
		(*calNoMineNum)--;/*将除了雷以外的待排查坐标减1 */
		show[x][y] = count + '0';
	}
}

/*
函数名称:ScreenMine
函数功能:排查雷
编写时间:2022-07-14 21:56:54
*/
void ScreenMine(char mine[][COLS], char show[][COLS], int row, int col)
{
	int calNoMineNum = (ROW)*(COL) - MINE_NUM;/*注意 这里不能用ROWS 应该除了雷 9*9棋盘实际上只剩下71个坐标*/
	/*循环退出的条件 排查完毕*/
	while (calNoMineNum > 0)
	{
		int screenRow = 0, screenCol = 0;/*放在循环中定义作用是为了防止在第N此输入的时候,用户又输入了一个错误的值 导致死循环*/
		system("cls");/* 清屏 */
		DisplayBoard(show, row, col);/* 打印展示棋盘 */
		Gotoxy(13, 3);
		printf("请输入要排查的坐标:");
		scanf("%d%d", &screenRow, &screenCol);
		if ((screenRow >= 1 && screenRow <= 9) && (screenCol >= 1 && screenCol <= 9))
		{
			if (mine[screenRow][screenCol] == '1')/* 如果踩到了地雷 */
			{
				MineBoom(mine, show, row, col);
				return;
			}
			else if (show[screenRow][screenCol] != '*')/* 如果位置已经排查过了 */
			{
				Gotoxy(45, 3);
				printf("已排查");
				Sleep(500);
			}
			else/* 如果没有踩到了地雷 并且没有排查过*/
			{
				/*递归排查 将坐标周边无地雷的进行扩展和显示地雷数量 */
				ExpandBoard(mine, show, screenRow, screenCol, &calNoMineNum);
			}
		}
		else
		{
			char ch;
			Gotoxy(45, 3);
			printf("坐标异常");
			/*循环的作用是为了避免出现用户异常输入的时候 进入死循环 直接把输入缓冲区的内容读取干净*/
			while ((ch = getchar()) != '\n')
				;
			Sleep(500);
		}

	}

	if (calNoMineNum <= 0)/* 如果calNoMineNum等于0,说明排雷成功 */
	{
		FindAllMine(show, row, col);
	}
	
}

/*
函数名称:StartGame
函数功能:开始游戏
编写时间:2022-07-14 20:49:28
*/
void StartGame(void)
{
	system("cls");
	char chEixt = '0';
	char mineArray[ROWS][COLS] = { 0 };/*定义埋雷数组*/
	char ShowArray[ROWS][COLS] = { 0 };/*定义显示给用户看的数组*/

	/*将两个数组进行初始化*/
	memset(mineArray, '0', (sizeof(char) * (ROWS) * (COLS)));/* 这里要括号括起来 因为出现了宏替换不带括号问题 */
	memset(ShowArray, '*', (sizeof(char) * (ROWS) * (COLS)));

	SetMine(mineArray, ROW, COL);/*埋地雷*/
	/*
	测试程序用
	DisplayBoard(mineArray, ROW, COL);
	Sleep(5000);
	system("cls");
	*/
	
	ScreenMine(mineArray, ShowArray, ROW, COL);/*排查地雷*/

	while (1)
	{
		Gotoxy(18, 27);
		printf("请按下ESC退出当前界面");
		if ((chEixt = getch()) == VK_ESCAPE)
			break;
	}
	
}

/*
函数名称:Menu
函数功能:打印游戏开始界面菜单
编写时间:2022-07-14 19:13:58
*/
void Menu(void)
{
	SetWindowSize();/*设置控制台大小标题等*/
	Gotoxy(18, 9);
	printf("******************************");
	Gotoxy(18, 11);
	printf("*******  1:开始游戏   *******");
	Gotoxy(18, 13);
	printf("*******  0:退出游戏   *******");
	Gotoxy(18, 15);
	printf("******************************");
	Gotoxy(18, 17);
	printf("请输入你的选择:");
}

/*
函数名称:FindAllMine
函数功能:排雷成功画面
编写时间:2022-07-14 23:23:21
*/
void FindAllMine(char show[][COLS], int row, int col)/**/
{
	system("cls");
	Gotoxy(25, 3);
	printf("恭喜你 排雷成功");
	DisplayBoard(show, row, col);
}

/*
函数名称:MineBoom
函数功能:踩到地雷画面
编写时间:2022-07-14 22:29:37
*/
void MineBoom(char mine[][COLS], char show[][COLS], int row, int col)
{
	system("cls");
	Gotoxy(18, 3);
	printf("踩到地雷,你被炸死了!!");

	/*显示当前棋盘上所有地雷*/
	for (int i = 1; i <= row; i++)
	{
		for (int j = 1; j <= col; j++)
		{
			if (mine[i][j] == '1')
			{
				show[i][j] = '@';
			}
		}
	}
	DisplayBoard(show, row, col);/*打印展示的扫雷棋盘*/
}

/*
函数名称:ShowSelectError
函数功能:选择错误画面
编写时间:2022-07-14 19:25:47
*/
void ShowSelectError(void)
{
	system("cls");
	Gotoxy(18, 10);
	printf("******************************");
	Gotoxy(18, 12);
	printf("*******    输入错误    *******");
	Gotoxy(18, 14);
	printf("******************************");
	Sleep(1000);
}

/*
函数名称:ShowGameOver
函数功能:打印游戏结束画面
编写时间:2022-07-14 19:25:50
*/
void ShowGameOver(void)
{
	system("cls");
	Gotoxy(18, 10);
	printf("******************************");
	Gotoxy(18, 12);
	printf("*******   Bye Bye !   *******");
	Gotoxy(18, 14);
	printf("******************************");
	printf("\n");
}

如果直接复制代码的话,在vs20XX系列编译器底下,可能会出现无窗口标题,窗口大小无法修改的情况,虽然用了system函数,但是还是无法修改,具体原因,可以在vs编译器中选择工具->选项->调试->常规->调试停止时自动关闭控制台打勾,就可以了

main.c

/**********************************************************/
/****************        扫雷游戏       *******************/
/*1:界面构造   2:布置两个棋盘 一个布置雷 一个显示给用户 */
/*3:打印界面   4:布置地雷  5:计算雷的数量              */
/*6:排查地雷   7:递归排查某个坐标周边的坐标  8:开始游戏*/
/**********************************************************/
# include "Mine_Clearance_Game.h"

int main(void)
{
	/* srand最好放在main函数中,也就是只调用一次
	如果调用多次,结果可能不够随机,time(NULL)表示
	srand随着时间戳的改变,调整rand每一次随机值*/
	srand((unsigned int)time(NULL));

	char userSelect = '0';
	do
	{
		Menu();/*打印菜单*/
		userSelect = getch();/*等待用户输入*/
		switch (userSelect)
		{
		case '1':
			StartGame();/*开始游戏*/
			break;
		case '0':
			ShowGameOver();/*结束游戏*/
			break;
		default:
			ShowSelectError();/*选择错误*/
			break;
		}

	} while (userSelect != '0');

	return 0;
}

好啦 ,如果有更优化的,希望多多评论 谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值