C语言怎样写扫雷

1.自己写的扫雷要有什么功能与机制(主要的)

1.生成基础雷盘

在游戏开始时,基础雷盘就生成完毕。在基础雷盘上,地雷已经随机分布,每一格周围有多少地雷也已经计算完成并显示在这一格上。可以理解为,透视版的扫雷。下图就是基础雷盘的展现(现实中并不会展现基础雷盘)
在这里插入图片描述

2.生成表面雷盘

现实中当然没有透视版的扫雷,基础雷盘并不是我们想让玩家看到的。这时候就需要表面雷盘了。表面雷盘的每一格都是未知,当玩家选中了某一格,那就会将基础雷盘中相应格的内容投射到表面雷盘,这个内容可能是数字,也可能是地雷,那得要看玩家怎么选了。下图就是表面雷盘。(表面雷盘才是真正给玩家看到的)
在这里插入图片描述

读到此处,相信你已经明白我们要写的扫雷的核心原理了。也就是当玩家选中了表面雷盘的某一格,就会将基础雷盘的内容投射到表面雷盘的对应格上。显然,随着游戏的逐步进行,表面雷盘的内容将不断接近基础雷盘。当然,现实中的基础雷盘与表面雷盘只是抽象的两个二维数组,并非就是上面的两张图。只是表面雷盘是我们想让玩家看到的,因此包装成上图的样子,而基础雷盘无需包装。

3.零的扩散机制

如何减少玩家操作次数,原版扫雷很聪明的利用了零的扩散机制。当玩家选中的点是零,那么就会往四周扩散,直到遇到不为零的点,具体请看功能与机制的实现第三点。(下图是经过一次操作后,扩散出来的一片数字)
在这里插入图片描述

2.功能与机制的实现(主要的)

1.基础雷盘随机布雷机制

为了方便存储及调用每一格的内容,作者采用了两个二维数组分别储存基础雷盘表面雷盘的内容。提到随机布雷,大家可能第一反应会想到用随机数生成一个随机坐标,若该坐标对应的格中没有地雷,那么就在这一格放一个地雷,若该坐标对应的格中已有地雷,就跳过该点,重复以上步骤直到地雷数量达到设定值。但是这种布雷方式有一个致命问题:当重复的次数不断增加时,所剩的空格也就越来越少,此时随机坐标找到空格的几率也就越来越小。因此布雷效率低下。作者采用了另一种布雷方式——遍历法。如下图。(图片来自知乎作者eee555,请忽视图片右下角水印)
在这里插入图片描述
运用这种方法,只需要将每一格都遍历一次,就完成了布雷,十分高效。如何让每一格的布雷概率是未布的雷数/未访问的雷位置数?作者用了一种方法:生成一个随机数a(范围:0——未访问的位置数-1),如果a的范围又正好在(0——未布的雷数-1)之间,那么就在这一格布雷。这样,每一格的布雷概率恰好是未布的雷数/未访问的雷位置数。以下是代码的实现。

void resetboard_1(char board_1[X][Y], int choosex, int choosey)//resetboard_1即重置基础雷盘,重新布雷
{
	int cx, cy, bombnum = num, boardnum = x * y - 1, tmp;//cx,cy
	//bombnum是剩余的未放置的地雷数,boardnum是剩余的未访问的点数
	for (cx = 0; cx < x; cx++)
	{
		for (cy = 0; cy < y; cy++)
		{
			if (cx != choosex || cy != choosey)
                //choosex,choosey的作用是为了不在玩家的第一个选中格中防雷,避免开局即结局
			{
				tmp = rand() % boardnum;//生成0~boardnum-1的随机数tmp
				if (tmp < bombnum)//如果tmp在0~bombnum-1内,那么就在该点布雷
				{
					board_1[cx][cy] = '*';//在board_1[cx][cy]处放置地雷,*代表地雷
					bombnum--;//放置一个地雷,bumbnum-1
				}
				boardnum--;//访问一个点,boardnum-1
			}
		}
	}
}

2.基础雷盘的计数方法

基础雷盘的每一个非地雷的格,都有一个数字,数字代表其周围的地雷数量。那么要如何计算得到这些数字呢?前面说到,我们用二维数组存放了基础雷盘的内容,那么答案显然是从数组入手,只要通过数组找出周围的格内容,并累计地雷数,就可以实现计数。但是要注意,当我们使用数组查找内容时,应避免出现数组越界访问。以下是代码的实现。

