当前位置: 首页 > article >正文

扫雷——完整版!!!!!!

今天来分享一下写的扫雷小游戏,其中包括了 展开一片的递归操作,标记,和取消标记。

除了操作界面不一样之外,逻辑和平时玩的扫雷基本是一样的。


游戏规则

  1. 给定一个棋盘,期棋盘中有若干个雷,玩家需要通过分析,把所有不是雷的格子给排查,也就是翻开

  1. 操作

1)点击格子进行排雷

排雷后的格子有两种状态

  • 该位置是雷,游戏结束。

  • 该位置不是雷,显示该位置周围八个坐标中雷的个数,如果周围八个坐标没有雷,则这里显示空格。

2)右击格子进行标记

标记分为两种

  • 玩家确定这个位置是雷,给上小红旗。

  • 玩家不确定这个位置是雷,给上一个问号。

  1. 胜利条件

玩家通过排雷和标记的配合,结合排雷后的格子上提示的信息,将棋盘中所有不是雷的格子都排查完毕。


游戏实现

棋盘

棋牌作用

  • 埋雷

  • 显示排雷情况

看一下实际的效果:

游戏中的扫雷棋盘

模拟的扫雷棋盘

埋雷的棋盘

扫雷棋盘,就是可以看成一个矩阵,这里面的是9*9的一个矩阵,所以我们要用二维数组来模拟。

规定:

  • 棋盘可操作的区域是9*9的二维字符数组

  • 棋盘有两个,一个用来埋雷,一个用来显示排查雷的情况

  • 埋雷的棋盘,‘1’代表该位置有雷,‘0’代表该位置没雷

  • 实际的棋盘要多出两行两列

解释

首先,我们先回忆一下,排雷的规则,给出一个坐标,如果该位置不是雷,对其周围八个格子雷的数量进行统计,显示在该位置。

但问题是,一定是8个吗,显然是不一定的,对于边上的坐标,理应排查五个位置,角上的坐标,理应排查三个位置,但是这样考虑,是不是显得很麻烦,因为大部分坐标排查的都是8个位置,所以我们做一下处理,增加两行两列,这些位置不埋雷,也就是雷的个数是 0,但这些位置需要可以访问,所以实际数组的大小为,11*11,如图所示:

如图,中间蓝色9*9的区域,是我们要排雷的区域,这些区域埋的有雷,而外层橙色的区域是多加的两行两列,这些区域没有雷,方便我们对特殊位置的排雷,保证每个位置都是对周围8个位置进行统计雷的个数。

为何用两个棋盘

你想,按照目前的规定,假如只用一个棋盘,我们对一个位置排雷了,并将这个位置周围雷的信息显示到该位置,比如是3个雷,那么如果,我们排查其它位置的时候,如果也需要排查这个位置,但我们用的是‘0’或‘1’进行判断是否为雷,但这个‘3’是不是就影响我们的判断了,而且处理起来也比较麻烦。

所以,我们干脆使用两个棋盘,它们的大小是一模一样的,排雷使用的是埋的有雷的棋盘,之后,我们将信息显示到另一个棋盘对应的位置,之后只显示该棋盘,这个问题就解决了。

埋雷的棋盘

显示信息的棋盘


辅助函数

接下来给出一些变量和常量的定义

#define rows 9//可操作的行
#define cols 9//可操作的列
#define ROWS rows+2//实际的行
#define COLS cols+2//实际的列
#define MINES 10//雷的个数
char board[ROWS][COLS] = { 0 };//埋雷 排雷的棋盘
char show[ROWS][COLS] = { 0 };//打印效果的棋盘

上面这些变量,是在函数里面要传的参数。

下面三个函数是这个游戏中要一直用的函数,先介绍一下:

    • 初始化函数

两个棋盘一个埋雷,一个显示,规定,埋雷的棋盘全部初始化为字符‘0’,而显示的棋盘,全部初始化为‘*’。

注意,这里需要传实际的行和列,尤其是对于埋雷的棋盘,那多加的两行两列要初始化为‘0’。

void InitBoard(char board[ROWS][COLS], int row, int col,char cur)
{for (int i = 0; i < row; i++){for (int j = 0; j < col; j++){board[i][j] = cur;}}
}

这个函数比较简单,比较巧的一点是,两个棋盘大小一样,但需要初始化的值不一样,那么就把需要的初始化值作为参数 cur传进来,初始化就好了。

    • 打印棋盘

我们打印棋盘肯定要打印的是中间那块能操作的区域,也就是9*9那块,所以干脆就传这个行和列。

