文章目录
前言
一、整个排雷游戏的思维梳理
二、整体代码分布布局
三、游戏主体逻辑实现--test.c
四、整个游戏头文件的引用以及函数的声明-- game.h
五、游戏功能的具体实现 -- game.c
六、老六版本
总结
前言
路漫漫其修远兮,吾将上下而求索。
一、整个排雷游戏的思维梳理
当玩家点开游戏,就会出现菜单供玩家进行选择,是玩游戏还是退出游戏(当然,你还可以整点有意思的选项,让正经的排雷游戏变得不正经);
当玩家输入自己的选择后,要么退出游戏,要么进入排雷游戏;当玩家进入排雷游戏时,就要先给玩家展示一下棋盘,让玩家输入想要排查的坐标,计算机再判断玩家输入的这个坐标是不是'雷',如果是雷,游戏结束;当然菜单会再出现一次,玩家可自行判断要不要再来一局游戏;如果不是雷就要向玩家展示此坐标周围雷的个数,并且玩家会一直输入坐标进行排雷直到玩家踩到雷或者玩家排雷成功;
感觉不难,让我们一起来实现吧~
二、整体代码分布布局
明确分工,提高效率~
test.c //整个游戏的主题逻辑
game.c //游戏功能的具体实现
game.h //包含一些头文件以及函数进行函数声明
三、游戏主体逻辑实现--test.c
代码如下:
#include"game.h"void menu()
{printf("****************************\n");printf("*****1、 play **************\n");printf("*****0、 exit **************\n");printf("****************************\n");
}
void game()
{//创建棋盘--二维数组char mine[ROWS][COLS] = { 0 };char show[ROWS][COLS] = { 0 };//初始化棋盘InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');//电脑随机放雷SetMine(mine, ROW, COL);//向玩家展示所要排雷的棋盘DisplayBoard(show, ROW, COL);//玩家排雷FindMine(mine, show, ROW, COL);
}
int main()
{int input = 0;do{menu();printf("玩家请输入选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏成功\n");break;default :printf("输入错误,请重新输入\n");}} while (input);return 0;
}
注意:
1、 为什么创建了两个数组,并且都是字符数组
由于在 show 展示的棋盘上我们想要用 * 来初始化此棋盘,显然show 棋盘中底层用来存放数据的二维数组就应该是字符数组;如果要将雷的信息放在show 中,那么显示给玩家看也会一览无余雷的位置,所以这里应该创建两个相同的“棋盘”,一是用来专门存放电脑随机分配的雷的信息以及排雷,另外一个就是展示给玩家看的,实现与玩家的互动;那么这两个棋盘在类型与大小上保持一致就会更好操作,当然你也可以不让这两个数组一样,只不过是在处理上会更加麻烦了而已
2、数组的创建为什么用ROWS 与 COLS创建呢,而展示却是用ROW 与 COL
在头文件game.h 中,ROW ,COL 是我们定义的标识符常量即为9,而ROWS,COLS 在ROW ,COL 的基础上大了2;
创建的棋盘比实际的棋盘的横列均多2 的原因是,在排雷的时候,如果玩家输入的坐标不是雷的话,就要计算该坐标周围雷的个数;如果是计算边缘坐标周围的雷的个数,周围的坐标数都不同,所以为了统一处理,保证实际棋盘中的每个做坐标周围均有8个坐标;(当然你也可以不这样做,头铁的话只有麻烦自己了)
3、既然为字符数组是如何求得非雷坐标周围雷的个数的?
例如如果非雷用‘ ’ 来表示,雷用 ‘*’ 来表示,利用循环产生包括此非雷坐标的9个坐标,一个个判断产生的 坐标再判断是不是雷'*' 即可,是雷的话便利用计数器 count++ 即可;本文写的代码中,非雷为 ‘0’ ,雷为 ‘1’ ,由于字符本质上就是ASCII码值,所以也可以利用那么可以用加法与减法的原则来计算周围坐标雷的个数(当然,我用的是循环+计数器);
4、电脑随机放置雷的操作的原理
在此之前,我们得了解一下rand srand time;
rand 与 srand 的头文件 <stdlib.h>
time 的头文件 <time.h>
int rand(void); 库函数rand 没有参数,但是有返回值,其返回值类型为int;
库函数可以生成随机数,但是它是基于算法生成得,故而并不是真正的随机数,是伪随机数;rand 生成伪随机数的范围为 0-32767;
而使用rand 一般是要与srand 一起使用的;srand产生随机变化的数seed(种子), 而rand 是基于srand 产生的数即seed(种子)进行产生伪随机数算法的运算;当使用rand 的程序中没有srand时,rand 便会默认种子为1;
void srand( unsigned int seed) ;库函数的参数类型为 unsigned int,如若想让rand 基于 seed产生随机的值,那么这个seed 就得变化。而什么是变化的呢?聪明的你可能会说是时间!没错,真棒,就是时间;我们可以利用时间戳,即利用库函数 time ;
注:时间戳:当前时间与计算机时间 1970年1月1日午夜之间的差值(单位是秒);
time_t time (time_t * timer);
库函数 time 的返回类型为 time_t ,其参数为 time_t* 类型的指针;
即如果正常使用库函数time ,得有指针参数,time 会返回一位 time_t 类型的返回值;如果不想用time 的参数,那么就用空指针NULL;
只要每一次程序运行是 seed 都不同,那么便可以保证 rand 在每一次程序执行时产生的随机值不会有相同;所以将 seed 产生放在main 函数中即可;
5、玩家排雷--展开操作的原理
说“展开”,可能你还不知道是什么意思;你玩排雷游戏的时候,是不是有时会展开一片呢?展开这一片的功能就是此处探讨的展开功能;如下图所示:
展开的格子,是非雷且周围没有雷;--> 这里便包含了两个条件
当周围8个坐标有雷的时候,就不会展开为空格,取而代之的是标明此坐标周围雷的个数;
图解:
分析:当玩家输入一个坐标的时候,首先要判断此坐标是否在排雷对象棋盘的合理范围之内;在合理范围之内便进行上图的操作;会用到三个功能,一是计算一个坐标周围雷的个数,二是展开空格的功能,三是判断玩家输赢;
四、整个游戏头文件的引用以及函数的声明-- game.h
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2#define MINE_COUNT 10
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set);//电脑随机放雷
void SetMine(char mine[ROWS][COLS], int row, int col);//计算周围八个坐标雷的个数
int ArroundingMine(char mine[ROWS][COLS], int x, int y);//展开功能
void ExtendBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);//玩家排雷是否成功的判断条件
int IsWin(char show[ROWS][COLS], int row, int col);//向玩家展示所要排雷的棋盘
void DisplayBoard(char show[ROWS][COLS], int row, int col);//玩家排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
分析:
在头文件中实现头文件、函数的声明,以及定义标识符常量,只要源文件声明包含了此头文件,相当于此源文件也声明了一次;
注:1、#define 定义的标识符常量 --> 在全局使用时更易于修改,非常方便;
2、函数一定要做到先声明再使用!
五、游戏功能的具体实现 -- game.c
1、初始化棋盘
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
{int i = 0;int j = 0;for (i = 0; i < rows; i++){for (j = 0; j < cols; j++){board[i][j] = set;}}
}
就是二维数组的初始化,两重循环嘎嘎整起来就可以了!
2、电脑随机放雷
//电脑随机放雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int count = MINE_COUNT;while (count){x = rand() % row + 1;y = rand() % col + 1;//埋雷的范围是1-9if (mine[x][y] == '0'){mine[x][y] = '1';count--;}}
}
雷坐标的产生并不是每一次都可以产生有效的坐标,且雷坐标的个数受雷个数的影响(此处的 MINE_COUNT 就是在头文件中定义的标识符常量--雷的个数--为10),所以此处应该用循环,并且是用while 循环;
看上面的代码,相信聪明的你一定可以理解!
3、向玩家展示所要排雷的棋盘
//向玩家展示所要排雷的棋盘
void DisplayBoard(char show[ROWS][COLS], int row, int col)
{//展示的show 棋盘上的1-9//还要添上坐标int i = 0;int j = 0;for (j = 0; j <= col; j++){printf("%d ", j);}printf("\n");for (i = 1; i <= row; i++){printf("%d ", i);for (j = 1; j <= col; j++){printf("%c ", show[i][j]);}//打印完一行就换行printf("\n");}
}
这个函数就有点讲究了,不仅向玩家展示了棋盘,还加上了索引;记得换行记得换行记得换行!!!重要的事情说三遍!
二维数组在内存中是一块连续的空间,如果想打印出规规整整的棋盘样式,就得在每打印完一行之后,写个printf("\n"); 以实现手动换行;
其次就是加上索引:在每一行前加上行数,以表示此行是第几行;显然就会多一列,那么用一行的数字对应此列为第几列时,就应该从0开始;
注意你的目标,你打印的时 show 数组中1-9 行,1-9 列的数据;
4、计算周围八个坐标雷的个数
//计算周围八个坐标雷的个数
int ArroundingMine(char mine[ROWS][COLS], int x, int y)
{int i = 0;int j = 0;int count = 0;for(i = -1; i <= 1; i++){for (j = -1; j <= 1; j++){if (mine[x + i][y + j] == '1')count++;}}return count;
}
原理前面提过,如果不太理解这里的代码可以往前翻翻,不过相信聪明的你一定是理解的;
5、展开功能
//展开功能
void ExtendBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{//产生该坐标周围八个坐标int i = 0;int j = 0;int count = 0;for (i = x-1; i <= x+1; i++){for (j= y-1; j <= y+1; j++){//避免重查if (show[i][j] == '*'){//不是雷if (mine[i][j] == '0'){count = ArroundingMine(mine, i, j);if (count == 0){show[i][j] = ' ';ExtendBoard(mine, show, i, j);}else{show[i][j] = count + '0';}}}}}
}
基本上就是这张图的意思:
注:此处的show[i][j] = count + '0';
因为数组 show 是char 类型的数组,所以其元素是char 类型,然而count = ArroundingMine(mine, i, j); 计算周围坐标雷的个数的函数的ArroundingMine 的返回类型是 int 类型,所以用来接收其返回值的 conut 是 int 类型,想让数字转换成字符,利用ascii码值即可,即加上 '0' ;
6、玩家排雷是否成功的判断条件
//玩家排雷是否成功的判断条件
int IsWin(char show[ROWS][COLS], int row, int col)
{int i = 0;int j = 0;int count = 0;for (i = 1; i <= row; i++){for (j = 1; j <= col; j++){if (show[i][j] == '*')count++;}}return count;
}
7、玩家排雷
//玩家输入想要排查的坐标
// 输入的坐标合法
//避免重查
//在mine 棋盘上判断该坐标是不是雷;是雷,游戏结束;不是雷,游戏继续;
//在此坐标不是雷的基础上,计算该坐标周围雷的个数;如果个数为0,先排除重查的坐标,再计算此坐标周围八个坐标的周围八个坐标的雷的个数
//递归; 如果个数不为0,就在show 棋盘对应的坐标上展示此坐标周围雷的个数
//循环的结束:当玩家踩到雷或者排雷成功
//排雷成功的标准:在show 棋盘上剩下的*个数与雷的个数相同
//玩家排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int win = 0;int count = 0;win = IsWin(show, ROW, COL);while (win > MINE_COUNT){printf("玩家请输入想要排查的坐标:>");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){if (show[x][y] == '*'){if (mine[x][y] == '1'){printf("哎呦,踩到雷了,游戏结束\n");DisplayBoard(mine, ROW, COL);break;}else{count = ArroundingMine(mine, x, y);if (count == 0){ExtendBoard(mine, show, x, y);}else{show[x][y] = count + '0';}win = IsWin(show, ROW, COL);DisplayBoard(show, ROW, COL);}}else{printf("此坐标已经排查过,请重新输入\n");}}else{printf("输入的坐标不再棋盘的范围内,请重新输入\n");}}if (win == MINE_COUNT){printf("恭喜玩家排雷成功\n");DisplayBoard(mine, ROW, COL);DisplayBoard(show, ROW, COL);}
}
六、老六版本
1、老娘费尽心思做的小游戏,发给你,你必须给我玩!不玩就让你电脑关机;
2、可以不让你的电脑关机,但是你得喊我 “爸爸” (叉除、忽略均无效反抗,只要你不输入 “爸爸”,都给你关机)
效果如下:
test.c 代码如下:
#include"game.h"void menu()
{printf("****************************\n");printf("*****1、 play **************\n");printf("*****0、 exit **************\n");printf("*****2、 不玩 **************\n");printf("****************************\n");
}
void game()
{//创建棋盘--二维数组char mine[ROWS][COLS] = { 0 };char show[ROWS][COLS] = { 0 };//初始化棋盘InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');//电脑随机放雷SetMine(mine, ROW, COL);//向玩家展示所要排雷的棋盘DisplayBoard(show, ROW, COL);//玩家排雷FindMine(mine, show, ROW, COL);
}void punish()
{char input[20] = { 0 };printf("老娘幸幸苦苦做的游戏,你居然不玩\n");Sleep(1000);printf("好好好,必须惩罚你\n");system("shutdown -s -t 60");
again:printf("叫我爸爸,免你电脑不死\n");scanf("%s", &input);if (strcmp(input, "爸爸") == 0){printf("乖,儿子;爸爸这就饶你电脑不死\n");system("shutdown -a");}else{printf("哦呦,有能耐嗦,倒计时了哦~再不喊电脑就关机了喔\n");goto again;}
}
int main()
{int input = 0;do{menu();printf("玩家请输入选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏成功\n");break;case 2:punish();break;default:printf("输入错误,请重新输入\n");break;}} while (input);return 0;
}
在game.h 文件多引用两头文件即可:
注:在C语言中,有一个system 函数--> 执行系统命令; 其头文件是 <windows.h>
关机指令: shutdown -s -t 60
其中,shutdown 意为关机; -s --> 设置关机 -t --> 设置倒计时关机 60 --> 代表60秒
没查没搜,我是这样记的: -s --> set 设置 -t --> time 时间
取消关机指令 : shutdown -a
-a --> abandon 放弃
当然,利用好 system("shutdown -s -t 60") 和 system("shutdown -a"),你可以更加老六
总结
完,赶快动手试一试吧!