【编程之路(007)扫雷游戏】(C语言实现)

目录

引言

菜单的打印

初始化棋盘

棋盘的打印

布置雷区 

确定周围雷数量值

 标记雷

取消标记

递归展开空白安全区

统计被正确标记的雷数

扫雷的具体过程

源码


引言

扫雷这个游戏想必大家都不陌生。如果你知道扫雷游戏的规则,那么你可以跳过这一段,直接到main函数的整体逻辑这里开始。如果你还不知道扫雷怎么玩,我也给你复制好了游戏规则。

胜利条件:

在一个9*9方块矩阵中随机布置一定量的地雷10个。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。

游戏主区域由很多个方格组成。使用鼠标左键随机点击一个方格,方格即被打开并显示出方格中的数字;方格中数字则表示其周围的8个方格隐藏了几颗雷。

在判断出不是雷的方块上按下左键(我们的是输入坐标),可以打开该方块。如果方块上出现数字,则该数字表示其周围3×3区域中的地雷数,一般为8个格子,对于边块为5个格子,对于角块为3个格子。所以扫雷中最大的数字为8;如果方块上为空(相当于0),则可以递归地打开与空相邻的方块;如果不幸触雷,则游戏结束。


main函数的整体逻辑

int main()
{
	srand((unsigned int)time(NULL));//暂时不管
	int choice = 0;
	do
	{
		printf("            游戏介绍:            \n");
		printf("输入 1 为开始游戏,输入 0 为退出游戏\n");
		printf("游戏最终的胜利即为把全部的雷标记出来\n");
		menu();
		printf("请选择\n");
		scanf("%d", &choice);
		switch (choice)
		{
		case 1:
		{
			game();
			break;
		}
		case 0:
		{
			printf("游戏退出\n");
			break;
		}
		default:
		{
			printf("选择错误,重新选择\n");
			break;
		}
		}
	} while (choice);
	return 0;
}

这里的逻辑与上一篇三子棋【编程之路(006)三子棋游戏】(C语言实现)_p_fly的博客-优快云博客一样,也很简单,如果不太明白,可以点击链接进去看看。接下来我们便实现游戏各个函数的功能。


菜单的打印

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

初始化棋盘

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

这里要注意的是我们扫雷游戏其实是有两个棋盘(二维数组实现)的。一个是玩家玩的棋盘界面;另一个是我们布置雷区的棋盘。有了整体的把控,我们对于游戏的实现便有了大致的方向。

由于扫雷要探索周围一圈的区域,也就是八个格子,对于中间的格子还好,我们可以很好的表示,但是对于边缘和四角的的格子,我们探索周围就会出现越界的情况(如下图)。解决方法是直接定义比棋盘规模多两行的行列。如9*9的规模我们便定义成(9+2)*(9+2)的规模。先说明ROWS和COLS是9+2的规模,ROW和COL是9的规模。那么为什么还要多次一举定义出ROW和COL呢,那是因为虽然我们的规模大,但还是有很多操作是需要用到9*9的。接下来其他功能的实现便可以很直观的看到。


棋盘的打印

void Display(char board[ROWS][COLS], int row, int col)
{
	printf("-------------------\n");
	int i = 0, j = 0;
	for (j = 0; j <= col; j++)//这个for循环是打印棋盘的列坐标
	{
		printf("%d ", j);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//这一行语句是打印棋盘的行坐标
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-------------------\n");
}

这里所打印出来的棋盘是展示棋盘,而不是雷区棋盘。我们在函数传参的时候把'*'传个展示棋盘,让其保持待探索的感觉。效果如下


布置雷区 

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0;
	int count = AMOUNT;//AMOUNT是我们定义的雷的数量
	while (count)
	{
		x = rand() % row + 1;//(0~8)+1==1~9
		y = rand() % col + 1;//(0~8)+1==1~9
		if (board[x][y] == '0')
		{
			board[x][y] = '1';//雷我们用字符'1'来表示,因为形参的类型是char
			count--;
		}
	}
}

rand()函数和main()函数中的srand()函数随机生成了AMOUNT个雷。

对于rand函数和srand函数不懂的可以参考这一篇文章

rand()和srand()函数的用法_diluosixu的博客-优快云博客_srand和rand函数怎么用


确定周围雷数量值

int AmountMine(char board[ROWS][COLS], int x, int y)
{
	return (board[x - 1][y] +
		board[x - 1][y - 1] +
		board[x][y - 1] +
		board[x + 1][y - 1] +
		board[x + 1][y] +
		board[x + 1][y + 1] +
		board[x][y + 1] +
		board[x - 1][y + 1] - 8 * '0');
       //减去8*'0':因为我们的雷区中都是字符型的'0'//非雷'1'//雷。
       //所以如果有n个雷,就会产生n*'1'-n*'0'的整数。('1'-'0'=1)
}