对于这个打印我们提供了两个函数,其实也就是加上了边框,看一下效果

没加边框打印

加边框打印

代码如下,自取哦~

没加边框打印:

void ShowBoard(char board[ROWS][COLS], int row, int col)
{printf("----------扫雷游戏----------\n");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++){printf(" %c ", board[i][j]);}printf("\n");}
}

加边框打印:

void ShowBoard(char board[ROWS][COLS], int row, int col)
{printf("----------扫雷游戏----------\n");for (int i = 0; i <= col; i++){if (i == 0)printf(" %d ",i);else{printf(" %d  ", i);}}printf("\n");for (int i = 0; i <= col; i++){if (i == 0){printf("  |");}else{printf("---|");}}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");for (int k = 1; k <= row; k++){if (k == 1){printf("  ");}if(k==1)printf("|---|");else{printf("---|");}}printf("\n");}
}

    • 埋雷

埋雷就是生成一个坐标,只要这个位置还没雷,就在这个位置埋一个雷。

void Setmine(char board[ROWS][COLS], int row, int col)
{//坐标int x = 0;int y = 0;int mines = MINES;//10个雷while (mines){//生成1到9的坐标x = rand() % 9 + 1;y = rand() % 9 + 1;if (board[x][y] == '0'){board[x][y] = '1';mines--;}}
}

    • 统计雷

写统计雷之前,我们先复习一个小的知识点,就是0到9的数字它对应的字符的ASCII码值和这个数字的关系。

字符零‘0’它的ASCII值是48,‘1’的ASCII值是49,‘2’的ASCII值是50,后边的依次类推。

那么通过字符‘1’如何得到整数1,是不是就用49减去48,就得到整数1了,其它的数字也是同样的道理,因为对字符进行算数运算,操作的还是它的ASCII值。

代码如下:

int MineCount(char board[ROWS][COLS], int x, int y)
{return  board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] +board[x][y - 1] +                          +board[x][y + 1] +board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1]- 8 * '0';
}

看这个函数,传过来了我们埋雷的数组,和一个坐标,对于埋雷的数组,它的每个位置不是‘0’就是‘1’,我们把它的周围八个位置上字符加起来,其实加的是ASCII值,然后根据上面的小知识点,再减去8*‘0’,其实是8*48,得到的整数不就是周围雷的个数吗,所以这个代码就是这个原理。

再举个实例,比如一个坐标周围有5个雷,也就是 5个‘1’和3个‘0’,加起来是

3*48+5*49=389,8*48=384,389-384=5,5不就是雷的个数吗,5再加上48,对应的字符不就是‘5’吗。

看一下排雷的效果:

显示的棋盘

埋雷的棋盘

如图所示,显示棋盘中,左上角的 2,就代表周围红方框内有两个雷,中间的2 就代表,中间红方框内有两个雷,对应到埋雷的棋盘,确实是如此。


标记和取消标记

这两个操作相对简单,先介绍这两个操作,最后再介绍排雷的操作。

标记

1.注意
  • 坐标输入要合理

  • 操作的是显示的棋盘

  • 已标记的位置不重复标记

  • 已排查的位置不能标记

  • 标记分为两种,‘!’和'?'

  • 被标记的位置不能排查

前面四种比较好理解,重点解释一下最后两种。

标记分为两种:

  • '!'代表玩家确定这个位置是雷,

  • '?'代表玩家不确定这个位置是否有雷,可能是有的。

被标记的位置不能排查:

就是说这个位置玩家认为它是雷或者可能为雷,在排雷的过程中,这些位置是不能被排查,在递归的过程中,也要考虑这种情况,因为递归的过程也是在排查雷。

2.代码
void PosMark(char show[ROWS][COLS], int row, int col)
{int x = 0, y = 0;printf("请输入标记的坐标:>");scanf("%d%d", &x, &y);system("cls");if (x >= 1 && x <= row && y >= 1 && y <= col)//输入的坐标数值合法{if (show[x][y] == '*'){show[x][y] = '!';}else{if (show[x][y] == '!'||show[x][y]=='?'){printf("该位置,已标记不可重复标记!!\n");}else{printf("该位置已经排查过,不能标记!!\n");}}}else{printf("输入坐标不合法,请重新输入!!\n");}
}

对于正确的标记,只需把原来位置上的'*'改为!即可,其它错误的标记,给出合理的提示即可。

取消标记

    • 注意
  • 坐标输入要合理

  • 操作的是显示的棋盘

  • 未标记的、已排查的位置不能取消标记

    • 正确的取消标记
  • 第一次,先把!改为?

  • 第二次,再把?改为*

    • 代码
