扫雷游戏(非常详细版的讲解)

扫雷游戏(非常详细版的讲解)本文非常详细地介绍了排雷小游戏的递归与非递归版本 多多支持 有错请批评改正 扫雷

大家好,欢迎来到IT知识分享网。

前言

本文主要介绍排雷小游戏实现的非递归版本与递归版本,并采用多文件并写的形式。

提示:以下是本篇文章正文内容,下面案例可供参考

一、扫雷游戏的分析和设计

(一)、游戏概述与功能说明

  • 本文主要展示9*9扫雷棋盘的实现。网页版的扫雷游戏想必大家都玩过。(网络版扫雷网址:添加链接描述
    这里先实现最基本的逻辑,后面再做扩展补充。
  • 使⽤控制台实现经典的扫雷游戏
  • 游戏可以通过菜单实现继续玩或者退出游戏
  • 扫雷的棋盘是9*9的格⼦
  • 默认随机布置10个雷
  • 可以排查雷
    如果位置不是雷,就显⽰周围有⼏个雷;
    如果位置是雷,就炸死游戏结束;
    把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束;
    具体的游戏界面:
    在这里插入图片描述

(二)、游戏的分析与设计

  • 第一步:设计菜单函数用于进入或者退出游戏
  • 第二步:分别创建存放雷与显示雷个数的棋盘(为了避免排查雷的时候越界我们通常将棋盘四周增大一行)并对两个棋盘进行初始化处理。并写出打印棋盘的函数,打印以下显示雷的棋盘看看效果。
  • 第三步:写出存放雷的函数,将原先定的雷的个数放入存放雷的棋盘中。
  • 第四步:写出排雷的函数。将排查的结果显示到显示雷的棋盘中,并打印显示雷的棋盘
  • 第五步:反复进行排查雷的过程,直到被炸死或者排雷成功。
    注意:在扫雷的过程中,创建棋盘布置雷和排查雷都需要储存信息,所以我们需要一定的数据结构来存储这些信息,在布置雷时,如果是雷就放1,不是雷就放0;同时利用数组来存储。
    同时为了便于我们书写代码,并使得该游戏的代码更加有逻辑性,我们采用多文件共同实现扫雷游戏
    这里我们设计了三个文件:
    在这里插入图片描述
    而且,为了修改游戏的难度,通过增加雷的数量或者改变棋盘的大小我们通常再game.h头文件中用define直接定义行,列,和雷的数量便于修改。
    在这里插入图片描述

二、扫雷游戏的模块化代码的实现

(一)、创建主函数main()

  • 此函数是放在test.c文件中的。
  • 在主函数中我们先创建一个input值用于输入判断我们菜单的选择;
    用do while判断我们输入的选择是否结束;
    在这里我们需要创建菜单函数,每一次循环我们都调用菜单函数;
    接着使用switch语句中,来执行输入的结果,如果输入1则提示玩家开始游戏并调用游戏函数game(),输入0则退出游戏,输入其他数值则提示错误,并循环执行switch语句,直至玩家主动退出;
    将whlie()中的判断值直接设置为input,这样正好可以满足如果input输入为零的情况,正好退出循环即退出游戏。
    代码实现:
int input = 0; do { 
    srand((unsigned int)time(NULL)); meau(); printf("请输入:"); scanf("%d", &input); switch (input) { 
    case 1: game(); break; case 0: printf("游戏结束\n"); break; default: printf("输入错误,请重新输入:\n"); break; } } while (input); 

在这里写了srand函数是为了更新rand函数的随机取值,后面会详细介绍这个原因。

(二)、写菜单函数与game函数

  • 菜单函数:
    因为不需要返回值所以使用void,菜单函数会在mian()函数中被调用
    代码实现:
void meau() { 
   //菜单函数 printf("---------------------------------\n"); printf("--------------1.play-------------\n"); printf("--------------0.exit-------------\n"); printf("---------------------------------\n"); } 
  • game()函数的创立:
    首先我们创建两个数组,分别为mine[ROWS][COLS]和show[ROWS][COLS],分别为放置雷的数组以及显示排雷效果的数组,这里考虑到后面排雷时越界问题所以将数组行和列的范围各自都扩大1
    在这里插入图片描述
    其次是初始化两个数组棋盘,接着打印显示排雷效果的 棋盘来检验一下初始化的效果;
    接下来就是开始布置雷函数与排雷函数。
    这就展现了排雷游戏的基本测试逻辑。
void game()//游戏进行函数 { 
    //定义两个数组,mine是排雷数组;show是展示数组 char mine[ROWS][COLS] = { 
    0 }; char show[ROWS][COLS] = { 
    0 }; //初始化棋盘 Initboard(mine, ROWS, COLS,'0'); Initboard(show, ROWS, COLS,'*'); //打印棋盘: //print_board(mine, ROW, COL); print_board(show, ROW, COL); //布置雷: setmine(mine, ROW, COL); // print_board(mine, ROW, COL); //排查雷: findmine(mine, show, ROW, COL); } 

(三)、game()函数内部函数的实现

  • 这块代码是放在game.c文件中的(专门写游戏中具体函数的实现)
  • 初始化棋盘函数:
    因为没有涉及到返回值,所以函数为void类型,这里专门设置了一个参数set,用来限制将棋盘初始化的基本模样。布置雷的棋盘初始化为‘0’,而显示排雷效果的棋盘初始化为‘*’。这里用双重for循环即可实现棋盘的初始化。

代码实现:

void Initboard(char board[ROWS][COLS], int rows, int cols,char set) { 
    for (int i = 0; i < rows; i++) { 
    for (int j = 0; j < cols; j++) { 
    board[i][j] = set; } } } 
  • 打印棋盘函数:
    因为没有涉及到返回值,所以函数为void类型。用双重for循环即可实现。在双重for循环之前,我们这里先用一遍for循环打印了一下列号,在双重循环中我们打印行号,是为了更加清晰地找到排雷位置,为后面做准备的。
  • 代码如下:
void print_board(char board[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++) { 
    printf("%c", board[i][j]); } printf("\n"); } } 
  • 布置雷函数:
    因为没有涉及到返回值,所以函数为void类型。在这里,我们呢首先定义count为我们要布置雷的总数,紧接着我们呢进入while循环中,没布置一次雷我们让count–。直到所有的雷布置完毕。
    在这里我们为了保证布置雷的随机行,我们呢引入了rand随机值函数。而rand函数通常是配合前面的srand函数与time函数一块使用的,是为了避免每次rand出来的数据都一样。
    x = rand() % row + 1; y = rand() % col + 1;
    这两行代码的解释是为了限制x,y的范围为合法的。不会超出现实意义的棋盘大小。
    现在给出rand函数,srand函数和time函数的详细分析解释:
    (1) rand() 函数
    在这里我们就需要用到随机数生成函数rand(),使用这个函数需要包含一个头文件 stdlib.h,但我们需要注意的是,由于随机数生成器的实现方式,生成的随机数可能不是完全随机的,而是一种伪随机数,在一些固定的模式下以它的种子为基准值生通过某种算法根据成的可以算的伪随机数。而rand函数的种子一般是默认为一个固定值,那么现在我们可以知道,如果想让rand函数生成一个真正的随机数就需要时刻改变它的种子就可以了。
    (2) srand() 函数
    现在我们就要用到srand()函数了,它一般用于初始化随机数生成器,通常是为rand函数设置种子,以便生成不同的随机数序列。srand()函数的原型为:void srand(unsigned int seed);
    所以我们在每次调用rand函数之前应先调用srand函数,以传入不同的种子。而srand函数通过参数seed来设置它的随机生成数,也就是说我们需要种子的种子不是一个固定值,只有这样,rand函数生成的数才是真正的随机数。
    (3) time()函数
    因为时间是每时每刻都在变化的,所以我们一般使用当前时间作为srand函数的种子,这里我们就需要使用time函数了,time函数的原型是time_t time(time_t *timer)这样的,
    time_t类型本质上其实就是32位或者64位的整型类
    型,使用时需要包含头文件time.h,其实time函数所返回的时间就是自1970年1月1日 00:00:00 UTC(协调世界时)以来的秒数,它的返回值是一个长整数time_t类型的值,表示秒数。如果time函数的参数timer不为空,那么time函数就会将这个返回当前时间的值,并将当前时间存储在参数所指向的变量中。但是如果参数为空,time函数将会返回当前时间的值,而不存储当前时间。
    time函数返回的这个时间差也被叫做:时间戳。
    (4) 结合
    所以结合以上三个函数,我们知道了一个可以生成真正随机数的方法。即调用这三个函数,以time(NULL)函数返回当前时间的值,然后将其转化为无符号整数,并传递给srand函数,用来初始化随机数生成器。这样,每次调用rand函数的时候,都会基于当前时间生成不同的随机数了!代码如下
    srand((unsigned int)time(NULL));//生成一个随机数,time函数无参返回,返回类型强制转换为unsigned