void scoreboard_1(char board_1[X][Y])//用于计算每一个格周围有多少地雷
{
	int dx, dy, score;//dx,dy遍历整个数组
	for (dx = 0; dx < x; dx++)
	{
		for (dy = 0; dy < y; dy++)
		{
			if (board_1[dx][dy] != '*')//非地雷格才需要计数,地雷格不用计数
			{
				score = 0;
				//向上判断 1
				if (dx > 0)//避免数组越界访问
				{
					if (board_1[dx - 1][dy] == '*')
					{
						score++;
					}
				}
				//向右上判断 2
				if (dx > 0 && dy < y - 1)//避免数组越界访问
				{
					if (board_1[dx - 1][dy + 1] == '*')
					{
						score++;
					}
				}
				//向右判断 3
				if (dy < y - 1)//避免数组越界访问
				{
					if (board_1[dx][dy + 1] == '*')
					{
						score++;
					}
				}
				//向右下判断 4
				if (dx < x - 1 && dy < y - 1)//避免数组越界访问
				{
					if (board_1[dx + 1][dy + 1] == '*')
					{
						score++;
					}
				}
				//向下判断 5
				if (dx < x - 1)//避免数组越界访问
				{
					if (board_1[dx + 1][dy] == '*')
					{
						score++;
					}
				}
				//向左下判断 6
				if (dx < x - 1 && dy>0)//避免数组越界访问
				{
					if (board_1[dx + 1][dy - 1] == '*')
					{
						score++;
					}
				}
				//向左判断 7
				if (dy > 0)//避免数组越界访问
				{
					if (board_1[dx][dy - 1] == '*')
					{
						score++;
					}
				}
				//向左上判断 8
				if (dx > 0 && dy > 0)//避免数组越界访问
				{
					if (board_1[dx - 1][dy - 1] == '*')
					{
						score++;
					}
				}
				board_1[dx][dy] = score + 48;//加48,请查询asc码表
                //score即为周围的雷数,加48转化为字符的数字
			}
		}
	}
}

3.投射及零的扩散机制

当玩家选中了表面雷盘的某一格时,将会把基础雷盘中相应的内容投射表面雷盘中(包括地雷),对应的代码实现就是board_2[x][y]=board_1[x][y];(board_2是表面雷盘的数组,board_1是基础雷盘的数组)。在投射中有一种特殊情况,那就是投射的内容为零,也就是选中格周围没有地雷,那将其四周的格再次投射,若其周围的格还是零,就将其周围的格的周围的格投射……这就是零的扩散。以下是代码的实现。

void chooseboard_2(char board_1[X][Y], char board_2[X][Y], int choosex, int choosey)//选择投射该点
{//board_1是基础雷盘的数组,board_2是表面雷盘的数组
	if (board_2[choosex][choosey] == ' ' || board_2[choosex][choosey] == '?')//若board_2该点未被占用
	{
		if (board_1[choosex][choosey] != 48)//board_1该点不等于'0',不扩散
		{
			board_2[choosex][choosey] = board_1[choosex][choosey];
		}
		else if (board_1[choosex][choosey] == 48)//若该点等于'0',往四个方向扩散			
		{//后来作者查了一下原版扫雷机制,好像是往周围八格扩散,而不是周围四格,如果想要改的话,将chooseboard_2函数多嵌套几次就行,如果不太清楚原理的话还是不要改了doge
			board_2[choosex][choosey] = board_1[choosex][choosey];
			if (choosex < x - 1)//往下
			{
				chooseboard_2(board_1, board_2, choosex + 1, choosey);
			}
			if (choosex > 0)//往上
			{
				chooseboard_2(board_1, board_2, choosex - 1, choosey);
			}
			if (choosey < y - 1)//往右
			{
				chooseboard_2(board_1, board_2, choosex, choosey + 1);
			}
			if (choosey > 0)//往左
			{
				chooseboard_2(board_1, board_2, choosex, choosey - 1);
			}
		}
	}
}

3.次要功能的实现

1.雷盘大小设置

众所周知,经典的扫雷有三种规格(长,宽,雷数):1.小(9,9,9);2.中(16,16,40);3.大(30,16,99);作者同样设计了三种规格。如下图。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
已知创建数组char board[X][Y]={0};时,X和Y必须为常量,如果想要创建多个不同元素数量的数组来实现不同规格的转换,显然十分复杂。要如何实现规格的转换而不需要调整代码呢?不妨直接创建元素数量为30*16(最大规格)的数组,但打印表面雷盘以及基础雷盘的内容存储只使用相应规格的元素数量,这样问题就以迎刃而解了。

