实现Windows经典游戏——扫雷

扫雷游戏以前是windows的内置小游戏,相信不少人都玩过这么一款游戏。

1.扫雷游戏规则

以9*9的简单模式为例,游戏开始时系统会生成10颗雷随机分布在这9*9的格子当中。
在这里插入图片描述

当你点开其中的一格,将会显示该点周围8格的雷的数量
在这里插入图片描述

游戏的胜利条件就是排查所有的雷。

2.游戏实现

每次游戏都是要有菜单的,下面会写一次功能简单的菜单.仅支持游玩和退出。

2.1 简单的游戏菜单

#include <stdio.h>
void menu()
{
	printf("***********************\n");
	printf("******** 1.play    ****\n");
	printf("******** 0.exit    ****\n");
	printf("***********************\n");
}
int main()
{
	int input = 0;
	do
	{
		menu();//打印菜单
		printf("请输入>");
		scanf("%d",&input);
		switch(input)
		{
			case 1:
				break;
			case 0:
				break;
			default:
				printf("请重新输入\n");
				break;
		}
	}while(input);
	return 0;
}

2.2游戏逻辑

当玩家通过菜单进入到游戏中,我们要创建一个棋盘来玩游戏啊,那么棋盘要怎么生成呢?利用字符数组来输出,以9*9的棋盘为例,我们将字符数组的长和宽定义为11*11.为什么要这么写呢,目的是为了方便后续对棋盘位置周围雷数的计算。我们先画一个棋盘。
在这里插入图片描述

统计图中标有红色圆圈周围有多少雷,难道我们还要去特别判断一下位置吗,这样会让游戏的实现变得繁琐且无意义,所以我们要改变思路,为这个棋盘加上两行两列。
在这里插入图片描述

加上的两行两列上不会布置雷,这样统计棋盘位置周围有多少颗雷就很方便了,所有的棋盘位置都是一样计算。

//test.c
void game()
{
	char mine[11][11];//利用字符型数组表示棋盘
	char show[11][11];
}

为什么要用到两个字符型数组呢?

我们要知道扫雷棋盘中的每个格子都是有两种信息的,在没点开前每个格子就已经存放了周围8格的雷的数量的信息,除此之外还有要隐藏信息的字符格覆盖信息。两种信息在不使用结构体的情况下是无法表示的,所以我使用了两格字符数组,一个存放地雷信息,一个存放覆盖信息的字符。

为了后续代码的可维护性我们要定义标识常量

//game.h
#define Row 9
#define Col 9

#define Rows Row+2
#define Cols Col+2

//test.c
void game()
{
	char mine[Rows][Cols] = {0};//利用字符型数组表示棋盘
	char show[Rows][Cols] = {0};
}
//...

2.3 棋盘初始化

我们将棋盘分为两个,一个是展示棋盘(给玩家看的),一个是藏雷棋盘(不给玩家看的)。
展示棋盘的初始化我们全初始化为’*’ 藏雷棋盘全部初始化为‘0’.

//game.c
//初始化展示棋盘
void InitBoard(char board[Rows][Cols],int rows,int cols,char ch)
{
	for(int i = 0;i<rows;++i)
	{
		for(int j = 0;j<cols;++j)
		{
			board[i][j] = ch;//ch == '*'
		}
	}
}
//初始化藏雷棋盘
void InitBoard(char board[Rows][Cols],int rows,int cols,char ch)
{
	for(int i = 0;i<rows;++i)
	{
		for(int j = 0;j<cols;++j)
		{
			board[i][j] = ch;//ch == '0'
		}
	}
}
//test.c
void game()
{
	char mine[Rows][Cols] = {0};//利用字符型数组表示棋盘
	char show[Rows][Cols] = {0};
	//初始化棋盘
	InitBoard(mine,Rows,Cols,'0');
	InitBoard(show,Rows,Cols,'*');
}

2.4 打印棋盘

为方便玩家找到棋盘各个位置的坐标,我们会打印相应的数字对玩家进行提示,便于玩家找到坐标。

void PrintBoard(char board[Rows][Cols],int row,int col)
{
	printf("0 1 2 3 4 5 6 7 8 9\n");
	for(int i = 1;i<=row;++i)
	{
		printf("%d",i);
		for(int j = 1;j<=col;++j)
		{
			printf(" %c",board[i][j]);
		}
		printf("\n");
	}
}

2.5 随机布雷

初始化完棋盘后我们要往藏雷棋盘里面开始布置地雷了,为了达到随机布置的效果,我们要利用到rand函数。因为这了我们用了rand函数,所以我必须在前面写上srand来给rand函数提供随机种子,为此我们还需要用到time为srand提供数字来帮助它输出随机种子.

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//为rand提供随机种子
	do
	{
		menu();//打印菜单
		printf("请输入>");
		scanf("%d",&input);
		switch(input)
		{
			case 1:
				break;
			case 0:
				break;
			default:
				printf("请重新输入\n");
				break;
		}
	}while(input);
	return 0;
}

开始布置雷

//game.h
#define Row 9
#define Col 9

#define Rows Row+2
#define Cols Col+2

#define MineNum 10//藏雷个数

//game.c
void SetMine(char mine[Rows][Cols],int row,int col)
{
	int count = MineNum;//布置10颗雷
	int x = 0;
	int y = 0;
	while(count)
	{
		x = rand()%row+1;//产生的x范围为1~9
		y = rand()%col+1;
		if(mine[x][y]=='0')
		{
			mine[x][y] = '1';
			count-=1;
		}
	}
}

2.6 玩家操作

