扫雷游戏的实现(C语言详细版)

目录

前言

实现思路

游戏框架的设计

游戏界面的设计

游戏操作的实现

功能函数

初始化函数

打印棋盘

布置雷

判断游戏是否结束

计算九宫格内雷的个数

扩展式排雷

标记雷

排查雷

源文件

game.h

game.c

test.c


前言

       扫雷最原始的版本可以追溯到1973年一款名为“方块”的游戏。

       不久,“方块”被改写成了游戏“Rlogic”。在“Rlogic”里,玩家的任务是作为美国海军陆战队队员,为指挥中心探出一条没有地雷的安全路线,如果路全被地雷堵死就算输。两年后,汤姆·安德森在“Rlogic”的基础上又编写出了游戏“地雷”,由此奠定了现代扫雷游戏的雏形。

       1981年,微软公司的罗伯特·杜尔和卡特·约翰逊两位工程师在Windows3.1系统上加载了该游戏,扫雷游戏才正式在全世界推广开来。

       这款游戏的玩法是在一个9*9(初级),16*16(中级),16*30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。

实现思路

       我们在实现扫雷游戏时,首先应该掌握他的游戏规则。

       以下图为例,我们选择红圈所在的位置,如果他不是雷,则会在该位置显示以他为中心的九宫格中所有雷的个数。如果他是雷,则游戏结束。如果这个位置显示n则表明在他周围的八个位置中存在n颗地雷。

 

       以下图为例

 

       小红旗处一定为雷,但数字2右边三块位置右下角那块不是雷,但上方两块位置谁是雷无法判断,我们可以先排查(4,3)这个位置,然后再进一步推理。

 

       在掌握游戏规则之后,我们来实现这个程序。

 

游戏框架的设计

       程序运行之后,我们要有一个主菜单,在游戏结束之后我们能够返回主菜单选择开始游戏或是退出游戏。

游戏界面的设计

       游戏开始时,我们应该有一个雷区的界面(这里我采用9*9行的雷区,如果要改可以在头文件中修改ROW和COL的值),然后有一个选择操作的提示。为了方便玩家找到对应的位置,我在这里做了一个行和列号的输出。每次玩家操作完之后使用了 system(“cls”) 清屏函数。

       在输出雷区界面之前,使用两个二维数组分别存贮地雷位置的信息mine[ROWS][COLS]和输出界面的字符show[ROWS][COLS]。(这里的ROWS = ROW + 2, COLS = COL + 2,目的是为了防止边界数组元素计算雷数时越界)

       结束界面,扫雷成功或是失败都需要显示布雷图给玩家看,并给出相应提示,这里我用到了Sleep(3000)函数,让结束界面停留三秒。(Sleep()函数的参数在VC环境下单位是毫秒,而在Linux环境下单位是秒,这里应该注意一下)

 

游戏操作的实现

布置雷

       在进行排查雷之前,首先应该随机在雷区内布置雷。(雷数不能超过雷区内的空间)这里使用到了时间戳来进行随机生成雷。通过time()获取时间戳,再通过srand()和rand()的配合使用产生伪随机数序列,进行雷的布置。

排查雷

       首先应该明确只有未被排查过和未被标记过的位置可以排查。

       如果排查到的位置为雷,则游戏结束。

       若排查到的位置不是雷,则在该位置显示周围一圈雷的个数。

       但在扫雷游戏中实际会有一个展开一片的功能,当要排查的位置周围一圈雷的个数为零时,会继续排查周围一圈八个位置,并显示那些位置周围的雷数。

标记雷

       我们在判断出一个位置是地雷后,应该对其进行标记,防止下次排雷时踩到他。

       所以设计出一个标记雷的函数。

       1.我们只能对未被排查过的位置进行标记

       2.标记后的位置无法排查

       3.标记标记过的位置可以取消标记

