【C语言】扫雷(可展开空白版与鼠标操作版)

        扫雷游戏是一款经典的单人电脑游戏,最初由微软公司开发。玩家需要根据数字提示,在不触雷的情况下揭开所有格子。这是一款考验逻辑思维和运气的游戏,而我们将用C语言来实现它。

        该程序有一个头文件和两个源文件构成。 在头文件game.h中进行函数的声明,在源文件game.c中进行函数的实现,在test.c中进行程序测试,实现主函数。接下来我将从主函数开始进行代码的逐一讲解,需要源代码可以在勤奋的土豆 (rolltudou) - Gitee.com中的bite_begin路径下进行查找或者在文章结尾会有全部源代码。

一、test.c

1、main

int main(void)
{
	test();

	return 0;
}

        在mian函数中用test来实现过程,用于启动游戏。

2、test 

void test()
{
	int input = 0;
	do
	{
		menu();
		printf("please choice number to take part in game or give out >");
		scanf("%d",&input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("game over\n");
			break;
		default:
			printf("you put nmber is fault,please choice again\n");
			break;
		}
	} while (input);
}

        在test中进行整体游戏的实现,首先用do while循环进行持续进行,循环中首先进行打印菜单menu(),然后通过menu()中的选项,用switch进行选择,可以进行游戏、结束游戏,在输入的数字有问题的时候会提示输入有误,让用户进行重新输入。

        接下来由从上到下,从内到外的顺序进行逐个讲解。

3、menu

void menu()
{
	printf("\n******1.game begin****\n");
	printf("******0.game over ****\n");
}

 4、game

void game()
{

	//mine is mine's information
	char mine[ROWS][COLS] = { 0 };
	
	//The show array stores the information of the detected mines
	char show[ROWS][COLS] = { 0 };
	
	//Initialize the board  (The show and mine all use one funk)
	InitBoard(mine, ROWS,COLS,'0');
	InitBoard(show, ROWS, COLS, '*');

	//set  mine 
	//9*9   10 mine
	SetMine(mine, ROW, COL);


	//print board
	DisplayBoard(show,ROW,COL);

	//find mine
	FindMine(mine, show, ROW, COL);

}

        在game.h中对ROW,COL,ROWS,COLS进行宏定义,方便对棋盘大小进行更改。

#define ROW 9
#define COL 9

#define ROWS ROW + 2
#define COLS COL + 2

        在游戏中需要两个数组,一个是雷的数组,一个用来展示给玩家的看的数组。

        用InitBoard函数对mine和show数组进行初始化。

        用SetMine进行对雷的放置。

        用DisplayBoard进行棋盘的打印。

        用FindMine进行雷的查找。

二、game.c

1.InitBoard

void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < ROWS; ++i)
	{
		int j = 0;
		for (j = 0; j < COLS; ++j)
		{
			arr[i][j] = set;
		}
	}
}

        为了保证函数的通用性用char set进行形参,来保证可以用不同的字符进行初始化。另外,用ROWS和COLS将棋盘制造出一个空白框,保证在查找的时候对空白地方进行扫描的时候不会影响查找雷。

 2、SetMine

void SetMine(char arr[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % 9 + 1;
		int y = rand() % 9 + 1;

		//judge arr[x][y] ,when arr[x][y] don't setted mine just do it.
		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			--count;
		}
	}
}

       在game.h中宏定义EASY_COUNT的数量,方便更改游戏难度。

       为了做到雷可以在棋盘上随机坐标进行排布,用rand()获取随机坐标进行雷的放置。另外进行if判断,如果这个坐标没有放置过的话才会进行放置‘1’。

