C语言实战项目—扫雷小程序

扫雷游戏是微软自带的一款小游戏。扫雷游戏的玩法是,以9*9棋盘为例,棋盘上随机分布着10个地雷,玩家在棋盘上进行点击,如果被点击的格子是地雷,则玩家被炸“死”,游戏结束;如果被点击的格子上没有地雷,与被点击的格子相邻的格子(被点击格子的上下左右还有斜向,共八个格子)有地雷,则在被点击的格子上显示这些地雷的总数,如果与被点击的格子相邻的八个格子都没有地雷,则棋盘自动展开,直到与展开的格子相邻的格子有地雷才停止。此时最后被展开的格子显示其相邻格子共有的地雷数。

除上述功能外,如果玩家认为某个格子有地雷,则在该格子上插入旗子。后期如果确定某个插了旗子的格子没有地雷,则可以将旗子拔掉。

扫雷是一款集运气与逻辑的游戏,一般为了保证可玩性,不至于刚上来就游戏结束,所以还需确保玩家点击的第一步不是雷。

下面将使用C语言实现上述功能。因为初学C语言,对许多功能了解的不够深入,代码写的难免有瑕疵,如有问题,还望批评指正。

本例使用Win10系统下的 Visual Studio 2019 进行编写的,对于所有使用scanf()函数的文档,需在首行加入如下代码(必须是在首行,加入其他行无效):

#define _CRT_SECURE_NO_WARNINGS 1

游戏框架

对于任意游戏,都需要菜单来让玩家进行选择,其次还有游戏的主体部分。所以建立三个文件,分别为”game.c”(用于存放游戏中各个部分功能的函数),”game.h”(用于声明”game.c”中的函数),以及主文件”main.c”.

菜单部分

游戏开始,首先在屏幕上打印菜单,供玩家进行选择。程序根据玩家的选择进行下一步操作。菜单功能使用menu()函数进行实现,用户输入则用scanf()函数来接收。规定:菜单打印两个选项,当用户键入1时,则开始进行游戏(游戏部分由game()函数实现);当用户键入0时,则退出程序;若用户键入其他字符,则提示用户输入有误,需重新出入。因此可以使用switch()函数来实现此部分功能。具体实现代码如下:

//扫雷游戏
#include "game.h"

int main(void)
{
	int input;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				game();
				break;
			case 0:
				printf("退出游戏。\n");
				break;
			default:
				printf("输入错误,请重新输入.\n");
				break;
		}
	} while (input);
	return 0;
}

其中menu()代码如下:

void menu()
{
	printf("********************\n");
	printf("*1.play******0.exit*\n");
	printf("********************\n");
}

游戏部分

首先要生成两个棋盘,其中一个为后台,玩家看不见,用于存储地雷的信息和判断游戏的输赢。一个为对玩家显示的棋盘,供玩家进行操作。后台棋盘使用数组mineboard()实现,对玩家显示的棋盘用showboard()实现。

生成棋盘后,则需对棋盘进行初始化,规定:后台的棋盘用字符0表示没有地雷,用字符1表示有地雷;玩家的棋盘用*表示没有被点击,用#表示插入旗子。初始化则将后台的棋盘全部置为没有地雷,玩家棋盘全部置为*。初始化棋盘使用InitBoard()实现。

初始化棋盘后需要在后台棋盘上布置地雷同时向玩家展示玩家棋盘,布置地雷使用随机值布置10个地雷。布置地雷使用SetMine()函数,展示棋盘使用DisplayBoard()函数。

布置地雷后,开始游戏,为确保玩家第一次点击(由于程序限制,本例全部使用坐标输入代替玩家点击)不踩到地雷,所以需要对第一次点击进行判断,如果第一次刚好点到地雷,则将此处改为没有地雷,并在其他位置随机生成地雷,以确保地雷总数不变。此功能使用First_SafeMine()函数实现。

从第二步开始,每次都询问玩家的操作(是点击还是插旗还是拔旗)。同时进行输赢判断。从第二部开始为真正排雷过程,使用FindMine()函数进行实现。

输赢判断包括以下几个方面:

(1)玩家点到了地雷,游戏直接结束;

