六 数组和函数实践:扫雷游戏

六.数组和函数实践:扫雷游戏

原文地址:https://beryl-licorice-3a8.notion.site/5421ccb8a34c4ebbb185b6b70e0ba9b9?pvs=4

  • 规则简介
    1. 玩家点击一个方格后,方格会显示以下几种情况:一个数字,表示周围八个方格中隐藏的地雷数量;一个空白,表示周围八个方格内没有地雷;或者一个地雷,表示玩家触发了地雷,游戏失败。

    2. 玩家需要通过解读数字的线索,推理出哪些方格安全(非雷区域),哪些方格可能藏有地雷。安全的方格点击后会揭示更多信息,有助于玩家找出其他的安全方格。

    3. 如果玩家成功找出所有的安全方格,而没有触发任何地雷,那么游戏胜利

1.游戏的分析和设计

扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要一定的数据结构来存储这些信息:我们用1存放雷,用0存放正常块。

在这里插入图片描述

1.1数据结构的分析:

首先我们用已知的知识进行分析,布置的雷和排查的雷都需要存储,所以我们需要一定的数据结构来存储这些信息。迅速反应过来-数组!

如何实现排雷的算法呢?根据规则,当我们点开一个块后,它现实的数字告诉我们它周围有多少个雷,因此我们需要获取以它为中心的信息:

在这里插入图片描述

这又会导致一个新的问题:如何防止数组越界访问?根据已有的知识,我觉得再套一层都为0的外壳就能完美解决问题1

在这里插入图片描述

继续分析,我们在棋盘上布置了雷,棋盘上雷的信息(1)和⾮雷的信息(0),假设我们排查了某⼀个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪⾥呢?答案是数组

解决方案:我们采取两个数组存储信息,一个存取雷,一个存取排查出的信息

1.2文件结构设计

这么多行代码放在同一个文件中肯定是很臃肿的,我们使用三个文件来实现程序。多文件组织形式随代码实战讲述。

1.3注意事项:

 存放数据的类型太多,容易产生歧义

在统计一个坐标周围的雷的个数的时候,可能会越界

用两个数组,一个存放雷的信息,一个存放排查雷的信息

char show[11][11];存放’0’

char mine[11][11];存放’*’

2.代码实战:

2.1建立文件:

  • 快速补充多文件知识

    ⼀般情况下,函数的声明、类型的声明放在头⽂件(.h)中,函数的实现是放在源⽂件(.c)⽂件中。如下:

    add.c

    int Add(int x,int y)
    {   return x+y:
    		}//定义函数
    

    add.h 将这个文件放在头文件中,其余都是源文件

    int Add(int x,int y);//函数的声明
    

    test.c

    #include<stdio.h>
    #include"add.h"//自己定义的头文件特殊写法
    int main(){
        int a=10; int b=20;
    		int c = Add(a,b)//函数调用
    

    运行结果:

    在这里插入图片描述

  1. test.c——文件中写游戏的测试逻辑
  2. game.c——文件中写游戏中函数的实现等
  3. game.h——文件中写游戏需要的数据类型和函数声明等

2.2封装界面

void menu() {
	printf("********************");
	printf("*********1go********");
	printf("*********2out*******");
	printf("********************");
}
void game() {
}

int main()
{
	int input = 0;
	do {//使用do-while循环的原因是要先进入再作循环,避免输入0无操作情况
		menu();
		printf("请输入");
		scanf("%d", &input);
		switch (input) {
		case 1:
			printf("扫雷");
			game();//封装函数
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("非法输入,请重新选择");
			break;
		}
	} while (input);
	
	return 0;
}

初始化棋盘:使用宏定义的好处之一就是便于简化和理解代码

由上文,我们需要:

#define ROW 9 #define COL 9

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

#include<stdio.h>
#include"game.h"
void InitBoard(char arr[ROWS][COLS], int rows, int cols,char set) {
	int i = 0;//行
	for(i=0;i<rows;i++){
		int j = 0;
		for (j = 0; j < cols; j++) {
			arr[i][j] = set;
		}
	}
}
void DisplayBoard(char arr[ROWS][COLS],int row,int col) {
	int i = 1;
	for (i = 1; i <= row; i++) {
		int j = 0;
		for (j = 1; j < col; j++) {
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

我们解决了数组访问越界的问题,同时我们要注意为了节省代码,我们尽量要让一个函数实现更大的用处。

头文件:

#pragma once

#define ROW 9
#define COL 9

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

void InitBoard(char arr[ROWS][COLS], int rows, int cols,char set);
void DisplayBoard(char arr[ROWS][COLS],int row,int col);

函数构造:

void game() {
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	//初始化棋盘
	InitBoard(mine, ROWS, COLS,'0');//'0'
	InitBoard(show, ROWS, COLS,'*');//'*'
	//打印棋盘
	DisplayBoard(show,ROW,COL);
}
                                        效果图:

在这里插入图片描述

2.3建立坐标系

我们如何才能获取用户输入的合法数据呢?我们可以建立坐标系,让用户输入列坐标和行坐标来获取信息。

在这里插入图片描述

void DisplayBoard(char arr[ROWS][COLS],int row,int col) {
	int i = 1;
	//先打印行坐标:
	for (i = 0; i <= row; i++) {// i=0开始,使得棋盘符合实际情况
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; i++) {  
		int j = 0;
		for (j = 1; j <= col; j++) {
			if (1 == j) printf("%d ", i);//打印列坐标
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

为什么要先打印行坐标?因为按照打印顺序,如果先打印列坐标,行坐标就会在下面了。

我把打印坐标的代码单列出来,为了代码对齐和美观,我们注意:

  • 行坐标打印的空格处理和i从0开始
  • 列坐标打印的空格处理和i从1开始
for (i = 0; i <= row; i++) {// i=0开始,使得棋盘符合实际情况
		printf("%d ", i);
	}
for (j = 1; j <= col; j++) {
			if (1 == j) printf("%d ", i);//打印列坐标

2.4实现游戏操作

2.4.1布置雷

setmine(mine, ROW, COL);//在test.c中的void game中进行函数的使用
void setmine(char arr[ROWS][COLS], int row, int col);函数声明
void setmine(char arr[ROWS][COLS], int row, int col) {
	//随机布置10个雷
	int count = 10;
	while (count) {
		int x = rand() % COL + 1;
		int y = rand() % ROW + 1;

		if (arr[x][y] == '0') {
			arr[x][y] = '1';
		}
		else count++;
		count--;
	}
}//函数的实现注意

!在这里插入图片描述

我们将1当作雷存放在0的表格中。

这里我要提醒几点:

  • 注意打印顺序,先设置雷再打印雷的数组
  • 使用rand()时,需要stdlib.h和time.h头文件
  • 注意if语句中的条件判断,以确保我们放置了10个雷
  • 一定要随时转动视角:

现在test.c中设立函数并放置game()函数中

再在game.h中完成函数的声明

最后在game.c文件中完成函数的实现

2.4.2排查雷

在show数组中验证信息,在mine数组中完成排雷。

finedmind(mine, show, ROW, COL); 由此建立函数。

void finemine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col); 建立函数声明

建立函数实现:

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 - 10) {//因为排查71次!
		printf("请输入要排查的坐标\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
		{
			if (mine[x][y] == '1')
			{
				printf("you lose!");
			}
			else
			{
				int n = getminecount(mine, x, y);
				show[x][y] = n + '0';
				DisplayBoard(show, ROW, COL);
				win++;
			}

		}
		else printf("非法坐标,请重新输入\n");
	}
}
//嵌套子函数形式:
int getminecount(char arr[ROWS][COLS], int x, int y) {
	return mine[x - 1][y] + mine[x - 1][y - 1] 
	+mine[x][y - 1] + mine[x + 1][y - 1] +
	mine[x + 1][y] + mine[x + 1][y + 1] +
	mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0';}

至此我们的程序完成。

3.总结-再一次回顾

  1. 我们首先对“扫雷”的玩法和规则研究,我们知道了构造两个数组进行处理,一个进行Show,一个进行Mine。同时知道了数组访问越界的处理手段-构造一层代码壳。
  2. 然后我们对游戏进行拆解:大厅界面→显示扫雷操作模块→扫雷游戏的命令总执行模块→预处理数组和显示模块→坐标轴定位模块→雷生成模块→排雷模块和结束处理模块。
  3. 几个难点回顾:
  • 建立坐标如何实现坐标和元素对齐,以及如何坐标和元素一一对应
  • 布置雷如何实现随机布置,且数量正好为10
  • 我承认排雷太难了,排雷时如何反馈结果以及如何限定范围问题很关键!

game.c

game.h

test.c

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值