游戏状态的判断

       我们在进行扫雷的时候只有两种情况能够结束游戏:

       1.玩家排查的位置是雷,则玩家被炸死,游戏结束

       2.玩家将所有非雷的区域排查完,玩家获胜,游戏结束,即未排查的位置和标记过的位置总数等于所布置的地雷的总数。

功能函数

初始化函数

//多一个参数set便于布置雷和打印棋盘
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0, j = 0;
	for (i = 0; i < rows;i++)
	{
		for (j = 0;j < cols;j++)
		{
			arr[i][j] = set;
		}
	}
}

打印棋盘

void ShowBoard(char show[ROWS][COLS], int row, int col)
{
	printf("------------------扫雷------------------\n");
	int i = 0, j = 0;
	printf("   |");
    //打印行号
	for (i = 1;i <= row;i++)
	{
		printf(" %d ", i);
		if (i<row)
		{
			printf("|");
		}
	}
	printf("\n");
	for (i = 1;i <= row;i++)
	{
		for (j = 0;j <= col;j++)
		{
			printf("---");
			if (j < col)
			{
				printf("|");
			}
		}
        //打印列号
		printf("\n %d |", i);
        //雷区
		for (j = 1;j <= col;j++)
		{
			printf(" %c ", show[i][j]);
			if (j < col)
			{
				printf("|");
			}
		}
		printf("\n");
	}
	printf("------------------扫雷------------------\n");
}

布置雷

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = MINES;
	int x = 0, y = 0;
	//每布置一个雷count自减1,直到雷布置完
	while (count)
	{
		x = rand() % row + 1;
		y = rand() % row + 1;
        //只有该位置未布置雷才能布置
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

判断游戏是否结束

//玩家获胜,游戏结束返回0,游戏未结束返回1
int IsWin(char show[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;
	int count = 0;
	for (i = 1;i <= row;i++)
	{
		for (j = 1;j <= col;j++)
		{
			//只有该位置为*或者#,才记为剩余的可操作区域,
			//当剩余的可操作区域等于雷数时则游戏获胜
			if (show[i][j] == '*' || show[i][j] == '#')
			{
				count++;
			}
		}
	}
	//若未打开区域和标记区域等于雷数则返回0,否则返回1
	if (count == MINES)
		return 0;
	else
		return 1;
}

计算九宫格内雷的个数

int GetMines(char mine[ROWS][COLS], int x, int y)
{
    //若为雷则该位置为字符1,减去八个字符0即可获得雷的个数
	return 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';

}

扩展式排雷

void ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	//如果该位置周围8快没雷,则对周围8块检索,展开
	if (GetMines(mine, x, y) == 0)
	{
		//展开的位置都置为0
		show[x][y] = '0';
		int i = 0;
		int j = 0;
		for (i = x - 1; i <= x + 1; i++)
		{
			for (j = y - 1; j <= y + 1; j++)
			{
				//只有该位置未被打开才能检索,并且检索位置应在棋盘之内
				if (show[i][j] == '*' && i > 0 && i <= ROW && j > 0 && j <= COL)
				{
					ExpandMine(mine, show, i, j);
				}
			}
		}
	}
	else
	{
		//不需要展开则显示附近雷的个数
		show[x][y] = GetMines(mine, x, y) + '0';
	}
}

标记雷

void SignMine(char show[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0;
	printf("请输入坐标;>");
	scanf("%d %d", &x, &y);
	//标记雷
	if (show[x][y] == '*')
	{
		show[x][y] = '#';
	}
	//取消标记
	else if (show[x][y] == '#')
	{
		show[x][y] = '*';
	}
	//若该位置被打开则无法标记
	else
		printf("标记失败");
}

排查雷

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0;
	while (IsWin(show, row, col))
	{
		int input = 0;
		//1.排查雷
		//2.标记雷
		//每次操作可以选择是排查雷还是标记雷
		do
		{
			printf("请选择操作1.排查雷 2.标记雷:>");
			scanf("%d", &input);
			switch (input)
			{
				//排查雷
			case 1:
				printf("请输入坐标;>");
				scanf("%d %d", &x, &y);
				if (x > 0 && x <= row && y > 0 && y <= col && show[x][y] != '#')
				{
					if (mine[x][y] == '1')
					{
						system("cls");
						printf("很遗憾你被炸死了\n");
						ShowBoard(mine, ROW, COL);
						Sleep(3000);
						//被炸死直接退出
						return ;
					}
					else
					{
						ExpandMine(mine, show, x, y);
						system("cls");
						ShowBoard(show, ROW, COL);
					}
				}
				else
				{
					//如果该位置被标记则无法排查
					if (show[x][y] == '#')
					{
						printf("该位置被标记为雷,请重新输入\n");
					}
					//如果排查位置越界则报错
					else
						printf("坐标非法,请重新输入\n");
				}
				break;
				//标记雷
			case 2:
				SignMine(show, row, col);
				system("cls");
				ShowBoard(show, ROW, COL);
				break;
			default:
				break;
			}
		} while (IsWin(show, row, col));//当玩家获胜或是踩雷时循环结束
	}
	//如果玩家扫雷成功,则显示布雷图
	if (!IsWin(show, row, col))
	{
		system("cls");
		printf("恭喜您扫雷成功!:>\n");
		ShowBoard(mine, ROW, COL);
		Sleep(3000);
	}
}

源文件

game.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
//雷数需小于等于ROW*COL
#define MINES 80

//1.标记
//2.展开一片

//初始化棋盘
void InitBoard(char arr[ROWS][COLS], int row, int col, char set);

//打印棋盘
void ShowBoard(char show[ROWS][COLS], int row, int col);

//布置雷
void SetMine(char show[ROWS][COLS], int row, int col);

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

//标记雷
void SignMine(char show[ROWS][COLS], int row, int col);

game.c

#include"game.h"

//初始化棋盘
//多一个参数set便于布置雷和打印棋盘
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0, j = 0;
	for (i = 0; i < rows;i++)
	{
		for (j = 0;j < cols;j++)
		{
			arr[i][j] = set;
		}
	}
}