(2)玩家运气足够好,在棋盘上插得旗子数等于地雷数,并且这些旗子下刚好都有地雷,玩家胜利,游戏结束;

(3)玩家将棋盘上没有地雷的区域全部点开,玩家胜利,游戏结束。

 

对于玩家的操作,又分为以下几部分:

玩家点击:以输入坐标的形式代替点击,需判断坐标是否合法,如果坐标超出了棋盘范围,则提示玩家坐标超出范围并等待玩家重新输入;如果玩家输入的坐标已经被排查过(包括该位置为空,该位置上有数字,该位置上有旗子),则提示玩家该位置已经被排查过,并让玩家对照此三种情况进行检查然后等待玩家重新输入;如果坐标没有被排查过,则判断该位置是否有地雷,如果有,则游戏直接结束;如果没有地雷,则需判断该位置相邻棋盘是否有地雷,如果没有,则棋盘自动展开,直到相邻格子有地雷为止;如果相邻棋盘有地雷,则在该位置显示地雷数。如果棋盘已全部排查完,则提示玩家成功并打印后台棋盘然后退出游戏。

其中判断相邻棋盘是否有地雷用GetMineCount()函数实现,棋盘自动展开用OpenMine()实现,判断棋盘是否排查完使用Countmine()函数实现。

 

插入旗子:同玩家点击一样,需判断坐标是否合法以及插旗位置是否已经被排查过。此外,需判断插入旗子位置对应的真雷数,如果旗子数等于真雷数并且旗子对应的位置都是真雷,则认为玩家胜利。判断旗子对应的真雷数使用TrueMineCount()函数实现。

拔掉旗子:首先要判断棋盘是否有旗子,没有旗子则提示玩家棋盘上没有旗子同时退出拔旗操作,如果有旗子则判断坐标的合法性以及拔旗位置是否已经排查过或拔旗位置没有旗子。同时也需要判断剩余旗子位置对应的真雷数,如果旗子数等于真雷数并且旗子对应的位置都是真雷,则认为玩家胜利。

 

代码实现

下面则对上述各个函数进行实现。

在统计某处雷的个数时,对于边界还需判断是否越界,这样每次判断都会麻烦。因此把棋盘设置的比实际玩的棋盘大一圈,这一圈上不放置地雷,统计时可以省去判断边界的麻烦。

1.初始化棋盘:遍历棋盘,对后台的棋盘全部置0,对玩家看到的棋盘置*。函数可以接收棋盘和棋盘大小,同时接收相应的字符。

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int cols, int rows, char set)
{
	int i, j;
	for (i = 0; i < cols; i++)
	{
		for (j = 0; j < rows; j++)
		{
			board[i][j] = set;
		}
	}
}

2.显示棋盘:遍历玩家棋盘,将玩家棋盘的内容输出在屏幕上。为防止坐标轴与棋盘上信息混在一起,将其分开打印。

//显示棋盘
void DisplayBoard(char board[ROWS][COLS], int col, int row)
{
	int i, j;
	printf("    ");
	for (j = 1; j <= col; j++)
	{
		printf("%d ", j);
	}
	printf("\n\n");
	for (i = 1; i <= col; i++)
	{
		printf("%d   ",i);
		for (j = 1; j <= row; j++)
		{
			printf("%c ",board[i][j]);
		}
		printf("\n");
	}
}

生成的棋盘如下图所示:

5X]Q0[%]6V66NAKGEU7[}KD.png

      3.初始化地雷位置:使用随机函数在后台棋盘上生成10个地雷,规定字符1是地雷,字符0是安全地带。

