本文章是边分析编写的,便于理解,不用再去分析我的代码,所以会很长,而且都是每一步骤下该思考的,该实现的片段具有特定功能代码;
结尾会有完整的代码哒~可以看目录嘛~
首先是整个游戏设计的基本思路:
一、创建文件
利用“封装”的思想,简化编码复杂度,提高编码流畅性,将一整个项目划分为三个部分:
(1)游戏逻辑文件:test.c
(2)游戏具体函数实现文件:game.c
(3)游戏相关内容头文件:game.h(符号的声明,函数的声明,泛用头文件的包含)
二、创建简易菜单建议用do--while+switch语句
这样可以根据选择方便的进行下一步(比较简单,就不啰嗦啦~)
三、开始思考
就算什么都不会也可以先做前面这几步;
(1)首先扫雷扫雷,先得有地雷对吧?那地雷放哪呢?二维数组啦~
那要几个数组呢?布置雷一个,玩家肯定也有另外做一个,最基本就是两个啦
所以先定义两个数组,多大?因为大小要能方便更改,所以在头文件里面用宏定义就好了
(2)定义完数组接下来就是初始化,初始化成什么就看心情呗,初始化完顺便打印一下下看看
(3)接下来就到布置雷,随机生成坐标什么的就算不会也能想到吧
(4)布置完,就应该可以开始游戏啦
1.怎么进行呢?
玩家选项有:排雷 :标记雷 (switch语句?熟悉吗?)
大致就是根据输入坐标来进行操作呗(具体 怎么实现不是现在想哒)
2.既然是玩,那就要有输赢判定啦
那判断输赢的函数就少不了咯
四、正式开始敲代码 每次写完一个函数都要看一下实际效果,再来修改哦 下面的步骤边看边写都可以,不要全部看完再写,记不住哒首先呢,因为玩家输入的坐标都是1开始的,而数组是从0开始的,所以要准备大一圈的数组呢
(其实主要是预防数组越界)
所以行和列就要有两个啦,顺便设置雷的个数:
#include#include #include #include #define ROW 3 //行 #define COL 3 //列 #define ROWS ROW+2 //实际的行 #define COLS COL+2 //实际的列 #define MINE_COUNT 1 //地雷数
先写游戏逻辑,具体函数实现先不管:
(1)定义并初始化数组
实在test.c 的 game()函数里面哦~
void game()
{
//创建两个char数组
char mine[ROWS][COLS] ;
char show[ROWS][COLS] ;
char arr[ROWS][COLS] ;
//初始化mine-->'0';show-->'*'
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
InitBoard(arr, ROWS, COLS, '0');
//打印数组函数
Display(mine, ROW, COL);
Display(show, ROW, COL);
}
由于我们用的是实际上的大数组,所以是ROWS和COLS
我们约定:字符'0'代表无雷,字符‘1’代表有雷
展示给玩家看的数组show:星号*代表有雷,
arr数组先别管哦,不打也可以的,现在还没用到
由于让初始化函数功能更强大,所以我们是根据最后输入的符号来确定把数组初始化成什么样的
在test.c里面写完上面这些后,到game.c里面声明一下:
//在game.h里面: void InitBoard(char board[ROWS][COLS], int rows, int cols, char set); void Display(char board[ROWS][COLS], int row, int col);
接下来就是要具体实现函数功能了吧?(初始化与展示)
那是在game.c里面具体实现呢:
首先要先包含一下“game.h”哦~
#include"game.h"关于即将出现的函数:函数体里面要用到什么参数,就传什么参数,这个也可以后期再慢慢加上去,不需要烦恼,欸?这个函数到底该设置几个参数呢? 1.初始化函数:双循环遍历二维数组啦~(在game.c文件里面)
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
//printf("%c ", arr[i][j]);//测试
}
//printf("n");//测试
}
}
ps:看到那两个测试了吗?那是方便你运行查看实际效果哒~
2.展示数组的函数:有看到传参用的是ROW和COL吗?
//打印函数 Display(mine, ROW, COL); Display(show, ROW, COL);
因为展示给玩家看的是下标1~9的小数组啊!但是数组本身是0~11的大数组所以board后面是ROWS和COLS,为什么是0~11而不是0~10?多了两行和两列嘛
void Display(char board[ROWS][COLS], int row, int col)
{
int i, j;
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
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.设置雷的函数//在test.c里面: //设置雷 SetMine(mine, ROW, COL); Display(mine, ROW, COL);//测试
在mine[][]里面设置,在玩家看得到的小范围里面设置雷:所以传的是ROW和COL
接着在game.h里面声明:
void SetMine(char mine[ROWS][COLS], int row, int col);
然后呢?到game.c里面具体实现:
随机生成坐标要game.h里面的头文件
#include
#include
#include
然后在主函数main()里面:
//test.c里面哦
int main()
{
srand((unsigned int)time(NULL));
}
假设row=9
%row就能生成0~8的数字,但是我们要的是1~9,所以+1啦~
坐标重复了,也要考虑到:
带有//测试的那一行代码都是不用打的给你自己看哒:
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = MINE_COUNT;
while (count)
{
//随机生成1~9的坐标
int x = rand() % row + 1;
int y = rand() % col + 1;
//坐标不能重复
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
printf("(%d,%d)n",x,y);//测试
}
}
}
好,看一下效果:(我顺便把坐标打印了)
4、排雷和标记雷的函数
有选项那就用-----switch~~~
排雷要想到三点:
1)玩家输入的坐标的情况对游戏的影响:
坐标是否合法呢?重复了该怎么办呢?越界了又怎么办呢?(都不难哒~)
1.坐标不是雷,继续游戏
2.坐标正好是雷,强制结束游戏,公布雷的分布(那要怎么才能实现结束游戏呢?)
2).什么情况下要结束游戏?结束游戏要涉及哪几个因素?(变量的值该如何变化?)
3)结束游戏有几种情况?
要新增加几个变量?
解释一下:下面的代码(FindMine这个函数就是用来排雷的函数)
case 1: a = FindMine(mine, show,arr, ROW, COL, &count); 下面就是针对黄字问题的解决措施:
1)正常排雷,(x,y)不是雷,那a就是1不变,一旦(x,y)是雷,那么立即更改a为0,强制结束游戏,这也要求FindMine()这个函数返回类型为int
2)为什么要&count呢?由于每排了一个不是雷的格子那么等待排的格子就会减一个,减到0那就排完雷了,所以这个count必须能够实时变换,所以要用地址传参哦,对于就要用整形指针接收:
int *所以在game.h声明里面:
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], char arr[ROWS][COLS],
int row, int col,int *count);
3)关于标记雷:一旦标记完所有雷,也是排雷成功的:所以也要取出mint_quantity的地址,以地址传参呢!每标记对一个mint_quantity的数值就会减一,减到0,就赢啦~
所以在game.h声明里面:
void MarkMine(char show[ROWS][COLS], char mine[ROWS][COLS],
int row, int col ,int * mint_quantity);
3.不想玩游戏了,中断游戏
//在test.c里面的game()里面:
int way; //就是选项的意思啦,不想玩了,0可以直接退出
int count = ((ROW * COL) - (MINE_COUNT));//要排查的数目:格子总数-雷的数啦
int mint_quantity = MINE_COUNT; //雷的数啦
int a=1; //特殊作用呢!万一正好排到雷,可以提前结束游戏哒
do
{
printf("请选择:(1)排雷 ;(2)标记雷 ;(0)结束游戏n");
scanf("%d", &way);
switch (way)
{
case 0:
break;
case 1:
a = FindMine(mine, show,arr, ROW, COL, &count);//排雷的函数
Display(show, ROW, COL);
break;
case 2:
MarkMine(show,mine, ROW, COL, &mint_quantity); //标记雷的函数
Display(show, ROW, COL);
break;
default:
printf("请重新选择n");
break;
}
if (!count) //排雷成功用跳出(1.排完所有空格)
{
printf("排雷 成功!n");
break;
}
if (!mint_quantity) //2.排雷成功用跳出(2.标记完雷)
{
printf("排雷 成功!n");
break;
}
if (!a) //排雷失败用跳出
break;
} while (way);
这样思路会不会很清晰呢?
虽然排雷和标记雷的函数都没有具体写,但是已经有了初步蓝图了
接下来就是排雷函数的实现啦:
4、排雷函数的具体代码实现:1)坐标合法性:再数字1~row里面就好了
2)非重复性:因为玩家一开始看到的show数组是这样的嘛:
每排过雷的地方是星号,每当排一个不是雷的格子,格子就会显示为周围八个格子的总雷数
这个数字再转化成对应的数字字符存到那个位置的show数组里面啦~所以判断是否重复就看这个坐标下的show数组是不是字符 “*” 啦
if (show[x][y] == '*')//非重复
但是呢,我偷懒没写重复了要弹出什么文字告诉玩家;毕竟没什么影响是不是?
3)紧接着就要判断是不是雷啦:
if (mine[x][y] == '0')//不是雷
每当排了一个不是雷的格子对应的count就应该减一个是不是?
但是使用的是地址传参,现在的count是个指针,所以是:
(*count)--;
不加括号就会出错呢!因为符号优先级的关系,我不解释了哦
4)既然不是雷那就要返回周围有几个雷,并把它存到show数组里面给玩家看:
amount=mine_amount(mine,show,arr,row,col,x,y,count);//返回周围雷的数目 show[x][y] = amount + '0';
因为字符“0”对应的数字是48嘛,所以返回的雷的数字加上一个字符“0”就是对应的数字字符啦!
但是mine_amount()这个函数在下一步才实现哦
一旦排到的正好是雷,我们就把show数组对应坐标改成字符“@”模拟炸死了~
下面就是1,2,3,4四个小小步骤全部完整实现的排雷代码:
直接扔给你肯定不好看懂啦~
//在game.c里面具体实现哒:
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], char arr[ROWS][COLS],
int row, int col, int* count)
{
int x, y,amount;
printf("请输入排雷坐标(x y)n");
scanf("%d %d", &x, &y);
//坐标合法性与非重复
if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法性
{
if (show[x][y] == '*')//非重复(因为玩家看到的是show里面的数组嘛~)
{
//判断当前的坐标;
if (mine[x][y] == '0')//不是雷
{
(*count)--;
arr[x][y] = '1';
//Display(arr, ROW, COL);
amount=mine_amount(mine,show,arr,row,col,x,y,count);//返回周围雷的数目
//amount=mine_amount()是一个新的函数呢~下一步再实现
show[x][y] = amount + '0';
return 1;
}
else
{
printf("你太搓啦!死了啦!n");
show[x][y] = '@';
return 0;
}
}
}
}
终于到了递归的核心函数了啦!
1)什么时候才开始递归呢? (当然是(x,y)这个坐标不是雷而且周围八个格子也没有雷啦~)
2)怎么统计周围八个格子的雷数呢?看了这个图就大概懂了吧?双循环遍历啊!
横坐标由x-1到x+1
纵坐标由y-1到y+1
那这样不需要跳过(x,y)吗?当然不需要啦!它本身周围没有雷嘛,加上(x,y)本身的0又不会改变什么,对不对?
3)开始递归条件:周围雷的数目为0呢:(tmp就代表周围雷的数目啦)只有tmp等于0,
!tmp才会是真对不对?
if (!tmp)
{
....;
}
我们思考一下就会发现不加限制的话,递归会重复坐标呀:
假设(3,3)周围一个雷也没有,那么,下一步就会把那些蓝色坐标依次传给
mine_amount()这个函数(就是函数的自我调用啦~)
假设第一个传过去的就是(2,2),那么这个坐标假设周围八个坐标又没有雷,是不是会再次把(2,2)周围的八个坐标传过去?这八个坐标其中就有(2,3)(3,2)(3,3),这就重复了嘛!
| 1,1 | 1,2 | 1,3 | 1,4 |
| 2,1 | 2,2 | 2,3 | 2,4 |
| 3,1 | 3,2 | 3,3 | 3,4 |
| 4,1 | 4,2 | 4,3 | 4,4 |
那怎么办呢?最好就是每次访问一个坐标我们就把它打上一个标记,下次要递归的时候做一个判断,只递归那些没有标记的坐标嘛!
所以哟!我们临时决定再创建一个标记数组(大小要和他们两个数组一样大哦)
一开始我们用初始化函数InitBoard()把它初始化成字符“0”(还记得初始化函数吧?)
没访问的坐标是字符“0”,每访问一个不是雷的坐标我们就把他改为字符“1”,这样只有坐标上是字符“0”的我们才递归,就不会重复啦!
所以现在一个看得懂上面那个排雷的函数里面的这部分:
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], char arr[ROWS][COLS],
int row, int col, int* count)
{
int x, y,amount;
printf("请输入排雷坐标(x y)n");
scanf("%d %d", &x, &y);
//坐标合法性与非重复
if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法性
{
if (show[x][y] == '*')//非重复
{
//判断当前的坐标;
if (mine[x][y] == '0')//不是雷
{
(*count)--;
///
arr[x][y] = '1'; //这里啦!
//
//Display(arr, ROW, COL);
amount=mine_amount(mine,show,arr,row,col,x,y,count);//返回周围雷的数目
show[x][y] = amount + '0';
return 1;
}
.........//下面的省略啦
}
}
}
好:既然递归的条件(tmp==0)和防止重复递归的都解决了那接下来就是,具体怎么递归啦:
我们是要做到这种效果哒:
输入一个坐标如果周围没有雷就会出现好多的0哒:
那我们在确定了当前坐标(x,y)周围没有雷的情况下,是不是也应该把(x,y)周围的八个坐标依次统计这八个坐标周围各自的地雷数啊?那么每个坐标都重复一样的事情!是不是就可以用递归了呀!所以啦:我们要和双循环遍历那八个坐标一样在遍历一次,但是每次访问一个坐标都会把这个坐标传给mine_amount()这个函数用来返回每个坐标周围地雷数目对应的字符啦
提个小问题:那递归什么时候停止捏?还记得递归的条件嘛?这个啦:
if (!tmp)
{
....;
}
然后就是一下常规的控制坐标不要越界和不重复的if语句啦:
注意一下每访问一个不是雷的坐标就要(*count)--
有同学看到static了吗?想到鹏哥没有?那我就不解释为什么要用static了哦~不用也行哒!
static int mine_amount(char mine[ROWS][COLS], char show[ROWS][COLS],
char arr[ROWS][COLS], int row, int col,
int x, int y, int* count)
{
int i, j;
int tmp = 0;
for (i = -1; i <= 1; i++)//用双循环把这9个坐标全部访问一遍再加起来嘛
{
for (j = -1; j <= 1; j++)
{
tmp = (mine[x + i][y + j] + tmp - 48);
}
}
//递归展开:当tmp==0
if (!tmp)
{
int a;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
//防止越界防止重复
if ( (x + i) != x || (y + j) != y)
{
if ((x + i) >= 1 && (y + j) >= 1 && (x + i) <= row
&& (y + j) <= col)
{
if (arr[x + i][y + j] == '0')
{
arr[x+i][y+j] = '1';
a = mine_amount(mine, show, arr, row, col, x + i, y + j,
count);
//count也要传过去啦!
show[x + i][y + j] = a + '0';
(*count)--;
}
}
}
}
}
}
return tmp;
}
5、标记雷的函数具体实现与声明:
比较简单就不解释啦:
//首先是game.h里面:
void MarkMine(char show[ROWS][COLS], char mine[ROWS][COLS],
int row, int col,int * mint_quantity);
再就是:如果标记了对的雷的话,那么mine_quality(等待排雷的数目)就要减一个:
void MarkMine(char show[ROWS][COLS], char mine[ROWS][COLS],
int row, int col, int* mint_quantity)
{
int x, y;
printf("请输入坐标:(x y)n");
scanf("%d %d", &x, &y);
if (mine[x][y] == '1')
{
(*mint_quantity)--;
}
show[x][y] = '!';
}
以上就讲完了哦!看到这里你也应该打完了才对,不然就浪费时间了呢
6、结尾:完整代码:(1)game.h:
#pragma once #define _CRT_SECURE_NO_WARNINGS 1 #include#include #include #include #define ROW 5 #define COL 5 #define ROWS ROW+2 #define COLS COL+2 #define MINE_COUNT 2 // //看函数名字就知道是什么作用的函数啦!! //初始化函数 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set); //打印函数 void Display(char board[ROWS][COLS], int row, int col); //设置雷 void SetMine(char mine[ROWS][COLS], int row, int col); //排雷 int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], char arr[ROWS][COLS], int row, int col,int *count); //标记雷函数 void MarkMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col,int * mint_quantity);
(2)test.c:
#include"game.h"
void menu()
{
printf("***************************n");
printf("***************************n");
printf("****** 1.play *****n");
printf("****** 0.exit *****n");
printf("***************************n");
printf("***************************n");
printf("***************************n");
}
extern void game();
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("结束游戏n");
break;
default:
printf("重新选择n");
break;
}
} while (input);
return 0;
}
void game()
{
//创建两个char数组
char mine[ROWS][COLS] ;
char show[ROWS][COLS] ;
char arr[ROWS][COLS] ;
//初始化mine-->'0';show-->'*'
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
InitBoard(arr, ROWS, COLS, '0');
//打印函数
//Display(mine, ROW, COL);
printf("下面这个是玩家看到的界面n");
Display(show, ROW, COL);
//设置雷
SetMine(mine, ROW, COL);
printf("下面这个是作弊用的地雷分布图啦:(不能给玩家看的那个)n");
Display(mine, ROW, COL);//测试
//正式开始游戏:排雷or标记雷
int way;
int count = ((ROW * COL) - (MINE_COUNT));//空格数
int mint_quantity = MINE_COUNT;
int a=1;
do
{
printf("请选择:(1)排雷 ;(2)标记雷 ;(0)结束游戏n");
scanf("%d", &way);
switch (way)
{
case 0:
break;
case 1:
a = FindMine(mine, show,arr, ROW, COL, &count);
Display(show, ROW, COL);
break;
case 2:
MarkMine(show,mine, ROW, COL, &mint_quantity);
Display(show, ROW, COL);
break;
default:
printf("请重新选择n");
break;
}
if (!count)//排雷成功用跳出(1.排完所有空格)
{
printf("排雷 成功!n");
break;
}
if (!mint_quantity)//2.排雷成功用跳出(2.标记完雷)
{
printf("排雷 成功!n");
break;
}
if (!a)//排雷失败用跳出
break;
} while (way);
Display(mine, ROW, COL);
}
(3)game.c
#include"game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
//printf("%c ", arr[i][j]);//测试
}
//printf("n");//测试
}
}
void Display(char board[ROWS][COLS], int row, int col)
{
int i, j;
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
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");
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
printf("下面这些是地雷的坐标咯,现在地雷数是%dn", MINE_COUNT);
int count = MINE_COUNT;
while (count)
{
//随机生成1~9的坐标
int x = rand() % row + 1;
int y = rand() % col + 1;
//坐标不能重复
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
printf("(%d,%d)n",x,y);//测试
}
}
}
static int mine_amount(char mine[ROWS][COLS], char show[ROWS][COLS],
char arr[ROWS][COLS], int row, int col, int x, int y, int* count)
{
int i, j;
int tmp = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
tmp = (mine[x + i][y + j] + tmp - 48);
}
}
//递归展开:当tmp==0
if (!tmp)
{
int a;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
//防止越界防止重复
if ( (x + i) != x || (y + j) != y)
{
if ((x + i) >= 1 && (y + j) >= 1 &&
(x + i) <= row && (y + j) <= col)
{
if (arr[x + i][y + j] == '0')
{
arr[x+i][y+j] = '1';
a = mine_amount(mine, show, arr, row, col, x + i, y + j, count);
show[x + i][y + j] = a + '0';
(*count)--;
}
}
}
}
}
}
return tmp;
}
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], char arr[ROWS][COLS],
int row, int col, int* count)
{
int x, y,amount;
printf("请输入排雷坐标(x y)n");
scanf("%d %d", &x, &y);
//坐标合法性与非重复
if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法性
{
if (show[x][y] == '*')//非重复
{
//判断当前的坐标;
if (mine[x][y] == '0')//不是雷
{
(*count)--;
arr[x][y] = '1';
//Display(arr, ROW, COL);
amount=mine_amount(mine,show,arr,row,col,x,y,count);//返回周围雷的数目
show[x][y] = amount + '0';
return 1;
}
else
{
printf("你太搓啦!死了啦!n");
show[x][y] = '@';
return 0;
}
}
}
}
void MarkMine(char show[ROWS][COLS], char mine[ROWS][COLS],
int row, int col, int* mint_quantity)
{
int x, y;
printf("请输入坐标:(x y)n");
scanf("%d %d", &x, &y);
if (mine[x][y] == '1')
{
(*mint_quantity)--;
}
show[x][y] = '!';
}
暑期编程PK赛
得CSDN机械键盘等精美礼品!


