【C语言习题】12.扫雷游戏

文章目录

    • 1.扫雷游戏分析和设计
      • 1.1 扫雷游戏的功能说明
      • 1.2游戏界面:
      • 1.3游戏的分析和设计
        • 1.2.1 数据结构的分析
        • 1.2.2 ⽂件结构设计
    • 2.扫雷游戏的代码实现
    • 3.代码讲解


1.扫雷游戏分析和设计

1.1 扫雷游戏的功能说明

  • 使用控制台实现经典的扫雷游戏
  • 游戏可以通过菜单实现继续玩或者退出游戏
  • 扫雷的棋盘是9*9的格子
  • 默认随机布置10个雷
  • 可以排查雷
    • 如果位置不是雷,就显示周围有几个雷
    • 如果位置是雷,就炸死游戏结束
    • 把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束

1.2游戏界面:

在这里插入图片描述在这里插入图片描述
初始界面排雷界面
在这里插入图片描述在这里插入图片描述
排雷失败界面排雷成功界面

为了方便演示,我把排雷成功的设置成了2x2的,方便成功。


1.3游戏的分析和设计

1.2.1 数据结构的分析

我们先看一下扫雷的图片:

在这里插入图片描述在这里插入图片描述

这一个个框框看着很像数组,事实上我们可以用数组来存放扫雷游戏需要的数据。

  • 点击前:全是*

  • 点击后:有雷,我们就存放1,没有雷就存放0。

如果我们没有被雷炸到,那么我们点击的地方会显示周围有几个雷。

在这里插入图片描述在这里插入图片描述

上面的表格里面,一开始我们不知道里面有啥,然后点了最中间的那个。

然后就出现了1(因为点击的周围有一个雷)。图2里面的雷和无雷是为了方便理解写出来的。

在这里插入图片描述

如果我们在[1][1]处点击,那么就要统计[0][0],[0][1],[0][2],[1][0],[1][2],[2][0],[2][1],[2][2]这8个地方一共有几个雷。

可如果在[2][2]这个地方点击呢?只要统计3个地方了。

难道我们要分多钟情况去考虑吗?

当然不是,我们可以建造一个比实际需要的扫雷数组大一圈的数组。

在这里插入图片描述

例如我们只需要中间的3X3的数组用作扫雷游戏,那么我们可以准备一个5X5的数组,这样每个扫雷游戏的区域都可以采用一样的方法来统计周围有几个雷了。

同样的,我们要准备两个结构一样的数组,一个用来存放雷和非雷的数据,另一个用来显示。

可以理解为一个是前端,一个是后端。

  • 然后我们让雷是1,非雷是0(后端)

  • 未知的地方放*,已知的地方显示周围雷的数字(前端)

1.2.2 ⽂件结构设计

因为工程有点大,我们可以用3个文件来完成这个游戏。

test.c //文件中写游戏的测试逻辑 
game.c //文件中写游戏中函数的实现等
game.h //文件中写游戏需要的数据类型和函数声明等

2.扫雷游戏的代码实现