//初始化地雷的位置
void SetMine(char board[ROWS][COLS], int col, int row)
{
	int x, y;
	int count = EASY_CONST;
	while (count)
	{
		x = rand() % col + 1;
		y = rand() % row + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

    4.确保第一个不是地雷

//保证第一个不踩雷
void First_SafeMine(char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int ret = 1;
	int count = 0;
	int a, b;
	printf("请输入要排查的坐标:");
	scanf("%d%d", &x, &y);
	if (mineboard[x][y] == '1')//如果为雷,则改为没有雷
	{
		mineboard[x][y] = '0';
		while (ret)//随机生成一个雷
		{
			a = rand() % ROW + 1;
			b = rand() % COL + 1;
			if (mineboard[a][b] == '0')
			{
				mineboard[a][b] = '1';
			}
			ret--;
		}
	}
	count = GetMineCount(mineboard, x, y);//在上述步骤完成后,需统计第一个坐标周围雷的个数
	showboard[x][y] = count + '0';
	OpenMine(mineboard, showboard, row, col, x, y);//周围没有雷需要自动展开棋盘
	DisplayBoard(showboard, row, col);
}

    5.统计某位置处周围雷的个数。

    雷在棋盘上是字符1,所以需判断字符1的个数。字符1在内存中存放的实际上是ASCII码值,所以可以用一个整型变量来统计有几个字符1.

//统计坐标周围雷的个数
int GetMineCount(const char mineboard[ROWS][COLS], int x, int y)
{
	return mineboard[x - 1][y + 1] + mineboard[x][y + 1] + mineboard[x + 1][y + 1] +
		mineboard[x - 1][y] + mineboard[x + 1][y] +
		mineboard[x - 1][y - 1] + mineboard[x][y - 1] + mineboard[x + 1][y - 1] - 8 * '0';
}

    6.棋盘自动展开:棋盘不展开的条件是某位置的周围有雷。所以先要判断是否有雷。如果雷的个数为0就展开,采用递归来实现。

//展开无雷区:如果该坐标周围没有雷就自动展开。
void OpenMine(const char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int col, int row,int x,int y)
{
	int ret = 0;
	ret=GetMineCount(mineboard, x, y);
	if (ret == 0)
	{
		showboard[x][y] = ' ';
		if (x > 0 && y + 1 <= col && showboard[x][y + 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x, y + 1);
		}
		if (x - 1 > 0 && y + 1 <= col && showboard[x - 1][y + 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x - 1, y + 1);
		}
		if (x - 1 > 0 && y >0 && showboard[x - 1][y] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x - 1, y);
		}
		if (x - 1 > 0 && y - 1 >0 && showboard[x - 1][y - 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x - 1, y - 1);
		}
		if (x > 0 && y - 1 > 0 && showboard[x][y - 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x, y - 1);
		}
		if (x + 1 <= row && y - 1 > 0 && showboard[x + 1][y - 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x + 1, y - 1);
		}
		if (x + 1 <= row && y > 0 && showboard[x + 1][y] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x + 1, y);
		}
		if (x + 1 <=row && y + 1 <= col && showboard[x + 1][y + 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x + 1, y + 1);
		}
	}
	else
	{
		showboard[x][y] = '0' + ret;
	}
}

    7.统计棋盘上旗子对应真雷的个数

//统计棋盘上旗子下对应的真雷个数
int TrueMineCount(const char mineboard[ROWS][COLS],const char showboard[ROWS][COLS], int cols, int rows,int* pcount)
{
	int i, j;
	int count = 0;
	if (*pcount > 0)
	{
		for (i = 0; i < cols; i++)
		{
			for (j = 0; j < rows; j++)
			{
				if (showboard[i][j] == '#')
				{
					if (mineboard[i][j] == '1')
					{
						count++;
					}
				}
			}
		}
	}
	return count;
}

    8.统计棋盘上除(已排除的区域)和(雷区)以外的其他区域大小

//统计棋盘上已排除的区域
int Countmine(char showboard[ROWS][COLS],int col ,int row)
{
	int i,j;
	int check;
	int win = col * row - EASY_CONST;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= row; j++)
		{
			check = showboard[i][j] - '0';
			if (showboard[i][j] == ' ' || (check > 0 && check < 9))
			{
				win--;
			}
		}
	}
	return win;
}

    9.排雷实现,具体步骤看代码注释