void EraMark(char show[ROWS][COLS], int row, int col)
{int x = 0, y = 0;printf("请输入取消标记的坐标:>");scanf("%d%d", &x, &y);system("cls");if (x >= 1 && x <= row && y >= 1 && y <= 9)//坐标合理{if (show[x][y] == '!')//标记为 '!'{show[x][y] = '?';//标记改为 '?'}else if (show[x][y]=='?')//标记为'?'{show[x][y] = '*';//改为初始值}else{if (show[x][y] == '*')//对未标记的坐标进行取消标记{printf("该位置未被标记,不可取消标记!!\n");}else//对排查过的位置进行取消标记{printf("该位置已排查,不可取消标记!!\n");}}}else//坐标输入错误{printf("坐标输入错误!!\n");}
}

只有当坐标输入正确且该位置已经标记,才能取消标记,其它位置给出合理提示即可。

排雷

排雷中,难点在于那种一大片的效果,那是采用的递归算法。

    • 注意

  • 该位置有雷,游戏结束

  • 该位置没雷,进行排雷

  • 坐标不越界、不重复排雷

    • 代码

int  Findmine(char board[ROWS][COLS], char show[ROWS][COLS], int row, int col) {int x = 0, y = 0;//system("cls");printf("请输入排雷坐标:>");scanf("%d%d", &x, &y);system("cls");if (x >= 1 && x <= row && y >= 1 && y <= col) {//坐标合理if (show[x][y] != '*') {//该位置不是初始值,不能排雷if (show[x][y] == '!' || show[x][y] == '?') {//该位置被标记,不能排雷printf("该位置已被标记,暂时不可排雷,请取消标记后,再进行排雷!!\n");return 1;} else {//该位置已经排雷,不能排雷printf("该位置已排雷,不可重复排雷!!\n");return 1;}} else { //统计雷的个数if (board[x][y] == '1') { //该位置是雷,游戏结束return  0;} else {//该位置需要排雷RecuMine(board, show, x, y);//递归排雷return 1;}}} else {//坐标输入不合法printf("坐标输入错误!!!\n");return 1;}
}

排雷本身的逻辑并不复杂,只要在正确的位置排雷即可,对于不同排雷的情况,我们给出不同的提示。

里面返回值: 1 代表本次排雷后,游戏继续(输赢是在这个函数外面判断的),0 代表排到雷了,游戏结束。

    • 递归排雷

1)条件:
  • 该位置周围没有雷(该位置显示的是空格)

  • 该位置没有被排查过(不重复递归)

  • 坐标不越界

2)递归的代码
void RecuMine(char board[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{if (x < 1 || x>9 || y < 1 || y>9)//坐标不合理不递归return;if (show[x][y] == '!'||show[x][y]!='*'||show[x][y]=='?')
// 该位置被标记(!或?)、该位置被排查过 不递归
//您想被排查的坐标只要不是雷,那肯定有信息,一定不会是初始值 ‘*’
//那么就可确定只要不等于 * 的位置,就排查过了return;int count = 0;count = MineCount(board,x, y);//先统计改坐标周围雷的个数if (count == 0)//雷的个数为 0,即周围 8个位置都不是雷,那么这个八个位置就放心排雷了{show[x][y] = ' ';//先把该位置置为空格
//递归它周围 8个位置的坐标,进行排雷RecuMine(board, show, x - 1, y - 1);RecuMine(board, show, x - 1, y );RecuMine(board, show, x - 1, y + 1);RecuMine(board, show, x, y - 1);RecuMine(board, show, x, y + 1);RecuMine(board, show,x+1, y - 1);RecuMine(board, show, x+1, y);RecuMine(board, show, x+1, y +1);}else//这里代表这个位置周围是有雷的,那么把的个数显示上去,程序走到尾部,递归也就返回了{show[x][y] = count + '0';}
}

递归的代码如上,接下来看一下递归产生的效果。

排查的是(4,4)这个位置

排查的是(3,6)这个位置

以上就是对这个递归排雷的介绍,我们尤其要注意的是递归进行的条件!!


判断输赢

这个比较简单,只要所有的非雷格子都被翻开,那么就赢了,我们只需要统计这些格子的数量就好了。

代码

//判断输赢
int  IsWin(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]!='*'&&show[i][j]!='!'&&show[i][j]!='?')//这个地方不是雷  他被掀开了count++;}}if (count ==  rows*cols-MINES)return 1;elsereturn 0;
}

这个的功能其实是判断是否赢了,输了我们是在排雷那里根据返回值判断的,这里返回值为1 代表赢了,返回值为 0代表没有赢,游戏继续。

