基于VSc2022的扫雷游戏实践

一、游戏前的准备

第一步,不管三七二十一,创建main()函数,没有main()函数,程序找不到运行的入口,所以代码一开始可以写成这样:

int main()
{
    return 0;
}

第二步,我们玩大部分游戏,进去的第一个页面都是“开始游戏”or“退出游戏”这种菜单页面。我们能不能也整一个呢?于是代码变成了这样(用了printf()别忘了添加头文件stdio.h):

#include<stdio.h>
int main()
{
    printf("********************\n");
    printf("***  1.继续游戏  ***\n");
    printf("***  2.退出游戏  ***\n");
    printf("********************\n");
    return 0;
}

运行一下看看:

第三步,这时候界面已经出来了,但是你发现不能控制,你希望输入1——开始游戏,输入2——退出游戏。有输入?你马上就想到了scanf()函数,输入1——开始游戏,输入2——退出游戏,输入其他数字——提醒输入有误,你马上想到用switch语句来实现多情况问题。这时候代码变成:

#include<stdio.h>
int main()
{
    printf("********************\n");
    printf("***  1.继续游戏  ***\n");
    printf("***  2.退出游戏  ***\n");
    printf("********************\n");
    scanf("%d", &input);
	switch (input)
	{
	case 1:
	{
		//游戏;
	}
	case 2:
	{
		//????
	}
	default:
	{
		printf("输入有误,请重新输入:");
	}
    return 0;
}		

第四步,写着写着问题来了,输入为2的时候退出游戏?这一步要怎么实现,聪明的你又想到了——用循环,这个时候用do-while循环特别合适,因为你想进来就开玩,每玩玩一把后,你还可以选择是否继续游玩。于是代码变成这样:

#include<stdio.h>
int main()
{
    printf("********************\n");
    printf("***  1.继续游戏  ***\n");
    printf("***  2.退出游戏  ***\n");
    printf("********************\n");
    scanf("%d", &input);
	do
	{
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		{
			//游戏
		}
		case 2:
		{
			break;
		}
		default:
		{
			printf("输入有误,请重新输入:");
		}
		}
	} while (input != 2);
    return 0;
}	

第五步,这还没开始,代码就开始冗长了,这时候能不能用函数来模块化?这时候你想把界面放到一个函数里面,游戏放到一个函数里面,会不会看起来要简便很多,答案是:是的,现在代码变成了这样:

main函数部分:

int main()
{
	int input = 0;
	print();
	do
	{
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		{
			game();
			again();
		}
		case 2:
		{
			break;
		}
		default:
		{
			printf("输入有误,请重新输入:");
		}
		}
	} while (input != 2);
	return 0;
}

开始菜单部分:

void print()
{
	printf("********************\n");
	printf("***  1.开始游戏  ***\n");
	printf("***  2.退出游戏  ***\n");
	printf("********************\n");
	return;
}

继续游玩界面部分:

void again()
{
	printf("********************\n");
	printf("***  1.继续游戏  ***\n");
	printf("***  2.退出游戏  ***\n");
	printf("********************\n");
	return;
}

游戏内容暂时写成这样,方便调试对不对,后续将着力开发这个:
 

void game()
{
	printf("扫雷...\n");

}

别忘了头文件:

#include<stdio.h>

总的就是这样:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//开始菜单
void print()
{
	printf("********************\n");
	printf("***  1.开始游戏  ***\n");
	printf("***  2.退出游戏  ***\n");
	printf("********************\n");
	return;
}
//继续游玩?
void again()
{
	printf("********************\n");
	printf("***  1.继续游戏  ***\n");
	printf("***  2.退出游戏  ***\n");
	printf("********************\n");
	return;
}
//游戏内容
void game()
{
	printf("扫雷...\n");

}
int main()
{
	int input = 0;
	print();
	do
	{
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		{
			game();
			again();
		}
		case 2:
		{
			break;
		}
		default:
		{
			printf("输入有误,请重新输入:");
		}
		}
	} while (input != 2);
	return 0;
}