//排雷
void FindMine(const char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int col, int row)
{
	int x = 0;
	int y = 0;
	int win = 0;
	char input1;
	int bool = 1;
	int ret_trueminecount = 0; //用于接收旗子对应的真雷数
	int flag_count = 0;        //用于统计棋盘上旗子的数量
	do
	{
	        //提示玩家进行选择模式
		printf("请选择(输入a,b或c):\n");
		printf("a.排雷 b.插旗 c.取消插旗\n");
		//scanf("%s", &input1);
		input1 = getchar();
		switch (input1)
		{
		case 'a':
		{
			printf("请输入要检查的坐标:>\n");
			int bool0 = 1;
			//玩家输入正确坐标或者玩家胜利或者玩家失败退出循环
			while (bool0) 
			{
				scanf("%d %d", &x, &y);
				if (x > 0 && x <= row && y > 0 && y <= col)  //玩家输入的坐标在合法范围内
				{
					if (showboard[x][y] != '*')  //先判断是否已经排查
					{
						if (showboard[x][y] == '#')
						{
							printf("此处有旗子,请拔掉后再排查。\n");
						}
						else
						{
							printf("\n此处已经排查过了,请重新输入。\n");
						}
					}
					else if (mineboard[x][y] == '1') //如果没有排查,判断是否踩到了雷
					{
						printf("\n很遗憾,你踩到了地雷,游戏结束!\n");
						DisplayBoard(mineboard, col, row);
						bool = 0;
						break;
					}
					else
					{
						int count = GetMineCount(mineboard, x, y);  //判断输入坐标的周围雷的个数
						if (0 == count)
						{
							OpenMine(mineboard, showboard, COL, ROW, x, y);
							bool0 = 0;
							//判断棋盘上除真雷区域和已排查区域之外的格子数,如果为0,说明玩家排了所有的雷,win置1.
							int ret_win = Countmine(showboard, COL, ROW); 
							if (ret_win == 0)
							{
								bool = 0;
								win = 1;
								break;
							}
						}
						else
						{
							showboard[x][y] = count + '0';
							int ret_win = Countmine(showboard, COL, ROW);
							bool0 = 0;
							if (ret_win == 0)
							{
								bool = 0;
								win = 1;
								break;
							}
						}
						DisplayBoard(showboard, COL, ROW);
					}
				}
				//玩家坐标输入不在棋盘范围内,则提示玩家输入错误,重新输入
				else
				{
					printf("\n您输入的坐标有误,请重新输入\n");
				}
			}
			break;
		}
		//插旗
		case 'b':
		{
			int bool1 = 1;
			while (bool1)
			{
				printf("请输入要插旗的坐标:>");
				scanf("%d %d", &x, &y);
				if (x > 0 && x <= row && y > 0 && y <= col)
				{
					if (showboard[x][y] == '*')
					{
						showboard[x][y] = '#';
						flag_count++;         //每插入一个旗子,旗子数+1
						bool1 = 0;            
						ret_trueminecount = TrueMineCount(mineboard, showboard, COLS, ROWS, &flag_count);
						//如果旗子数与对应的雷数相等,且旗子下都是真雷,则胜利,win置1.
						if (flag_count==EASY_CONST && ret_trueminecount == EASY_CONST)
						{
							bool = 0;
							win = 1;
						}
					}
					else
					{
						printf("此处无法放置旗子。请按下列情况进行检查:\n(1)坐标是否有误;\n(2)此处已经有旗子;\n(3)此处是否已经排查过了。\n");
					}
				}
				else
				{
					printf("输入的坐标有误,请重新输入。\n");
				}
			}
			DisplayBoard(showboard, COL, ROW);
			break;
		}
		
		//拔掉旗子
		case 'c':
		{
			int bool2 = 1;
			int ret = FlagCount(showboard, COLS, ROWS);
			while (bool2)
			{
				if (ret == 0)
				{
					printf("\n棋盘上没有旗子;请选择排雷或插旗。\n");
					bool2 = 0;
				}
				else
				{
					printf("\n请输入要拔旗的坐标:>");
					scanf("%d %d", &x, &y);
					if (x > 0 && x <= row && y > 0 && y <= col)
					{
						if (showboard[x][y] == '#')
						{
							showboard[x][y] = '*';
							flag_count--;    //旗子拔掉后,旗子数要-1
							bool2 = 0;
							//如果旗子数与对应的雷数相等,且旗子下都是真雷,则胜利,win置1.
							ret_trueminecount = TrueMineCount(mineboard, showboard, COLS, ROWS, &flag_count);
							if (flag_count == EASY_CONST && ret_trueminecount == EASY_CONST)
							{
								bool = 0;
								win = 1;
							}
						}
						else
						{
							printf("\n此处无法移除旗子。请按下列情况进行检查:\n(1)坐标是否有误;\n(2)此处没有旗子;\n(3)此处旗子是否已经拔掉。\n");
						}
					}
					else
					{
						printf("输入的坐标有误,请重新输入。\n");
					}
				}
			}
			DisplayBoard(showboard, COL, ROW);
			break;
			}
		}
		
		//根据上述代码,win等于1时,表示玩家排出了全部的雷。
		if (win == 1)
		{
			printf("恭喜你,你已排出全部的雷。\n");
			bool = 0;
			DisplayBoard(mineboard,COL,ROW);
		}
	}while ( bool == 1);
}

    最后游戏的主体部分代码如下:

void game()
{
	//存储雷的位置:生成两个棋盘,一个用于打印
	char mineboard[COLS][ROWS] ;
	char showboard[COLS][ROWS];

	//初始化棋盘
	InitBoard(mineboard, COLS, ROWS, '0');
	InitBoard(showboard, COLS, ROWS, '*');

	//显示棋盘
	DisplayBoard(showboard, COL, ROW);

	//布置雷
	SetMine(mineboard,COL,ROW);
	//第一次点击
	First_SafeMine(mineboard, showboard, ROW, COL);
	//排雷
	FindMine(mineboard, showboard, COL, ROW);
}

总结

最后,对3个文件的代码进行汇总,如下所示。

头文件“game.h”:

//头文件引用
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#ifndef __GAME_H__
#define __GAME_H__

//常量定义区
#define EASY_CONST 10
#define COL 9
#define ROW 9
#define COLS COL+2
#define ROWS ROW+2

//函数声明
void menu();
void game();
void InitBoard(char board[ROWS][COLS], int cols,int rows, char set);
void DisplayBoard(const char board[ROWS][COLS], int cols, int rows);
void SetMine(char board[ROWS][COLS], int col, int row);
void FindMine(const char mineboard[ROWS][COLS], char showboard[ROWS][COLS],int col,int row);
int GetMineCount(const char mineboard[ROWS][COLS], int x, int y);
void OpenMine(const char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int col, int row, int x, int y);
void First_SafeMine(char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int row, int col);
int TrueMineCount(const char mineboard[ROWS][COLS], const char showboard[ROWS][COLS], int cols, int rows, int* pcount);
int Countmine(char showboard[ROWS][COLS], int col, int row);
#endif


game.c文件代码:

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

//菜单
void menu()
{
	printf("********************\n");
	printf("*1.play******0.exit*\n");
	printf("********************\n");
}

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int cols, int rows, char set)
{
	int i, j;
	for (i = 0; i < cols; i++)
	{
		for (j = 0; j < rows; j++)
		{
			board[i][j] = set;
		}
	}
}

//显示棋盘
void DisplayBoard(char board[ROWS][COLS], int col, int row)
{
	int i, j;
	printf("    ");
	for (j = 1; j <= col; j++)
	{
		printf("%d ", j);
	}
	printf("\n\n");
	for (i = 1; i <= col; i++)
	{
		printf("%d   ",i);
		for (j = 1; j <= row; j++)
		{
			printf("%c ",board[i][j]);
		}
		printf("\n");
	}
}