2.表面雷盘的格的标记与取消标记

实现起来也很简单,若表面雷盘的选中格为空(当然也不包括数字),则使这一格的内容为’ ? ‘,实现标记;若选中格为’ ? ',则使这一格的内容为空,实现取消标记。以下为代码的实现。

				if (board_2[choosex][choosey] == ' ')
				{
					board_2[choosex][choosey] = '?';//标记该点
					hudnum--;
				}
				else if (board_2[choosex][choosey] == '?')
				{
					board_2[choosex][choosey] = ' ';//取消标记该点
					hudnum++;
				}
			}

3.输赢判断机制

  1. :很简单,当表面雷盘被投射了地雷,也就相当于玩家选中了有地雷的格,判断为输;
  2. :即表面雷盘的所有格已被探明,且没有点开地雷格;
  3. 未结束:即表面雷盘仍有格未被探明,且没有点开地雷格。

输的判断十分简单,这里作者想讨论一下赢和未结束的判断。首先我们定义一个概念重合度:表面雷盘与基础雷盘内容相同的格数。当玩家选择某一格,那么基础雷盘的相应内容就会被投射到表面雷盘,当游戏不断进行,越来越多的内容从基础雷盘投射到表面雷盘,这样他俩的重合度显然会逐步升高。假设雷盘的规格为小(9*9),假若基础雷盘与表面雷盘有1格相同,那么重合度为1;假若基础雷盘与表面雷盘有10格相同,那么重合度为10……假若基础雷盘与表面雷盘有81格相同,那么重合度为81?错误,因为地雷格的内容不会被投射,否则就是输。

至此,判断赢和未结束的方法已经出现。假如重合度=雷盘总格数-地雷总数,那么判断为赢,也就是除了地雷格,其余格子完全相同;假如重合度<雷盘总格数-地雷总数,那么判断为未结束。当然,赢和未结束的判断都要建立在没有输的基础上。以下为代码的实现。

int testboard_2(char board_1[X][Y], char board_2[X][Y], int choosex, int choosey)
{//board_1是基础雷盘的数组,board_2是表面雷盘的数组,choosex和choosey并没有什么用,不过也不影响
	int ex, ey, score = 0;//ex,ey遍历整个数组
	for (ex = 0; ex < x; ex++)
	{
		for (ey = 0; ey < y; ey++)
		{
			if (board_2[ex][ey] == '*')//踩到地雷啦
			{
				return 0;//判断为输
			}
			if (board_1[ex][ey] == board_2[ex][ey])//计算board_1和board_2的重合度
			{
				score++;//累计重合度
			}
		}
	}
	if (score == x * y - num)//重合度=x*y-num,即所偶有点位已被探明,胜利
	{//x*y即雷盘总格数,num即总地雷数
		return 1;//判断为赢
	}
	else
	{
		return 2;//重合度<x*y-num,判断为未结束,其实连返回值都不用,没有结束,程序不需要作出任何反应
	}
}

4.全部代码

1.说明

作者使用的编译器是visual studio 2022。除了以上提到的内容以为,作者还增加了不同数字的配色、设置界面、主界面、游戏界面、剩余未标记地雷数HUD显示等内容。具体请看运行程序或全部代码。各位记得一键三联哦!!!

2.game.h

#pragma once

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<windows.h>
#include<time.h>
#include<stdlib.h>

#define X 16
#define Y 30//雷盘的二维数组行列数(最大规格)

int x, y, z, num, hudnum;
//行数,列数,雷盘大小标号,雷数
char board_1[X][Y], board_2[X][Y];


void initboard(char board[X][Y]);//基础雷盘与表面雷盘数组的初始化函数
void printboard_2(char board_2[X][Y]);//打印表面雷盘的函数
void resetboard_1(char board_1[X][Y], int choosex, int choosey);//基础雷盘的布雷函数
void scoreboard_1(char board_1[X][Y]);//基础雷盘的计数函数
void chooseboard_2(char board_1[X][Y], char board_2[X][Y], int choosex, int choosey);//表面雷盘格的选择与零的扩散函数
void printnum(char board_2[X][Y], int bx, int by);//给表面雷盘每一格的内容上色
void alldisplay(char board_1[X][Y], char board_2[X][Y]);//玩家输的时候所有地雷显现的函数
int testboard_2(char board_1[X][Y], char board_2[X][Y], int choosex, int choosey);//输赢或未结束的判断函数

3.game.c

#include"game.h"