//打印棋盘
void ShowBoard(char show[ROWS][COLS], int row, int col)
{
	printf("------------------扫雷------------------\n");
	int i = 0, j = 0;
	printf("   |");
	for (i = 1;i <= row;i++)
	{
		printf(" %d ", i);
		if (i<row)
		{
			printf("|");
		}
	}
	printf("\n");
	for (i = 1;i <= row;i++)
	{
		for (j = 0;j <= col;j++)
		{
			printf("---");
			if (j < col)
			{
				printf("|");
			}
		}
		printf("\n %d |", i);
		for (j = 1;j <= col;j++)
		{
			printf(" %c ", show[i][j]);
			if (j < col)
			{
				printf("|");
			}
		}
		printf("\n");
	}
	printf("------------------扫雷------------------\n");
}

//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = MINES;
	int x = 0, y = 0;
	//每布置一个雷count自减1,直到雷布置完
	while (count)
	{
		x = rand() % row + 1;
		y = rand() % row + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

//判断游戏是否结束
int IsWin(char show[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;
	int count = 0;
	for (i = 1;i <= row;i++)
	{
		for (j = 1;j <= col;j++)
		{
			//只有该位置为*或者#,才记为剩余的可操作区域,
			//当剩余的可操作区域等于雷数时则游戏获胜
			if (show[i][j] == '*' || show[i][j] == '#')
			{
				count++;
			}
		}
	}
	//若未打开区域和标记区域等于雷数则返回0,否则返回1
	if (count == MINES)
		return 0;
	else
		return 1;
}


//计算周围雷的个数
int GetMines(char mine[ROWS][COLS], int x, int y)
{
	return 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';

}

//扩展式排雷
void ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	//如果该位置周围8快没雷,则对周围8块检索,展开
	if (GetMines(mine, x, y) == 0)
	{
		//展开的位置都置为0
		show[x][y] = '0';
		int i = 0;
		int j = 0;
		for (i = x - 1; i <= x + 1; i++)
		{
			for (j = y - 1; j <= y + 1; j++)
			{
				//只有该位置未被打开才能检索,并且检索位置应在棋盘之内
				if (show[i][j] == '*' && i > 0 && i <= ROW && j > 0 && j <= COL)
				{
					ExpandMine(mine, show, i, j);
				}
			}
		}
	}
	else
	{
		//不需要展开则显示附近雷的个数
		show[x][y] = GetMines(mine, x, y) + '0';
	}
}

//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0;
	while (IsWin(show, row, col))
	{
		int input = 0;
		//1.排查雷
		//2.标记雷
		//每次操作可以选择是排查雷还是标记雷
		do
		{
			printf("请选择操作1.排查雷 2.标记雷:>");
			scanf("%d", &input);
			switch (input)
			{
				//排查雷
			case 1:
				printf("请输入坐标;>");
				scanf("%d %d", &x, &y);
				if (x > 0 && x <= row && y > 0 && y <= col && show[x][y] != '#')
				{
					if (mine[x][y] == '1')
					{
						system("cls");
						printf("很遗憾你被炸死了\n");
						ShowBoard(mine, ROW, COL);
						Sleep(3000);
						//被炸死直接退出
						return ;
					}
					else
					{
						ExpandMine(mine, show, x, y);
						system("cls");
						ShowBoard(show, ROW, COL);
					}
				}
				else
				{
					//如果该位置被标记则无法排查
					if (show[x][y] == '#')
					{
						printf("该位置被标记为雷,请重新输入\n");
					}
					//如果排查位置越界则报错
					else
						printf("坐标非法,请重新输入\n");
				}
				break;
				//标记雷
			case 2:
				SignMine(show, row, col);
				system("cls");
				ShowBoard(show, ROW, COL);
				break;
			default:
				break;
			}
		} while (IsWin(show, row, col));//当玩家获胜或是踩雷时循环结束
	}
	//如果玩家扫雷成功,则显示布雷图
	if (!IsWin(show, row, col))
	{
		system("cls");
		printf("恭喜您扫雷成功!:>\n");
		ShowBoard(mine, ROW, COL);
		Sleep(3000);
	}
}

