ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C语言之扫雷游戏(功能扩展+代码解析!)

C语言之扫雷游戏(功能扩展+代码解析!)

首先声明,此博客不照搬抄袭,存在自己的见解,我所使用的编译器是VS2022

一、项目介绍(分析与设计)

(1)扫雷游戏的玩法介绍

玩家通过输入坐标翻开对应格子,依靠数字提示判断周围地雷数量,合理排查安全区域。若点开地雷则游戏失败,成功找出所有无雷格子即可获胜。

(2)扫雷游戏的分析与设计

分析:程序需要实现的功能

- 给玩家一个菜单,玩家可通过菜单选择是否进入游戏。

- 雷每一把都是随机布置的。

- 玩家可输入坐标进行排查,若所选位置无雷,则显示该位置周围的地雷总数。

- 若踩中地雷,则游戏失败。

- 排查出所有的无雷格子后(排查的无雷的格子总数大于等于(或者就等于)格子总数(包括雷)减去雷的数量时),则判断游戏胜利。或者判断未翻出的格子,如果小于等于(或者就等于)雷的个数,而且没被炸死,则说明游戏胜利。

- 简易版本为9*9的扫雷棋盘,10个雷。中等版本为16*16的棋盘,40个雷。困难版本为30*16的棋盘,99个雷。

设计:扫雷功能模块的架构

首先,我们需要9*9的格子数,来存放我们的雷和无雷区域,很容易联想到二维数组

无雷则存0,有雷则存1

其次,因为我们要计算的是排查的每一格该格周围一圈雷的总数,当我们计算上图(8,6)坐标时,会出现下图情况,数组越界计算,因为我们只有9*9的方格

所以我们要将数组扩展一圈,变成11*11的数组,以便于计算排查格子周围雷的数量,但是真正存放雷,和排查雷,还是在里面的9*9方格里去实现

如上图所示,扩展为11*11的格子数,此时我们注意一个巧妙的点,虽然我们扩展为11*11的数组,但我们展示存放和排查的还是里面9*9的数组,此时看9*9数组对应的下标行和列,刚好都是从1开始,这更便于真实用户进行游戏时,找到自己想排查格子的对应下标

我们采用两个数组分别用于存储和展示数据:

- mine用于存放雷的操作('1'表示雷,'0'表示无雷)

- show用于将排查雷的信息展现给游玩者('*'表示未排查格子,数字表示该格周围雷的总数)

我们需要几个函数实现上述功能 :

首先需要两个源文件(test.c)(game.c)和一个头文件(game.h)

- test.c 用于存放主函数,实现游戏逻辑的整体运行

- game.c用于实现各个函数的定义

- game.h用于对各个函数的声明

其次需要两个数组 :

- mine用于存放雷的操作('1'表示雷,'0'表示无雷)

- show用于将排查雷的信息展现给游玩者('*'表示未排查格子,数字表示该格周围雷的总数)

其次函数模块的划分 :

- InitBoard :用于初始化棋盘

- PrintBoard :用于打印棋牌

- SetMine :用于布置雷

- GetGround :用于统计坐标周围雷的个数

- FindMine :用于实现排查雷的操作

二、代码详细解析(未扩展功能版)

头文件的声明和宏定义(game.h文件中):

#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #define Row 9 #define Col 9 #define Rows 11 #define Cols 11 #define Low 10 void menu(); void game(); void InitBoard(char arr[Rows][Cols], int rows, int cols, char set); void PrintBoard(char arr[Rows][Cols], int row, int col); void SetMine(char mine[Rows][Cols], int row, int col, char set); void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col); #include <time.h> #include <stdlib.h>

因为我们需要对二维数组进行操作,我们即需要对内层9*9的方格进行操作也需要对外层11*11的方格进行操作,所以在此我们设计四个宏定义,分别表示行和列

主函数游戏总体逻辑(time.c文件中):