运行一下看看:

没问题,输入1、2以外的数字提示输入有误,输入1开始游玩,输入2退出游戏。

二、游戏分析

我们分析一下扫雷游戏,

       

以简单版的扫雷游戏为例,在9*9的表格中埋了有10个雷,我们点开后会出现各种数字,数字代表周围一圈雷的个数。

以下图为例,数字1代表红圈内雷的个数,这里只剩一个没翻开,所以这个位置一定是雷。

     

如果翻到雷,就结束游戏,显示所有的雷,同时上图的黄色表情发生改变,提醒游戏失败。

         

如果成功排除所有的雷,上图的黄色表情也会发生改变,不过这时候小黄人戴了墨镜,提醒游戏成功。

这些分析对我们有啥帮助呢?

1.扫雷游戏是在9*9的表格上进行的,所以我们可以直接创建二维数组。

2.每局游戏雷的分布都是随机的,所以我们还要随机进行埋雷。

3.如果点到雷,就提示失败。

4.完成全部排雷过程,提示成功。

三、游戏编程

3.1 创建雷盘

雷盘内肯定有两种情况,有雷和无雷,这里我们可以用1和0表示,1代表有雷,0代表无雷

但是,如果我们翻开一个棋盘后,出现了个数字1,这个时候就犯难了,这个1到底是表示这里是个雷还是说附近有一个雷?

最好的处理办法是用两个二维数组表示,一个数组只有0和1,表示埋雷的情况,同时便于我们调试程序,这里不妨用A数组表示;另一个数组用B数组,用*表示还没翻开,当我们输入行号和列号的时候,在俩程序同时进行比对,A数组中该行该列元素是0,则计算附近雷的个数,在B数组打印,如果是1,则将A数组中数字1在B数组同位置显示,并显示游戏失败。

输入(33)为例,A33=0,表示不是雷,而A33)附近元素有三个雷,所以在B33)中打印3,图中红色仅便于观察到变化,无其他用意。

以输入44为例,A44=1,表示该位置有雷,这里可以直接用printf函数输出:你踩中雷了,游戏失败!!

如果输入(14),A14=0,表示不是雷,而计算机在检索A14)附近元素时,由于上侧没有元素,所以还须考虑上下左右四个边界问题,非常麻烦,在这里我们可以将数组变成11*11的二维数组,埋雷只在A[1][1]~A[9][9]区间完成,其余部分没有雷,所以全部赋值为0,在B数组扫雷过程中,也只在B[1][1]~ B[9][9]区间完成,再以输入(14)为例,A[1][4]=A[0][3]+A[0][4]+A[0][5]+ A[1][3]+A[1][4]+A[1][5]+ A[2][3]+A[2][4]+A[2][5] A[0][3]A[0][4]A[0][5]恒为0 A[1][3]+A[1][4]+A[1][5]+ A[2][3]+A[2][4]+A[2][5]=1,所以B[1][4]打印为1,没问题。

这一方法既解决了边界问题,又简化了代码编程。

上述长篇分析只为证明11*11的二维数组能解决边界问题,由于B数组的*要创建字符型二维数组,在这里将A数组也创建成字符型,用字符型的‘0’和‘1’表示是否有雷,便于创建函数将A和B模块化处理。

此时游戏部分的代码为:

//游戏内容
void game()
{
	char show[11][11] ;//埋雷的位置
	char saolei[11][11] ;//游戏界面
	//初始化数组
	
	//随机埋雷

	//打印雷盘

	//判断是否有雷


}

3.2初始化数组

//初始化
void init(char arr[11][11], int x, int y, char z)
{
	for (int i = 0; i < x; i++)
	{
		for (int j = 0; j < y; j++)
		{
			arr[i][j] = z;
		}
	}
}

