数组的应用实例2—扫雷

整体思路:

1. 程序开始时的思路和三子棋一样,展示菜单,1是开始游戏,0是退出程序,其他选项提示错误输入。使用switch语句实现这个逻辑,判断条件就是玩家的输入。这个程序想让玩家可以重复玩,所以在将这个switch语句放入do while循环中,do while的判断条件也是玩家输入。

为此需要菜单函数和游戏功能实现函数。

2. 假设棋盘是9*9大小,并且放置10个雷。没有雷的地方用'0',有雷的地方用'1'。这部分需要一个9*9的数组来存放,即布置好的雷的信息数组( char mine[ROWS][COLS] = { 0 }; )。下面会提到为什么使用字符数组。(后续代码部分会详细解释使用字符数组的原因)

3. 如果选择坐标有雷,则游戏结束;如果没有雷那么要在给定坐标处显示给定坐标周围8个位置有几个雷。对于没有排查的位置用'*'来表示,因为这里使用了*号字符,就要把显示雷个数的数字也转化为字符。同时,为了方便与布置雷的数组进行比较,布置雷的数组也设置为字符数组。

举例说明为什么需要两个数组:

假如周围8个位置有1个雷,那么就需要显示1。但是这里的1跟表示有雷的1可能会产生歧义,所以还需要一个9*9的数组用于显示棋盘即提示周围有多少雷,排查出雷信息的数组( char show[ROWS][COLS] = { 0 }; )。并且在游戏没有结束时,不能显示布置雷信息的数组,只能显示排查雷信息的数组。

4. 在检测周围有多少雷时,有一种特殊情况:如果要检查的给定坐标在棋盘最边缘,此时访问周围坐标就会造成越界访问。如果每次都对周围坐标都进行合法性判断,这种方法太麻烦。换一个思路:在创建数组的时候上下各增加一行,左右各增加1列,但不论是布置雷,还是显示棋盘都在9*9的范围内,这样就可以直接统计给定坐标周围有几个雷。

下方为test.c文件,用于测试游戏逻辑

#include "game.h"

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

void game()
{
	//不能使用这种方式初始化char mine[ROWS][COLS] = { '0' };因为只初始化了一个元素
	char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息 
	char show[ROWS][COLS] = { 0 };//存放排查出雷的信息
	//初始化数组的内容为指定的内容
	//mine 数组在没有布置累的时候,都是'0'
	InitBoard(mine, ROWS, COLS, '0');
	//show 数组在没有排查雷的时候,都是'*'
	InitBoard(show, ROWS, COLS, '*');

	//设置雷,可以放在显示前,也可以放在显示后
	SetMine(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);

	//一般情况不打印mine,因为雷布置好后不能给玩家看
	//DisplayBoard(mine, ROW, COL);//打印只需要9*9实际的棋盘

	//排查雷
	//输入一个坐标:如果有雷,游戏结束;如果没雷,显示周围有几个雷
	//排查雷要在布置雷信息的数组(mine)查看,是雷,游戏结束,
	//如果不是,就在存放排查出雷信息的数组(show)显示
	FindMine(mine, show, ROW, COL);//在mine数组找雷,在show数组显示雷信息
}

int main()
{
	int input = 0;
	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;
}

下方为game.h头文件,用于游戏代码的声明(函数声明,符号定义)

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

//11*11和9*9都需要,因为雷只放到9*9里,打印信息也只打印9*9的场地
//但是判断坐标周围是否有雷时,担心越界访问,所以使用11*11
#define ROW 9
#define COL 9

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

#define EASY_COUNT 10

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

//虽然传过来是9*9,但是棋盘还是11*11,但是只接收9*9(只用9*9)。所以前面是ROWS和COLS,后面是row和col
//传参传了数组名,就是把11*11的数组传过来
void DisplayBoard(char board[ROWS][COLS], int row, int col);

void SetMine(char board[ROWS][COLS], int row, int col);

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

game.c中各函数实现

1. 初始化棋盘函数
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			//这里没有办法同时初始化'0'和'*',所以需要再增加一个参数
			//参数传过来什么,初始化什么
			board[i][j] = set;
		}
	}
}