首先,当我们想要游玩这个游戏之前,我们先要给玩家添加一个游戏菜单,供玩家选择是否游玩

//test.c文件中 #include <stdio.h> void menu() { printf("*******************\n"); printf("******1.enter******\n"); printf("******0.exit*******\n"); printf("*******************\n"); } int main() { int k = 0; //srand((unsigned int)time(NULL)); do { menu(); scanf("%d", &k); switch (k) { case 1: { printf("进入游戏\n"); //printf("-------------------\n");//每次进入游戏后用这个隔开再打印棋盘,能让画面更清白明了 //game();进入game函数 break; } case 0: { printf("退出游戏\n"); break; } default: { printf("输入错误,请重新输入\n"); break; } } } while (k); return 0; }

运行截图:

如上图所示,输入1进入游戏,输入0退出游戏,因为无论怎么,我们都要先打印菜单,供玩家选择是否进入游戏,所以我们采用do_while循环,上来就先打印菜单,告诉玩家要做什么,对于菜单这种可选固定几个整型常量表达式的,我们采用switch语句来实现功能,会比if更好,更快

输入1,我们就进入(game函数),开始游戏:

//在test.c文件中 void game() { char mine[Rows][Cols] = { 0 }; char show[Rows][Cols] = { 0 }; char ch1 = '0'; char ch2 = '*'; char ch3 = '1'; InitBoard(mine, Rows, Cols, ch1);//初始化棋盘 InitBoard(show, Rows, Cols, ch2);//初始化棋盘 //PrintBoard(mine, Row, Col); //mine棋盘是给设计者看的 PrintBoard(show, Row, Col); //show棋盘是给玩家看的 SetMine(mine, Row, Col, ch3); //设置棋盘 // PrintBoard(mine, Row, Col);//观察是否设置雷成功 FindMine(mine, show, Row, Col); }

我们创造了两个数组分别为mine和show,首先对他们分别进行初始化,初始化好后打印show棋盘给玩家看,供玩家进行操作,当然这步操作也可以放在将棋盘的雷设置好之后,就是打印show数组的指令PrintBoard(show, Row, Col)放在SetMine(mine, Row, Col, ch3)之后也是可以的,但要在FindMine()函数之前,不然玩家看不到棋盘,没法排查

游戏逻辑具体函数的定义(game.c文件中):

(1)InitBoard()函数:初始化棋盘

void InitBoard(char arr[Rows][Cols], int rows, int cols, char set) { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { arr[i][j] = set; } } }

用于初始化数组,我们传入了mine和show数组,传这两个数组函数只能用char arr[Rows][Cols]接收,因为数组的大小为11*11的格子,我们要全部初始化,这里不是只对里面的9*9进行操作,而是全部都要操作

(2)PrintBoard()函数:打印棋盘的函数