void initboard(char board[X][Y])
{
	for (int ax = 0; ax < X; ax++)
	{
		for (int ay = 0; ay < Y; ay++)
		{
			board[ax][ay] = ' ';
		}
	}
}

void printnum(char board_2[X][Y], int bx, int by)//这一个函数其实可以简化很多,但作者直接暴力复制粘贴了
{
	switch (board_2[bx][by])
	{
	case '0':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 14 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//白色
		printf("0");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '1':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//亮白色
		printf("1");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '2':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 10 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//淡绿色
		printf("2");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '3':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 9 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//淡蓝色
		printf("3");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '4':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 12 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//淡红色
		printf("4");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '5':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 13 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//淡紫色
		printf("5");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '6':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 3 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//湖蓝色
		printf("6");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '7':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 1 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//蓝色
		printf("7");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '8':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 5 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//紫色
		printf("8");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '*':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//红色
		printf("*");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case '?':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);//红色
		printf("?");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	case ' ':
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		printf(" ");
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
		break;
	}
}

void printboard_2(char board_2[X][Y])
{
	int bx, by, i = 0;
	for (bx = 0; bx < x - 1; bx++)
	{
		for (by = 0; by < y - 1; by++)
		{
			printf(" ");
			printnum(board_2, bx, by);//???
			printf(" |");
		}
		printf(" ");
		printnum(board_2, bx, by);//???
		printf("  %d", bx);
		if (i == 0)
		{
			printf("    剩余未标记地雷数:%d", hudnum);//显示未被标记的雷数
			i++;
		}
		printf(" \n");
		for (by = 0; by < y - 1; by++)
		{
			printf("---|");
		}
		printf("---\n");
	}
	for (by = 0; by < y - 1; by++)
	{
		printf(" ");
		printnum(board_2, bx, by);//???
		printf(" |");
	}
	printf(" ");
	printnum(board_2, bx, by);//???
	printf("  %d", bx);
	printf(" \n\n");
	for (by = 0; by < y; by++)
	{
		if (by < 10)
		{
			printf(" %d  ", by);
		}
		else
		{
			printf(" %d ", by);
		}
	}
	printf("\n");
}

void resetboard_1(char board_1[X][Y], int choosex, int choosey)
{
	int cx, cy, bombnum = num, boardnum = x * y - 1, tmp;
	//cx,cy,剩余的未放置的地雷数,剩余的未访问的点数
	for (cx = 0; cx < x; cx++)
	{
		for (cy = 0; cy < y; cy++)
		{
			if (cx != choosex || cy != choosey)
			{
				tmp = rand() % boardnum;//生成0~boardnum-1的随机数tmp
				if (tmp < bombnum)//如果tmp在0~bombnum-1内,那么就在该点布雷
				{
					board_1[cx][cy] = '*';//在board_1[cx][cy]处放置地雷
					bombnum--;//放置一个地雷,bumbnum-1
				}
				boardnum--;//访问一个点,boardnum-1
			}
		}
	}
}

void scoreboard_1(char board_1[X][Y])//用于计算每一个点周围有多少地雷
{
	int dx, dy, score;
	for (dx = 0; dx < x; dx++)
	{
		for (dy = 0; dy < y; dy++)
		{
			if (board_1[dx][dy] != '*')
			{
				score = 0;
				//向上判断 1
				if (dx > 0)
				{
					if (board_1[dx - 1][dy] == '*')
					{
						score++;
					}
				}
				//向右上判断 2
				if (dx > 0 && dy < y - 1)
				{
					if (board_1[dx - 1][dy + 1] == '*')
					{
						score++;
					}
				}
				//向右判断 3
				if (dy < y - 1)
				{
					if (board_1[dx][dy + 1] == '*')
					{
						score++;
					}
				}
				//向右下判断 4
				if (dx < x - 1 && dy < y - 1)
				{
					if (board_1[dx + 1][dy + 1] == '*')
					{
						score++;
					}
				}
				//向下判断 5
				if (dx < x - 1)
				{
					if (board_1[dx + 1][dy] == '*')
					{
						score++;
					}
				}
				//向左下判断 6
				if (dx < x - 1 && dy>0)
				{
					if (board_1[dx + 1][dy - 1] == '*')
					{
						score++;
					}
				}
				//向左判断 7
				if (dy > 0)
				{
					if (board_1[dx][dy - 1] == '*')
					{
						score++;
					}
				}
				//向左上判断 8
				if (dx > 0 && dy > 0)
				{
					if (board_1[dx - 1][dy - 1] == '*')
					{
						score++;
					}
				}
				board_1[dx][dy] = score + 48;//加48,请查询asc码表
			}
		}
	}
	//printboard_2(board_1);//测试用,请忽视
	//Sleep(20000000);
}