game.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h>
#include <stdlib.h>#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2#define EASY_COUNT 10//初始化棋盘
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);
//排查雷
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 set) {int i = 0;int j = 0;for (i = 0; i < rows; i++) {for (j = 0; j < cols; j++) {board[i][j] = set;}}
}
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col) {int i = 0;int j = 0;printf("-----------------扫雷游戏-----------------\n");for (j = 0; j <= col; j++) {printf("%d ", j);}printf("\n");for (i = 1; i <= row; i++) {printf("%d ", i);for (j = 1; j <= col; j++) {printf("%c ", board[i][j]);}printf("\n");}printf("-----------------扫雷游戏-----------------\n");
}//布置雷
void SetMine(char board[ROWS][COLS], int row, int col) {//布置10个雷//生成随机的坐标,布置雷int count = EASY_COUNT;//横坐标:1-9;纵坐标:1-9while (count) {int  x = rand() % row + 1;int  y = rand() % col + 1;if (board[x][y] == '0') {board[x][y] = '1';count--;}}
}
//计算指定位置附近雷的个数
int get_mine_count(char board[ROWS][COLS], int x, int y) {return (board[x - 1][y] +board[x - 1][y - 1] +board[x][y - 1] +board[x + 1][y - 1] +board[x + 1][y] +board[x + 1][y + 1] +board[x][y + 1] +board[x - 1][y + 1] - 8 * '0');//8个坐标的值要么是'0'要么是'1',加起来后减去8个'0'就可以得到他们的和了,也就是有几个雷
}
//排查雷
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 - EASY_COUNT) {printf("请输入要排查的坐标:>");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col) {if (show[x][y] != '*') {printf("该坐标被排查过了,不能重复排查\n");}else {//如果是雷if (mine[x][y] == '1') {printf("很遗憾,你被炸死了\n");DisplayBoard(mine, ROW, COL);break;}//如果不是雷else {win++;//统计mine数组中x,y坐标周围有几个雷int count = get_mine_count(mine, x, y);show[x][y] = count + '0';//转换成  数字字符DisplayBoard(show, ROW, COL);}}}else {printf("输入坐标非法,请重新输入\n");}}if (win == row * col - EASY_COUNT) {printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"void menu() {printf("************************************\n");printf("********  1. play  0. exit  ********\n");printf("************************************\n");
}void game() {char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息//初始化内容为指定的内容//mine 数组在没有布置雷的时候,都是‘0’InitBoard(mine, ROWS, COLS, '0');//show 数组在没有排查雷的时候,都是‘*’InitBoard(show, ROWS, COLS, '*');//设置雷SetMine(mine, ROW, COL);//DisplayBoard(mine, ROW, COL);DisplayBoard(show, 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;
}

3.代码讲解

  1. 我们先找main()函数,在test.c文件里。

  2. int input = 0;这个定义了变量input用来让我们确认自己是玩游戏还是退出游戏。

  3. srand((unsigned int)time(NULL));这个是为了给rand 函数设定随机数种子。关于rand函数的详细解释在我之前发布的2.猜数字游戏这个文档里有详细解释,这里就不多赘述了。

    time()函数需要用到#include <time.h>这个头文件。
    rand函数的使用需要包含#include <stdlib.h>这个头文件。

  4. 接下来进入do while循环,先进入menu()函数【这个函数在test.c里面】,打印

    ************************************
    ********  1. play  0. exit  ********
    ************************************
    
  5. 然后进入【test.c】35行,printf("请选择:>");打印:请选择:>

  6. 进入【test.c】36,输入选择,根据不同选择进入switch语句里的case 1,case 0,default

  7. 进入case 0就打印退出游戏,然后退出游戏

  8. 进入default就打印选择错误,然后跳出switch语句,进入while的判断。因为input为0的时候才跳出循环,所以这里会继续进入循环。

  9. 进入case 1就进入game()函数【这个函数在test.c里面】

  10. 进入【test.c】11行char mine[ROWS][COLS] = { 0 };存放布置好的雷的信息(我们表面上看不到的后台的数据)

  11. 进入【test.c】12行char show[ROWS][COLS] = { 0 };存放排查出的雷的信息(我们看到的部分,前台的图案)

  12. 然后初始化内容为指定的内容,进入【test.c】15行 InitBoard(mine, ROWS, COLS, '0');设置mine 数组(后台)在没有布置雷的时候,都是‘0’

  13. 这个InitBoard()函数在【game.c】里面第5行,用来初始化棋盘。

    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;}}
    }
    

    这里第4个参数设置成了char set,设置的很完美,这样不管是想在里面放'*'还是'0'都可以完成,而不需要设置两个结构一样的函数。

  14. 进入【test.c】17行InitBoard(show, ROWS, COLS, '*');设置show 数组(前台)在没有排查雷的时候,都是‘*’

  15. 进入【test.c】20行 SetMine(mine, ROW, COL);用来设置雷

  16. 这个SetMine()函数在【game.c】里面第35行,用来布置雷

    //布置雷
    void SetMine(char board[ROWS][COLS], int row, int col) {//布置10个雷//生成随机的坐标,布置雷int count = EASY_COUNT;//横坐标:1-9;纵坐标:1-9while (count) {int  x = rand() % row + 1;int  y = rand() % col + 1;if (board[x][y] == '0') {board[x][y] = '1';count--;}}
    }
    

    因为实参是mine(mine 数组(后台)),所以这里设置的雷是给后台设置的。

  17. 进入int count = EASY_COUNT;,定义了一个变量count,这个count赋值为EASY_COUNT,EASY_COUNT在【game.h】里面第12行

    #define EASY_COUNT 10
    

    相当于一个宏定义,定义EASY_COUNT就是10。这么做有个好处,就是如果想要更改雷的数量直接更改宏定义里面的10就可以了,否则的话,我们要去定义变量的地方更改,变量多了就容易分不清。

  18. 这里是定义了有10个雷,因为count=10,【之前的宏定义里面EASY_COUNT就是10】

  19. 然后进入while循环,进入第10行,int x = rand() % row + 1;这个是给x一个1-9的随机数【x是数组横坐标】。

    为什么rand() % row + 1是1-9的随机数?

    之前在我之前发布的2.猜数字游戏这个文档里有详细解释,我取关键的一段结论出来:

    如果我们想要生成a~b的随机数,

    a + rand()%(b-a+1)
    

    这就得到了,a~b的值。

    这里面1-9就相当于:

    1 + rand()%(9-1+1)
    

    化简一下就是:

    1 + rand()%(9)
    

    前面【game.h】文件里还有四个宏定义

    #define ROW 9//代表扫雷行号9行
    #define COL 9//代表扫雷列号9列#define ROWS ROW+2//代表11行
    #define COLS COL+2//代表11列
    

    这里的ROWSCOLS是前面说过的建造一个比实际需要的扫雷数组大一圈的数组,来避免分类讨论带来的复杂情况,所以加2,大了一圈。

    让我们回到int x = rand() % row + 1;这个row是形参,形参是由实参传递过来的。它对应的实参是ROW,也就是9。

    所以符合上面化简得结果。

  20. 和上一条类似,int y = rand() % col + 1;这个是给y一个1-9的随机数【y是数组纵坐标】。

  21. 然后:

    if (board[x][y] == '0') {board[x][y] = '1';count--;
    }
    

    是一个简单的判断条件:如果我们生成随机数的地方是0(也就是没雷),那么就把0改成1。同时雷的个数就减1。然后重新进入while循环。

    如果我们生成随机数的地方是1(也就是有雷),那么就不执行这个if语句,然后重新进入while循环。

  22. 然后让我们回到【test.c】文件的第23行,DisplayBoard(show, ROW, COL);这个会让我们进入【game.c】文件的15行,为了方便看,我把代码拿下来

    //打印棋盘
    void DisplayBoard(char board[ROWS][COLS], int row, int col) {int i = 0;int j = 0;printf("-----------------扫雷游戏-----------------\n");for (j = 0; j <= col; j++) {printf("%d ", j);}printf("\n");for (i = 1; i <= row; i++) {printf("%d ", i);for (j = 1; j <= col; j++) {printf("%c ", board[i][j]);}printf("\n");}printf("-----------------扫雷游戏-----------------\n");
    }
    

    show 数组(前台),show作为实参,将show数组传递给了board数组。

  23. 先定义前台数组的行号i和列号j。然后打印-----------------扫雷游戏-----------------

    在这里插入图片描述

    这个行号就是我花红色方框的那行,列号是蓝色方框那列。

  24. 进入第一个for循环打印行号0-9,然后回车。

  25. 进入第二个for循环先打印列号1然后进入内嵌的for循环,将列号为1 的那行全打印*,然后进入外面那个for循环打印列号2然后进入内嵌的for循环,依此类推。

  26. 最后打印:-----------------扫雷游戏-----------------

  27. DisplayBoard(show, ROW, COL);函数结束,进入FindMine(mine, show, ROW, COL);来排查雷。

  28. 这个会让我们进入【game.c】文件的65行,为了方便看,我把代码拿下来:

    //排查雷
    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 - EASY_COUNT) {printf("请输入要排查的坐标:>");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col) {if (show[x][y] != '*') {printf("该坐标被排查过了,不能重复排查\n");}else {//如果是雷if (mine[x][y] == '1') {printf("很遗憾,你被炸死了\n");DisplayBoard(mine, ROW, COL);break;}//如果不是雷else {win++;//统计mine数组中x,y坐标周围有几个雷int count = get_mine_count(mine, x, y);show[x][y] = count + '0';//转换成  数字字符DisplayBoard(show, ROW, COL);}}}else {printf("输入坐标非法,请重新输入\n");}}if (win == row * col - EASY_COUNT) {printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}
    }
    

    show 数组(前台),mine 数组(后台)

  29. .先定义了数组横坐标x和纵坐标y,初始化为0。然后定义win为找到非雷的个数。

  30. 进入while循环,如果win < row * col - EASY_COUNT那么循环继续,也就是说只要玩家排查的非雷格子数量小于需要排查的总数,游戏的主循环就会继续运行。在这个循环中,玩家输入坐标来排查雷,如果排查到雷,游戏结束;如果排查到非雷格子, win 的值会增加,并且游戏会继续。

    如果运气非常好,把所有雷都排除了,并且把其他非雷的地方也全选了,那么就会让win = row * col - EASY_COUNT从而跳出while循环,执行下面的:

    if (win == row * col - EASY_COUNT) {printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);
    }
    

    打印:恭喜你,排雷成功,然后调用DisplayBoard(mine, ROW, COL);,将输入结果打印。

  31. 在没有把雷排完前会先在循环里,先打印:请输入要排查的坐标:>,然后输入自己选择的坐标,

    if (x >= 1 && x <= row && y >= 1 && y <= col)这个是为了确认自己输入的坐标没有越界。

    然后进入内层判断,如果输入的坐标处已经不是*了,也就是之前这个坐标输入过了,就打印:该坐标被排查过了,不能重复排查

    否则就进入else,然后进入内部的if,

    //如果是雷
    if (mine[x][y] == '1') {printf("很遗憾,你被炸死了\n");DisplayBoard(mine, ROW, COL);break;
    }
    

    如果输入的地方在mine数组(也就是后台)里面放的是1(地雷),就会输入:很遗憾,你被炸死了

    如果没被炸死就进入else,

    //如果不是雷
    else {win++;//统计mine数组中x,y坐标周围有几个雷int count = get_mine_count(mine, x, y);show[x][y] = count + '0';//转换成  数字字符DisplayBoard(show, ROW, COL);
    }
    

    先给win加1,然后定义count来接受来自get_mine_count(mine, x, y);这个函数的返回值。

    这个函数在【game.c】文件的第53行,为了方便看,我把代码拿下来:

    //计算指定位置附近雷的个数
    int get_mine_count(char board[ROWS][COLS], int x, int y) {return (board[x - 1][y] +board[x - 1][y - 1] +board[x][y - 1] +board[x + 1][y - 1] +board[x + 1][y] +board[x + 1][y + 1] +board[x][y + 1] +board[x - 1][y + 1] - 8 * '0');//8个坐标的值要么是'0'要么是'1',加起来后减去8个'0'就可以得到他们的和了,也就是有几个雷
    }
    

    返回的值是我们输入数组坐标的身旁8个位置的字符减去8个'0'

    因为'0'-'0'=0'1'-'0'=1,8个位置依次完成这个操作就相当于8个位置加起来减去8个'0',得到的返回值就是输入坐标周围的雷的个数的值。

  32. 我们把这个值给count,然后把count的值加上'0',就可以得到输入坐标周围的雷的个数的字符。(注意上面是值,这边是字符)

  33. 然后用这个字符来替换数组输入位置原来的符号。

  34. 最后打印:DisplayBoard(show, ROW, COL);,将输入结果打印。

  35. 直到被炸死或者排雷成功游戏才会结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/704392.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

