函数和数组实践:扫雷游戏
在这次的实践项目中,需要编写一个可以在在控制台运行的经典的扫雷游戏。
一、游戏要求
- 游戏有菜单,可以通过菜单实现继续玩或者退出游戏
- 游戏要求棋盘9*9,雷(10个)要求随机布置
- 可以实现排查雷
- 如果位置不是雷,就显示周围有几个雷
- 如果位置是雷就炸死结束游戏
- 把除了雷之外的所有非雷都找出来,排雷成功,游戏结束
二、游戏设计思路
游戏界面:
①首先对于菜单部分,可以使用基本的printf函数根据自己的想法打印出菜单。
/*初始菜单*/
void menu()
{printf("************************\n");printf("****** 1.play ******\n");printf("****** 0.exit ******\n");printf("************************\n");
}
②其次是关于棋盘的设置
- 如何制造一个像经典扫雷游戏那样的界面? 可以用一个二维数组存放扫雷游戏需要的布置好的雷的信息。
- 如何将雷和非雷区分开? 首先可以想到用 ‘*’ 表示雷,用 ‘#’ 表示非雷,这种想法没有问题,但是当描述周围有多少雷的时候并不方便,下面是一个新的思路,可以将雷用字符 ‘1’ 表示,非雷用字符 ’0‘ 表示,这种方式在排雷时就是快速得到周围雷的个数,只需要将周围的8个格子的数字相加就是雷的数量
- 当雷布置完成开始游戏之后,玩家点击一个格子之后,这个格子显示出来的1,到底是雷,还是表示这个不是雷是周围8个格子中存在一个雷这一信息,如何分辨这两种情况呢? 可以通过建立两个相同大小与类型的二位数组,一个用于布置雷 ’1‘ 和非雷 ’0‘ ,这个是玩家看不到的,另一个用于展示给玩家看的棋盘,存放的是玩家排查雷的信息,这样这两种情况就不会发生冲突。展示给玩家看的那一个二维数组为了保持神秘感,最开始都放 ’ *’ ,这样一开始第一种区分雷和非雷的想法也被否决。
- 当在排雷的时候,如果排查到边缘的格子会出现下列图示中的情况,并且这个格子不是雷的话,在统计周围8个格子中雷的信息的时候就会发生越界访问的情况,这种问题应该如何解决? 可以将一开始设置的9*9大小的二位数组扩张成11*11大小的二位数组即可,这样遍历的时候就不会发生越界访问的情况。
#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2char mine[ROWS][COLS]; //存放雷的信息
char show[ROWS][COLS]; //存放显示给玩家的信息
③文件结构设计
由于游戏规则的实现需要多种函数与逻辑之间的配合,并且锻炼自己的多文件处理和函数的声明和定义的能力,所以这个项目用下面三个文件实现。
test.c //文件中写游戏的测试逻辑
game.c //文件中写游戏中函数的实现等
game.h //文件中写游戏需要的数据类型和函数声明等
具体逻辑的实现
1、菜单选择部分的逻辑实现
按照游戏要求需要实现持续游玩的要求,所以这里需要利用一个循环语句,所以这里的思路是:设置一个变量,用1表示开始游戏,用0表示结束游戏,利用do-while循环,并检测这个变量,正好在输入0时跳出循环并结束游戏。
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");break;}} while (input);return 0;
}
2、初始化棋盘和显示棋盘中的逻辑实现
由于游戏规则设置需要在显示台进行游戏的显示和操作,所以需要写一个自定义函数填入前面分析的字符,并再写一个自定义函数用于将二维数组显示在控制台。
设计思路:
-
这里因为前面设置的mine和show数组是同一类型的,所以只需要写一个函数就可以适用于这两个数组,这就是mine数组中雷用字符 ‘1’ 表示,非雷用字符 ’0‘ 表示的原因。
-
而初始化棋盘和在控制台显示棋盘本质都是对二维数组的遍历。
main.c
void game()
{char mine[ROWS][COLS]; //存放雷的信息char show[ROWS][COLS]; //存放显示给玩家的信息/*初始化棋盘*/InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');/*显示用于展示给玩家的棋盘*/DisplayBoard(show, ROW, COL);
}
game.c
/*
函数:初始化棋盘函数
参数:board:要操作的二维数组棋盘row:棋盘宽度col:棋盘高度set:要填入二维数组棋盘的字符
返回值:无
*/
void InitBoard(char board[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++){board[i][j] = set;}}
}/*
函数:显示棋盘函数
参数:board:要显示的二维数组棋盘row:显示的棋盘宽度col:显示的棋盘高度
返回值:无
*/
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{printf("--------扫雷游戏--------\n");int i = 0;for (i = 1; i < row; i++){int j = 0;for (j = 1; j < col; j++){printf("%c", board[i][j]);}printf("\n");}
}
注意:
- 因为两个二维数组的类型都是char[ROWS][COLS],所以传过去的数组都是11*11,但是在显示棋盘的函数中遍历的时候只需要遍历中间的9*9的区域即可,所以在显示棋盘的函数中的for循环都将i,j都控制在了1~9的范围内。而初始化棋盘则无所谓为了方便直接全部遍历即可。
- 其中为了后续排雷部分的坐标输入方便,这里需要在显示棋盘部分的周围列出行号和列号。在一开始就把列号打印出来,然后可以在每一行打印之前,先打印行号即可实现打印出行号和列号的目的。
运行结果:
3、布置雷的逻辑实现
对于布置雷实际上还是把雷放在了中间9*9的这块区域中去,而且这是属于雷的信息只需要对mine数组进行操作即可,但是这一部分最重要的还是如何实现**“随机”放雷**这一要求。
- 第一点:直接操作mine数组中的9*9这一块区域即可
- 第二点:利用rand函数,实现随机放雷这一要求
设计思路:
- 定义变量COUNT规定放置的雷的数量,并循环放置直到雷的数量与COUNT的大小一样
- 利用rand函数,结合公式**(随机生成a~b的数:a+rand( )%b-a+1)**
- 注意使用rand函数之前需要添加头文件
<stdlib.h> <time.h>
- 并在main函数中加上语句
srand((unsigned int)time(NULL));
- 注意使用rand函数之前需要添加头文件
- 定义变量X Y分别作为雷的横坐标和纵坐标,对利用rand函数随机生成X Y
- 要对生成的雷的坐标利用if语句进行检测,看是否已经放过雷
- 其中对于生成雷的区域也是在中间的9*9这一区域,所以在调用放置函数是参数要写ROW和COL
game.c
/*
函数:布置雷函数(随机布置10个)
参数:board:要布置雷的二维数组棋盘row:布置雷的棋盘宽度col:布置雷的棋盘高度
返回值:无
*/
void SetMine(char board[ROWS][COLS], int row, int col)
{int count = COUNT; //定义雷的数量while (count) //循环放雷{int x = rand() % row + 1;int y = rand() % col + 1;if (board[x][y] == '0') //判断是否已经放过雷{board[x][y] = '1';count--;}}
}
main.c
int main()
{/*以时间为基准生成用于产生随机数的种子*/srand((unsigned int)time(NULL));
}
4、排查雷的逻辑实现(第一版)
排查雷是在mine数组中查找,然后将查找的数据放在show数组中显示给玩家。
设计思路:
- 首先编写一个自定义的排查雷函数,将mine和show两数组传过去用于对比
- 在定义变量x y用于接收玩家选择的扫雷坐标,在定义一个变量win用于记录已经排查过的雷的数量
- 用win表示已经排查出的雷,并将其作为跳出排查循环的参数
- 对于接收坐标(x , y)需要加入if判断语句检测是否越界
- 总结:在外界接收的信息都应该进行判断再使用,防止产生越界造成错误
- 判断玩家输入的坐标是否是雷,并返回相应结果
- 输入坐标是雷,结束游戏,显示mine数组展示答案
- 输入坐标不是雷,显示周围的雷的个数并将该数据展示在show数组中,继续游戏
- 最后结束游戏,展示最终答案,显示mine数组
game.c
/*
函数:计算周围雷个数函数
参数:mine:要布置雷的二维数组棋盘x:玩家输入的横坐标y:玩家输入的纵坐标
返回值:无
*/
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}/*
函数:排查雷函数
参数:mine:包含雷的信息的二维数组棋盘show:展示给玩家的二维数组棋盘row:排查雷的棋盘宽度col:排查雷的棋盘高度
返回值:无
*/
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int win = 0;while (win < row * col - COUNT){printf("请输入要排查的坐标:");scanf("%d %d", &x, &y);if (x >= 1 && x < row && y >= 1 && y < col) //检测坐标是否合法{if (mine[x][y] == '1') //排到雷,游戏结束{printf("很遗憾,你被炸死了\n");DisplayBoard(mine, ROW, COL);break; //直接跳出while循环,结束游戏}else //未排到雷,显示周围雷的个数{int count = GetMineCount(mine, x, y);show[x][y] = count + '0'; //显示周围的雷的个数DisplayBoard(show, ROW, COL);win++; //排雷数加1}}else{printf("输入坐标有误,请重新输入\n"); //坐标非法,此时win还不满足跳出循环条件故,重新回到while循环中}}if (win == ROW * COL - COUNT) //找到除了雷的所有位置,排雷成功{printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}
}
注意:
- 上述代码中对数字和字符的转化十分灵活。字符‘0’的ASCLL码值是48,数字0的ASCLL码值是0,当数字0加上‘0’的时候,得到的结果就是字符数字‘0’。上述代码通过这种转化将显示出来的字符和实际的数字建立了紧密的联系,这种思想十分重要。
三、最终代码汇总
game.h
#pragma once/*头文件*/
#include <stdio.h>
#include <time.h>
#include <stdlib.h>/*宏定义*/
#define COUNT 10 //雷的数量#define ROW 9 //棋盘宽度
#define COL 9 //棋盘高度#define ROWS ROW+2
#define COLS COL+2/*函数声明*/
/*初始化棋盘函数*/
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);/*显示棋盘函数*/
void DisplayBoard(char board[ROWS][COLS], int row, int col);/*随机布置雷*/
void SetMine(char board[ROWS][COLS], int row, int col);/*计算周围雷的个数*/
int GetMineCount(char mine[ROWS][COLS], int x, int y);/*排雷函数*/
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS#include "game.h"/*
函数:初始化棋盘函数
参数:board:要操作的二维数组棋盘row:棋盘宽度col:棋盘高度set:要填入二维数组棋盘的字符
返回值:无
*/
void InitBoard(char board[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++){board[i][j] = set;}}
}/*
函数:显示棋盘函数
参数:board:要显示的二维数组棋盘row:显示的棋盘宽度col:显示的棋盘高度
返回值:无
*/
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{printf("-----扫雷游戏-----\n");int i = 0;for (i = 0; i < col; 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 ", board[i][j]);}printf("\n");}
} /*
函数:布置雷函数(随机布置10个)
参数:board:要布置雷的二维数组棋盘row:布置雷的棋盘宽度col:布置雷的棋盘高度
返回值:无
*/
void SetMine(char board[ROWS][COLS], int row, int col)
{int count = COUNT; //定义雷的数量while (count) //循环放雷{int x = rand() % row + 1;int y = rand() % col + 1;if (board[x][y] == '0') //判断是否已经放过雷{board[x][y] = '1';count--;}}
}/*
函数:计算周围雷个数函数
参数:mine:要布置雷的二维数组棋盘x:玩家输入的横坐标y:玩家输入的纵坐标
返回值:无
*/
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y]+ mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}/*
函数:排查雷函数
参数:mine:包含雷的信息的二维数组棋盘show:展示给玩家的二维数组棋盘row:排查雷的棋盘宽度col:排查雷的棋盘高度
返回值:无
*/
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int win = 0;while (win < row * col - COUNT){printf("请输入要排查的坐标:");scanf("%d %d", &x, &y);if (x >= 1 && x < row && y >= 1 && y < col) //检测坐标是否合法{if (mine[x][y] == '1') //排到雷,游戏结束{printf("很遗憾,你被炸死了\n");DisplayBoard(mine, ROW, COL);break; //直接跳出while循环,结束游戏}else //未排到雷,显示周围雷的个数{int count = GetMineCount(mine, x, y);show[x][y] = count + '0'; //显示周围的雷的个数DisplayBoard(show, ROW, COL);win++; //排雷数加1}}else{printf("输入坐标有误,请重新输入\n"); //坐标非法,此时win还不满足跳出循环条件故,重新回到while循环中}}if (win == ROW * COL - COUNT) //找到除了雷的所有位置,排雷成功{printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}
}
main.c
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>
#include "game.h"/*初始菜单*/
void menu()
{printf("************************\n");printf("****** 1.play ******\n");printf("****** 0.exit ******\n");printf("************************\n");
}/*游戏流程*/
void game()
{char mine[ROWS][COLS]; //存放雷的信息char show[ROWS][COLS]; //存放显示给玩家的信息/*初始化棋盘*/InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');/*显示用于展示给玩家的棋盘*/DisplayBoard(show, ROW, COL);/*随机布置雷*/SetMine(mine, ROW, COL);/*排雷*/FindMine(mine, show, ROW, COL);
}/*主函数流程*/
int main()
{int input = 0; //用于菜单选择/*以时间为基准生成用于产生随机数的种子*/srand((unsigned int)time(NULL)); do{menu();printf("请选择:");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;
}
我的Gitee链接如下:[扫雷游戏](扫雷游戏/扫雷游戏 · Tanecious./C语言练习代码 - 码云 - 开源中国)