3、FindMine

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;

	while (win < row * col - EASY_COUNT)
	{
		printf("Please enter the coordinates you want to troubleshoot ");
		scanf("%d %d",&x,&y);

		//judgement
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '*')
			{
				//if mine ,you're died; if no,show mine number and open block
				if (mine[x][y] == '1')
				{
					//clean
					system("cls");
					printf("you are dead");
					DisplayBoard(mine, ROW, COL);
					break;
				}
				else
				{
					//count mine number
					
					//int count = GetMineCount(mine, x, y);
					//show[x][y] = count + '0';

					//clean
					system("cls");
					open(mine, show, x, y);
					DisplayBoard(show, ROW, COL);
					++win;
					
				}
			}
			else
			{
				printf("The coordinates have been checked\n");
			}
		}
		else
		{
			printf("The coordinates are invalid, please re-enter them\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("You're such a f**king genius\n");
		DisplayBoard(mine, ROW, COL);
	}
}

 代码逻辑为以下:

  • 使用 while 循环来持续进行雷区的查找,直到玩家找到所有非雷区的格子。
  • 通过 scanf 获取玩家输入的坐标,然后进行合法性判断。
  • 根据玩家的输入,判断是否触雷,如果触雷则游戏结束,否则显示周围雷的数量并打开相应的格子。

4、open

void open(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
	{
		int count = GetMineCount(mine, x, y);//rerurn mine number
		//如果该格子周围雷的个数不为0,我们就把该格子周围雷的个数传到玩家玩的show棋盘对应的格子中,而如果是0,我们传入空格‘ ’ 
		if (count != 0)
		{
			show[x][y] = count + '0';//由于count返回的是整数,而存入数组中的是字符,我们需要加上‘0’,相当于加上ASCII值48
		}
			
		else if (show[x][y] != ' ')
		{
			show[x][y] = ' ';
			int i = 0;
			for (i = x - 1; i <= x + 1; i++)
			{
				int j = 0;
				for (j = y - 1; j <= y + 1; j++)
				{
					//Function recursion,until around all have mine.
					open(mine, show, i, j);
				}
			}
		}
		else
		{
			return;
		}
	}
}

        open函数使用递归的方法进行每次查找到空白的时候展开周围不是雷的一片区域。

        对于递归地方的说明:如果该位置周围没有地雷且展示数组show的对应位置不是空格,那么将该位置的展示数组show的值设为空格,并遍历该位置周围的8个格子,对每个格子再次调用open函数,实现递归展开空白区域的功能。如果该位置周围没有地雷且展示数组show的对应位置已经是空格,则直接返回,不做任何处理。 

5、GetMineCount

static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	int i = 0;
	int count = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		int j = 0;
		for (j = y - 1; j <= y + 1; j++)
		{
			count += (mine[i][j] - '0');
		}
	}
	return count;
}

         在扫雷中通过查找选中坐标九宫格内的雷的个数,然后在选中坐标进行显示。

        也可以通过以下代码进行查询,效果相同:

int get_mine(char mine[ROWS][COLS], int x, int y)
{
	return mine[x][y + 1] + mine[x][y - 1] + mine[x - 1][y] + mine[x + 1][y]
		+ mine[x + 1][y + 1] + mine[x - 1][y - 1] + mine[x + 1][y - 1] +
		mine[x - 1][y + 1] - 8 * '0';
}

 二、源代码

1、键盘输入坐标查找(本篇代码)

①game.h

#pragma once

#define _CRT_SECURE_NO_WARNINGS 1

#define ROW 9
#define COL 9

#define ROWS ROW + 2
#define COLS COL + 2

#define EASY_COUNT 10

#include 
#include


//initialize board
void InitBoard(char arr[ROWS][COLS], int rows, int cols);

//set mine
void SetMine(char arr[ROWS][COLS], int row, int col);

//print board
void DisplayBoard(char arr[ROWS][COLS], int row, int col);

//find mine
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