07-Fortran基础--Fortran指针(Pointer)的使用

07-Fortran基础--Fortran指针Pointer的使用 0 引言1 指针&#xff08;Poionter&#xff09;的有关内容1.1 一般类型指针1.2 数组指针1.3 派生类(type)指针1.4 函数指针 2 可运行code 0 引言 Fortran是一种广泛使用的编程语言&#xff0c;特别适合科学计算和数值分析。Fortran 9…

第9章.Keil5-MDK软件简介

目录 0. 《STM32单片机自学教程》专栏 9.1 主界面 9.2 文本格式编辑 9.3 代码提示&语法检测&代码模版 9.4 其他小技巧 9.4.1 TAB 键的妙用 9.4.2 快速定位函数/变量被定义的地方 9.4.3 快速注释与快速消注释 9.4.4 快速打开头文件 9.4.5 查找替换…

数据结构初阶 顺序表的补充

一. 题目的要求 写出三种链表的接口函数 它们的功能分别是 1 查找数的位置 2 在pos位置插入值 3 在pos位置删除值 二. 实现pos 这个其实很简单 找到一步步遍历 找到这个数字就返回 找不到就提示用户下 这个数字不存在 int SLFind(SL* ps,SLDateType x) {assert(ps);int…

醉了,面个功能测试,还问我Python装饰器

Python 装饰器是个强大的工具&#xff0c;可帮你生成整洁、可重用和可维护的代码。某种意义上说&#xff0c;会不会用装饰器是区分新手和老鸟的重要标志。如果你不熟悉装饰器&#xff0c;你可以将它们视为将函数作为输入并在不改变其主要用途的情况下扩展其功能的函数。装饰器可…

[ciscn 2022 东北赛区]math

1.题目 import gmpy2 from Crypto.Util.number import * from flag import flag assert flag.startswith(b"flag{") assert flag.endswith(b"}") messagebytes_to_long(flag) def keygen(nbit, dbit):if 2*dbit < nbit:while True:a1 getRandomNBitIn…

(实测验证)【移远EC800M-CN 】TCP 透传

引言 本文章使用自研“超小体积TTL转4GGPS集成模块”进行实测验证&#xff1b; 1、配置移远EC800M-CN TCP 透传 串口助手发送&#xff1a; ATQIOPEN1,0,"TCP","36.137.226.30",39755,0,2 //配置服务器地址和端口号&#xff1b; 4G模组返回…

Go微服务: 接入Prometheus性能监控平台与Grafana平台

接入Prometheus 在 go-micro 生成的模板中, 我们一如既往的完成基础工作之后 进入main.go工作的代码编写&#xff0c;main.go package mainimport ("fmt""log""strconv""github.com/go-micro/plugins/v4/registry/consul"opentracing…

MaxKB创建本地知识库

上节已经可以通过MaxKB创建简单的问答系统了&#xff0c;这节开始做自己的知识库&#xff0c;实际上就是把一些本地文件上传到大模型中&#xff0c;让大模型学会这些文件内容&#xff0c;你在问他问题的时候可以通过此文件的内容来回答你&#xff0c;尤其是在针对特定场景或者特…

(实测验证)Gitee代码托管尝试(一)——克隆/下载

一、登录 Gitee&#xff08;码云&#xff09;代码托管平台&#xff1a; Gitee - 基于 Git 的代码托管和研发协作平台 新建个人账户如下&#xff1a; 二、SSH 公钥设置 1、在git安装目录打开“git-cmd.exe”; 2、通过命令 ssh-keygen 生成 SSH Key&#xff1a; ssh-keygen …

指针在函数的应用(C++)

一、传递地址 实参传递进函数体内后&#xff0c;生成的是实参的副本&#xff0c;在函数内改变副本的值并不影响实参。指针传递参数时&#xff0c;指针变量产生了副本&#xff0c;但副本与原变量指向的内存区域是同一个。改变指针副本指向的变量&#xff0c;就是改变原指针变量指…

网络安全 会飞的狗狗 网络安全狗是什么

1.概述 网站安全狗是一款集网站内容安全防护、网站资源保护及网站流量保护功能为一体的服务器工具。功能涵盖了网马/木马扫描、防SQL注入、防盗链、防CC攻击、网站流量实时监控、网站CPU监控、下载线程保护、IP黑白名单管理、网页防篡改功能等模块。能够为用户提供实时的网站安…

做简单易用的GIS资源管理软件

在室外资源管理领域&#xff0c;采用基于GIS的解决方案已成为主流趋势&#xff0c;旨在实现资源的高效利用和管理。GIS技术结合资源对象的规划、定位和监控&#xff0c;为企业提供全面的管理方案&#xff0c;从而优化资源使用、提高运营效率和降低成本。 然而&#xff0c;许多资…