本篇介绍,讲解如何使用c语言实现扫雷小游戏.
金句分享:
✨✨✨爱你所爱,行你所行✨✨✨
目录
- 前言:
- 一、游戏设计思路介绍:
- 效果展示
- 二、游戏的分步讲解
- 2.1、主函数测试区(test.c)基本构成
- 2.2、游戏中函数实现区(game.c) (重点)
- 2.21、雷盘的创建与初始化函数
- 2.22、雷盘的打印函数
- 2.23、模式选择函数
- 2.24、布置雷函数
- 2.25、排查雷函数
- 2.26、统计坐标周围雷的个数函数
- 2.27、自动递归排雷函数
- 2.28、判断输赢
- 三、游戏总代码
- 主函数测试区(test.c) :
- 函数实现区(game.c)
- 函数声明区(game.h):
前言:
游戏规则:
我们随便点一个格子,方格即被打开并显示出方格中的数字,方格中数字则表示其周围的8个方格隐藏雷的数目.根据数字,排查出所有的雷即为游戏成功,当点击到有雷的格子时,会被炸死,游戏失败.
一、游戏设计思路介绍:
- 设置游戏的菜单(自由设计):
- 游戏函数的创建:
- 创建雷盘
- 初始化雷盘
- 打印雷盘
- 模式选择:(用于确定雷的个数)
- 布置雷
- 排查雷
- 自动递归循环排雷
- 判断输赢
效果展示
二、游戏的分步讲解
2.1、主函数测试区(test.c)基本构成
主函数测试区的作用是.设计菜单,和game函数的调用.
菜单可自由设计,牛牛就不过多介绍了.
主要介绍一下,game函数的实现:
通过调用各函数来实现游戏的总体结构,具体函数的实现放在game.c文件中.主要作用是完成游戏的总体框架.合理的调用相应的函数.
void game(){//创建雷盘char secret[ROWS][COLS] = { 0 };char show[ROWS][COLS] = { 0 };//初始化雷盘initboard(secret, ROWS, COLS,'0');//初始化答案的雷盘initboard(show, ROWS, COLS,'*');//初始化玩家的雷盘//打印雷盘//printboard(secret, ROW, COL);//打印给自己看的答案雷盘printboard(show, ROW, COL);//打印给玩家的雷盘//布置雷int num = c_pattern();//模式选择函数setmine(secret, ROW, COL,num);////printboard(secret, ROW, COL);//打印给自己看的答案雷盘//排查雷findmine(secret, show, ROW, COL,num);//排查雷}
2.2、游戏中函数实现区(game.c) (重点)
2.21、雷盘的创建与初始化函数
如果只有一个雷盘,那么该雷盘既要保存雷的信息,又不能显示给玩家看雷的位置.这边不能很好的进行初始化雷盘.所以我们需要创建两个雷盘:
1.“秘密雷盘”:布置雷的雷盘(只给牛牛自己看的)
2.“展示雷盘”:玩家所看到的雷盘
问题:1
了解扫雷规则的小伙伴知道,当我们输入一个坐标的时候,该坐标就会显示出统计的周围八个坐标雷的个数.所以在创建雷盘的时候会遇到一个问题,玩家在排查雷盘的边角坐标时,周围八个坐标的位置很有可能会越界.
解决方法:
我们可以创建一个更大的数组,比如,当我们需要9×9的数组时,我们创建一个11×11的数组.这样就可以防止越界访问,
越界情况: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _解决方法:
雷盘的创建:
ROWS是一个宏定义的值,在函数声明区中定义.暂时可以理解为数值11.
//创建雷盘char secret[ROWS][COLS] = { 0 };//秘密雷盘char show[ROWS][COLS] = { 0 };//展示雷盘
雷盘的初始化:
雷盘创建好之后,我们怎样为棋盘进行合理的初始化呢” />
美观雷盘的打印:
与前面三子棋打印方法类似.
牛牛都留好注释了,没看懂备注的,可以点这里,有分步骤教学讲解:
三子棋棋盘打印
//美观雷盘:void printboard(char board[ROWS][COLS], int row, int col)//打印棋盘//建议参照棋盘的外观查看代码{printf("-----------------扫雷游戏-------------\n");int i = 0, j = 0;printf(" ");//打印空格是为了对齐(因为下面的行号占用位置)//打印显示在第一行的列标for (i = 1; i <= row; i++){printf("%3d ", i);//因为一个数据行的格子占3个位置,所以我们这里用%3d占用三个位置的空间}printf("\n");//打印列标后换行。printf(" +");//先打印一个+,可以观察棋盘外观,将棋盘外观拆分打印。//为了对齐加了一个空格,因为分割行前面没有行号占用位置,只能补空格。for (i = 0; i < row; i++)//打印第一行的分割线{printf("---+");}printf("\n");//每次打印一行就要换行//开始打印数据行for (i = 1; i <= row; i++){printf("%2d", i );//打印数据行前面的行号,%2d是因为当行数>9的时候,两位数会占用两个位置,影响对齐。(细节)printf("|");//和上面一样,先打印一个 | ,可以观察棋盘外观,将数据行拆分打印。for (j = 1; j <= col; j++)//打印一行中间的棋子和其它分割线{printf(" %c |", board[i][j]);//这里打印的是“空格”“棋子”“空格”“|”}printf("\n");//每次打印一行就要换行//打印剩余的分割行printf(" +");//先打印一个+,可以观察棋盘外观,将棋盘外观拆分打印。//为了对齐加了一个空格for (j = 0; j < col; j++)//打印一行外观的分割线{printf("---+");//每次打印一行就要换行}printf("\n");}printf("-----------------扫雷游戏-------------\n");}
效果图:
2.23、模式选择函数
为了让玩家可以控制难度,牛牛设置了一个难度选择函数,根据玩家的选择来设置相应的雷的数量.
此函数重点在于,要使用getchar()函数将缓存区的清除,否则影响下面的难度选择的输入.(牛牛当时疏忽了,找了好久才找到原因,缓存区有一个换行符被直接读取给了scanf(“%c”, &pattern);)
//模式选择函数的实现(返回设置雷的个数):int c_pattern(){int num = 0;//表示布置雷的数量again://玩家选择模式错误时返回到此处printf("欢迎玩家进入游戏:\n");printf("请新选择难度:(num代表雷的数量)\n");printf("A.简单模式:num=5B.中等模式:num=15C.困难模式:num=30D.自定义难度(自由输入雷的个数)\n ");char pattern = 0;getchar();//清楚缓存区scanf("%c", &pattern);//玩家模式选择switch (pattern){case 'A':case 'a':printf("简单模式:num=5\n");num = 5;return num;case 'B':case 'b':printf("中等模式:num=10\n");num = 15;return num;case 'C':case 'c':printf("困难模式:num=15\n");num = 30;return num;case 'D':case 'd':printf("自定义难度:");printf("请输入布置雷的个数:\n");getchar();//清楚缓存区int intput = 0;scanf("%d", &intput);//用户自定义的雷的个数num = intput;return num;default:printf("不好意思,牛牛还没有开发此模式,请重新选择:\n\n");goto again;//让玩家重新选择break;}}
2.24、布置雷函数
布置雷的逻辑与三子棋的电脑落子逻辑上是一样的.
通过生成两个随机数,将其作为坐标,修改(秘密棋盘)该坐标的值为’1’(表示雷).
//布置雷盘函数实现void setmine(char board[ROWS][COLS], int row, int col,int num){int x = 0, y = 0;int count = 0;for (count = 0; count < num; ){x = 1 + rand() % row;y = 1 + rand() % col;if (board[x][y] == '0'){board[x][y] = '1';count++;//每次布置好一个雷之后,才会计数}}}
2.25、排查雷函数
让玩家输入要排查雷的坐标,先判断坐标的合法性,是否越界.
如果坐标合法,统计该坐标周围八个坐标有多少个雷.
如果该坐标周围没有雷,就将该坐标设置为空格,并递归排查周围八个坐标的值.
如果该坐标是雷,则游戏结束.
每次排查一个坐标后,判断玩家是否取得胜利.
//排查雷函数的实现void findmine(char secret[ROWS][COLS], char show[ROWS][COLS], int row, int col,int num){int x = 0, y = 0;int win = 0;//表示被排查的雷的个数while (win < (row * col -num)){printf("请输入排查雷的坐标:\n格式为:行号 列标\n");scanf("%d%d", &x, &y);if (show[x][y] != '*')//被排查过的坐标不是*{printf("该坐标已经被排查过了.请重新输入:");continue;}if (x >= 1 && x <= row && y >= 1 && y <= col){if (secret[x][y] == '1')//如果是1就代表是雷,游戏结束{printf("很遗憾,你失败了\n");printf("请看答案:\n");printboard(secret, ROW, COL);//失败后,给玩家看答案雷盘printf("很遗憾,你失败了\n上面是答案:\n");break;}else//此坐标不是雷{digui(secret, show, ROW, COL,x,y,&win);//自动递归排雷函数win=is_win(show, ROW, COL);printboard(show, ROW, COL);//打印给玩家的雷盘}}else{printf("坐标非法,请重新输入:\n");}}if (win == (row * col - num))//行号*列标表示总共的坐标数-已经被排查的坐标数{printf("恭喜你排雷成功\n");printf("牛牛为你点赞!!!\n");}}
2.26、统计坐标周围雷的个数函数
由于是存放的都是字符,所以计算结果-8×’0’,得到数值.
//统计坐标周围雷的数量int countmine(char secret[ROWS][COLS], int x, int y){int ret = secret[x - 1][y - 1]+ secret[x - 1][y] + secret[x - 1][y + 1] + secret[x][y - 1] + secret[x][y + 1]+ secret[x + 1][y - 1] + secret[x + 1][y] + secret[x + 1][y + 1] - 8 * '0';return ret;}
2.27、自动递归排雷函数
如果一次只能排查一个坐标,那这游戏是不是太难了” />小伙伴们的点赞就是给牛牛最大的支持,能不能给牛牛来一个一键三连呢?谢谢支持。
最后附上总代码.
三、游戏总代码
主函数测试区(test.c) :
#define _CRT_SECURE_NO_WARNINGS 1#include "game.h"void menu(){printf("*******************************************************************\n");printf("************ * *************\n");printf("****************** * *****************\n");printf("************************ * ***********************\n");printf("******************************* * ******************************\n");printf("******** 1.玩游戏 * 2.退出游戏 *********\n");printf("******************************* * ******************************\n");printf("************************ * ***********************\n");printf("****************** * *****************\n");printf("************ * ************\n");printf("*******************************************************************\n");printf("请选择:\n");}void game(){//创建雷盘char secret[ROWS][COLS] = { 0 };//秘密雷盘char show[ROWS][COLS] = { 0 };//展示雷盘//初始化雷盘initboard(secret, ROWS, COLS,'0');//初始化答案的雷盘initboard(show, ROWS, COLS,'*');//初始化玩家的雷盘//打印雷盘//printboard(secret, ROW, COL);//打印给自己看的答案雷盘printboard(show, ROW, COL);//打印给玩家的雷盘//布置雷int num = c_pattern();//模式选择函数setmine(secret, ROW, COL,num);//布置雷函数//printboard(secret, ROW, COL);//打印给自己看的答案雷盘//排查雷findmine(secret, show, ROW, COL,num);//排查雷}int main(){int n = 0;//记录玩家在菜单中的选择srand((unsigned int)time(NULL));//与前面介绍的猜数字游戏一样,改变种子值来使得rand函数每次生成不同的随机数列。do{menu();scanf("%d", &n);//让玩家在菜单中选择switch (n){case 1://代表玩游戏game();printf("再来一局吗?\n");printf("1.再来一局 2. 没意思不玩了\n");int again = 0;//存放玩家是否再玩的结果。scanf("%d", &again);if (again == 1){break;}elsen = 2;//令n=2循环结束break;case 2:printf("退出游戏");break;default:system("cls");printf("没有这个选项哦。请重新选择:\n\n");//玩家不小心输错了getchar();//清楚缓存区continue;}} while (n - 2);//当玩家选择2时,代表不玩了}
函数实现区(game.c)
#define _CRT_SECURE_NO_WARNINGS 1#include "game.h"//初始化雷盘函数的实现void initboard(char board[ROWS][COLS], int rows, int cols, char ret){int i = 0, j = 0;for (i = 0; i < rows; i++){for (j = 0; j < cols; j++){board[i][j] = ret;}}}//打印雷盘函数的实现//美观雷盘:void printboard(char board[ROWS][COLS], int row, int col)//打印棋盘//建议参照棋盘的外观查看代码{printf("-----------------扫雷游戏-------------\n");int i = 0, j = 0;printf(" ");//打印空格是为了对齐(因为下面的行号占用位置)//打印显示在第一行的列标for (i = 1; i <= row; i++){printf("%3d ", i);//因为一个数据行的格子占3个位置,所以我们这里用%3d占用三个位置的空间}printf("\n");//打印列标后换行。printf(" +");//先打印一个+,可以观察棋盘外观,将棋盘外观拆分打印。//为了对齐加了一个空格,因为分割行前面没有行号占用位置,只能补空格。for (i = 0; i < row; i++)//打印第一行的分割线{printf("---+");//上图讲解了拆分过程。}printf("\n");//每次打印一行就要换行//开始打印数据行for (i = 1; i <= row; i++){printf("%2d", i );//打印数据行前面的行号,%2d是因为当行数>9的时候,两位数会占用两个位置,影响对齐。(细节)printf("|");//和上面一样,先打印一个 | ,可以观察棋盘外观,将数据行拆分打印。for (j = 1; j <= col; j++)//打印一行中间的棋子和其它分割线{printf(" %c |", board[i][j]);//这里打印的是“空格”“棋子”“空格”“|”}printf("\n");//每次打印一行就要换行//打印剩余的分割行printf(" +");//先打印一个+,可以观察棋盘外观,将棋盘外观拆分打印。//为了对齐加了一个空格for (j = 0; j < col; j++)//打印一行外观的分割线{printf("---+");//每次打印一行就要换行}printf("\n");}printf("-----------------扫雷游戏-------------\n");}//简易雷盘://void printboard(char board[ROWS][COLS], int row, int col)//{//printf("------扫雷游戏------\n");//int i = 0, j = 0;//for (i = 0; i <= row;i++)//打印列标//{//printf("%2d", i);//}//printf("\n");//for (i = 1; i <= row; i++)//{//printf("%2d", i);//for (j = 1; j <= col; j++)//{//printf("%2c",board[i][j]);//}//printf("\n");//打印一行后换行//}//printf("------扫雷游戏------\n");//printf("\n");//}//模式选择函数的实现(返回设置雷的个数):int c_pattern(){int num = 0;again://玩家选择模式错误时返回到此处printf("欢迎玩家进入游戏:\n");printf("请新选择难度:(num代表雷的数量)\n");printf("A.简单模式:num=5B.中等模式:num=15C.困难模式:num=30D.自定义难度(自由输入雷的个数)\n ");char pattern = 0;getchar();//清楚缓存区scanf("%c", &pattern);//玩家模式选择switch (pattern){case 'A':case 'a':printf("简单模式:num=5\n");num = 5;return num;case 'B':case 'b':printf("中等模式:num=10\n");num = 15;return num;case 'C':case 'c':printf("困难模式:num=15\n");num = 30;return num;case 'D':case 'd':printf("自定义难度:");printf("请输入布置雷的个数:\n");getchar();//清楚缓存区int intput = 0;scanf("%d", &intput);//用户自定义的雷的个数num = intput;return num;default:printf("不好意思,牛牛还没有开发此模式,请重新选择:\n\n");goto again;//让玩家重新选择break;}}//布置雷盘函数实现void setmine(char board[ROWS][COLS], int row, int col,int num){int x = 0, y = 0;int count = 0;for (count = 0; count < num; ){x = 1 + rand() % row;y = 1 + rand() % col;if (board[x][y] == '0'){board[x][y] = '1';count++;//每次布置好一个雷之后,才会计数}}}//统计坐标周围雷的数量int countmine(char secret[ROWS][COLS], int x, int y){int ret = secret[x - 1][y - 1]+ secret[x - 1][y] + secret[x - 1][y + 1] + secret[x][y - 1] + secret[x][y + 1]+ secret[x + 1][y - 1] + secret[x + 1][y] + secret[x + 1][y + 1] - 8 * '0';return ret;}//自动递归排雷函数digui(char secret[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y){if (x >= 1 && x <= row && y >= 1 && y <= col)//防止递归的时候坐标越界{int count = countmine(secret, x, y);//计算该坐标周围有几个雷if (count == 0)//如果周围八个坐标没有雷{show[x][y] = ' ';//周围没有雷的坐标变为空格int i = 0, j = 0;for (i = x - 1; i <= x +1; i++)//观察周围八个坐标he{for (j = y - 1; j <= y+1 ; j++){if (show[i][j] == '*' && (i != x || j != y))//防止重新递归show[x][y]坐标{digui(secret, show, ROW, COL, i, j);}}}}else//如果周围有雷{show[x][y] = count + '0';}}}//计算已经被排查过的位置int is_win(char show[ROWS][COLS],int row,int col){int count1 = 0;//已经被排查的坐标个数int i = 0, j = 0;for (i = 1; i <= row; i++){for (j = 1; j <= col; j++){if (show[i][j] != '*')//只要不是*,表示该坐标已经被排查了.{count1++;}}}return count1;}//排查雷函数的实现void findmine(char secret[ROWS][COLS], char show[ROWS][COLS], int row, int col,int num){int x = 0, y = 0;int win = 0;//表示被排查的雷的个数while (win < (row * col -num)){printf("请输入排查雷的坐标:\n格式为:行号 列标\n");scanf("%d%d", &x, &y);if (show[x][y] != '*')//被排查过的坐标不是*{printf("该坐标已经被排查过了.请重新输入:");continue;}if (x >= 1 && x <= row && y >= 1 && y <= col){if (secret[x][y] == '1')//如果是1就代表是雷,游戏结束{printf("很遗憾,你失败了\n");printf("请看答案:\n");printboard(secret, ROW, COL);//失败后,给玩家看答案雷盘printf("很遗憾,你失败了\n上面是答案:\n");break;}else//此坐标不是雷{digui(secret, show, ROW, COL,x,y,&win);//自动递归排雷函数win=is_win(show, ROW, COL);printboard(show, ROW, COL);//打印给玩家的雷盘}}else{printf("坐标非法,请重新输入:\n");}}if (win == (row * col - num)){printf("恭喜你排雷成功\n");printf("牛牛为你点赞!!!\n");}}
函数声明区(game.h):
#define _CRT_SECURE_NO_WARNINGS 1#include #include //清屏函数的头文件#include //srand函数的头文件#define ROW 12//方便修改棋盘大小#define COL 12#define ROWS ROW+2#define COLS COL+2//声明初始化雷盘函数void initboard(char board[ROWS][COLS], int rows, int cols, char ret);//声明打印雷盘函数void printboard(char board[ROWS][COLS],int row, int col);//声明布置雷函数void setmine(char board[ROWS][COLS], int row,int col,int num);//声明排查雷的函数void findmine(char secret[ROWS][COLS], char show[ROWS][COLS], int row,int col,int num);