这里新建一个函数,初始化过程两个循环就能完成,每次运行到一个位置,都将要赋值的字符输入进去,这时候游戏内容部分变成了

//游戏内容
void game()
{
	char show[11][11] ;
	char saolei[11][11] ;
	//初始化数组
	init(show, 11, 11, '0');
	init(saolei, 11, 11, '*');
	//随机埋雷
	
	//打印雷盘
	
	//判断是否有雷

}

3.3 随机埋雷

//随机埋雷
void mine(char arr[11][11],int x,int y )
{
	srand((unsigned int)time(NULL));
	int z = 0;
	while (z < 10)
	{
		int i = rand() % x+1;
		int j = rand() % y+1;
		if (arr[i][j] == '0')
		{
			arr[i][j] = '1';
			z++;
		}
	}
}

既然随机埋雷了,当然少不了rand()函数,用了rand()函数,记得加上头文件:

#include<stdlib.h>

同时,由于rand()输出的数是个伪随机数,所以记得加上时间戳,而用了时间记得加上头文件:

#include<time.h>

既然是随机数,当然也可能取到重复的值,所以这里用了个循环,z表示埋雷个数,埋到有效雷才计数,当埋完10个雷跳出循环。

3.4 打印雷盘

//打印雷盘
void print_mine(char arr[11][11],int x,int y)
{
	printf("-----------------\n");
	for (int i = 0; i <= x; i++)
	{
		for (int j = 0; j <= y; j++)
		{
			if (i == 0)
			{
				printf("%c ", j+'0');
			}
			else if (j == 0)
			{
				printf("%c ", i + '0');
			}
			else
			{
				printf("%c ", arr[i][j]);
			}
		}
		printf("\n");
	}
}

虽然创建的数组是11*11,但实际埋雷和找雷的区域是9*9的,所以这里只要打印最中间9*9的数组,同时为了寻找方便,特意在打印后的数组的上区域和左区域各加一行(列),表上数字0~9。

到这里代码运行后为:

3.5判断是否有雷

void find(char arr1[11][11], char arr2[11][11], int x, int y)
{
	int z = 71;
	while (z)
	{
		printf("输入你翻开的区域:");
		scanf("%d %d", &x, &y);
		if (x > 0 && x < 10 && y>0 && y < 10)
		{
			if (arr1[x][y] == '1')
			{
				printf("boom!你踩中雷了,游戏失败!\n");
				break;
			}
			else
			{
				arr2[x][y] = arr1[x - 1][y - 1] + arr1[x - 1][y] + arr1[x - 1][y + 1] + arr1[x][y - 1] +
					arr1[x][y] + arr1[x][y + 1] + arr1[x + 1][y - 1] + arr1[x + 1][y] + arr1[x + 1][y + 1] - 8 * '0';
				print_mine(arr1, 9, 9);
				print_mine(arr2, 9, 9);
			}
		}
		else
		{
			printf("你输入有误哦~请重新输入\n");
		}
		z--;
	}
	if (z == 0)
	{
		printf("游戏成功,你真棒");
	}
}

通过输入行号和列号,判埋show数组内,该位置是‘1’还是‘0’,是‘1’则提醒游戏失败,是‘0’,则计算附近一圈‘1’的个数,并表示在数组saolei上,输入其他值则表示输入有误,当找到所有不是雷的区域后结束游戏,提醒游戏成功。

四、代码整理

到这里,代码初稿就算出来了,如下所示:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//开始菜单
void print()
{
	printf("********************\n");
	printf("***  1.开始游戏  ***\n");
	printf("***  2.退出游戏  ***\n");
	printf("********************\n");
	return;
}

//继续游玩?
void again()
{
	printf("********************\n");
	printf("***  1.再来一把  ***\n");
	printf("***  2.退出游戏  ***\n");
	printf("********************\n");
	return;
}

