扫雷小游戏保姆级教程

扫雷是一款经典的小游戏,今天带大家用C语言实现这款小游戏。

游戏规则

扫雷规则如下:在一个X*Y的棋盘上,分布着N颗雷,找出N颗雷且不踩雷便胜利。

棋盘如图1所示:

 图1 扫雷棋盘

当我们点击某一坐标时,存在两种可能,有雷或无雷,有雷判定为输,无雷着继续游戏,并且会标记出该坐标附近3*3的单元格内有几颗雷,如图2所示。

图2 扫雷示意图

以上为游戏的基本规则,以下开始相关编程介绍。

扫雷程序编写

 1、整体思路

我们计划编写一个棋盘大小为9*9,总共10颗雷的扫雷游戏。因此我们需要一个二维数组来充当我们的棋盘,我们可以输入数组对应的下标完成对棋盘的操作。

整体的逻辑如下:

1、初始化棋盘,即初始化二维数组,保证我们每一次游玩都不会受先前的影响。

2、在棋盘上设置10颗雷。

3、显示棋盘。

4、用户输入。

5、根据用户输入坐标判断该处是否有雷,有雷则游戏结束,无雷则判断周围8个坐标雷的个数,将雷的个数打印在该坐标出,如图2所示。

6、找出全部雷后,判定用户胜利。

2、初始化棋盘

思路理清后,我们就可以开始编程了。首先是初始化棋盘。

在我们上面的思考中,可以发现如果埋雷,判断,输入坐标,显示雷等一系列操作都在一个二维数组中完成的话,未免有些太麻烦了,因此我们可以建立两个大小相同的二维数组,一个数组用来埋雷,一个数组用来显示。如下所示:

	char mine[ROWS][COLS] = {0};
	char surface[ROWS][COLS] = { 0 };

而考虑到我们每输入一个坐标,就会像图2那样判断坐标周围雷的个数,如果出现图3的情况,会发生越界,因此,我们需要将棋盘的每一条边长度加一,防止越界。

图3 当坐标位于棋盘边缘时,发生越界

因此我们设定:

#define ROW 9
#define COL 9
#define ROWS (ROW+2)
#define COLS (COL+2)

 在完成以上思考后,终于可以实现棋盘的初始化了。为了方便我们下一步操作,在雷区数组全部写入“ 0 ”,在显示数组全部写入“ * ”,程序如下:

void InitMineBoard(char mine[ROWS][COLS], int rows, int cols)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			mine[i][j] = '0';
		}
	}

}

void InitSurBoard(char surface[ROWS][COLS], int rows, int cols)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			surface[i][j] = '*';
		}
	}
}

3、显示棋盘

该部分较为简单,直接打印我们想让用户看到的部分数组即可,代码如下:

void DisplayBoard(char Board[ROWS][COLS], int rows, int cols)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows - 1; i++)//打印列号,方便观察
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i < rows-1; i++)
	{
		printf("%d ", i);//打印行号,方便观察
	{
		for (j = 1; j < cols-1; j++)
		{
			printf("%c ", Board[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

4、设置地雷

接下来,我们需要在雷区数组内设置10颗雷,即横纵坐标在1~9范围内随机设置10颗雷。这里我们可以采用随机数对9取余再加一的方式来设置雷,每设定好一颗雷后计数器加一,直到等于10后退出循环,完成设置。程序如下:

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int n = 0;
	while (n!=10)
	{
		x = 1 + rand() % 9;
		y = 1 + rand() % 9;
		if (mine[x][y] != '1')
		{
			mine[x][y] = '1';
			n++;
		}
	}
}

5、玩家操作

玩家的操作方式为输入对应坐标判断是否有雷。如果输入坐标正确且没有下过子,则输入成功。程序判断是否有雷,有雷则游戏结束,无雷则游戏继续并统计周围雷的个数。程序如下:

int PlayerMove(char surface[ROWS][COLS], char mine[ROWS][COLS], int rows, int cols)
{
	int x = 0;
	int y = 0;
	printf("请输入坐标:");
	while (1)
	{
		int n = 0;
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
		{
			n = JudgeMine(surface,mine,x,y);
			if (n != 9)
			{
				if (surface[x][y] == '*')
				{
					/*surface[x][y] = n+'0';*/
					SpaceGen(surface, mine, x, y);
					return 1;
				}
				if (surface[x][y] != '*')
				{
					printf("已有坐标,请重新输入:");
				}
			}
			if (n == 9)
			{
				printf("你输了!\n");
				return 0;
			}
		}
		else
			printf("输入非法,请重新输入:");
	}
}

6、判断是否有雷

首先根据输入的坐标,在雷区数组中判断该坐标是否有雷,有则返回9,无则跟据周围雷的个数返回0~8.程序如下:

int JudgeMine(char surface[ROWS][COLS], char mine[ROWS][COLS], int x, int y)//判断选定点周围雷的个数
{
	int n = 0;
	int flag = 0;
	int i = 0;
	int j = 0;
	if (mine[x][y] == '1')//找到雷后,返回9,因为周围最多有8个雷
	{
		flag = 9;
		return flag;
	}
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			if (mine[i][j] == '1')
			{
				n++;
			}
		}
	}
	return n;
}

7、输赢判断

输游戏很简单,踩中一颗雷就输,赢游戏很难,需要找出全部的雷。因此,胜利往往是在已经找完了整个棋盘之后才会有。

因此,我们可以知道,在找完了整个棋盘且没有踩雷的情况下,只有10个“ * ”存在于棋盘中,通过计算剩余“ * ”的个数就可以判断用户是否胜利。程序如下:

int IsWin(char surface[ROWS][COLS], char mine[ROWS][COLS], int rows, int cols)//当只剩下10个" * "时,表明雷已全部找出
{
	int i = 0;
	int j = 0;
	int n = 0;
	for (i = 1; i < 9; i++)
	{
		for (j = 1; j < 9; j++)
		{
			if (surface[i][j] == '*')//判断已经找出雷的个数
			{
				n++;
			}
		}
	}
	if (n == 10)
		return 1;//结束游戏
	else
		return 2;//继续
}

8、总结

自此,我们的扫雷程序已经编写完毕,根据总体思路,将各子程序写入主程序中,主程序如下:

#include <stdio.h>
#include "game.h"
void menu()
{
	printf("********************\n");
	printf("****** 1 play ******\n");
	printf("****** 0 exit ******\n");
	printf("********************\n");
}

void game()
{
	int flag = 0;
	char mine[ROWS][COLS] = {0};
	char surface[ROWS][COLS] = { 0 };
	InitMineBoard(mine, ROWS, COLS);
	InitSurBoard(surface,ROWS,COLS);
	SetMine(mine, ROWS, COLS);
	DisplayBoard(mine, ROWS, COLS);
	while (1)
	{
		DisplayBoard(surface, ROWS, COLS);
		flag = PlayerMove(surface, mine, ROWS, COLS);
		if (flag == 0)
			break;
		flag = IsWin(surface, mine, ROWS, COLS);
		if (flag == 1)
		{
			printf("你赢了!\n");
			DisplayBoard(surface, ROWS, COLS);
			DisplayBoard(mine, ROWS, COLS);
			break;
		}
	}
}
int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("game start\n");
			game();
			break;
		case 0:
			printf("game end\n");
			break;
		default:
			printf("非法输入,请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

进阶相关

在以上程序的编写中,我们实现了扫雷的基本功能,但是一个一个的去点未免太麻烦了,一个9*9大小的棋盘需要71步操作。而在我们常见的扫雷中,经常由如图4一样的,点一下出来一大片的情况。该方式极大地方便了我们继续游戏。

图4

而要实现该功能,我们可以采用递归的方式完成。递归的结束条件有两个:

1、判断的坐标接触到边界

2、判断的坐标周围有雷

在明确了以上两个条件以后,我们就可以开始递归了,原理如下:

如果判断的坐标周围没有雷,则向下一个坐标移动,判断下一个坐标周围是否有雷。

程序如下:

void SpaceGen(char surface[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
	if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1)//判断是否越界
		return;
	if (surface[x][y] != '*')
		return;
	int n = JudgeMine(surface, mine, x, y);
	if (n > 0)
	{
		surface[x][y] = n + '0';
		return;
	}
	if (n == 0)
	{
		surface[x][y] = ' ';
		SpaceGen(surface, mine, x - 1, y - 1);
		SpaceGen(surface, mine, x - 1, y);
		SpaceGen(surface, mine, x, y + 1);
		SpaceGen(surface, mine, x, y - 1);
		SpaceGen(surface, mine, x, y + 1);
		SpaceGen(surface, mine, x + 1, y - 1);
		SpaceGen(surface, mine, x + 1, y);
		SpaceGen(surface, mine, x + 1, y + 1);
	}
}

 总结

至此,扫雷程序的编写完成。效果如图5所示,该程序比较简单,但是值得思考的地方很多,包括防止数组越界,胜负判断等等,希望这篇博文对大家有所帮助!

图5 效果演示

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值