void PrintBoard(char arr[Rows][Cols], int row, int col) { // 打印顶部列号0 1 2 3 ... for (int i = 0; i <= col; i++) { printf("%-2d ", i); } printf("\n"); // 打印每一行:行号 + 格子内容 for (int j = 1; j <= row; j++) { printf("%-2d ", j); for (int k = 1; k <= col; k++) { printf("%-2c ", arr[j][k]); } printf("\n"); } printf("-------------------\n"); }

我们在初始化完后可以打印一下这两个数组,如上图是在test.c文件中,看看是否初始化成功

运行截图:

可以看到初始化成功,我们还在数组前给他添加了坐标,便于游玩者寻找正常的坐标

(3)SetMine()函数:利用rand函数,在随机位置设置雷在mine棋盘中

需要在主函数mian()函数中调用srand((unsigned int)time(NULL))函数,才能正确使用rand()函数

void SetMine(char mine[Rows][Cols], int row, int col, char set) { int count = Low; while (count) { int x = rand() % row + 1; int y = rand() % col + 1; if (mine[x][y] != set) { mine[x][y] = set; count--; } } }

我们可以观察是否设置好雷:

运行截图:

10颗雷已设置完成

(4)FindMine()函数:用于实现排查坐标的函数

void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col) { int i = 0; int j = 0; int count = Low; int amount = 0; while (amount < (row * col - Low)) { printf("请选择需要排查的坐标:\n"); scanf("%d%d", &i, &j); if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] != '*') { printf("已经被排查过了\n"); continue; } if (mine[i][j] == '0') { show[i][j] = GetGround(mine, i, j) + '0'; PrintBoard(show, Row, Col); // PrintBoard(mine, Row, Col); amount++; } else { printf("不好意思,你被炸死了\n"); PrintBoard(mine, Row, Col); break; } } else { printf("坐标非法输入\n"); } } if (amount == row * col - Low) { printf("恭喜你,排雷成功!\n"); PrintBoard(mine, Row, Col); } }

因为show棋盘中打印的是字符,而GetGround()函数返回的是整型类型的数字,当数字加上字符'0'时,对应的才是ascll里的该数字,才可以用字符占位符打印出来它本身,如printf("%c", 2+'0');因为0的asll码值是48,所以就是2+48==50,ascll码值50对应的数字就是2,所以才会打印2,当你写printf("%c",2)时,打印的是ascll值为2对应的字符,而不会打印数字2

这里的循环条件是,每翻一个格子,amount就加1,直到amount加到等于(格子总数-雷的个数)时,则说明游戏胜利,结束循环

(5)GetGround()函数:用于统计坐标周围雷的总数

int GetGround(char mine[Rows][Cols], int i, int j)//统计周围坐标周围雷的个数 { return (mine[i - 1][j - 1] + mine[i - 1][j] + mine[i - 1][j + 1] + mine[i][j - 1] + mine[i][j + 1] + mine[i + 1][j - 1] + mine[i + 1][j] + mine[i + 1][j + 1]) - 8 * '0'; }

当然也可以用for循环来写:

int GetGround(char mine[ROWS][COLS], int x, int y) { int sum = 0; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { sum = sum + mine[x+i][y+j]; } } return sum - 9 * '0'; }

因为mine数组里不是雷的位置存的本来就是0,所以我们将该格包括该格周围所有数的ascll码的值相加后还要9个字符‘0’相乘,才能得到十进制整型数字

到此扫雷扫码整体的功能已实现:

效果展示:

详细总代码(照搬可直接运行):

game.h

#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #define Row 9 #define Col 9 #define Rows 11 #define Cols 11 #define Low 10 void menu(); void game(); void InitBoard(char arr[Rows][Cols], int rows, int cols, char set); void PrintBoard(char arr[Rows][Cols], int row, int col); void SetMine(char mine[Rows][Cols], int row, int col, char set); void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col); #include <time.h> #include <stdlib.h>

game.c

#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void InitBoard(char arr[Rows][Cols], int rows, int cols, char set) { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { arr[i][j] = set; } } } void PrintBoard(char arr[Rows][Cols], int row, int col) { // 打印顶部列号0 1 2 3 ... for (int i = 0; i <= col; i++) { printf("%-2d ", i); } printf("\n"); // 打印每一行:行号 + 格子内容 for (int j = 1; j <= row; j++) { printf("%-2d ", j); for (int k = 1; k <= col; k++) { printf("%-2c ", arr[j][k]); } printf("\n"); } printf("-------------------\n"); } void SetMine(char mine[Rows][Cols], int row, int col, char set) { int count = Low; while (count) { int x = rand() % row + 1; int y = rand() % col + 1; if (mine[x][y] != set) { mine[x][y] = set; count--; } } } int GetGround(char mine[Rows][Cols], int i, int j)//统计周围坐标周围雷的个数 { return (mine[i - 1][j - 1] + mine[i - 1][j] + mine[i - 1][j + 1] + mine[i][j - 1] + mine[i][j + 1] + mine[i + 1][j - 1] + mine[i + 1][j] + mine[i + 1][j + 1]) - 8 * '0'; } void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col) { int i = 0; int j = 0; int count = Low; int amount = 0; while (amount < (row * col - Low)) { printf("请输入要排查的坐标:"); scanf("%d%d", &i, &j); if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] != '*') { printf("已经被排查过了\n"); continue; } if (mine[i][j] == '0') { show[i][j] = GetGround(mine, i, j) + '0'; PrintBoard(show, Row, Col); // PrintBoard(mine, Row, Col); amount++; } else { printf("不好意思,你被炸死了\n"); PrintBoard(mine, Row, Col); break; } } else { printf("坐标非法输入\n"); } } if (amount == row * col - Low) { printf("恭喜你,排雷成功!下面给出雷所在的棋盘供你查看:\n"); PrintBoard(mine, Row, Col); } }