//open block
void open(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

②test.c       

#include"game.h"

void game()
{

	//mine is mine's information
	char mine[ROWS][COLS] = { 0 };
	
	//The show array stores the information of the detected mines
	char show[ROWS][COLS] = { 0 };
	
	//Initialize the board  (The show and mine all use one funk)
	InitBoard(mine, ROWS,COLS,'0');
	InitBoard(show, ROWS, COLS, '*');

	//set  mine 
	//9*9   10 mine
	SetMine(mine, ROW, COL);


	//print board
	DisplayBoard(show,ROW,COL);

	//find mine
	FindMine(mine, show, ROW, COL);

}

void menu()
{
	printf("\n******1.game begin****\n");
	printf("******0.game over ****\n");
}

void test()
{
	int input = 0;
	do
	{
		menu();
		printf("please choice number to take part in game or give out >");
		scanf("%d",&input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("game over\n");
			break;
		default:
			printf("you put nmber is fault,please choice again\n");
			break;
		}
	} while (input);
}

int main(void)
{
	test();

	return 0;
}

③game.c 

#include"game.h"

void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < ROWS; ++i)
	{
		int j = 0;
		for (j = 0; j < COLS; ++j)
		{
			arr[i][j] = set;
		}
	}
}

void SetMine(char arr[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % 9 + 1;
		int y = rand() % 9 + 1;

		//judge arr[x][y] ,when arr[x][y] don't setted mine just do it.
		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			--count;
		}
	}
}

void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	//print the first row nmber show
	for (i = 0; i <= col; ++i)
	{
		printf("%d ",i);
	}
	printf("\n");
	for (i = 1; i <= row; ++i)
	{
		printf("%d ",i);
		int j = 0;
		for (j = 1; j <= col; ++j)
		{
			printf("%c ",arr[i][j]);
		}
		printf("\n");
	}
}

static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	int i = 0;
	int count = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		int j = 0;
		for (j = y - 1; j <= y + 1; j++)
		{
			count += (mine[i][j] - '0');
		}
	}
	return count;
}

get around mine numbers
//int get_mine(char mine[ROWS][COLS], int x, int y)
//{
//	return mine[x][y + 1] + mine[x][y - 1] + mine[x - 1][y] + mine[x + 1][y]
//		+ mine[x + 1][y + 1] + mine[x - 1][y - 1] + mine[x + 1][y - 1] +
//		mine[x - 1][y + 1] - 8 * '0';
//}



//关于open函数中 递归处的解读
//如果该位置周围没有地雷且展示数组show的对应位置不是空格,
//那么将该位置的展示数组show的值设为空格,并遍历该位置周围的8个格子,对每个格子再次调用open函数,实现递归展开空白区域的功能。
//
//如果该位置周围没有地雷且展示数组show的对应位置已经是空格,则直接返回,不做任何处理。
//open mine
void open(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
	{
		int count = GetMineCount(mine, x, y);//rerurn mine number
		//如果该格子周围雷的个数不为0,我们就把该格子周围雷的个数传到玩家玩的show棋盘对应的格子中,而如果是0,我们传入空格‘ ’ 
		if (count != 0)
		{
			show[x][y] = count + '0';//由于count返回的是整数,而存入数组中的是字符,我们需要加上‘0’,相当于加上ASCII值48
		}
			
		else if (show[x][y] != ' ')
		{
			show[x][y] = ' ';
			int i = 0;
			for (i = x - 1; i <= x + 1; i++)
			{
				int j = 0;
				for (j = y - 1; j <= y + 1; j++)
				{
					//Function recursion,until around all have mine.
					open(mine, show, i, j);
				}
			}
		}
		else
		{
			return;
		}
	}
}


void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;

	while (win < row * col - EASY_COUNT)
	{
		printf("Please enter the coordinates you want to troubleshoot ");
		scanf("%d %d",&x,&y);

		//judgement
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '*')
			{
				//if mine ,you're died; if no,show mine number and open block
				if (mine[x][y] == '1')
				{
					//clean
					system("cls");
					printf("you are dead");
					DisplayBoard(mine, ROW, COL);
					break;
				}
				else
				{
					//count mine number
					
					//int count = GetMineCount(mine, x, y);
					//show[x][y] = count + '0';

					//clean
					system("cls");
					open(mine, show, x, y);
					DisplayBoard(show, ROW, COL);
					++win;
					
				}
			}
			else
			{
				printf("The coordinates have been checked\n");
			}
		}
		else
		{
			printf("The coordinates are invalid, please re-enter them\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("You're such a f**king genius\n");
		DisplayBoard(mine, ROW, COL);
	}
}

