一、构思
我想做一个9*9的10雷版扫雷游戏。类似这样的。
那么展示出来的就是9*9的数组。
扫雷游戏的逻辑是这样的:确定坐标,如果不是雷,显示周围8个坐标雷的总数,如果是雷,那么游戏结束。
难点难于如何设计确定坐标以及怎么统计雷的个数。同时,由于在统计边缘格子的时候,只有9*9的数组可能会发生数组越界。
如果用0和1来代表的话,0就表示不是雷,1表示是雷,那么在统计的时候,只需要把周围数字加一圈,有几个1就是几个雷。问题在于,数组是一组相同类型的元素组成的,而字符和数字不是相同类型的,不能放在同一个数组中。另外,当展示1的时候,显示出来的是这个位置是雷,还是周围雷的个数是雷,这可能会混淆。
所以我创建两个数组,一个用来放置雷,以及统计雷的个数,以0和1组成的数组。一个用来展示,以字符*组成的数组。两个数组大小一样。
别的想法:这个游戏,为了保持它的简洁性,我希望把函数的声明放在头文件,把函数的定义放在一个game.c文件,主函数放在play.c文件。
准备好这些,就可以开始动手了。
二、函数定义
2.1写出主函数的内容,大体的思路。
我想设计一个Print函数,它一开始就要打印。让我选择游戏开始还是退出。选择1,游戏开始,选择0,游戏退出。退出以后不再循环,游戏结束。选择1,则游戏继续。
因此,这里采用do while循环,条件写上 i 的值。和switch循环配套。选择0以后,退出游戏,但我想要它再显示一下进入游戏的界面,所以它要打印一下再退出。选择1,则开始游戏,写一个game函数,我需要game函数帮我实现玩游戏的功能。其他选择就是选择错误,就再输入一遍。
到这里我的主函数的内容就写完了。
#include "game.h" //包含一个头文件的内容,函数的声明
//都在头文件中
int main()
{
int i = 0;
do{ //使用do while循环,一开始就执行,输入i
//当i==1时,继续玩,当i==0时,程序结束。
Print();//定义一个函数,展示选择内容
printf("请输入数字:>>");
scanf("%d", &i);
switch (i)
{
case 0: Print();//选择0,打印了开局选择再结束进程
break;
case 1: game();//选择1 玩游戏,写个函数定义一下游戏内容
break;
default:
printf("输入错误,请重新输入\n");
}
} while (i);
return 0;
}
2.2函数的定义
2.2.1实现游戏开场选择设置
接下来,我要在另一个文件game.c来写各函数的定义。
进行开局打印的Print函数
游戏开场的设置如下图所示,它只需要展示下列内容,不需要返回值,所以用void。1是游戏开始,0是退出游戏。
void Print(void)
{
printf("***********************\n");
printf("***** 1.play ******\n");
printf("***** 0.exit ******\n");
printf("***********************\n");
}
2.2.2实现game函数
首先我要把game函数写出来,然后再往里面加内容。
前面分析过了,我用9*9的数组可能会导致数组越界,所以我要创建一个11*11的二维数组,此时,雷所展示的区域就在中间的9*9的位置。这样在统计边缘位置的雷的个数时,就不会发生数组越界的情况了。
arr1的数组全是字符0,待会我要往里面放雷,这个是白板,里面放着谜底(雷布置的区域等等)。arr2的数组全是*,是黑板,它用来给我玩扫雷的。
void game()
{
char arr1[11][11] = {'0'};
char arr2[11][11] = {'*'};
Boarddefine(arr1,arr2); //初始化棋盘
/*Boardplay(arr1);*/ //打印棋盘 arr1的棋盘如果提前打印出来,字符就会发生改变
//前面说了,arr1和arr2的排列一样第一行和第一列的字符都是
//坐标,所以统计雷的时候,若提前打印了arr1,就会统计出错。
//真正玩的时候,只需要打印arr2就好。而这个打印棋盘的代码是给
//自己调试的时候用的。
mineset(arr1);//放置雷
printf("\n");
Boardplay(arr2); //展示arr2的暗棋盘。
playgame(arr1,arr2);//扫雷
}
有了两个棋盘就要把里面放东西,一个放字符0,一个放字符*。写一个函数Boarddefine帮我初始化棋盘,参数是两个数组。
初始化完之后,下面可以再写个函数Boardplay(其实是boarddisplay,但当时忘了单词)打印一下两个棋盘的情况,是否有得到想要的效果。如果是的话,那么先把这行代码屏蔽,再往下走。不是的话,看一下自己哪个代码没写对,先修改再往下走。一边写一边调试。
我需要在白板的棋盘放置雷,写个函数mineset(arr1)帮我实现随机放置雷的功能。参数是arr1,因为我只需要在arr1放置雷就可以了。
放好雷以后,再用个换行功能。这样待会打印出来层次分明看的清楚。把刚刚需要的打印棋盘的函数放在这里。用来打印一下arr2的棋盘。打印出来是为了下一步进行玩游戏。展示给玩家没有被扫的雷的初始情况。
打印出来以后,就可以玩游戏了。具体的判断雷,游戏成功,游戏失败等内容,我也封装在一个叫playgame的函数里面,让它帮我实现玩游戏的功能。因为这一步要用到两个数组,所以传参是两个数组都传过去。
这样,我的game函数就写完了。接下来要填充刚才需要具体实现的内容。
2.2.3 填充函数具体内容
初始化棋盘函数
void Boarddefine(char arr1[11][11], char arr2[11][11])
{
int a = 0, b = 0;
for (a = 0; a < 11; a++)
{
for (b = 0; b < 11; b++)
{
arr1[a][b] = '0'; //arr1 全部都是0
arr2[a][b] = '*';
}
}
}
我没有改变形参的名字。初始化棋盘代码没什么复杂的。学过二维数组都知道,需要注意的是给char类型的初始化要用单引号。
打印棋盘的函数
void Boardplay(char arr2[11][11])
{
int i = 0, j = 0;
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
{ //展示棋盘 棋盘2
//虽然这个数组是11×11,但我只需要
//9乘以9 第一行和第一列作为坐标
if (0 == i && 0 == j) //这样布置会导致数组arr1也是按照arr2的方式布置的
arr2[i][j] = i+'0';
else if(0==i)
arr2[i][j] = j+'0';
else if (0 == j)
arr2[i][j] = i+'0';
printf("%c ", arr2[i][j]);
if (j == 9)
printf("\n");
}
}
}
明确一下我玩游戏时,想见到的arr2(黑板数组)是这样的。它需要有坐标,这样我扫雷的时候直接输入坐标就可以。内容依然是9*9,但展示的数组是10*10.整个数组是11*11的大小,但我只需要展示出这10*10的内容就可以。由于这是个字符数组,所以,作为整型数字的i加上作为字符的0才等于作为字符的 i 。
布置雷的函数
void mineset(char arr1[][11])
{
int i = 0, j = 0, m = 0;
srand((int unsigned)time(NULL));
while(m<10) //布置十个雷
{
int i = rand() % 9 + 1; //把雷布置在 1-9的坐标上
int j = rand() % 9 + 1;
if (arr1[i][j] == '0')
{ //不能重复布置
arr1[i][j] = '1';
m++;
}
}
}
引用随机数函数,随机布置十个雷。但是我只需要在1-9的位置放置雷就可以了。那是我们玩扫雷9*9的区域。只有当这个位置是‘0’的时候,代表它没有雷,那么放置雷‘1’,雷的数量+1。用m来计算雷的数量,如果是1的话,m是不变的。这样当m=10的时候,就可以跳出循环,也代表雷放置成功了。
放置成功以后,也可以再把打印棋盘的函数打印一下arr1看看是否是自己想要的。
玩扫雷的函数
void playgame(char arr1[][11],char arr2[][11]) //arr1和arr2的元素个数必须一致,不然
//可能会出现坐标错误
{
int i = 0, j = 0, n = 0; //n的声明不能放在循环里面,不然只有n==1时才可以通关
do {
printf("请输入坐标:>>");
scanf("%d %d", &i, &j);
n++;
if (arr1[i][j] == '1')
{
Boardplay(arr1);
printf("很遗憾,你被炸死了\n");
break;
}
else
{
int lei = countmine(arr1,i,j);//我需要一个把雷的数量统计一下的函数
arr2[i][j] = lei + '0';//显示周围雷的个数 数字+字符等于字符
Boardplay(arr2);//再展示一下现在的游戏进度
}
if (n == 71)
{
printf("恭喜你,游戏通关\n");//如果排了71个雷,说明已经成功了
break;
}
} while (1);
}
仍然用do while循环,因为我希望上来就可以直接玩。前面打印出来了这个arr2,所以下面我要输入坐标。如果输入1 1,那么要判断这个位置是不是雷,如果是雷,那么游戏结束。如果不是雷,那么周围有几个雷。雷放在arr1里面,但arr2和arr1的大小坐标是一样的。所以arr2输入的坐标在arr1中也是有效的。
我们用arr1来判断雷。如果输入的坐标值是字符1,那么被炸死,如果不是1,那么统计周围雷的个数。统计完以后这个坐标要显示数字。我写一个函数countmine用来计算周围雷的个数,并把计算的结果返回到这个坐标。然后再展示剩下的扫雷情况。
当我扫完10个雷的时候,游戏通关。每输入一次坐标就相当于排雷一次,如果我能排雷71次,我就成功了。所以用n计数,每输一次坐标就+1.当然后期想玩点大的,71也可以改成变量的形式。这里要注意的是,变量的声明要在do while循环外面,不然每次进循环,n都会重新变为0,那么永远无法判定成功通过游戏了。
无论被炸死还是通过游戏,都要用break来跳出循环。回到选择1玩游戏还是0退出游戏的页面。
数雷的函数
下面只剩下数雷的函数,把周围坐标加一圈就可以了。这里要注意的只是字符减字符等于数字。减字符0的话,值不变。x是整型数字,而arr1的元素都是字符,所以这里要通过减字符0来得到数字,再返回。当然也可以创建一个字符变量,这样不用减字符0。返回以后直接把字符数值赋值在对应的arr2的坐标上就可以。
int countmine(char arr1[][11],int i, int j) //把周围雷的数量都加一下就知道了
{
int x= arr1[i + 1][j + 1]-'0' + arr1[i + 1][j] - '0' + arr1[i + 1][j - 1] - '0' + arr1[i][j - 1] - '0' + arr1[i - 1][j - 1] - '0' + arr1[i - 1][j] - '0' + arr1[i - 1][j + 1] - '0' + arr1[i][j + 1] - '0';
return x;
}
接下来再在game.h的头文件里面把需要声明的函数都声明一下,在其他两个文件中包含一下头文件就可以了。
三、运行
运行以后就是这样,可以玩通关了。游戏通关后,会再显示主页面功能给玩家选择继续游戏还是退出游戏。运行一次可以玩到腻。
ps:刚准备写的时候真没想到会写这么长。