if (show[i][j]!='*'&&show[i][j]!='!'&&show[i][j]!='?')//这个地方不是雷  他被掀开了count++;

这个语句的意思是,该位置已经被排查过了(同时不能被标记),那么这个格子就是我们要统计的。

再来

if (count ==  rows*cols-MINES)return 1;elsereturn 0;

总的格子数减去埋有雷的格子数,就是我们要统计的个数,只要是这个数,那么就赢了。


总结

    • 主函数

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu() {printf("*************************\n");printf("*********  1.play *******\n");printf("*********  0.exit *******\n");printf("*************************\n");
}
void  menu2() {printf("1.排雷      2.标记      3.取消标记\n");
}
void game() {system("cls");char board[ROWS][COLS] = { 0 };//埋雷 排雷的棋盘char show[ROWS][COLS] = { 0 };//打印效果的棋盘//初始化棋盘InitBoard(board, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');//打印棋盘//ShowBoard(show, rows, cols);//埋雷Setmine(board, rows, cols);ShowBoard(board, rows, cols);int cur = 1;
//这里初始值设为1,是为了防止第一次没有排雷,直接判断cur等于0就结束了
//所以这个初始值 要非零int input = 0;while (1) {ShowBoard(show, rows, cols);menu2();printf("请选择操作:>");scanf("%d", &input);switch (input) {case 1://排雷cur = Findmine(board, show, rows, cols);break;case 2://标记PosMark(show, rows, cols);break;case 3://取消标记EraMark(show, rows, cols);default:printf("输入错误,请重新输入!!\n");break;}if (cur == 0) {printf("很遗憾,你被炸死了,游戏结束!!!\n");ShowBoard(board, rows, cols);break;}if (IsWin(show, rows, cols)) {printf("恭喜你,你赢了!!!\n");ShowBoard(show, rows, cols);ShowBoard(board, rows, cols);break;}}}
int main() {srand((unsigned int)time(NULL));//随机数的种子int input = 0;do {menu();printf("请选择:>");scanf("%d", &input);switch (input) {case 1:game();break;case 0:printf("退出游戏!!\n");break;default:break;}} while (input);return 0;
}
    • 注意

  • 把游戏各个功能分成一个个模块,逐个去写,并且写一部分调试一部分

  • 扫雷游戏难点在棋盘的设计和操作的实现


以上就是本次分享的全部内容啦


如果对您有帮助,希望给博主点点赞,收藏,分享一下,感谢您的支持!!

源代码和往常一下,放在gitee仓库里面啦,有需自取哦~~~

我们下期,再见~

链接如下~~

扫雷完整版_1_2_25 · 琦琦爱敲代码/琦琦的日常代码 - 码云 - 开源中国 (gitee.com)

老铁们,可以玩一玩这个扫雷哦,评论区可以留下你成功了多少次,用时多少哦~~

http://www.lryc.cn/news/2415016.html

相关文章:

  • JSON数据生成器教程
  • IDEA最详细配置让开发效率起飞,这还不赶紧收藏?,赶紧收藏
  • 房内事对白经典!!!!
  • VSCode 安装流程与基础操作(图文版)
  • 开心网不开心:用户,该怎样让你留下来?
  • 程序员面试时候出的一些逻辑问题
  • ESET NOD32最新单机、企业中、英文版 + 个人专有ID(90天使用期)申请方法
  • CNGI高校驻地网IPv6用户数量排名
  • Java设置cookie原理
  • R语言检索网址汇总
  • 复习第20天(File对象)
  • MSVCP71.DLL msvcr71.dll丢失 64位
  • 已解决socket.timeout : The read operation timed out
  • 摄像机主要指标及参数
  • PHP极简网盘系统源码
  • ueditor使用指南
  • 腾讯云mysql默认支持双主备吗_最大支持1主15备模式,腾讯云张青林详解CynosDB四大核心性能...
  • 2018年省赛题
  • 阿里云挖矿病毒解决
  • 0基础学Python有多难?Python入门简单吗?怎么学Python?
  • iOS6.1 beta 固件下载
  • 新站长建设像淘宝网这样的网站的一些经验分享
  • 锟斤拷?UTF-8与GBK互转,为什么会乱码?
  • 专升本英语作文
  • 黑鹰的VIP数据库(一)
  • 强网杯2021 misc 复现
  • 安装mysql时磁盘空间不足 linux系统下如何正确扩展磁盘空间
  • 凤凰传说
  • 脉冲神经网络的五脏六腑
  • Ubuntu chmod 命令修改文件权限