五、扫雷
0x00 游戏介绍
扫雷是一款大众类的益智小游戏,于1992年发行。
游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
0x01 实现思路
📚 分模块:
① test.c 测试游戏的逻辑;
② game.c 游戏相关函数的实现;
② game.h 关于游戏相关的函数声明、符号声明以及头文件的包含;
0x02 游戏界面
📚 思路:
① 设计开始页面,提供选择以下选择:开始游戏、退出游戏(并且检查是否输入错误);
② 为了实现玩一把还能继续玩,使用do...while函数,用户不输入0程序就一直运行;
③ 引入头文件 game.h,将头文件、函数声明、宏定义等全部置于game.h中;
💬 test.c main函数、LoadGameMenu函数
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void LoadGameMenu() { printf("\n"); printf("*********************************\n"); printf("********** 1. 开始游戏 ***********\n"); printf("********** 0. 退出游戏 ***********\n"); printf("*********************************\n"); } int main() { int input = 0; do { LoadGameMenu(); printf("请选择: "); scanf("%d", &input); switch (input) { case 1: printf("开始游戏\n"); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新选择\n"); break; } } while (input); return (0); }
💬 game.h
#include <stdio.h>
🚩 代码运行结果如下
0x03 初始化9x9的棋盘
📚 思路:
① 设计Game函数,用来调用实现游戏功能的函数;
② 创建两个二维数组,分别存放布置好的雷的信息和已排查的雷的信息;
③ 将他们初始化,布置雷的信息用0表示(暂且设定0为非雷,1为雷),已排查的雷的信息用 * 表示;
④ 由于需要一个9x9的扫雷棋盘,外面还需要显示对应的坐标,所以实际数组的大小应该为11x11;
⑤ 定义ROW和COL,为了后续可以修改棋盘,ROWS = ROW+2,COLS = COL+2;
💬 test.c ( main函数、Game函数 )
void Game() { char mine[ROWS][COLS] = { 0 }; // 存放布置好雷的信息 char show[ROWS][COLS] = { 0 }; // 存放排查好雷的信息 /* 初始化棋盘 */ InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); } int main() { ... case 1: Game(); // 扫雷游戏 break; ... return (0); }
💬 game.h
#include <stdio.h> #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);
💬 test.c ( InitBoard 函数 )
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; } } }
0x04 打印棋盘
📚 思路:
① 设计一个打印棋盘的函数,把棋盘打印出来;
② 虽然mine棋盘是不能被玩家看到的,但是为了测试我们把mine棋盘也打印出来;
③ 由于棋盘为9x9,不能出9x9之外,所以传入的应该是ROW和COL,而不是ROWS和COLS;
💬 test.c ( Game 函数 )
void Game() { char mine[ROWS][COLS] = { 0 }; // 存放布置好雷的信息 char show[ROWS][COLS] = { 0 }; // 存放排查好雷的信息 /* 初始化棋盘 */ InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); /* 打印棋盘 */ DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); } int main() {...}
💬 game.h
... /* 打印棋盘 */ void DisplayBoard(char board[ROWS][COLS], int row, int col); 💬 game.c ( DisplayBoard 函数 ) void DisplayBoard ( char board[ROWS][COLS], int row, int col ) { int i = 0; int j = 0; printf("\n---------------------\n"); // 分界线 /* 打印列号 */ for (i = 0; i <= col; i++) { if (i == 0) { printf(" "); // 去除左上角xy的交接零点部分 continue; } printf("%d ", i); if (i == 9) { printf("┆"); // 打印竖边框 } } printf("\n"); for (i = 1; i <= row; i++) { /* 打印行号 */ printf("%d ", i); for (j = 1; j <= col; j++) { /* 打印内容 */ printf("%c ", board[i][j]); if (j == 9) { printf("┆"); // 打印竖边框 } } printf("\n"); // 打印完一行的内容后换行 } printf("---------------------\n"); // 分界线 }
🚩 运行结果如下
0x05 布置雷
📚 思路:
① 随机生成若干个雷(暂且定为10个),可以通过 rand 函数实现;( srand放在主函数中 );
② 由于布雷要在9x9棋盘内部布置,不能出9x9之外,所以传入的应该是ROW和COL;
③ 为了测试雷是否布置成功,我们把mine棋盘先打印出来;
④ define 雷的个数,为了测试,布置10个雷;
💬 test.c ( Game 函数 )
void Game() { char mine[ROWS][COLS] = { 0 }; // 存放布置好雷的信息 char show[ROWS][COLS] = { 0 }; // 存放排查好雷的信息 /* 初始化棋盘 */ InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); /* 打印棋盘 */ // DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); /* 布置雷 */ SetMine(mine, ROW, COL); DisplayBoard(mine, ROW, COL); // 暂时打印出来 } int main() { srand((unsigned int)time(NULL)); // 置随机数种子 ... }
💬 game.h
#include <stdio.h> #include <stdlib.h> #include <time.h> #define EASY_COUNT 10 ... /* 设置雷 */ void SetMine(char mine[ROWS][COLS], int row, int col);
💬 game.c ( SetMine 函数 )
void SetMine ( char mine[ROWS][COLS], int row, int col ) { /* 布置10个雷 */ int count = EASY_COUNT; while (count) { /* 生成随机的下标 */ int x = rand() % row + 1; // 余上row变成个位数 int y = rand() % col + 1; // 余上col变成个位数 if (mine[x][y] == '0') { // 判断某个坐标是否已经有雷 mine[x][y] = '1'; // 设1为雷 count--; } } }
🚩 运行结果
0x06 排查雷
📚 思路:
① 让玩家排查雷,输入雷的坐标进行排查,并且判断玩家输入的坐标是否合法;
② 如果输入的坐标上有雷(为1)则宣告游戏失败,打印出棋盘让玩家死个明白;
③ 如果输入的坐标上没有雷,那么统计周围有几个雷,并且将雷的个数显示在该坐标上,显示排查出雷的信息;
④ 统计周围雷的方法如下图所示,以xy为中心的上下左右、上左上右、下左下右的坐标进行count;
⑤ 这里要传入 ROWS 和 COLS ,就算xy在边上,计算xy周围时,也不会导致数组越界;
💬 test.c ( Game 函数 )
void Game() { char mine[ROWS][COLS] = { 0 }; // 存放布置好雷的信息 char show[ROWS][COLS] = { 0 }; // 存放排查好雷的信息 /* 初始化棋盘 */ InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); /* 打印棋盘 */ //DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); /* 布置雷 */ SetMine(mine, ROW, COL); //DisplayBoard(mine, ROW, COL); /* 排查雷 */ FindMine(mine, show, ROW, COL); } int main() {...}
💬 game.h
... /* 排查雷 */ void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col); 💬 game.c ( FineMine 函数 和 get_mine_count 函数 ) static int get_mine_count ( char mine[ROWS][COLS], int x, int y ) { /* * (x-1, y-1) (x-1, y) (x-1, y+1) * * ( x , y-1) ( x , y) ( x , y+1) * * (x+1, y-1) (x+1, y) (x+1, y+1) */ 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' ); } void FindMine ( char mine[ROWS][COLS], char show[ROWS][COLS], int row,int col ) { /* * 注释: * 1. 输入排查的坐标 * 2. 检查坐标处是不是雷 * (1)是雷 - 很遗憾炸死了 - 游戏结束 * (2)不是雷 - 统计坐标周围有几个雷 - 存储排查类的信息 */ int x = 0; int y = 0; while (1) { printf_s("\n请输入要排查雷的坐标: "); // x(1~9) y(1~9) scanf_s("%d%d", &x, &y); /* 判断坐标的合法性 */ if (x >= 1 && x <= row && y >= 1 && y <= col) { if (mine[x][y] == '1') { /* 是雷,宣告游戏失败 */ printf_s("\n很遗憾,你被炸死了\n"); DisplayBoard(mine, row, col); break; } else { /* 不是雷,统计x,y坐标有几个雷 */ int count = get_mine_count(mine, x, y); show[x][y] = count+'0'; // ASCII化为字符 /* 显示排查出的信息 */ DisplayBoard(show, row, col); } } else { printf("\n坐标非法,请重新输入!\n"); } } }
0x07 设置胜利条件
📚 思路:
① 加入一个计数器win,统计排查的雷的个数,当个数等于雷数时,说明雷都被排完了,宣告游戏胜利;
② while 循环的条件可以设置为 只要 win 仍然小于 9x9 减雷数,就进入循环;
💬 game.c ( FineMine 函数 )
void FindMine ( char mine[ROWS][COLS], char show[ROWS][COLS], int row,int col ) { /* * 注释: * 1. 输入排查的坐标 * 2. 检查坐标处是不是雷 * (1)是雷 - 很遗憾炸死了 - 游戏结束 * (2)不是雷 - 统计坐标周围有几个雷 - 存储排查类的信息 */ int x = 0; int y = 0; int win = 0; while (win<row*col - EASY_COUNT) { printf_s("\n请输入要排查雷的坐标: "); // x(1~9) y(1~9) scanf_s("%d%d", &x, &y); /* 判断坐标的合法性 */ if (x >= 1 && x <= row && y >= 1 && y <= col) { if (mine[x][y] == '1') { /* 是雷,宣告游戏失败 */ printf_s("\n很遗憾,你被炸死了\n"); DisplayBoard(mine, row, col); break; } else { /* 不是雷,统计x,y坐标有几个雷 */ int count = get_mine_count(mine, x, y); show[x][y] = count+'0'; // ASCII化为字符 /* 显示排查出的信息 */ DisplayBoard(show, row, col); win++; } } else { printf("\n坐标非法,请重新输入!\n"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功!\n"); DisplayBoard(mine, row, col); } }
0x08 代码运行
🚩 排查雷的坐标
🚩 非法输入坐标
🚩 很遗憾,你被炸死了