//标记雷
void SignMine(char show[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0;
	printf("请输入坐标;>");
	scanf("%d %d", &x, &y);
	//标记雷
	if (show[x][y] == '*')
	{
		show[x][y] = '#';
	}
	//取消标记
	else if (show[x][y] == '#')
	{
		show[x][y] = '*';
	}
	//若该位置被打开则无法标记
	else
		printf("标记失败");
}

test.c

#include"game.h"
void game()
{
	char Mine[ROWS][COLS] = { 0 };
	char Show[ROWS][COLS] = { 0 };
	//先初始化雷区和游戏界面
	InitBoard(Mine, ROWS, COLS, '0');
	InitBoard(Show, ROWS, COLS, '*');
	//布置雷
	SetMine(Mine, ROW, COL);
	//展示隐藏的雷区
	ShowBoard(Show, ROW, COL);
    //若需作弊,可以直接显示布置后的雷区
	/*ShowBoard(Mine, ROW, COL);*/
	//游戏操作函数
	FindMine(Mine, Show, ROW, COL);
	system("cls");
}
//主菜单
void menu()
{
	printf("+---------------------------+\n");
	printf("|          1. play          |\n");
	printf("|          0. exit          |\n");
	printf("+---------------------------+\n");
}
//测试函数
void test()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:>\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			system("cls");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			break;
		}
	} while (input);
}
int main()
{
	test();
	return 0;
}

       文章到此就结束了,感谢大家的阅读。

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值