相应的代码实现:

void setmine(char mine[ROWS][COLS], int row, int col) { 
    int count = EASY_COUNT; int x = 0; int y = 0; while (count) { 
    //每次都要设置一个随机值 x = rand() % row + 1; y = rand() % col + 1; if (mine[x][y] != '1') { 
    mine[x][y] = '1'; count--; } } } 

注意:srand((unsigned int)time(NULL));已经在上面test.c文件中的主函数main中实现。

  • 排查雷函数,获得坐标周围雷的个数的函数有和判断排雷成功的函数:
    我们定义win为记录排查次数并以此作为while函数结束的判断,进入循环以后
    我们输入我们呢要排查的坐标位置,判断一下输入坐标的合理性,如果超出范围就重新输入,如果在合理范围,我们就要判断一下这个位置是不是雷了,如果是雷的话,游戏结束我们呢直接跳出循环,如果不是雷,我们调用,记录坐标周围雷的个数的函数,来将这个坐标周围雷的个数记录到show数组中,并打印出来,依次循环往复,每一次循环最后我们要调用判断排雷成功的函数来判断是否排雷成功,如果排雷成功了我们呢直接跳出循环。
    对于获得坐标周围雷的个数的函数:我们只需要调用双重循环来判断一下所输入坐标周围8个位置是否有雷,如果有雷的话,我们利用这条代码mine[x + i][y + j] – ‘0’(因为为char类型,所以我们要得到个数就要减去‘0’,用差值作为记录的基准)
    对于判断排雷成功的函数:
    我们利用双重循环来记录未排查的位置总数,如果与雷的总数相等,那么我们就可以认为,剩下的位置全部是雷,其他位置都已排查清楚,那么咱们就胜利了。
    代码实现:
 int get_mine_count(char mine[ROWS][COLS], int x, int y)//获得坐标周围雷的数目的函数 { 
    int count = 0; for (int i = -1; i <=1; i++) { 
    for (int j = -1; j <=1; j++) { 
    count += (mine[x + i][y + j] - '0'); } } return count; } int EndMine(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++; } } } if (count == EASY_COUNT) { 
    return 1; } else { 
    return 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 (mine[x][y] == '1') { 
    printf("排到雷了,已被炸死\n"); print_board(mine, ROW, COL); break; } else//不是雷 { 
    int count =get_mine_count(mine, x, y);//获得坐标周围雷的数目 show[x][y] = count + '0'; print_board(show, ROW, COL); win++; if (EndMine(show, row, col))//判断是否排雷成功 { 
    printf("恭喜排雷成功\n"); print_board(mine, ROW, COL); break; } } } else { 
    printf("输入错误,请重新输入\n"); continue; } } } 

