文章目录
@[TOC]:video_game:前言一、游戏效果展示二、代码基本思路讲解 :video_game:具体实现一、菜单栏函数(test、menu)的编写(test.c中)二、游戏函数(game)的编写(test.c中)三、随机布置雷区(SetMine)四、计算该位置周围雷的个数(get_mine_count)五、递归实现该区域的展开(Unfold)六、标记/取消标记(sign_mine)七、玩家找雷(FindMine)+判断输赢 :video_game:代码模块化展示一、test.c二、game.h三、game.c
🎮前言
一、游戏效果展示
二、代码基本思路讲解
我们如果想要实现扫雷游戏,首先要打印出来一个棋盘。在此棋盘中,玩家看不到雷(mine)的分布,玩家可以通过输入坐标的方式来进行排雷。对于我们程序员来说,也要同时布置一个棋盘,与玩家不同的是我们可以清晰的看到雷区的分布,以此来判断玩家是否踩雷。于是我们就有了目标,想要初始化两个棋盘。先创建两个二位数组,然后初始化的时候我们可以将雷视为字符’1’,将非雷视为字符’0’。当然了想要随机设置雷就要编写代码来具体实现,文章后面会有详细讲解。其打印结果如下:
接下来就是玩家开始找雷,当玩家输入二维数组的坐标时,我们来判断此位置是否为雷’1’,如果非雷’0’则玩家继续排查,若踩到雷则玩家失败。想必看到这篇文章的同学都是玩过扫雷的,但是到现在为止,我们还是没有真正的感受到扫雷的乐趣,我们的代码到底差在哪里呢?代码还可以如何优化呢?欲知后事如何,请看下文分析。
🎮具体实现
在讲解之前首先要明确一个模块化编写代码的思路,就是通过源文件、头文件相结合的方式来实现的。
这里有test.c和game.c文件,以及game.h头文件
一、菜单栏函数(test、menu)的编写(test.c中)
1.为了整体代码的功能和美观我们可以先编写一个菜单提示栏(menu)
void menu(){printf("**************************\n");printf("******* 1.play ********\n");printf("******* 2.exit ********\n");printf("**************************\n");}
2.编写扫雷游戏前要写出菜单栏信息,让玩家可以实现基本的游戏/退出游戏的操作,此操作可以放在test函数中。如果玩家选择游戏则调用game函数开始游戏,反之则退出游戏。
void test(){int temp = 0;srand((unsigned)time(NULL));//后文会讲到,定位到 三、随机布置周围雷的个数do{menu();printf("请选择:>");scanf("%d",&temp);switch (temp){case 1:game();break;case 2:printf("游戏退出\n");break;default:printf("选择错误!请重新输入!\n");}}while(temp);}
二、游戏函数(game)的编写(test.c中)
game中实现扫雷的基本功能,其中包括两个棋盘的初始化(InitBoard)、打印(DisplayBoard)、随机设置雷(SetMine)、玩家找雷(FindMine)函数。这是扫雷游戏的基本流程。
❓ 什么是初始化? 答:向二位数组中添加内容,这里是加字符’0’或’1’。
❓ 什么是打印棋盘? 答:用printf()来输出二位数组里的内容。
void game(){char mine[ROWS][COLS] = {0 };//布置好的雷的信息char show[ROWS][COLS] = {0 };//排查出雷的信息printf("游戏开始\n");//初始化两个棋盘InitBoard(mine, ROWS, COLS,'0');InitBoard(show, ROWS, COLS,'*');//打印棋盘DisplayBoard(show, ROW, COL);//随机设置雷SetMine(mine, ROW, COL);//DisplayBoard(mine, ROW, COL);//玩家开始查找雷FindMine(mine, show, ROW, COL);}
这里的ROWS和COLS是在game.h头文件中设定的值12,ROW和COL同样也是在game.h头文件中设定好的值9;
❓ 这里有人会问到为什么我们打印 9 *9 棋盘但是却要初始化 11 *11 的棋盘呢???不要慌,带着疑问看下去可能你的理解会更加深刻(答案在 :四、计算该位置周围雷的个数)
三、随机布置雷区(SetMine)
设置x和y来接收随机函数(rand)的数值。如果想要接收 1~9 之间任意一个数,需要先把rand函数取余(%)9这样会得到 0~9之间的数,再加上1就会得出 1~9之间的数。
❓什么是srand()??,怎么用呢???
答: 为了得到随机数,在此之前我们需要先初始化随机种子srand().不过为了防止随机数每次重复,常常使用系统时间来初始化 即: srand((unsigned int) time(NULL));如果想在一个程序中生成随机数序列,需要至多在生成随机数之前设置一次随机种子。即:只需在主程序开始处调用srand((unsigned)time(NULL)); 后面直接用rand就可以了。不要在for等循环放置srand((unsigned)time(NULL));
void SetMine(char board[ROWS][COLS], int row, int col){int n = count;while (n){int x = rand() % 9 + 1;int y = rand() % 9 + 1;if (board[x][y]=='0'){board[x][y] = '1';n--;}}}
四、计算该位置周围雷的个数(get_mine_count)
以图中所打印的二位数组为例,若我们想计算该位置周围雷的个数,就需要对周围8个元素逐个判断。
到这里我们不难想到可以把8个字符加起来,再转换成数字就可以判断该位置周围雷的个数。代码如下:
int get_mine_count(char mine[ROWS][COLS],int x,int y){int tmp = 0;for (int i=-1;i<=1;i++){for (int j=-1;j<=1;j++){tmp+= mine[x + i][y + j]-'0';}}return tmp;}
❓ 那么问题来了,我要是判断如下位置的元素应该怎么办呢?
还记得上文我创建的 11 *11 的二维数组吗,这里只是显示的是 9 *9,实际上也是11 *11。由于判断如上元素周围雷的个数不好判断,所以我们才创建了11 *11的二维数组,这时候我们就可以其轻而易举的算出周围雷的个数。
五、递归实现该区域的展开(Unfold)
⚙️ 根据我们玩扫雷的多年经验可知,我们点下任意位置,若该位置周围没有雷则它就会向周围展开一圈,若果该位置的周围有雷它则会停止并显示周围雷的个数。我们可以这样理解,电脑会一直重复这个展开操作,直到不能展开(周围位置有雷)为止。如下图所示展开的位置都没有雷,但是在边界的周围有雷。
再来对代码进行想象,这段代码会是一段循环,循环体内会有判断。判断条件就应该是:该位置周围如果没有雷就直接展开,反之则显示该位置周围雷的个数。这里我们需要借助get_mine_count函数的思想,对该位置周围8个进行判断,但与之不同的是,这里需要不断循环的往下递归,不断的对新位置周围8个元素进行判断吗,所以这里还需要调用到get_mine_count函数。
具体代码的编写思想出来了,剩下的就是添加限制条件:1.如果该位置被判断过则停止递归 2.若果该位置超过9 *9二位数组的边界则递归停止。由此我们就可以写代码喽。
//递归展开void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y){if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1){return ;}//判断是否已经被排除if (show[x][y] != '*'){return ;}int n = get_mine_count(mine, x, y); //周围有几个炸弹if (n>0) //周围如果没有炸弹,则展开{show[x][y] = n+'0';}else if (n==0){show[x][y] = ' ';for (int i=-1;i<=1;i++){for (int j=-1;j<=1;j++){Unfold(mine, show, x+i, y+j);}}}}
六、标记/取消标记(sign_mine)
这里我们用到mine和show数组,这里的代码编写起来比较简单,大多数用到的就是循环条件和判断条件。玩家标记了一个未被判断过的位置,若此位置为炸弹则炸弹总数量减1,反之则炸弹总数量不变。相信读者大大仔细阅读下列代码都会明白。🌗
//标记或取消标记int sign_mine(char mine[ROWS][COLS],char show[ROWS][COLS]){int n = 0,a=0,b=0;int win = 0;while (1){//美化界面printf("\n****************\n");printf(" 1.mark_mine(标记)\n 2.cancel_mine(取消标记)\n 3.continue(跳过)\n");printf("****************\n请选择:>");scanf("%d", &n);if (n == 1){printf("请输入要标记的坐标:>");scanf("%d%d", &a, &b);if (show[a][b] == '*'){show[a][b] = 'Y';win += 1;DisplayBoard(show, ROW, COL);continue;}else{printf("标记错误,请重新标记!!\n");continue;}}else if (n == 2){printf("请输入要取消标记的坐标:>");scanf("%d%d", &a, &b);if (show[a][b]=='Y'){show[a][b] = '*';DisplayBoard(show, ROW, COL);win -= 1;continue;}else{printf("操作错误,请重新输入!!\n");continue;}}elsereturn win;}}
七、玩家找雷(FindMine)+判断输赢
这个模块就是玩家输入坐标查询,1.若该位置被查询过则请玩家重新输入 2.若该位置为雷则游戏失败 3.若该位置非雷则调用(Unfold)函数展开同时打印棋盘 4.若该位置超过边界则输入错误。
最后就是判断输赢,在标记雷(sign_mine)函数中,若标记正确则雷的总数减1,直到总数为0时则排雷成功。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col){int x = 0;int y = 0;int win = count;while (win>0){printf("\n请输入想要排查的坐标:>");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){if (show[x][y] != '*'){printf("该坐标被排查过了\n");continue;}if (mine[x][y] == '1'){printf("抱歉!你已经被炸死了!\n");DisplayBoard(mine, ROW, COL);break;}if (mine[x][y] == '0'){Unfold(mine, show, x, y); //利用递归展开非雷区域printf("\n");DisplayBoard(show, ROW, COL);//打印棋盘int n=sign_mine(mine, show);//标记雷或取消标记雷win = win - n;printf("\n");printf("还剩%d个雷\n",win);}}elseprintf("坐标非法!\n");}if (win==0){printf("恭喜你,排雷成功!!!!!\n");//DisplayBoard(mine, ROW, COL);Distribution_mine(mine,ROW,COL);}}
🎮代码模块化展示
一、test.c
#define _CRT_SECURE_NO_WARNINGS 1;#include "game.h"void menu(){printf("**************************\n");printf("******* 1.play ********\n");printf("******* 2.exit ********\n");printf("**************************\n");}void game(){char mine[ROWS][COLS] = {0 };//布置好的雷的信息char show[ROWS][COLS] = {0 };//排查出雷的信息printf("游戏开始\n");//初始化两个棋盘InitBoard(mine, ROWS, COLS,'0');InitBoard(show, ROWS, COLS,'*');//打印棋盘DisplayBoard(show, ROW, COL);//随机设置雷SetMine(mine, ROW, COL);//DisplayBoard(mine, ROW, COL);//玩家开始查找雷FindMine(mine, show, ROW, COL);}void test(){int temp = 0;srand((unsigned int)time(NULL));do{menu();printf("请选择:>");scanf("%d",&temp);switch (temp){case 1:game();break;case 2:printf("游戏退出\n");break;default:printf("选择错误!请重新输入!\n");}}while(temp);}int main(){test();return 0;}
二、game.h
#pragma once#include<stdio.h>#include<stdlib.h>#include<time.h>#define ROW 9#define COL 9#define ROWS 11#define COLS 11#define count 5//雷的数量//初始换棋盘void InitBoard(char board[ROWS][COLS],int rows, int cols,char ret);//打印棋盘void DisplayBoard(char board[ROWS][COLS],int row,int col);//布置雷区void SetMine(char board[ROWS][COLS], 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 board[ROWS][COLS], int rows, int cols, char ret){for (int i = 0; i < rows; i++){for (int j = 0; j < cols; j++){board[i][j] = ret;}}}//打印棋盘void DisplayBoard(char board[ROWS][COLS], int row, int col){printf("\n--------扫雷--------\n");for (int i=0;i<=row;i++){printf("%d ",i);}printf("\n");for (int i=1;i<=row;i++){printf("%d ",i);for (int j=1;j<=col;j++){printf("%c ",board[i][j]);}printf("\n");}printf("--------扫雷--------\n");}//随机设置雷void SetMine(char board[ROWS][COLS], int row, int col){int n = count;while (n){int x = rand() % 9 + 1;int y = rand() % 9 + 1;if (board[x][y]=='0'){board[x][y] = '1';n--;}}}//周围雷的个数int get_mine_count(char mine[ROWS][COLS],int x,int y){int tmp = 0;for (int i=-1;i<=1;i++){for (int j=-1;j<=1;j++){tmp+= mine[x + i][y + j]-'0';}}return tmp;}//递归展开void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y){if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1){return ;}//判断是否已经被排除if (show[x][y] != '*'){return ;}int n = get_mine_count(mine, x, y); //周围有几个炸弹if (n>0) //周围如果没有炸弹,则展开{show[x][y] = n+'0';}else if (n==0){show[x][y] = ' ';for (int i=-1;i<=1;i++){for (int j=-1;j<=1;j++){Unfold(mine, show, x+i, y+j);}}}}//标记或取消标记int sign_mine(char mine[ROWS][COLS],char show[ROWS][COLS]){int n = 0,a=0,b=0;int win = 0;while (1){//美化界面printf("\n****************\n");printf(" 1.mark_mine(标记)\n 2.cancel_mine(取消标记)\n 3.continue(跳过)\n");printf("****************\n请选择:>");scanf("%d", &n);if (n == 1){printf("请输入要标记的坐标:>");scanf("%d%d", &a, &b);if (show[a][b] == '*'){show[a][b] = 'Y';win += 1;DisplayBoard(show, ROW, COL);continue;}else{printf("标记错误,请重新标记!!\n");continue;}}else if (n == 2){printf("请输入要取消标记的坐标:>");scanf("%d%d", &a, &b);if (show[a][b]=='Y'){show[a][b] = '*';DisplayBoard(show, ROW, COL);win -= 1;continue;}else{printf("操作错误,请重新输入!!\n");continue;}}elsereturn win;}}//成功后打印最终雷区分布void Distribution_mine(char mine[ROWS][COLS],int row,int col){for (int i=0;i<=col;i++){printf("%d ",i);}printf("\n");for (int i=1;i<=row;i++){printf("%d ", i);for (int j=1;j<=col;j++){if (mine[i][j]=='1'){mine[i][j] = 'Y';printf("%c ",mine[i][j]);}else if (mine[i][j]=='0'){mine[i][j] = ' ';printf("%c ",mine[i][j]);}}printf("\n");}printf("\n");}void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col){int x = 0;int y = 0;int win = count;while (win>0){printf("\n请输入想要排查的坐标:>");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){if (show[x][y] != '*'){printf("该坐标被排查过了\n");continue;}if (mine[x][y] == '1'){printf("抱歉!你已经被炸死了!\n");DisplayBoard(mine, ROW, COL);break;}if (mine[x][y] == '0'){Unfold(mine, show, x, y); //利用递归展开非雷区域printf("\n");DisplayBoard(show, ROW, COL);//打印棋盘int n=sign_mine(mine, show);//标记雷或取消标记雷win = win - n;printf("\n");printf("还剩%d个雷\n",win);}}elseprintf("坐标非法!\n");}if (win==0){printf("恭喜你,排雷成功!!!!!\n");//DisplayBoard(mine, ROW, COL);Distribution_mine(mine,ROW,COL);}}
如果觉得《C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程》对你有帮助,请点赞、收藏,并留下你的观点哦!