//初始化
void init(char arr[11][11], int x, int y, char z)
{
	for (int i = 0; i < x; i++)
	{
		for (int j = 0; j < y; j++)
		{
			arr[i][j] = z;
		}
	}
}

//随机埋雷
void mine(char arr[11][11],int x,int y )
{
	srand((unsigned int)time(NULL));
	int z = 0;
	while (z < 10)
	{
		int i = rand() % x+1;
		int j = rand() % y+1;
		if (arr[i][j] == '0')
		{
			arr[i][j] = '1';
			z++;
		}
	}
}

//打印雷盘
void print_mine(char arr[11][11],int x,int y)
{
	printf("-----------------\n");
	for (int i = 0; i <= x; i++)
	{
		for (int j = 0; j <= y; j++)
		{
			if (i == 0)
			{
				printf("%c ", j+'0');
			}
			else if (j == 0)
			{
				printf("%c ", i + '0');
			}
			else
			{
				printf("%c ", arr[i][j]);
			}
		}
		printf("\n");
	}
}

void find(char arr1[11][11], char arr2[11][11], int x, int y)
{
	int z = 71;
	while (z)
	{
		printf("输入你翻开的区域:");
		scanf("%d %d", &x, &y);
		if (x > 0 && x < 10 && y>0 && y < 10)
		{
			if (arr1[x][y] == '1')
			{
				printf("boom!你踩中雷了,游戏失败!\n");
				break;
			}
			else
			{
				arr2[x][y] = arr1[x - 1][y - 1] + arr1[x - 1][y] + arr1[x - 1][y + 1] + arr1[x][y - 1] +
					arr1[x][y] + arr1[x][y + 1] + arr1[x + 1][y - 1] + arr1[x + 1][y] + arr1[x + 1][y + 1] - 8 * '0';
				print_mine(arr1, 9, 9);
				print_mine(arr2, 9, 9);
			}
		}
		else
		{
			printf("你输入有误哦~请重新输入\n");
		}
		z--;
	}
	if (z == 0)
	{
		printf("游戏成功,你真棒");
	}
}
//游戏内容
void game()
{
	char show[11][11] ;
	char saolei[11][11] ;
	int x = 0;
	int y = 0;

	//初始化数组
	init(show, 11, 11, '0');
	init(saolei, 11, 11, '*');
	//随机埋雷
	mine(show,9,9);
	//打印雷盘
	print_mine(show, 9, 9);
	print_mine(saolei, 9, 9);
	//判断是否有雷
	find(show, saolei, x, y);
}

int main()
{ 
	int input = 0;
	print();
	do
	{
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		{
			game();
			again();
		}
		case 2:
		{
			break;
		}
		default:
		{
			printf("输入有误,请重新输入:");
		}
		}
	} while (input != 2);
	return 0;
}

可以看到,代码过于冗长,同时该代码只能输出9*9的雷盘,如果我想增加雷的个数和更改雷盘的大小,需要修改代码的很多地方,非常的麻烦。

所以对于冗长部分,我们可以把自定义函数放到其他文件中,再自定义个头文件,包含自定义函数的声明和头文件。

对于后一个问题,则可以把行号和列好用同一的定义表示,后续更改直接更改定义的值就好。

整理后的代码如下

自定义函数部分:

function.h   :函数声明部分

function.c   :  自定义函数部分

test_1.c      :  游戏主体部分

function.h

#pragma once

#define rows 9
#define cols 9
#define row rows+2
#define col cols+2
#define boom 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void print();

//继续游玩?
void again();

//初始化
void init(char arr[row][col], int x, int y, char z);

//随机埋雷
void mine(char arr[row][col], int x, int y);

//打印雷盘
void print_mine(char arr[row][col], int x, int y);

// 判断是否有雷
void find(char arr1[row][col], char arr2[row][col], int x, int y);

//游戏内容
void game();

function.c