2、鼠标操作(可选择难度)

        该程序将在之后的博客进行讲解。

        欢迎关注。

①game.h

#pragma once

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  // time 函数需要的头文件
#include  // rand、 srand、malloc 函数需要的头文件
#include 
#include 

void initBoard(char** arr, int rows, int cols, char set);

int display(char** arr, int row, int col, int y, int x, int* time);

void setMine(char** mine, int row, int col, int y, int x);

void findMine(char** mine, char** show, int row, int col);

void spread(char** mine, char** show, int y, int x, int* win, int row, int col);

void numberSpread(char** mine, char** show, int y, int x, int num, int* win, int row, int col);

// 动态扫雷实现
char** apply(int* rows, int* cols);

void release(char** arr, int rows, int cols);

int getMine();

void myApply(int* rows, int* cols);

int myGetMine();

// 伪图形扫雷实现
void game();

void gotoxy(int a, int b);

void HideCursor();

int option(int* rows, int* cols);

// 鼠标操作
void getPower();

void mouseOperateGame(char** mine, char** show, int mouseOperate, int y, int x, int row, int col, int first, int* win);
//        GetAsyncKeyState(VK_RBUTTON); // 右键
//        GetAsyncKeyState(VK_LBUTTON); // 左键
//右键
//mouseRecord.Event.MouseEvent.dwButtonState == RIGHTMOST_BUTTON_PRESSED
//左键
//if (mouseRecord.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)

②test.c

#include "game.h"

int getMineCount = 0;

//定义句柄变量
HANDLE get1;
//权限准备
DWORD get2 = ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT;
//定义输入事件结构体
INPUT_RECORD mouseRecord;
//用于存储读取记录
DWORD res;

void getPower() // 获取权限
{
	if (!SetConsoleMode(get1, get2))
		fprintf(stderr, "%s\n", "SetConsoleMode");
}

void adjustScreen()
{
	system("cls");
	printf("Ctrl + 滑动鼠标滑轮调整大小\n");
	printf("注意:若实际游戏画面大于程序窗口会出现刷屏哦\n");
	printf("按任意键返回:>\n");
	char ch = _getch();
	system("cls");
}

void menu1()
{
	getPower();
	char a = 0;
	int y = 0;
	int x = 0;
	int exitgame = 0;

	int judge = 1;

	while (1)
	{
		gotoxy(0, 0);

		//读取输入事件
		ReadConsoleInput(get1, &mouseRecord, 1, &res);
		//获取鼠标当前位置
		y = mouseRecord.Event.MouseEvent.dwMousePosition.Y;
		x = mouseRecord.Event.MouseEvent.dwMousePosition.X;

		char arr[3][13] = { "开始游戏", "调整画面", "退出游戏" }; 
		printf("*************************\n");
		if ((y == 1) && (4 <= x && x <= 21))
		{
			printf("****\033[41m     %-10s  \033[0m****\n", &arr[0][0]);
		}
		else
			printf("****   %-12s  ****\n", &arr[0][0]);
		if ((y == 2) && (4 <= x && x <= 21))
		{
			printf("****\033[41m     %-10s  \033[0m****\n", &arr[1][0]);
		}
		else
			printf("****   %-12s  ****\n", &arr[1][0]);

		if ((y == 3) && (4 <= x && x <= 21))
		{
			printf("****\033[41m     %-10s  \033[0m****\n", &arr[2][0]);
		}
		else
			printf("****   %-12s  ****\n", &arr[2][0]);

		printf("*************************\n");
		printf("若点一下无反应可重新点一下\n");

		int mouseOperate = 0;

		if (judge)
			mouseOperate = mouseRecord.Event.MouseEvent.dwButtonState;
		else if (GetAsyncKeyState(VK_LBUTTON))
			judge = 1;
		else
			mouseOperate = 0;

		if (mouseRecord.EventType == MOUSE_EVENT)
		{
			switch (mouseOperate)
			{
			case FROM_LEFT_1ST_BUTTON_PRESSED:
				if ((y == 1) && (4 <= x && x <= 21))
				{
					system("cls"); // 先清屏
					game();
					getPower();
				}
				if ((y == 2) && (4 <= x && x <= 21))
				{
					adjustScreen();
					judge = 0;
					getPower();
				}
				if ((y == 3) && (4 <= x && x <= 21))
				{
					system("cls");// 先清屏
					printf("%s\n", "退出游戏");
					exitgame = 1;
				}
				break;
			}
		}
		GetAsyncKeyState(VK_LBUTTON);
		Sleep(50);
		if (exitgame)
			break;
	}
}