test.c

#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void menu() { printf("*******************\n"); printf("******1.enter******\n"); printf("******0.exit*******\n"); printf("*******************\n"); } void game() { char mine[Rows][Cols] = { 0 }; char show[Rows][Cols] = { 0 }; char ch1 = '0'; char ch2 = '*'; char ch3 = '1'; InitBoard(mine, Rows, Cols, ch1);//初始化棋盘 InitBoard(show, Rows, Cols, ch2);//初始化棋盘 //PrintBoard(mine, Row, Col); PrintBoard(show, Row, Col); SetMine(mine, Row, Col,ch3); //PrintBoard(mine, Row, Col);//观察是否设置雷成功 FindMine(mine, show,Row, Col); } int main() { menu(); int k = 0; srand((unsigned int)time(NULL)); do { printf("请选择玩还是不玩\n"); scanf("%d", &k); switch (k) { case 1: { printf("进入游戏\n"); printf("-------------------\n"); game(); break; } case 0: { printf("退出游戏\n"); break; } default: { printf("输入错误,请重新输入\n"); break; } } } while (k); return 0; }

三、代码详细解析(扩展功能版)

扩展功能:

•如果排查位置不是雷,周围也没有雷,可以展开周围的一片

•是否可以标记雷

•是否可以加上排雷的时间显示

标记雷功能:

void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col) { int i = 0; int j = 0; int count = Low; int amount = 0; while (amount < (row * col - Low)) { printf("请选择需要排查的坐标(输入0 0进入标记模式):\n"); scanf("%d%d", &i, &j); //扩展标记雷的功能 if (i == 0 && j == 0)//扩展标记雷的功能; { printf("请输入要标记的坐标:"); scanf("%d%d", &i, &j); if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] == '*') { show[i][j] = '!'; PrintBoard(show, Row, Col); continue; } else if (show[i][j] == '!') { show[i][j] = '*'; printf("已经取消该坐标标记"); PrintBoard(show, Row, Col); continue; } else { printf("该位置已被掀开,无法标记"); PrintBoard(show, Row, Col); continue; } } else { printf("坐标非法输入"); continue; } } //排查雷的功能 if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] != '*') { printf("已经被排查过了\n"); continue; } if (mine[i][j] == '0') { show[i][j] = GetGround(mine, i, j) + '0'; PrintBoard(show, Row, Col); // PrintBoard(mine, Row, Col); amount++; } else { printf("不好意思,你被炸死了\n"); PrintBoard(mine, Row, Col); break; } } else { printf("坐标非法输入\n"); } } if (amount == row * col - Low) { printf("恭喜你,排雷成功!\n"); PrintBoard(mine, Row, Col); } }

如果输入0 0,我们就进入标记坐标模式,如果输入标记的坐标在合法范围内,则判断show[i][j]是‘*’,则改为‘!’;如果标记的坐标show[i][j]是‘!’,则改为'*';如果重复标记,则会说明该位置已被掀开,无法标记;而且每个if操作后都要加continue,跳过下方的排查雷的代码,否则标记完后那个坐标又会直接进入排查,逻辑就混乱了

排雷的时间显示:

首先普及一个知识点:time_t 本质上就是整数类型(一般是 long long / long )

void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col) { int i = 0; int j = 0; int count = Low; int amount = 0; time_t start_time = time(NULL); while (amount < (row * col - Low)) { int use_time = (int)(time(NULL) - start_time); printf("=========================\n"); printf(" 已用时:%d 秒\n", use_time); printf("=========================\n"); printf("请选择需要排查的坐标(输入 0 0 进入标记模式):\n"); scanf("%d%d", &i, &j); if (i == 0 && j == 0)//扩展标记雷的功能; { printf("请输入要标记的坐标:"); scanf("%d%d", &i, &j); if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] == '*') { show[i][j] = '!'; PrintBoard(show, Row, Col); continue; } else if (show[i][j] == '!') { show[i][j] = '*'; printf("已经取消该坐标标记"); PrintBoard(show, Row, Col); continue; } else { printf("该位置已被掀开,无法标记"); PrintBoard(show, Row, Col); continue; } } else { printf("坐标非法输入"); continue; } } if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] != '*') { printf("已经被排查过了\n"); continue; } if (mine[i][j] == '0') { show[i][j] = GetGround(mine, i, j) + '0'; PrintBoard(show, Row, Col); // PrintBoard(mine, Row, Col); amount++; } else { printf("不好意思,你被炸死了\n"); PrintBoard(mine, Row, Col); break; } } else { printf("坐标非法输入\n"); } } if (amount == row * col - Low) { time_t total_time = (int)time(NULL) - start_time; printf("=========================\n"); printf(" 已用时:%d 秒\n", total_time); printf("=========================\n"); printf("恭喜你,排雷成功!下面给出雷所在的棋盘供你查看:\n"); PrintBoard(mine, Row, Col); } }

time_t start_time = time(NULL);time()函数的返回类型是time_t,可以返回一个时间戳,所以我们创建了一个起始的时间变量start_time,表示初始时间

int use_time = (int)(time(NULL) - start_time); printf("=========================\n"); printf(" 已用时:%d 秒\n", use_time); printf("=========================\n");

我们使用变量use_time来记录走过时间的秒数,当前时间戳减去刚才创建的初始时间戳的值就是走过的秒数,并且打印出来,并在每次执行操作排查或者标记之前打印一遍,让我们做到每标记一个,每排查一个之前,都能看到所用时间,模拟时间一直在屏幕的效果

效果演示:

展开周围一片雷功能:

Expand()函数:利用递归实现展开

void Expand(char mine[Rows][Cols], char show[Rows][Cols], int x, int y, int row, int col) { // 1. 如果坐标越界,直接返回 if (x < 1 || x > row || y < 1 || y > col) return; // 2. 该格子已经被翻开/标记过,不再处理 if (show[x][y] != '*') return; //计算该位置周围有没有雷 int count = GetGround(mine,x,y); //如果有雷,则显示出来 if (count >0 ) { show[x][y] = count+'0'; //PrintBoard(show, Row, Col);可以看到一步步的递归过程,看到怎扩展的 return; } else { show[x][y] = '0'; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { Expand(mine, show, x+i, y+j ,Row, Col); } } } }

首先判断排查的该坐标是否越界,也就是是否会排查9*9格子以外的格子,如果越界的话,就直接返回上一层Expand函数,不执行下方的代码,防止越界。

然后判断show[x][y]如果不是'*',而是其他数字,说明已经被翻开过,不用再次展开。

上述两个条件很重要,这是递归运行下去的条件

然后我们利用int count = GetGround(mine,x,y)计算该位置周围有没有雷
如果count>0,贼将该位置改为周围雷的总数;如果count=0,说明没有雷,则对其周围所有位置再次进行自身函数调用,不断展开,形成递归

GetGround()函数:用于计算坐标周围雷的个数

int GetGround(char mine[Rows][Cols],int i, int j)//统计周围坐标周围雷的个数 { return (mine[i - 1][j - 1] + mine [i-1][j]+ mine[i-1][j+1]+ mine[i][j-1]+ mine[i][j+1]+ mine[i+1][j-1]+ mine[i + 1][j]+ mine[i + 1][j + 1]) - 8 * '0'; }

GetCount()函数:用于统计当前还有多少个没有翻开的格子

int GetCount(char show[Rows][Cols], int row, int col)//排查有多少个没开的格子 { int count = 0; for (int i = 1; i <= row; i++) { for (int j = 1; j <= col; j++) { if (show[i][j] == '*') { count++; } } } return count; }

FindMine()函数:用于排查和标记雷

void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col) { int i = 0; int j = 0; int count = Low; int amount = 0; time_t start_time = time(NULL); while (GetCount(show, Row, Col) > Low) { int use_time = (int)(time(NULL) - start_time); printf("=========================\n"); printf(" 已用时:%d 秒\n", use_time); printf("=========================\n"); printf("请选择需要排查的坐标(输入 0 0 进入标记模式):\n"); scanf("%d%d", &i, &j); if (i == 0 && j == 0)//扩展标记雷的功能; { printf("请输入要标记的坐标:"); scanf("%d%d", &i, &j); if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] == '*') { show[i][j] = '!'; PrintBoard(show, Row, Col); continue; } else if (show[i][j] == '!') { show[i][j] = '*'; printf("已经取消该坐标标记"); PrintBoard(show, Row, Col); continue; } else { printf("该位置已被掀开,无法标记"); PrintBoard(show, Row, Col); continue; } } else { printf("坐标非法输入"); continue; } } if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] == '!') { printf("该位置已被标记,不能排查"); PrintBoard(show, Row, Col); continue; } if (show[i][j] != '*'&& show[i][j] != '!') { printf("已经被排查过了\n"); PrintBoard(show, Row, Col); continue; } if (mine[i][j] == '0') { //show[i][j] = GetGround(mine, i, j)+'0'; Expand(mine,show,i,j,Row,Col); PrintBoard(show, Row, Col); } else { printf("不好意思,你被炸死了\n"); PrintBoard(mine, Row, Col); break; } } else { printf("坐标非法输入\n"); } } if (GetCount(show, Row, Col) == Low) { time_t total_time = (int)time(NULL) - start_time; printf("=========================\n"); printf(" 已用时:%d 秒\n", total_time); printf("=========================\n"); printf("恭喜你,排雷成功!\n"); PrintBoard(mine, Row, Col); } }

这里我们while循环的判断条件,原本是amount < (row * col - Low),需要改成GetCount(show, Row, Col) > Low,添加GetCount()函数来统计当前还有多少个没有翻开的格子,如果未翻开格子数大于Low,贼说明还有无雷格子没翻开,如果未翻开格子数等于Low,则说明棋盘上剩的全是有雷格子,已全部排查完,游戏胜利

为什么要这样改呢?

是因为我们在之前用amount统计的位置,现在调用了Expend()函数,Expend()函数会扩展展开很多个格子,但amount只加1,无法正确统计翻开的格子数,所以我们重新用一个GetCount()函数,一格一格验证是否翻开,然后统计我们在该逻辑下要的正确的未翻开的格子数

最后如果GetCount(show, Row, Col) == Low,则说明棋盘上剩余格子全是雷,如下图代码所示

if (GetCount(show, Row, Col) == Low) { time_t total_time = (int)time(NULL) - start_time; printf("=========================\n"); printf(" 已用时:%d 秒\n", total_time); printf("=========================\n"); printf("恭喜你,排雷成功!\n"); PrintBoard(mine, Row, Col); }

我们此时又创建了一个总时间total_time变量,并用当前时间戳,减去我们查找程序刚开始时创建的start_time变量,就是我们游戏使用的总时长时间,然后打印出来并说明游戏胜利,给出玩家雷所在的棋盘。

到此,功能扩展完成。

如果你想增加棋盘的难度,想扩展棋盘的大小,只需要在game.h的头文件里的宏定义中:

下方的game.h头文件是扩展功能之后的代码头文件

#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #define Row 9 #define Col 9 #define Rows 11 #define Cols 11 #define Low 10 void menu(); void game(); void InitBoard(char arr[Rows][Cols], int rows, int cols, char set); void PrintBoard(char arr[Rows][Cols], int row, int col); void SetMine(char mine[Rows][Cols], int row, int col, char set); #include <time.h> #include <stdlib.h> int GetGround(char mine[Rows][Cols], int i, int j); int GetCount(char show[Rows][Cols], int row, int col); void Expand(char mine[Rows][Cols], char show[Rows][Cols], int x, int y, int row, int col); void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col);

简单:9*9 棋盘,10 个雷
中等: 16*16 棋盘,40 个雷
困难 :30*16 棋盘,99 个雷

比如你想扩展成中等难度的棋盘游戏,就这样改,改棋盘大小和扩展棋盘的大小

#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #define Row 16 #define Col 16 #define Rows 18 #define Cols 18 #define Low 40 void menu(); void game(); void InitBoard(char arr[Rows][Cols], int rows, int cols, char set); void PrintBoard(char arr[Rows][Cols], int row, int col); void SetMine(char mine[Rows][Cols], int row, int col, char set); #include <time.h> #include <stdlib.h> int GetGround(char mine[Rows][Cols], int i, int j); int GetCount(char show[Rows][Cols], int row, int col); void Expand(char mine[Rows][Cols], char show[Rows][Cols], int x, int y, int row, int col); void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col);

就可以了

扩展后详细总代码(照搬可直接运行):

game.h

#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #define Row 16 #define Col 16 #define Rows 18 #define Cols 18 #define Low 40 void menu(); void game(); void InitBoard(char arr[Rows][Cols], int rows, int cols, char set); void PrintBoard(char arr[Rows][Cols], int row, int col); void SetMine(char mine[Rows][Cols], int row, int col, char set); #include <time.h> #include <stdlib.h> int GetGround(char mine[Rows][Cols], int i, int j); int GetCount(char show[Rows][Cols], int row, int col); void Expand(char mine[Rows][Cols], char show[Rows][Cols], int x, int y, int row, int col); void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col);

game.c

#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void InitBoard(char arr[Rows][Cols], int rows, int cols, char set) { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { arr[i][j] = set; } } } void PrintBoard(char arr[Rows][Cols], int row, int col) { // 打印顶部列号 1 2 3 ... for (int i = 0; i <= col; i++) { printf("%-2d ", i); } printf("\n"); // 打印每一行:行号 + 格子内容 for (int j = 1; j <= row; j++) { printf("%-2d ", j); for (int k = 1; k <= col; k++) { printf("%-2c ", arr[j][k]); } printf("\n"); } printf("-------------------\n"); } void SetMine(char mine[Rows][Cols], int row, int col, char set) { int count = Low; while(count) { int x = rand()%row+1; int y = rand()%col+1; if (mine[x][y] != set) { mine[x][y] = set; count--; } } } void Expand(char mine[Rows][Cols], char show[Rows][Cols], int x, int y, int row, int col) { // 1. 如果坐标越界,直接返回 if (x < 1 || x > row || y < 1 || y > col) return; // 2. 该格子已经被翻开/标记过,不再处理 if (show[x][y] != '*') return; //计算该位置周围有没有雷 int count = GetGround(mine,x,y); //如果有雷,则显示出来 if (count >0 ) { show[x][y] = count+'0'; //PrintBoard(show, Row, Col);可以看到一步步的递归过程,看到怎扩展的 return; } else { show[x][y] = '0'; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { Expand(mine, show, x+i, y+j ,Row, Col); } } } } int GetGround(char mine[Rows][Cols],int i, int j)//统计周围坐标周围雷的个数 { return (mine[i - 1][j - 1] + mine [i-1][j]+ mine[i-1][j+1]+ mine[i][j-1]+ mine[i][j+1]+ mine[i+1][j-1]+ mine[i + 1][j]+ mine[i + 1][j + 1]) - 8 * '0'; } int GetCount(char show[Rows][Cols], int row, int col)//排查有多少个没开的格子 { int count = 0; for (int i = 1; i <= row; i++) { for (int j = 1; j <= col; j++) { if (show[i][j] == '*') { count++; } } } return count; } void FindMine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col) { int i = 0; int j = 0; int count = Low; int amount = 0; time_t start_time = time(NULL); while (GetCount(show, Row, Col) > Low) { int use_time = (int)(time(NULL) - start_time); printf("=========================\n"); printf(" 已用时:%d 秒\n", use_time); printf("=========================\n"); printf("请选择需要排查的坐标(输入 0 0 进入标记模式):\n"); scanf("%d%d", &i, &j); if (i == 0 && j == 0)//扩展标记雷的功能; { printf("请输入要标记的坐标:"); scanf("%d%d", &i, &j); if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] == '*') { show[i][j] = '!'; PrintBoard(show, Row, Col); continue; } else if (show[i][j] == '!') { show[i][j] = '*'; printf("已经取消该坐标标记"); PrintBoard(show, Row, Col); continue; } else { printf("该位置已被掀开,无法标记"); PrintBoard(show, Row, Col); continue; } } else { printf("坐标非法输入"); continue; } } if (i > 0 && i <= row && j > 0 && j <= col) { if (show[i][j] == '!') { printf("该位置已被标记,不能排查"); PrintBoard(show, Row, Col); continue; } if (show[i][j] != '*'&& show[i][j] != '!') { printf("已经被排查过了\n"); PrintBoard(show, Row, Col); continue; } if (mine[i][j] == '0') { //show[i][j] = GetGround(mine, i, j)+'0'; Expand(mine,show,i,j,Row,Col); PrintBoard(show, Row, Col); } else { printf("不好意思,你被炸死了\n"); PrintBoard(mine, Row, Col); break; } } else { printf("坐标非法输入\n"); } } if (GetCount(show, Row, Col) == Low) { time_t total_time = (int)time(NULL) - start_time; printf("=========================\n"); printf(" 已用时:%d 秒\n", total_time); printf("=========================\n"); printf("恭喜你,排雷成功!\n"); PrintBoard(mine, Row, Col); } }

test.c

#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void menu() { printf("*******************\n"); printf("******1.enter******\n"); printf("******0.exit*******\n"); printf("*******************\n"); } void game() { char mine[Rows][Cols] = { 0 }; char show[Rows][Cols] = { 0 }; char ch1 = '0'; char ch2 = '*'; char ch3 = '1'; InitBoard(mine, Rows, Cols, ch1);//初始化棋盘 InitBoard(show, Rows, Cols, ch2);//初始化棋盘 //PrintBoard(mine, Row, Col); PrintBoard(show, Row, Col); SetMine(mine, Row, Col,ch3); //PrintBoard(mine, Row, Col);//观察是否设置雷成功 FindMine(mine, show,Row, Col); } int main() { menu(); int k = 0; srand((unsigned int)time(NULL)); do { printf("请选择玩还是不玩\n"); scanf("%d", &k); switch (k) { case 1: { printf("进入游戏\n"); printf("-------------------\n"); game(); break; } case 0: { printf("退出游戏\n"); break; } default: { printf("输入错误,请重新输入\n"); break; } } } while (k); return 0; }
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进