2. 显示棋盘函数

棋盘左边和上方需要行号和列好,方便玩家选择坐标。为了上方的列号和棋盘对齐,要从0开始。

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	//打印棋盘时,下标从1开始,因为从1开始的下标才是实际棋盘
	int i = 0;
	int j = 0;

	printf("-----扫雷 游戏-----\n");
	//打印列号在最开始,因为在最上方
	//这里j要从0开始才能把1~9的数字对齐棋盘
	for (j = 0; j <= col; j++)//这里是打印列号
	{
		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");

}

3. 布置雷函数

生成随机坐标,也就是数组下标。但是要注意,是在11*11数组的中间9*9的范围内。所以下标是1~9。假设'1'表示有雷, '0'表示没雷,总共放置10个雷。找到'0'才放雷,直到10个雷都布置完,即循环上述步骤,直到count = 0 

void SetMine(char board[ROWS][COLS], int row, int col)
{
	//布置一个少一个
	int count = EASY_COUNT;
	//行和列都是1~9
	while (count)//count为0时,循环停止。此循环最少10次,如果进入循环判断此坐标有雷,count不--
	{
		int x = rand() % row + 1;//row是9,模9余数是0~8,所以+1
		int y = rand() % col + 1;//同上

		//如果没有布置雷,可以放置;如果布置了雷,就不能放置
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

4. 统计坐标周围雷函数

int get_mine_count(char board[ROWS][COLS], int x, int y)
{
	//如果遍历周围8个坐标,就需要8个if语句,太复杂,不适用
	//拿出给定坐标周围的8个坐标储存的字符
	//这些字符都是'1'或'0',字符1的ASCII码值是49,字符0的ASCII码值是48
	//所以用这些字符分别去减去字符0,如果值是1,说明是雷,0则说明不是雷
	//最后把减去字符0的所有值相加,是几说明有几个雷
	//或者8个字符全部加起来,在减去字符0乘以8(即48*8)
	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');
}

5. 找雷函数

首先判断输入坐标是否合法,合法:进入下一步判断;不合法:重新输入。

如果坐标合法,还要判断之前是否排查过。

如果没有被排查过判断是否有雷,有雷游戏结束;没有雷调用统计雷的函数,并调用显示棋盘函数。

重复上述步骤,直到找出全部雷。

判断胜负条件是排查次数,如果排查次数=9*9(棋盘大小)-10(雷的个数),说明全部排查完毕。这时游戏胜利。

这里一定要有判断是否排查过的if语句,否则,玩家反复输入用一个没有雷的坐标,会导致排查次数错误的增加,最终即使没有排查完全部棋盘也会因为排查次数达到而判胜。

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{

	int x = 0;
	int y = 0;
	int win = 0;//找到非雷的次数

	while (win < row * col - EASY_COUNT)
	{
		printf("请输入坐标:>");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)//判断玩家输入坐标是否合法
		{
			if (show[x][y] != '*')//这里需要先判断坐标是否排查过,否则重复排查可能倒是win计数不对
			{
				printf("该坐标被排查过,不能重复排查\n");
			}
			else
			{
				//如果是雷
				if (mine[x][y] == '1')//判断输入的坐标是否有雷
				{
					printf("很遗憾,你被炸死了\n");
					DisplayBoard(mine, ROW, COL);
					break;
				}
				else//如果不是雷,就要统计周围有几个雷
				{
					win++;//不是雷,找到非雷的次数就要+1
					//统计mine数组中x,y坐标周围有几个雷
					int count = get_mine_count(mine, x, y);
					//得到的值(count)是周围有几个雷,需要发到show数组展示给玩家
					show[x][y] = count + '0';//转换成数字字符
					DisplayBoard(show, ROW, COL);//排查完后展示棋盘给玩家
				}
			}	
		}
		else
		{
			printf("坐标非法,重新输入\n");
		}
	}

	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜,排雷成功\n");
		DisplayBoard(mine, ROW, COL);//排雷成功后,显示所有雷的位置
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值