int option(int* rows, int* cols)
{
	GetAsyncKeyState(VK_LBUTTON);
	getPower();
	char a = 0;
	int y = 0;
	int x = 0;
	while (1)
	{
		gotoxy(0, 0); // 固定画面

		//读取输入事件
		ReadConsoleInput(get1, &mouseRecord, 1, &res);
		//获取鼠标当前位置
		y = mouseRecord.Event.MouseEvent.dwMousePosition.Y;
		x = mouseRecord.Event.MouseEvent.dwMousePosition.X;

		char arr[4][20] = { "1.初级(9×9)", "2.中级(16×16)", "3.高级(16×30)", "返回" };

		printf("*************************\n");
		if ((y == 1) && (4 <= x && x <= 22))
			printf("****\033[41m   %-15s\033[0m****\n", &arr[0][0]);
		else
			printf("****   %-15s****\n", &arr[0][0]);
		if ((y == 2) && (4 <= x && x <= 22))
			printf("****\033[41m   %-15s\033[0m****\n", &arr[1][0]);
		else
			printf("****   %-15s****\n", &arr[1][0]);
		if ((y == 3) && (4 <= x && x <= 22))
			printf("****\033[41m   %-15s\033[0m****\n", &arr[2][0]);
		else
			printf("****   %-15s****\n", &arr[2][0]);
		if ((y == 4) && (4 <= x && x <= 22))
			printf("****\033[41m   %-14s\033[0m****\n", &arr[3][0]);
		else
			printf("****   %-14s****\n", &arr[3][0]);
		printf("*************************\n");

		int mouseOperate = mouseRecord.Event.MouseEvent.dwButtonState;

		if (mouseRecord.EventType == MOUSE_EVENT)
		{
			switch (mouseOperate)
			{
			case FROM_LEFT_1ST_BUTTON_PRESSED:
				if ((y == 1) && (4 <= x && x <= 22))
				{
					*rows = 11;
					*cols = 11;
					return 1;
				}
				if ((y == 2) && (4 <= x && x <= 22))
				{
					*rows = 18;
					*cols = 18;
					return 2;
				}
				if ((y == 3) && (4 <= x && x <= 22))
				{
					*rows = 18;
					*cols = 32;
					return 3;
				}
				if ((y == 4) && (4 <= x && x <= 22))
				{
					return -1;
				}
				break;
			}
		}
		Sleep(50);
	}
	return 1;
}

void game()
{
	int rows = 0;
	int cols = 0;
	int num = option(&rows, &cols);
	system("cls");
	if (num < 0)
		return;
	char** mine = apply(&rows, &cols);
	char** show = apply(&rows, &cols);
	getMineCount = getMine(num);
	int row = rows - 2;
	int col = cols - 2;
	initBoard(mine, rows, cols, '0');
	initBoard(show, rows, cols, '*');
	findMine(mine, show, row, col);
	release(mine, rows, cols);
	release(show, rows, cols);
}

void test()
{
	get1 = GetStdHandle(STD_INPUT_HANDLE);
	HideCursor();
	srand((unsigned int)time(NULL)); // 使rand函数产生伪随机数
	menu1();
	CloseHandle(get1);
}

int main()
{
	test();
	return 0;
}

③game.c

#include "game.h"

extern int getMineCount;
extern HANDLE get1;
extern DWORD get2;
extern INPUT_RECORD mouseRecord;
extern DWORD res;

