目录
一:游戏要实现的目标
二:对多文件使用的基础介绍
在这次游戏代码的实现中,我们将建立多个文件,在main文件是我们游戏的主体和框架,game.c放置的是游戏实现的代码,game.h放的是我们自定义函数的声明。
其中我们要在主函数中引用我们自己定义的头文件格式:#include "game.h"。
先简单介绍一下多文件的好处:
1.有利于多人协作完成,在完成复杂代码中,多文件可以让多个程序员一起编程,每个人负责一个小部分
2.有利于寻找错误,方便数值的更改,让代码更有可读性(这句话在本次代码中有初步体现)
三:对游戏的分析设计
3.1游戏思路分析
1.对于最基础的扫雷格式,是9*9形式的,而要打印出9*9的表格,就需要用到二维数组的打印。
2.我们需要随机设置10个雷,这就需要用到生成随机数的函数rand,一想到rand就要想到在猜数字用戏中对rand种子数设置所用到的函数srand
3.还要在玩家确定一个地方后,如果这个地方不是雷,就要显示这个地方周围区域有几个雷
4.打印一个建议游戏菜单
3.2需要思考的问题
1.对于分析1和3来说,我们需要用二维数组打印一个9*9的表格,确定表格一个位置后,如果不是雷,还要数清周围区域(即周围八个格子)雷的数目,那就可能存在图片问题:
对于上面图片处于第八行的数字来说,它周围八个数字会超出数组的最大值,也就是说在我们编程时,如果玩家选择的是边框上的数字,再计算周围有几个雷时会出现数据溢出的情况,那怎么解决呢?难道我们对边框外的每一个溢出数据都要一一说明吗?不如换一个思路,让数组扩大,让数0据都在数组可存储的范围。例如下图:
最好的解决办法就是让数组扩大一圈,这样就可以排查9*9棋盘中所有坐标周围雷的数目。当让这个范围也不要扩的太大(比如扩大到100*100,绝对能装下),一是没这个必要,二是过大,会占多余的内存,也不易查找9*9棋盘内的数据。
2.对于分析3,我们还要再想一个问题
我们放置雷时,在最基础的版本,从上面两张图片也可以看出我们用0表示无雷,用1表示有雷,那如果我们选择了一个地方,它显示出1,它表示是的究竟是有雷的1还是这个地方无雷,但周围有一个雷呢?表述会出现歧义。所以我们不妨建立两个相同的11*11表格,一个负责放置雷的信息,另一个放置排雷的数目的信息,刚开始打印表格时,肯定不能让玩家直接看见那个地方有雷那个地方没有,所以需要将信息隐藏起来,不妨用打印*
如图,为设计预想
3.我们这个代码的长度在200+,所以如果一次行把所有的功能全打印在main函数中,太过冗长,不利于他人阅读,也不利于自己纠错,改进,所以我们要用多文件,自定义函数分多模块来完成。
四:代码的实践
了解游戏大概要实现的功能,分析完潜在的问题后,我们可以开始正示敲代码了。
4.1打印游戏菜单
再简单的游戏,也应该有一个游戏界面让玩家看到,并且让玩家拥有是否进入游戏的选择权。这是就需要在正式开始游戏部分的代码前,先打印一份游戏菜单。
上述代码就可以打印一份简易菜单。
注:对于代码较多的工程来说,最好写一小段代码就运行一下,判断代码没有出错,不然写完工程代码无法运行,代码太长又不好寻找。
4.2游戏模块之创建基本变量
确定好代码无误后就可以在case1中自定义一个game的函数,开始代码书写。
在图片中可以看出,我们要用*打印表格时,*是字符,而这两个要打印的表格所用到的函数程序是一样的,如果我们用数字1和数字0表示有无雷,数组类型就不一样,自定义函数时就要用两组,无法提现函数的复用性,也比较麻烦,所以在此处我们就可以用字符1和字符0(即;'1' '0')
4.3打印棋盘,并完成数组的初始化
我们创建完数组后,首先要对两个数组初始化,让其中所有的地址均为‘0‘或*,我们可以自定义一个函数InitMine来完成。
从第一张代码图看出,我们在初始化时只能初始化一个字符,而两个数组要初始化两个不同的字符,那怎么办呢?----再建立一个变量,用来接受字符
那我们初始化之后,想看看自己有没有初始化成功呢?正好来到游戏模块的另一步,打印棋盘。
在这里我们要注意在打印棋盘时,我们给玩家展示的依然是9*9棋盘,我们为防止数据溢出而多加的那一圈不再打印出来,也就是说,我们打印只打印1~9即可。
此时当我们运行时会有一个错误显示printf未定义,原因是我们在main.c文件中加了printf的头文件,而在game.c中没有加printf的头文件,这是由于两个文件都引用了game.h这个头文件,不妨把printf的头文件也放进去,这样即使以后工程有很多源文件,一些函数的头文件敲一次就可以了。
通过打印结果我们可以看出,目前我们的逻辑还没有错。那么打印完框后,我们发现,如果我们直接让玩家输入坐标,坐标的行列数目并不是一目了然的,这样不利于玩家游戏体验,所以我们不妨打印出行列的数目,让坐标更加清晰。
打印完列之后,发现又出现问题了,原本对齐的行因为多了一列,对不起了,那怎么办呢?我们可以让行也加一个,让i=1变成i=0
这样一个坐标清晰的9*9棋盘就打印出来了,当然我们为了让玩家知道游戏开始了,可以加一个小细节
更正上述代码中,j再循环中的赋值是1,而不是0
到此处,我们的棋盘就打印好并初始化了,下一环节就是:布雷
4.4布雷
在简单版的扫雷中,一般是10个雷,我们就用10个雷举例,各位友友想设计几个雷,也可以根据个人想法。
扫雷游戏的关键就是排除不同位置的雷,而且没局游戏位置都不能一样,这就要请我们猜字游戏用到的老朋友rand函数了,而对应rand函数的种子,又要请出srand了,在这两个函数的辅助下,我们就可以保证每次游戏雷的数目随机了。
在以上代码中,我们就成功的随机布置好了雷,在这过程中有以下两点要注意:
1.不要忘记rand,time函数的头文件
2.在x=rand()%row+1时,拿row=9举例,此时取模的范围是0~8,加一之后就变成了1~9.如果模10,范围在0~9就太大了。
在布雷完成后,就差最后一步排雷,就大功告成了!
4.5排雷
在排雷这一步中,我们首先要弄明白,最后展现给玩家的9*9棋盘,打印的是show数组的,所以我们排雷是,首先要在mine数组统计所输入的值再不是雷的情况下,周围有几个雷,再把有几个雷这个信息传送给show数组。
这样,基础的扫雷就可以运行了。
在这其中我们用来一个比较陌生的知识,字符与数字的转换:
‘0’-‘0’=0 就是字符对应的ASCLL码值相减,所以想要得到字符四就可以用‘0’加上4
‘1’-‘0’=1
‘4’-‘0’=4
这时,我想测一下游戏成功是,会不会有输出,我们玩完一场游戏,有点太浪费时间,这是不妨把10个雷改成80,但是这是又有一个问题,有很多地方有10,一个个该很麻烦,也不方便以后数值的改变,不妨设一个变量,把雷的数量存在变量中。
以上就是实现代码的全过程了
五:基础版扫雷可扩展
六:完整代码
game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include <math.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define num 80
//初始化棋盘
void InitMine(char arr[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void PrintMine(char arr[ROWS][COLS], int row, int col);
//虽然打印的区域变成了9*9,但是数组的大小还是不变11*11
//布雷
void PutMine(char arr[ROWS][COLS], int row, int col);
//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c
#include "game.h";
void InitMine(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 PrintMine(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
{
printf("-----扫雷-----\n");
for (i = 0; i <= row; i++)//打印行
{
printf("%d", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d",i);//打印列
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c", arr[i][j]);
}
printf("\n");
}
}
}
void PutMine(char arr[ROWS][COLS], int row, int col)
{
//让横纵坐标都是1~9之间
int count = num;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
//避免重复布雷
if (arr[x][y] == '0')//避免两次随机数模玩以后,值相同
{
arr[x][y] = '1';
count--;
}
}//循环产生10个雷,所以根据条件循环次数可能会大于10
}
int GetMine(char mine[ROWS][COLS],int x, int y)
{
//把字符转换成数字
return (mine[x - 1][y - 1] + mine[x - 1][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y - 1] +
mine[x - 1][y + 1] + mine[x - 1][y + 1] + mine[x + 1][y + 1] + mine[x + 1][y + 1]-8*'0');
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int win =0;
//一共有81格,除去10个雷,最多循环71次
while (win < row * col - num)
{
printf("请输入你要排查的坐标-->> ");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == 1)
{
printf("很遗憾,游戏失败!\n");
PrintMine(mine, ROW, COL);
break;
}
else
{
int count = GetMine(mine, x, y);
show[x][y] = count + '0';//把数字转为字符的形式
PrintMine(show, ROW, COL);
win++;//没成功一次,就加1,最多71次(以9为例)
}
}
else
printf("您输入的值不在范围内,请重新输入!\n");
}
if (win == row * col - num)
{
printf("恭喜你成功通关\n");
PrintMine(show, ROW, COL);
}
}
test.c
#include "game.h";
void game()//该函数是确保游戏运行的,也不需要返回值
{
char mine[ROWS][COLS] = { 0 };//用于布置雷
char show[ROWS][COLS] = { 0 };//用于排雷
//如果我们直接写11,在以后换棋盘规格后,所有的11都要更改,过于麻烦
//所以不妨用变量表示,以后只改变量就可以改变所有的数值
InitMine(mine, ROWS, COLS,'0');//'0'
InitMine(show, ROWS, COLS,'*');//'*'
//PrintMine(mine, ROW, COL);//在游戏时,排雷界面是不打印的
//此时只是看一下我们代码对不对
PrintMine(show, ROW, COL);
PutMine(mine, ROW, COL);//布雷只在mine中布置就行
PrintMine(mine, ROW, COL);//查看一下雷的分布
FindMine(mine, show, ROW, COL);
}
void menu()
{
printf("******************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("******************\n");
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();//不需要返回值,只要打印出菜单就行
printf("请输入 -> ");
scanf("%d", &input);
switch (input)//根据input的值选择不同的结果
{
case 1 :
game();
break;
case 0 :
printf("退出游戏\n");
break;
default :
printf("输入错误 请重新输入\n");
break;
}
} while (input);//用do while循环上来就会先执行一次,让玩家选择
return 0;
}
好啦!简易版的扫雷就完成了,友友们,有问题老规矩哦!