void chooseboard_2(char board_1[X][Y], char board_2[X][Y], int choosex, int choosey)//选择排雷该点
{
	if (board_2[choosex][choosey] == ' ' || board_2[choosex][choosey] == '?')//若board_2该点未被占用,被标记的点也不算占用,若扩散至标记点,且标记点不是地雷,则标记会被数字覆盖
	{
		if (board_1[choosex][choosey] != 48)//board_1该点不等于'0',不扩散
		{
			board_2[choosex][choosey] = board_1[choosex][choosey];
		}
		else if (board_1[choosex][choosey] == 48)//若该点等于'0',往四个方向扩散			
		{
			board_2[choosex][choosey] = board_1[choosex][choosey];
			if (choosex < x - 1)//往下
			{
				chooseboard_2(board_1, board_2, choosex + 1, choosey);
			}
			if (choosex > 0)//往上
			{
				chooseboard_2(board_1, board_2, choosex - 1, choosey);
			}
			if (choosey < y - 1)//往右
			{
				chooseboard_2(board_1, board_2, choosex, choosey + 1);
			}
			if (choosey > 0)//往左
			{
				chooseboard_2(board_1, board_2, choosex, choosey - 1);
			}
			//往四个方向扩散	
		}
	}
}

int testboard_2(char board_1[X][Y], char board_2[X][Y], int choosex, int choosey)
{
	int ex, ey, score = 0;
	for (ex = 0; ex < x; ex++)
	{
		for (ey = 0; ey < y; ey++)
		{
			if (board_2[ex][ey] == '*')//踩到地雷啦
			{
				return 0;
			}
			if (board_1[ex][ey] == board_2[ex][ey])//计算board_1和board_2的重合度
			{
				score++;
			}
		}
	}
	if (score == x * y - num)//重合度=x*y-num,即所有点位已被探明,胜利
	{
		return 1;
	}
	else
	{
		return 2;//重合度<x*y-num,未胜利
	}
}

void alldisplay(char board_1[X][Y], char board_2[X][Y])//踩中地雷时所有地雷显现
{
	int fx, fy;
	for (fx = 0; fx < x; fx++)
	{
		for (fy = 0; fy < y; fy++)
		{
			if (board_1[fx][fy] == '*')
			{
				board_2[fx][fy] = '*';
			}
		}
	}
}

4.main.c

#include"game.h"
//以下是主界面、设置界面、游戏界面等游戏{基础框架}的实现
void menu()//菜单 $$
{
	printf("输入以下数字以选择下一步:\n'0'结束游戏\n'1'开始游戏\n'2'游戏信息与设置\n请输入:");
}

int game()
{
	int a = 1, test;//a用于控制第一次选择排雷点时重置雷盘,即第一次排雷不可能遇到地雷
	int choosex, choosey, choose;
	hudnum = num;
	initboard(board_1);//$$
	initboard(board_2);//初始化底层雷区与表面雷区	$$
	printboard_2(board_2);//打印表面雷区 $$
	while (1)
	{
		while (1)
		{
			printf("输入你想要选择的点的横坐标:");
			scanf("%d", &choosey);
			printf("输入你想要选择的点的纵坐标:");
			scanf("%d", &choosex);
			printf("输入1选择排雷该点,输入2选择标记或取消标记该点:");
			scanf("%d", &choose);//选择要操作的点,并选择排雷/标记/取消标记
			if (-1 < choosex && choosex < x && -1 < choosey && choosey < y && choose == 1)//选择了雷盘内合法的点并排雷
			{
				if (a == 1)//在'第一次'选择排雷后重置雷盘并计算每个点周围的雷数 (后续将不起作用)
				{
					resetboard_1(board_1, choosex, choosey);//重新布雷底部雷区 $$
					scoreboard_1(board_1);//底部雷区计数 $$
					a++;
				}
				if (board_2[choosex][choosey] != '?')
				{
					chooseboard_2(board_1, board_2, choosex, choosey);//选择排雷点
				}
				system("cls");
				printboard_2(board_2);//$$
				break;//只有选择该点排雷,才调用break,进入检测环节
			}
			else if (-1 < choosex && choosex < x && -1 < choosey && choosey < y && choose == 2)//选择雷盘内合法的点并标记/取消标记
			{
				if (board_2[choosex][choosey] == ' ')
				{
					board_2[choosex][choosey] = '?';//标记该点
					hudnum--;
				}
				else if (board_2[choosex][choosey] == '?')
				{
					board_2[choosex][choosey] = ' ';//取消标记该点
					hudnum++;
				}
				system("cls");
				printboard_2(board_2);
			}
			else//其余一切操作皆为非法操作
			{
				printf("请输入正确的数字");
				Sleep(2000);
				system("cls");
				printboard_2(board_2);
			}
		}
		test = testboard_2(board_1, board_2, choosex, choosey);
		//test=0-->踩到地雷;test=1-->全部点位已被探明且未踩到地雷,胜利;test=2-->未踩到地雷,也没有探明所有点位,未结束
		if (test == 0)
		{
			alldisplay(board_1, board_2);
			system("cls");
			printboard_2(board_2);
			return 0;
		}
		else if (test == 1)
		{
			return 1;
		}
	}
}//return-->choose-->result:0-->输,1-->赢