三、扫雷游戏总的代码实现(非递归版本)

  • game.h文件:
#pragma once #include<stdio.h> #include<stdlib.h> #include<time.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 print_board(char board[ROWS][COLS], int row, int col); void setmine(char mine[ROWS][COLS], int row, int col); void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//(非递归版本) int get_mine_count(char mine[ROWS][COLS], int x, int y);//获得坐标周围雷的个数的函数 int EndMine( char show[ROWS][COLS], int row, int col);//判断获胜条件的函数 
  • game.c文件:
#define _CRT_SECURE_NO_WARNINGS 1//让scanf函数可以安全调用 #include"game.h"//引用我们自己定义的头文件 void Initboard(char board[ROWS][COLS], int rows, int cols,char set) { 
    for (int i = 0; i < rows; i++) { 
    for (int j = 0; j < cols; j++) { 
    board[i][j] = set; } } } void print_board(char board[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++) { 
    printf("%c", board[i][j]); } printf("\n"); } } void setmine(char mine[ROWS][COLS], int row, int col) { 
    int count = EASY_COUNT; int x = 0; int y = 0; while (count) { 
    //每次都要设置一个随机值 x = rand() % row + 1; y = rand() % col + 1; if (mine[x][y] != '1') { 
    mine[x][y] = '1'; count--; } } } int get_mine_count(char mine[ROWS][COLS], int x, int y) { 
    int count = 0; for (int i = -1; i <=1; i++) { 
    for (int j = -1; j <=1; j++) { 
    count += (mine[x + i][y + j] - '0'); } } } int EndMine(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++; } } } if (count == EASY_COUNT) { 
    return 1; } else { 
    return 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 (mine[x][y] == '1') { 
    printf("排到雷了,已被炸死\n"); print_board(mine, ROW, COL); break; } else//不是雷 { 
    int count =get_mine_count(mine, x, y); show[x][y] = count + '0'; print_board(show, ROW, COL); win++; if (EndMine(show, row, col)) { 
    printf("恭喜排雷成功\n"); print_board(mine, ROW, COL); break; } } } else { 
    printf("输入错误,请重新输入\n"); continue; } } } 
  • test.c文件:
# define _CRT_SECURE_NO_WARNINGS 1//让scanf函数可以安全使用 #include"game.h" void meau() { 
    printf("---------------------------------\n"); printf("--------------1.play-------------\n"); printf("--------------0.exit-------------\n"); printf("---------------------------------\n"); } void game() { 
    //定义两个数组,mine是排雷数组;show是展示数组 char mine[ROWS][COLS] = { 
    0 }; char show[ROWS][COLS] = { 
    0 }; //初始化棋盘 Initboard(mine, ROWS, COLS,'0'); Initboard(show, ROWS, COLS,'*'); //打印棋盘: //print_board(mine, ROW, COL); print_board(show, ROW, COL); //布置雷: setmine(mine, ROW, COL); //print_board(mine, ROW, COL); //排查雷: findmine(mine, show, ROW, COL); } int main() { 
    int input = 0; do { 
    srand((unsigned int)time(NULL)); meau(); printf("请输入:"); scanf("%d", &input); switch (input) { 
    case 1: game(); break; case 0: printf("游戏结束\n"); break; default: printf("输入错误,请重新输入:\n"); break; } } while (input); return 0; } 

四、扫雷游戏的扩展(转成递归版本)

(一)、扩展递归函数的实现

  • 我们在玩网页上的扫雷游戏的时候,常常看到点到一个位置后,常常会显示一大片。
    基本规律为:如果打开的格子周围的地雷数量与数字相符,则该格子周围的其他格子也会被打开。这个我们可以利用递归来实现。这里与以上代码稍微有不同的是,如果周围没有雷的情况下我们会将这个位置显示为‘ ’,而不是‘0’,这样子看起来舒服一点。
    在这里插入图片描述
  • 代码实现:
void ExpandBlank(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//实现扩展递归的函数 { 
    int num = get_mine_count(mine, x, y);//x,y坐标的附近雷的数目 if (num) { 
    show[x][y] = num + '0'; } else if (show[x][y] == '*') { 
    show[x][y] = ' ';//将没有雷的地方赋值为空格 int i = 0; int j = 0; for ( i = x-1; i <=x+1; i++) { 
    for ( j = y - 1;j <= y + 1;j++) { 
    ExpandBlank(mine, show, i, j); } } } } //排查雷(递归版本) void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { 
    int x = 0; int y = 0; int count = 0; while (count < row * col - EASY_COUNT) { 
    printf("请输入排查位置:"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { 
    //判断是否为雷: if (mine[x][y] == '1') { 
    printf("排到雷了,已被炸死\n"); print_board(mine, ROW, COL); break; } else//不是雷 { 
    ExpandBlank(mine, show, x, y); print_board(show, ROW, COL); if (EndMine(show, row, col)) { 
    printf("恭喜排雷成功\n"); print_board(mine, ROW, COL); break; } } } else { 
    printf("输入错误,请重新输入\n"); } } } 