//初始化地雷的位置
void SetMine(char board[ROWS][COLS], int col, int row)
{
	int x, y;
	int count = EASY_CONST;
	while (count)
	{
		x = rand() % col + 1;
		y = rand() % row + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}
//保证第一个不踩雷
void First_SafeMine(char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int ret = 1;
	int count = 0;
	int a, b;
	printf("请输入要排查的坐标:");
	scanf("%d%d", &x, &y);
	if (mineboard[x][y] == '1')//如果为雷,则改为没有雷
	{
		mineboard[x][y] = '0';
		while (ret)//随机生成一个雷
		{
			a = rand() % ROW + 1;
			b = rand() % COL + 1;
			if (mineboard[a][b] == '0')
			{
				mineboard[a][b] = '1';
			}
			ret--;
		}
	}
	count = GetMineCount(mineboard, x, y);
	showboard[x][y] = count + '0';
	OpenMine(mineboard, showboard, row, col, x, y);
	DisplayBoard(showboard, row, col);
}

//统计坐标周围雷的个数
int GetMineCount(const char mineboard[ROWS][COLS], int x, int y)
{
	return mineboard[x - 1][y + 1] + mineboard[x][y + 1] + mineboard[x + 1][y + 1] +
		mineboard[x - 1][y] + mineboard[x + 1][y] +
		mineboard[x - 1][y - 1] + mineboard[x][y - 1] + mineboard[x + 1][y - 1] - 8 * '0';
}

//展开无雷区:如果该坐标周围没有雷就自动展开。
void OpenMine(const char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int col, int row,int x,int y)
{
	int ret = 0;
	ret=GetMineCount(mineboard, x, y);
	if (ret == 0)
	{
		showboard[x][y] = ' ';
		if (x > 0 && y + 1 <= col && showboard[x][y + 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x, y + 1);
		}
		if (x - 1 > 0 && y + 1 <= col && showboard[x - 1][y + 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x - 1, y + 1);
		}
		if (x - 1 > 0 && y >0 && showboard[x - 1][y] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x - 1, y);
		}
		if (x - 1 > 0 && y - 1 >0 && showboard[x - 1][y - 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x - 1, y - 1);
		}
		if (x > 0 && y - 1 > 0 && showboard[x][y - 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x, y - 1);
		}
		if (x + 1 <= row && y - 1 > 0 && showboard[x + 1][y - 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x + 1, y - 1);
		}
		if (x + 1 <= row && y > 0 && showboard[x + 1][y] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x + 1, y);
		}
		if (x + 1 <=row && y + 1 <= col && showboard[x + 1][y + 1] == '*')
		{
			OpenMine(mineboard, showboard, col, row, x + 1, y + 1);
		}
	}
	else
	{
		showboard[x][y] = '0' + ret;
	}
}

//统计棋盘上旗子下对应的真雷个数
int TrueMineCount(const char mineboard[ROWS][COLS],const char showboard[ROWS][COLS], int cols, int rows,int* pcount)
{
	int i, j;
	int count = 0;
	if (*pcount > 0)
	{
		for (i = 0; i < cols; i++)
		{
			for (j = 0; j < rows; j++)
			{
				if (showboard[i][j] == '#')
				{
					if (mineboard[i][j] == '1')
					{
						count++;
					}
				}
			}
		}
	}
	return count;
}

//统计棋盘上已排除的区域
int Countmine(char showboard[ROWS][COLS],int col ,int row)
{
	int i,j;
	int check;
	int win = col * row - EASY_CONST;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= row; j++)
		{
			check = showboard[i][j] - '0';
			if (showboard[i][j] == ' ' || (check > 0 && check < 9))
			{
				win--;
			}
		}
	}
	return win;
}