对于玩家排查的一个(x,y)的格子,我们应该返回周围一圈有多少雷,如下图


 标记雷

void FlagMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		show[x][y] = '!';
	}
}

我们把标记的雷在展示数组上用'!'标记。


取消标记

void FlagCancel(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
	if (show[x][y] == '!')
	{
		show[x][y] = '*';
	}
}

我们想把原来标记的取消,直接在改成'*'即可。


递归展开空白安全区

void Spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	int around_x = 0;
	int around_y = 0;
	int count = 0;
	//坐标合法
	if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
	{
		//遍历周围坐标
		for (around_x = -1; around_x <= 1; around_x++)
		{
			for (around_y = -1; around_y <= 1; around_y++)
			{
				//如果这个坐标不是雷
				if (mine[x + around_x][y + around_y] == '0')
				{
					//统计周围雷的个数
					count = AmountMine(mine, x + around_x, y + around_y);
					if (count == 0)
					{
						if (show[x + around_x][y + around_y] == '*')
						{
							show[x + around_x][y + around_y] = ' ';
							Spread(mine, show, x + around_x, y + around_y);
						}
					}
                    //如果是雷就直接在展示棋盘上显示周围有几个雷的数字
					else
					{
						show[x + around_x][y + around_y] = count + '0';
					}
				}
			}
		}
	}
}

统计被正确标记的雷数

int ClearMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;
	int end = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			if (mine[i][j] == '1' && show[i][j] == '!')
              //在雷区棋盘上是雷并且在展示棋盘上是'!'才是被标记的正确的雷
				end++;
		}
	}
	return end;
}

如果玩家把所有的雷标记出来,那么就直接胜利了。


扫雷的具体过程

void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
	int n = 0, x = 0, y = 0;
	int win = 0;//统计被标记的个数(可能被标记的都是雷,也有可能标错)
	int flag = 0;//falg为0代表未取消标记,为1代表被取消标记//这是我为了方便展示棋盘而设置的
	int fail = 0;//fail为0代表没有被炸死,为1代表被炸死
again:
	while (win < AMOUNT)
	{
		printf("功能 1:排查雷 2:标记雷 3:取消标记\n");
		printf("请选择功能和排查的坐标(功能 横 纵)\n");
		scanf("%d %d %d", &n, &x, &y);
		switch (n)
		{
		case 1:
			break;
		case 2:
		{
			FlagMine(show, mine, row, col, x, y);
			win++;
		    break; 
		}
		case 3:
		{
			FlagCancel(show, mine, row, col, x, y); 
			flag = 1;
			win--;
			break;
		}
		}
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1' && show[x][y] == '*' && flag == 0)
			{
				printf("你被炸死了,下面是雷区分布图 1为雷  0非雷\n");
				Display(mine, ROW, COL);
				fail = 1;
				break;
			}
			else if (show[x][y] == '!')
				Display(show, ROW, COL);
			else if (flag == 1)
				Display(show, ROW, COL);
			else 
			{
				int count = AmountMine(mine, x, y);
				if (count == 0)
				{
					show[x][y] = ' ';
					Spread(mine, show, x, y);
					Display(show, ROW, COL);
				}
				else
				{
					show[x][y] = count + '0';
					Display(show, ROW, COL);
				}
			}
		}
		else
		{
			printf("输入错误,请重新输入");
		}
	}
	if (AMOUNT == ClearMine(mine,show,row,col))//如果雷的数量都被标记出来,就成功。
	{
		printf("恭喜你,扫雷成功\n");
	}
	else if(fail == 1)//如果触发到fail为1,则说明爆炸过了,就失败了。
	{
		printf("请选择是否再来一把\n");
	}
	else
	{
		printf("标记的雷有误,请仔细检查,重新标记\n");
        //到这里就是标记了至少一个安全区域,需要重新排雷
		goto again;//goto语句直接跳到重新排雷的地方
	}
}


源码

该部分代码为game.h文件中的


#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

#define ROW 9
#define COL 9

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

#define AMOUNT 10

//菜单
void menu();

//棋盘初始化
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch);

//展示棋盘
void Display(char board[ROWS][COLS], int row, int col);

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

//选择坐标区域雷的数量
int AmountMine(char board[ROWS][COLS], int row, int col);

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

//标记雷
void FlagMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y);

//取消标记
void FlagCancel(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y);

//排查选择坐标区域周围是否安全
void Spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);

//返回被标记的雷的个数
int ClearMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

该部分代码为game.c文件中的

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

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

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