void gotoxy(int a, int b)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos;
	pos.X = a;
	pos.Y = b;
	SetConsoleCursorPosition(handle, pos);
}

void HideCursor()
{
	CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

void initBoard(char** arr, int rows, int cols, char set)
{
	for (int i = 0; i < rows; i++)
		for (int j = 0; j < cols; j++)
			arr[i][j] = set;
}

int display(char** arr, char** mine, char** show, int row, int col, int y, int x, int time1, int* first, int* win, int mouseOperate)
{
	int FCount = 0;
	static int time2 = 0;
	if (win != NULL)
	{
		//游戏进行时间 为   当前时间    减去 进入游戏的时间
		time2 = (int)time(NULL) - time1;
	}
	int i, j;
	for (i = 0; i <= col / 2 - 2; i++)
		printf("--");
	printf("time:%-3d", time2);
	for (i = 0; i <= col / 2 - 3; i++)
		printf("--");
	printf("\n");

	for (i = 1; i <= row; i++)
		for (j = 1; j <= col; j++)
			if (arr[i][j] == 'F')
				FCount++;

	if (FCount > getMineCount) // 防止雷数量成为负数
		FCount = getMineCount;

	for (i = 1; i <= row; i++)
	{
		printf("|");
		for (j = 1; j <= col; j++) // 打印扫雷内容
		{
			if (i == y && j * 2 == x + 1)
			{
				printf("\033[42m%c\033[0m ", arr[i][j]);
				if (mouseRecord.EventType == MOUSE_EVENT)
				{
					mouseOperateGame(mine, show, mouseOperate, i, j, row, col, first, win, FCount);
				}
			}
			else
			{
				if (arr[i][j] == '*')
					printf("%c ", arr[i][j]);
				if (arr[i][j] == '0')
					printf("\033[30m%c\033[0m ", arr[i][j]); // 黑色
				if (arr[i][j] == '1')
					printf("\033[36m%c\033[0m ", arr[i][j]); // 浅蓝色
				if (arr[i][j] == '2')
					printf("\033[34m%c\033[0m ", arr[i][j]); // 蓝色
				if (arr[i][j] == '3')
					printf("\033[33m%c\033[0m ", arr[i][j]); // 黄色
				if (arr[i][j] == '4')
					printf("\033[31m%c\033[0m ", arr[i][j]); // 红色
				if (arr[i][j] == '5')
					printf("\033[35m%c\033[0m ", arr[i][j]); // 紫色
				if (arr[i][j] == '6')
					printf("%c ", arr[i][j]);
				if (arr[i][j] == '7')
					printf("%c ", arr[i][j]);
				if (arr[i][j] == 'F')
					printf("\033[32m%c\033[0m ", arr[i][j]); // 绿色
			}
		}
		printf("\n"); // 换行
	}

	for (i = 0; i <= col / 2 - 2; i++)
		printf("--");
	printf("雷:\033[31m%d\033[0m", getMineCount - FCount); // 表示当前雷的数量
	for (i = 0; i <= col / 2 - 2; i++)
		printf("--");
	printf("\n");

	int goBack = 0;
}

void setMine(char** mine, int row, int col, int y, int x)
{
	int count = 0;
	int i, j;
	// 记录输入行和旁边的两行
	int judgeRow[3] = { 0 };
	for (int k = -1, a = 0; k <= 1; k++, a++)
		judgeRow[a] = k + y;

	// 记录输入列和旁边的两列
	int judgeCol[3] = { 0 };
	for (int k = -1, a = 0; k <= 1; k++, a++)
		judgeCol[a] = k + x;

	while (count < getMineCount) // 布置雷的数量,数量到达时则跳出循环
	{
		i = rand() % row + 1; // 行

		// 当输入行且旁边两行与布置行不同时
		if (i != judgeRow[0] && i != judgeRow[1] && i != judgeRow[2])
			j = rand() % col + 1; // 列

		// 当输入行或旁边两行与布置行相同时
		else
		{
			do
			{
				j = rand() % col + 1;
				// 若输入列或旁边两列与布置列相同则进入循环
			} while (j == judgeCol[0] || j == judgeCol[1] || j == judgeCol[2]);
		}
		if (mine[i][j] == '0') // 若为 '0',则放置雷
		{
			mine[i][j] = '1';
			count++;
		}
	}
}

int mineCount(char** mine, int y, int x)
{
	int count = 0;
	for (int i = -1; i <= 1; i++)
		for (int j = -1; j <= 1; j++)
			if (mine[y + i][x + j] == '1')
				count++;

	return count;
}

void mouseOperateGame(char** mine, char** show, int mouseOperate, int y, int x, int row, int col, int first, int* win, int FCount)
{
	switch (mouseOperate)
	{
	case  FROM_LEFT_1ST_BUTTON_PRESSED:
		if (show[y][x] == '*')                    // 判断输入的坐标是否被占用
		{
			if (first)
			{
				setMine(mine, row, col, y, x);
				first = 0;
			}
			if (mine[y][x] == '1')                // 判断输入的坐标是否是雷
			{
				*win = -9;
			}
			else
			{
				int count = mineCount(mine, y, x);
				show[y][x] = count + '0';
				(*win)++;
				spread(mine, show, y, x, win, row, col);
			}
		}

		if (show[y][x] == 'F')
			show[y][x] = '*';

		if (show[y][x] >= '1' && show[y][x] <= '7')
			numberSpread(mine, show, y, x, show[y][x] - '0', win, row, col);

		break;
	case  RIGHTMOST_BUTTON_PRESSED:
		if (show[y][x] == '*' && FCount < getMineCount)
			show[y][x] = 'F';
		break;
	}
}

void findMine(char** mine, char** show, int row, int col)
{
	getPower();
	// 注:由于扫雷下标从1开始,在 3.游戏操作 中需要变通一下
	int x = 1; // 横轴移动
	int y = 1; // 纵轴移动
	int time1 = (int)time(NULL); // 获取进入游戏的时间
	int win = 0;
	int mouseOperate = 0;
	int first = 1;

	int falseTime = 1;
	int winTime = 1;

	while (1) 
	{
		gotoxy(0, 0);

		ReadConsoleInput(get1, &mouseRecord, 1, &res);
		//获取鼠标当前位置
		y = mouseRecord.Event.MouseEvent.dwMousePosition.Y;
		x = mouseRecord.Event.MouseEvent.dwMousePosition.X;

		mouseOperate = mouseRecord.Event.MouseEvent.dwButtonState;

		// 当win为负数意思为被雷炸死
		if (win < row * col - getMineCount && win >= 0)
			display(show, mine, show, row, col, y, x, time1, &first, &win, mouseOperate);

		if (win >= row * col - getMineCount)
		{
			if (winTime)
			{
				system("cls");
				getPower();
				winTime = 0;
			}
			display(show, mine, show, row, col, y, x, time1, NULL, NULL, 0);
			printf("**********************\n");
			printf("******  你赢了  ******\n");
			printf("**********************\n");
			if ((y == row + 5) && (5 <= x && x <= 16))
			{
				printf("*****\033[41m  %6s    \033[0m*****\n", "返回");
				if (mouseOperate == FROM_LEFT_1ST_BUTTON_PRESSED)
				{
					system("cls");
					break;
				}
			}
			else
				printf("*****  %6s    *****\n", "返回");
		}
		if (win < 0)
		{
			if (falseTime)
			{
				system("cls");
				getPower();
				falseTime = 0;
			}
			display(mine, mine, show, row, col, y, x, time1, NULL, NULL, 0);
			printf("**********************\n");
			printf("**很遗憾,你被炸死了**\n");
			printf("**********************\n");
			if ((y == row + 5) && (5 <= x && x <= 16))
			{
				printf("*****\033[41m  %6s    \033[0m*****\n", "返回");
				if (mouseOperate == FROM_LEFT_1ST_BUTTON_PRESSED)
				{
					system("cls");
					break;
				}
			}
			else
				printf("*****  %6s    *****\n", "返回");
		}
	}
}

void spread(char** mine, char** show, int y, int x, int* win, int row, int col)
{
	if (show[y][x] == '0')       // 若周围没有雷才进入
	{
		for (int i = -1; i <= 1; i++) // 行
		{
			for (int j = -1; j <= 1; j++) // 列
			{
				if (y + i >= 1 && y + i <= row && x + j >= 1 && x + j <= col) // 防止超出9×9的范围
				{
					if (show[y + i][x + j] == '*')        // 防止反复递归同一个坐标
					{
						int count = mineCount(mine, y + i, x + j);
						show[y + i][x + j] = count + '0'; // 将已经递归过的坐标显示它周围雷的数量,防止反复递归同一个坐标
						(*win)++;     // 增加查找非雷坐标的数量
						spread(mine, show, y + i, x + j, win, row, col); // 进入下一次递归
					}
				}
			}
		}
	}
}

void numberSpread(char** mine, char** show, int y, int x, int num, int* win, int row, int col)
{
	int unknown = 0;     // 未知坐标的数量
	int unknownBlank = 0;// 未知坐标且未插旗的数量
	int FCount = 0;		 // 插旗坐标的数量
	// 记录周围三者的数量
	for (int i = -1; i <= 1; i++)
	{
		for (int j = -1; j <= 1; j++)
		{
			if (show[y + i][x + j] == '*' || show[y + i][x + j] == 'F')
				unknown++;
			if (show[y + i][x + j] == 'F')
				FCount++;
			if (show[y + i][x + j] == '*')
				unknownBlank++;
		}
	}

	if (unknown > num && FCount >= num && unknownBlank != 0) // 周围未知的坐标的数量要大于周围雷的数量
	{
		for (int i = -1; i <= 1; i++) // 行
		{
			for (int j = -1; j <= 1; j++) // 列
			{
				// 防止进入边框坐标
				if (y + i >= 1 && y + i <= row && x + j >= 1 && x + j <= col)
				{
					// 数字展开时扫到雷
					if (show[y + i][x + j] == '*' && mine[y + i][x + j] == '1')
					{
						(*win) = -9;
					}
					// 数字展开扫到非雷
					else if (show[y + i][x + j] == '*' && mine[y + i][x + j] == '0')
					{
						int count = mineCount(mine, y + i, x + j);
						show[y + i][x + j] = count + '0';
						(*win)++;
						spread(mine, show, y + i, x + j, win, row, col);
					}
				}
			}
		}
	}
}

char** apply(int* rows, int* cols)
{
	// 申请二级指针(二维数组)行的数量
	char** arr = (char**)malloc(sizeof(char*) * (*rows));
	if (NULL == arr)
	{
		printf("游戏异常,已退出\n");
		exit(-1);
	}

	// 申请每行之中列的数量
	for (int i = 0; i < *rows; i++)
	{
		arr[i] = (char*)malloc(sizeof(char) * (*cols));
		if (NULL == arr[i])
		{
			printf("游戏异常,已退出\n");
			exit(-1);
		}
	}

	// 返回首元素地址
	return arr;
}

void release(char** arr, int rows, int cols)
{
	// 先释放一级指针空间
	for (int i = 0; i < rows; i++)
		free(arr[i]);
	// 后释放二级指针的空间
	free(arr);
}

int getMine(int num)
{
	switch (num)
	{
	case 1:
		return 10;
	case 2:
		return 40;
	case 3:
		return 99;
	}
	return 0;
}

        以上即是本篇博客的全部内容 ,关于扫雷有以下总结:整个程序的实现逻辑是:首先进行游戏初始化,包括初始化游戏板和设置雷的位置;然后进入游戏进行阶段,玩家根据提示逐步打开格子;最后根据玩家的操作进行递归地打开格子或者判断游戏是否结束。游戏菜单和测试函数用于启动游戏和提供交互界面。

你可能感兴趣的:(【C语言】实战项目,c语言,数据结构,开发语言)