//排雷
void FindMine(const char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int col, int row)
{
	int x = 0;
	int y = 0;
	int win = 0;
	char input1;
	int bool = 1;
	int ret_trueminecount = 0;
	int flag_count = 0;
	do
	{
		//ret_trueminecount = TrueMineCount(mineboard, showboard, COLS, ROWS,&flag_count);
		printf("请选择(输入a,b或c):\n");
		printf("a.排雷 b.插旗 c.取消插旗\n");
		//scanf("%s", &input1);
		input1 = getchar();
		switch (input1)
		{
		case 'a':
		{
			printf("请输入要检查的坐标:>\n");
			int bool0 = 1;
			while (bool0) 
			{
				scanf("%d %d", &x, &y);
				if (x > 0 && x <= row && y > 0 && y <= col)
				{
					if (showboard[x][y] != '*')
					{
						if (showboard[x][y] == '#')
						{
							printf("此处有旗子,请拔掉后再排查。\n");
						}
						else
						{
							printf("\n此处已经排查过了,请重新输入。\n");
						}
					}
					else if (mineboard[x][y] == '1')
					{
						printf("\n很遗憾,你踩到了地雷,游戏结束!\n");
						DisplayBoard(mineboard, col, row);
						bool = 0;
						break;
					}
					else
					{
						int count = GetMineCount(mineboard, x, y);
						if (0 == count)
						{
							OpenMine(mineboard, showboard, COL, ROW, x, y);
							bool0 = 0;
							int ret_win = Countmine(showboard, COL, ROW);
							if (ret_win == 0)
							{
								bool = 0;
								win = 1;
								break;
							}
						}
						else
						{
							showboard[x][y] = count + '0';
							int ret_win = Countmine(showboard, COL, ROW);
							bool0 = 0;
							if (ret_win == 0)
							{
								bool = 0;
								win = 1;
								break;
							}
						}
						DisplayBoard(showboard, COL, ROW);
					}
				}
				else
				{
					printf("\n您输入的坐标有误,请重新输入\n");
				}
			}
			break;
		}
		case 'b':
		{
			int bool1 = 1;
			while (bool1)
			{
				printf("请输入要插旗的坐标:>");
				scanf("%d %d", &x, &y);
				if (x > 0 && x <= row && y > 0 && y <= col)
				{
					if (showboard[x][y] == '*')
					{
						showboard[x][y] = '#';
						flag_count++;
						bool1 = 0;
						ret_trueminecount = TrueMineCount(mineboard, showboard, COLS, ROWS, &flag_count);
						if (flag_count==EASY_CONST && ret_trueminecount == EASY_CONST)
						{
							bool = 0;
							win = 1;
						}
					}
					else
					{
						printf("此处无法放置旗子。请按下列情况进行检查:\n(1)坐标是否有误;\n(2)此处已经有旗子;\n(3)此处是否已经排查过了。\n");
					}
				}
				else
				{
					printf("输入的坐标有误,请重新输入。\n");
				}
			}
			DisplayBoard(showboard, COL, ROW);
			break;
		}
		case 'c':
		{
			int bool2 = 1;
			int ret = FlagCount(showboard, COLS, ROWS);
			while (bool2)
			{
				if (ret == 0)
				{
					printf("\n棋盘上没有旗子;请选择排雷或插旗。\n");
					bool2 = 0;
				}
				else
				{
					printf("\n请输入要拔旗的坐标:>");
					scanf("%d %d", &x, &y);
					if (x > 0 && x <= row && y > 0 && y <= col)
					{
						if (showboard[x][y] == '#')
						{
							showboard[x][y] = '*';
							flag_count--;
							bool2 = 0;
							ret_trueminecount = TrueMineCount(mineboard, showboard, COLS, ROWS, &flag_count);
							if (flag_count == EASY_CONST && ret_trueminecount == EASY_CONST)
							{
								bool = 0;
								win = 1;
							}
						}
						else
						{
							printf("\n此处无法移除旗子。请按下列情况进行检查:\n(1)坐标是否有误;\n(2)此处没有旗子;\n(3)此处旗子是否已经拔掉。\n");
						}
					}
					else
					{
						printf("输入的坐标有误,请重新输入。\n");
					}
				}
			}
			DisplayBoard(showboard, COL, ROW);
			break;
			}
		}
		if (win == 1)
		{
			printf("恭喜你,你已排出全部的雷。\n");
			bool = 0;
			DisplayBoard(mineboard,COL,ROW);
		}
	}while ( bool == 1);
}

//游戏主体部分
void game()
{
	//存储雷的位置:生成两个棋盘,一个用于打印
	char mineboard[COLS][ROWS] ;
	char showboard[COLS][ROWS];

	//初始化棋盘
	InitBoard(mineboard, COLS, ROWS, '0');
	InitBoard(showboard, COLS, ROWS, '*');

	//显示棋盘
	DisplayBoard(showboard, COL, ROW);

	//布置雷
	SetMine(mineboard,COL,ROW);
	First_SafeMine(mineboard, showboard, ROW, COL);
	//排雷
	FindMine(mineboard, showboard, COL, ROW);
}


main.c文件

#define _CRT_SECURE_NO_WARNINGS 1
//扫雷游戏
#include "game.h"

int main(void)
{
	int input;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				game();
				break;
			case 0:
				printf("退出游戏。\n");
				break;
			default:
				printf("输入错误,请重新输入.\n");
				break;
		}
	} while (input);
	return 0;
}


猜你喜欢

转载自blog.51cto.com/14914896/2529192