void Display(char board[ROWS][COLS], int row, int col)
{
	printf("-------------------\n");
	int i = 0, j = 0;
	for (j = 0; j <= col; j++)//这个for循环是打印棋盘的列坐标
	{
		printf("%d ", j);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//这一行语句是打印棋盘的行坐标
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-------------------\n");
}

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0;
	int count = AMOUNT;
	while (count)
	{
		x = rand() % row + 1;//(0~8)+1==1~9
		y = rand() % col + 1;//(0~8)+1==1~9
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

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

void FlagMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		show[x][y] = '!';
	}
}

void FlagCancel(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
	if (show[x][y] == '!')
	{
		show[x][y] = '*';
	}
}


void Spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	int around_x = 0;
	int around_y = 0;
	int count = 0;
	//坐标合法
	if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
	{
		//遍历周围坐标
		for (around_x = -1; around_x <= 1; around_x++)
		{
			for (around_y = -1; around_y <= 1; around_y++)
			{
				//如果这个坐标不是雷
				if (mine[x + around_x][y + around_y] == '0')
				{
					//统计周围雷的个数
					count = AmountMine(mine, x + around_x, y + around_y);
					if (count == 0)
					{
						if (show[x + around_x][y + around_y] == '*')
						{
							show[x + around_x][y + around_y] = ' ';
							Spread(mine, show, x + around_x, y + around_y);
						}
					}
					else
					{
						show[x + around_x][y + around_y] = count + '0';
					}
				}
			}
		}
	}
}

int ClearMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;
	int end = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			if (mine[i][j] == '1' && show[i][j] == '!')
				end++;
		}
	}
	return end;
}

void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
	int n = 0, x = 0, y = 0;
	int win = 0;
	int flag = 0;
	int fail = 0;
again:
	while (win < AMOUNT)
	{
		printf("功能 1:排查雷 2:标记雷 3:取消标记\n");
		printf("请选择功能和排查的坐标(功能 横 纵)\n");
		scanf("%d %d %d", &n, &x, &y);
		switch (n)
		{
		case 1:
			break;
		case 2:
		{
			FlagMine(show, mine, row, col, x, y);
			win++;
		    break; 
		}
		case 3:
		{
			FlagCancel(show, mine, row, col, x, y); 
			flag = 1;
			win--;
			break;
		}
		}
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1' && show[x][y] == '*' && flag == 0)
			{
				printf("你被炸死了,下面是雷区分布图 1为雷  0非雷\n");
				Display(mine, ROW, COL);
				fail = 1;
				break;
			}
			else if (show[x][y] == '!')
				Display(show, ROW, COL);
			else if (flag == 1)
				Display(show, ROW, COL);
			else 
			{
				int count = AmountMine(mine, x, y);
				if (count == 0)
				{
					show[x][y] = ' ';
					Spread(mine, show, x, y);
					Display(show, ROW, COL);
				}
				else
				{
					show[x][y] = count + '0';
					Display(show, ROW, COL);
				}
			}
		}
		else
		{
			printf("输入错误,请重新输入");
		}
	}
	if (AMOUNT == ClearMine(mine,show,row,col))
	{
		printf("恭喜你,扫雷成功\n");
	}
	else if(fail == 1)
	{
		printf("请选择是否再来一把\n");
	}
	else
	{
		printf("标记的雷有误,请仔细检查,重新标记\n");
		goto again;
	}
}

该部分代码为test.c文件中的

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

void game()
{
	//存放雷的棋盘  雷为'1',非雷为'0'  这里是字符1,0
	char mine[ROWS][COLS] = { 0 };
	//展示排查后的棋盘  未排查的区域为'*',排查后显示周围雷的数字(也是字符)
	char show[ROWS][COLS] = { 0 };
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	SetMine(mine, ROW, COL);
	Display(show, ROW, COL);
	//Display(mine, ROW, COL);
	FindMine(show, mine, ROW, COL);
}

int main()
{
	srand((unsigned int)time(NULL));
	int choice = 0;
	do
	{
		printf("            游戏介绍:            \n");
		printf("输入 1 为开始游戏,输入 0 为退出游戏\n");
		printf("游戏最终的胜利即为把全部的雷标记出来\n");
		menu();
		printf("请选择\n");
		scanf("%d", &choice);
		switch (choice)
		{
		case 1:
		{
			game();
			break;
		}
		case 0:
		{
			printf("游戏退出\n");
			break;
		}
		default:
		{
			printf("选择错误,重新选择\n");
			break;
		}
		}
	} while (choice);
	return 0;
}

希望这个扫雷游戏可以帮到你。有什么错误和建议也希望大家可以在评论区发出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值