void choose_menu()//菜单选择
{
	int menuchoose = 0, result = 0;//menuchoose菜单选择结果,result游戏输赢结果
	z = 1, num = 9, x = 9, y = 9;
	int set = 0, boa = 1;
	do
	{
		system("cls");
		menu();
		scanf("%d", &menuchoose);
		switch (menuchoose)
		{
		case 0://结束游戏
			system("cls");
			printf("结束游戏");
			Sleep(2000);
			break;
		case 1://开始游戏
			system("cls");
			printf("开始游戏");
			Sleep(2000);
			system("cls");
			result = game();//game()_return:0-->输,1-->赢
			if (result == 0)
			{
				SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15 | 8 | 128 | 64);
				printf("you lost!!\n");
				SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
				printf("按下回车键以继续");
				getchar();
				getchar();
			}
			else if (result == 1)
			{
				SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15 | 8 | 128 | 64);
				printf("you won!!\n");
				SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | FOREGROUND_INTENSITY | 8 | BACKGROUND_INTENSITY);
				printf("按下回车键以继续");
				getchar();
				getchar();
			}
			break;
		case 2://信息与设置
			system("cls");
			printf("游戏信息与设置");
			Sleep(2000);
			system("cls");
			printf("游戏信息:\n\n游戏名:扫雷\n\n作者:li_zi_jin\n\n提示:\n1.游戏规则与Windows扫雷相同\n2.通过输入坐标选择相应的点,坐标原点为左上角\n3.可以调整雷盘大小,默认为小\n6.选择了你想要操作的点后,选择1排雷该点相当于原版左键,选择2标记/取消标记该点相当于原版右键\n5.每输入一次数字,按下Enter以进行下一步,不要输入数字以外的任何字符,会出bug\n\n");
			printf("信息:\n雷盘大小:");
			switch (z)
			{
			case 1:
				printf("小(9*9)\n");
				break;
			case 2:
				printf("中(16*16)\n");
				break;
			case 3:
				printf("大(16*30)\n");
				break;
			default:
				printf("出错\n");
				break;
			}
			printf("地雷数量:");
			printf("%d\n", num);
			printf("\n输入以下数字以选择下一步:\n'1'调整雷盘大小\n'其他数字'退回初始界面\n请输入:");
			scanf("%d", &set);
			if (set == 1)
			{
				printf("\n输入以下数字以选择相应雷盘大小:\n'1'小\n'2'中\n'3'大\n请输入:");
				scanf("%d", &boa);
				if (0 < boa && boa < 4)
				{
					z = boa;
					switch (z)
					{
					case 1:
						num = 9;
						//num = 1;//测试用,请忽略
						x = 9;
						y = 9;
						break;
					case 2:
						num = 40;
						x = 16;
						y = 16;
						break;
					case 3:
						num = 99;
						x = 16;
						y = 30;
						break;
					}
					printf("设置成功");
				}
				else
				{
					printf("请输入正确的数字");
				}
			}
			else
			{
				printf("退回初始界面");
			}
			Sleep(2000);
			break;
		default://非法输入
			system("cls");
			printf("请输入正确的数字\n");
			Sleep(2000);
			break;
		}
	} while (menuchoose != 0);
}

int main()
{
	srand((unsigned int)time(NULL));
	system("color 87");
	choose_menu();
	return 0;
}

作者:li_zi_jin
日期:2023_10_15
(未经同意禁止转载)

  • 29
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

li_zi_jin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值