布置完了雷后玩家就可以开始操作了,玩家扫雷利用的棋盘的坐标来排雷。
玩家操作的逻辑是,给个坐标供玩家选择,不能超过棋盘的范围,如果没超过,就判断是否说已经选取过的坐标。因为玩家会存在胜利和失败两种结果,为了达成胜利结局,我们就必须的排除所有的雷,也就是把所有非雷的位置找到,为此我们可以设置一个数字来表示当前棋盘内还有多少非雷位置未被找到。所以我们可以定义一个变量来表示剩余的非雷元素。当这个变量减为0时就代表玩家胜利了。失败的条件当然就是触雷了,当玩家触雷时,我们打印一下藏雷表给玩家看。

void Demining(char mine[Rows][Cols], char show[Rows][Cols], int row, int col)
{
	int x = 0;
	int y = 0;
	int count = row * col - MineNum;//正常格子的数目
	while (count)
	{
		printf("利用坐标进行排雷>");
		scanf("%d %d", &x, &y);
		if (x<1 || y<1 || x>row || y>col)
		{
			printf("坐标非法!\n");
		}
		else if (show[x][y] != '*')
		{
			printf("该坐标已被选择\n");
		}
		else
		{
			//判断所选坐标是不是雷,如果是雷则游戏失败
			if (mine[x][y] == '1')
			{
				printf("你被炸死了!\n");
				PrintBoard(mine, row, col);
				break;
			}
			else
			{
				//不是雷就开始计算周围雷的数目
				count -= 1;
				int ret = MineCount(mine, x, y);
				show[x][y] = '0' + ret;
				//打印棋盘展示
				PrintBoard(show, row, col);
			}
		}
	}
	if (count == 0)
	{
		printf("扫雷成功!\n");
	}
	else
	{
		printf("扫雷失败!\n");
	}
}

//计算周围雷的数目
int MineCount(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] - '0' * 8;
}

3.代码整合

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

#define Row 9
#define Col 9

#define Rows Row+2
#define Cols Col+2

#define MineNum 10

void InitBoard(char board[Rows][Cols], int rows, int cols, char ch);

void PrintBoard(char board[Rows][Cols], int row, int col);

void SetMine(char mine[Rows][Cols], int row, int col);

void Demining(char mine[Rows][Cols], char show[Rows][Cols], int row, int col);

int MineCount(char mine[Rows][Cols], int x, int y);


//game.c
#include "game.h"

void InitBoard(char board[Rows][Cols], int rows, int cols, char ch)
{
	for (int i = 0; i < rows; ++i)
	{
		for (int j = 0; j < cols; ++j)
		{
			board[i][j] = ch;//ch == '*'
		}
	}
}

void PrintBoard(char board[Rows][Cols], int row, int col)
{
	printf("0 1 2 3 4 5 6 7 8 9\n");
	for (int i = 1; i <= row; ++i)
	{
		printf("%d", i);
		for (int j = 1; j <= col; ++j)
		{
			printf(" %c", board[i][j]);
		}
		printf("\n");
	}
}

void SetMine(char mine[Rows][Cols], int row, int col)
{
	int count = MineNum;//布置10颗雷
	int x = 0;
	int y = 0;
	while (count)
	{
		x = rand() % row + 1;//产生的x范围为1~9
		y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count -= 1;
		}
	}
}

void Demining(char mine[Rows][Cols], char show[Rows][Cols], int row, int col)
{
	int x = 0;
	int y = 0;
	int count = row * col - MineNum;//正常格子的数目
	while (count)
	{
		printf("利用坐标进行排雷>");
		scanf("%d %d", &x, &y);
		if (x<1 || y<1 || x>row || y>col)
		{
			printf("坐标非法!\n");
		}
		else if (show[x][y] != '*')
		{
			printf("该坐标已被选择\n");
		}
		else
		{
			//判断所选坐标是不是雷,如果是雷则游戏失败
			if (mine[x][y] == '1')
			{
				printf("你被炸死了!\n");
				PrintBoard(mine, row, col);
				break;
			}
			else
			{
				//不是雷就开始计算周围雷的数目
				count -= 1;
				int ret = MineCount(mine, x, y);
				show[x][y] = '0' + ret;
				//打印棋盘展示
				PrintBoard(show, row, col);
			}
		}
	}
	if (count == 0)
	{
		printf("扫雷成功!\n");
	}
	else
	{
		printf("扫雷失败!\n");
	}
}

int MineCount(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] - '0' * 8;
}

//test.c
void game()
{
	char mine[Rows][Cols] = { 0 };//利用字符型数组表示棋盘
	char show[Rows][Cols] = { 0 };
	//初始化
	InitBoard(mine, Rows, Cols, '0');
	InitBoard(show, Rows, Cols, '*');
	//打印棋盘
	PrintBoard(show, Row, Col);
	//布置地雷
	SetMine(mine, Row, Col);
	//PrintBoard(mine, Row, Col);
	Demining(mine, show, Row, Col);
}
void menu()
{
	printf("***********************\n");
	printf("******** 1.play    ****\n");
	printf("******** 0.exit    ****\n");
	printf("***********************\n");
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//为rand提供随机种子
	do
	{
		menu();//打印菜单
		printf("请输入>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			break;
		default:
			printf("请重新输入\n");
			break;
		}
	} while (input);
		return 0;
}

效果
在这里插入图片描述

关于为什么没有展开
对于让周围没有雷的个格子展开,你们可以来实现,展开格子的特点就是:

  1. 该坐标不是雷,周围没有雷 该坐标也没有被展开过(用递归展开)
  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yui_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值