(二)、递归版本的代码总的实现

  • game.h文件:
#include<stdio.h> #include<stdlib.h> #include<time.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 print_board(char board[ROWS][COLS], int row, int col); void setmine(char mine[ROWS][COLS], int row, int col); void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//(递归版本) int get_mine_count(char mine[ROWS][COLS], int x, int y); int EndMine( 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) { 
    for (int i = 0; i < rows; i++) { 
    for (int j = 0; j < cols; j++) { 
    board[i][j] = set; } } } void print_board(char board[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++) { 
    printf("%c", board[i][j]); } printf("\n"); } } void setmine(char mine[ROWS][COLS], int row, int col) { 
    int count = EASY_COUNT; int x = 0; int y = 0; while (count) { 
    //每次都要设置一个随机值 x = rand() % row + 1; y = rand() % col + 1; if (mine[x][y] != '1') { 
    mine[x][y] = '1'; count--; } } } int get_mine_count(char mine[ROWS][COLS], int x, int y) { 
    int count = 0; for (int i = -1; i <=1; i++) { 
    for (int j = -1; j <=1; j++) { 
    count += (mine[x + i][y + j] - '0'); } } return count; } int EndMine(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++; } } } if (count == EASY_COUNT) { 
    return 1; } else { 
    return 0; } } void ExpandBlank(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) { 
    int num = get_mine_count(mine, x, y);//x,y坐标的附近雷的数目 if (num) { 
    show[x][y] = num + '0'; } else if (show[x][y] == '*') { 
    show[x][y] = ' ';//将没有雷的地方赋值为空格 int i = 0; int j = 0; for ( i = x-1; i <=x+1; i++) { 
    for ( j = y - 1;j <= y + 1;j++) { 
    ExpandBlank(mine, show, i, j); } } } } //排查雷(递归版本) void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { 
    int x = 0; int y = 0; int count = 0; while (count < row * col - EASY_COUNT) { 
    printf("请输入排查位置:"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { 
    //判断是否为雷: if (mine[x][y] == '1') { 
    printf("排到雷了,已被炸死\n"); print_board(mine, ROW, COL); break; } else//不是雷 { 
    ExpandBlank(mine, show, x, y); print_board(show, ROW, COL); if (EndMine(show, row, col)) { 
    printf("恭喜排雷成功\n"); print_board(mine, ROW, COL); break; } } } else { 
    printf("输入错误,请重新输入\n"); } } } 
  • test.c 文件:
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void meau() { 
    printf("---------------------------------\n"); printf("--------------1.play-------------\n"); printf("--------------0.exit-------------\n"); printf("---------------------------------\n"); } void game() { 
    //定义两个数组,mine是排雷数组;show是展示数组 char mine[ROWS][COLS] = { 
    0 }; char show[ROWS][COLS] = { 
    0 }; //初始化棋盘 Initboard(mine, ROWS, COLS,'0'); Initboard(show, ROWS, COLS,'*'); //打印棋盘: //print_board(mine, ROW, COL); print_board(show, ROW, COL); //布置雷: setmine(mine, ROW, COL); //print_board(mine, ROW, COL); //排查雷: Findmine(mine, show, ROW, COL); } int main() { 
    int input = 0; do { 
    srand((unsigned int)time(NULL)); meau(); printf("请输入:"); scanf("%d", &input); switch (input) { 
    case 1: game(); break; case 0: printf("游戏结束\n"); break; default: printf("输入错误,请重新输入:\n"); break; } } while (input); return 0; } 

总结

本文非常详细地介绍了排雷小游戏的实现,分为非递归与递归版本,采用多文件并写的方式,使得代码结构更加清晰。以上如有不足,请大家批评指正!!

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/143865.html

(0)
上一篇 2025-04-30 18:15
下一篇 2025-04-30 18:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信