#define  _CRT_SECURE_NO_WARNINGS
#include"function.h"
void print()
{
	printf("********************\n");
	printf("***  1.开始游戏  ***\n");
	printf("***  2.退出游戏  ***\n");
	printf("********************\n");
	return;
}

//继续游玩?
void again()
{
	printf("********************\n");
	printf("***  1.再来一把  ***\n");
	printf("***  2.退出游戏  ***\n");
	printf("********************\n");
	return;
}

//初始化
void init(char arr[row][col], int x, int y, char z)
{
	for (int i = 0; i < x; i++)
	{
		for (int j = 0; j < y; j++)
		{
			arr[i][j] = z;
		}
	}
}

//随机埋雷
void mine(char arr[row][col], int x, int y)
{
	srand((unsigned int)time(NULL));
	int z = 0;
	while (z < boom)
	{
		int i = rand() % x + 1;
		int j = rand() % y + 1;
		if (arr[i][j] == '0')
		{
			arr[i][j] = '1';
			z++;
		}
	}
}

//打印雷盘
void print_mine(char arr[row][col], int x, int y)
{
	printf("-----------------\n");
	for (int i = 0; i <= x; i++)
	{
		for (int j = 0; j <= y; j++)
		{
			if (i == 0)
			{
				printf("%d ", j);
			}
			else if (j == 0)
			{
				printf("%d ", i);
			}
			else
			{
				printf("%c ", arr[i][j]);
			}
		}
		printf("\n");
	}
}

//判断是否有雷
void find(char arr1[row][col], char arr2[row][col], int x, int y)
{
	int z = x * y - boom;
	while (z)
	{
		printf("输入你翻开的区域:");
		scanf("%d %d", &x, &y);
		if (x > 0 && x < 10 && y>0 && y < 10)
		{
			if (arr1[x][y] == '1')
			{
				printf("boom!你踩中雷了,游戏失败!\n");
				break;
			}
			else
			{
				arr2[x][y] = arr1[x - 1][y - 1] + arr1[x - 1][y] + arr1[x - 1][y + 1] + arr1[x][y - 1] +
					arr1[x][y] + arr1[x][y + 1] + arr1[x + 1][y - 1] + arr1[x + 1][y] + arr1[x + 1][y + 1] - 8 * '0';
				print_mine(arr1, 9, 9);
				print_mine(arr2, 9, 9);
			}
		}
		else
		{
			printf("你输入有误哦~请重新输入\n");
		}
		z--;
	}
	if (z == 0)
	{
		printf("游戏成功,你真棒");
	}
}
//游戏内容
void game()
{
	char show[row][col];//埋雷的位置
	char saolei[row][col];//游戏界面
	int x = 0;
	int y = 0;

	//初始化数组
	init(show, row, col, '0');
	init(saolei, row, col, '*');
	//随机埋雷
	mine(show, rows, cols);
	//打印雷盘
	print_mine(show, rows, cols);
	print_mine(saolei, rows, cols);
	//判断是否有雷
	find(show, saolei, x, y);
}

test_1.c 

#define  _CRT_SECURE_NO_WARNINGS
#include"function.h"
//开始菜单

int main()
{ 
	int input = 0;
	print();
	do
	{
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		{
			game();
			again();
		}
		case 2:
		{
			break;
		}
		default:
		{
			printf("输入有误,请重新输入:");
		}
		}
	} while (input != 2);
	return 0;
}

四、后计

学完这个,我个人感觉有两个很头疼的问题

1.正常游戏中,如果排查位置不是雷,周围也没有雷,可以展开周围的一片,但是目前这个无法实现,好像要学完递归才能完成这个功能。

2.正常游戏中不是输入行号和列号,而是直接点击某个位置,就能识别出你要翻开的位置,但这个好像涉及前端?

我还是小菜鸡,继续学吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

fresh_man2

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

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

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

打赏作者

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

抵扣说明:

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

余额充值