深入解析:【C语言】扫雷游戏2.0

前言

在之前,我用C语言设计了一个扫雷游戏:使用C语言基础知识实现一个扫雷游戏-CSDN博客

在此后,我又将扫雷游戏的设计思路详细的介绍了一遍:用C语言写一个扫雷游戏——深度解析版-CSDN博客

今天,我又将扫雷游戏重新改进,带来的新的扫雷版本——扫雷2.0。

        这次改进最主要的一点:就是当输入一个坐标,坐标周围没有雷的时候,就依次按照当前坐标周围的8个位置继续展开,直到某个坐标周围有雷的时候,就不再展开,这样就可以提升游戏体验,不需要每个坐标都去排查一遍,大大提升了游戏的效率,同时节约了用户排雷时花费的不必要的时间。

目录

前言

正文

test.c文件:

game.c文件:

game.h文件:

改变位置:

expand()函数:

get_void函数:

expand()函数的代码详细解释:

函数定义和参数

边界检查

已展开检查

计算并显示周围雷数

递归展开条件

递归展开周围格子

递归过程示例

效果

get_void 函数的代码详细解释

函数定义和目的

变量初始化

双重循环遍历棋盘

检查位置状态

返回值

实际应用场景

注意事项

示例说明

总结:


正文

        接下来我将依次展示“test.c”文件(主函数所在文件),“game.c”文件,“game.h”文件中的代码,在后面我会详细解释在哪里增加了代码,以及具体的代码解释。

test.c文件:

//test.c文件
#include "game.h"
void game(void)
{
    char mine[ROWS][COLS] = { 0 };
    char show[ROWS][COLS] = { 0 };
    initboard(mine, ROWS, COLS, '0');
    initboard(show, ROWS, COLS, '*');
    setmine(mine, ROW, COL);
    display(mine, ROW, COL);
    display(show, ROW, COL);
    findmine(mine, show, ROW, COL);
}
void menu(void)
{
    printf("**************************\n");
    printf("********  1.paly  ********\n");
    printf("********  0.exit  ********\n");
    printf("**************************\n");
}
void test(void)
{
    int input = 0;
    srand((unsigned int)time(NULL));
    do
    {
        menu();
        printf("请输入你的选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            //system("cls");
            game();
            break;
        case 0:
            printf("游戏结束,退出游戏\n");
            break;
        default:
            printf("\n输入错误,请重新输入\n");
            break;
        }
    } while (input);
}
int main()
{
    test();
    return 0;
}

game.c文件:

//game.c文件:
#include "game.h"
int get_void(char show[ROWS][COLS])
{
    int count = 0;
    for (int i = 1; i < ROWS - 1; i++)
    {
        for (int j = 1; j < COLS - 1; j++)
        {
            if (show[i][j] != '*')
                count++;
        }
    }
    return count;
}
void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
    // 检查坐标是否在有效范围内(1-9)
    if (x < 1 || x > 9 || y < 1 || y > 9)
    {
        return;
    }
    // 如果当前位置已经展开,则不再处理
    if (show[x][y] != '*')
    {
        return;
    }
    // 显示雷的数量
    show[x][y] = getminenumber(mine, x, y) + '0';
    // 只有当周围没有雷时才继续递归展开
    if (getminenumber(mine, x, y) == 0)
    {
        // 使用循环遍历周围的8个方向
        for (int dx = -1; dx <= 1; dx++)
        {
            for (int dy = -1; dy <= 1; dy++)
            {
                if (dx == 0 && dy == 0)
                    continue;
                expand(mine, show, x + dx, y + dy);
            }
        }
    }
}
int getminenumber(char arr[ROWS][COLS], int x, int y)
{
    int sum = 0;
    for (int i = x - 1; i <= x + 1; i++)
        for (int j = y - 1; j <= y + 1; j++)
        {
            sum += arr[i][j] - '0';
        }
    return sum;
}
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int x = 0, y = 0;
    int win = 0;
    while (win < ROW * COL - mine_number)
    {
        printf("请输入要排查的坐标:");
        scanf("%d %d", &x, &y);
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
            if (show[x][y] == '*')
            {
                if (mine[x][y] == '1')
                {
                    printf("很遗憾,你被炸死了\n");
                    display(mine, ROW, COL);
                    break;
                }
                else
                {
                    expand(mine, show, x, y);
                    win = get_void(show);
                    //system("cls");
                    display(show, ROW, COL);
                }
            }
            else
            {
                system("cls");
                display(show, ROW, COL);
                printf("该坐标已经被排查过了,请重新输入坐标\n");
            }
        }
        else
        {
            printf("非法坐标,请重新输入\n");
        }
    }
    if (win == ROW * COL - mine_number)
    {
        int flag = 0;
        printf("恭喜你成功完成挑战\n");
        do
        {
            printf("输入1返回菜单:");
            scanf("%d", &flag);
        } while (flag != 1);
    }
}
void setmine(char arr[ROWS][COLS], int row, int col)
{
    int count = mine_number;
    int x = 0, y = 0;
    while (count)
    {
        x = rand() % row + 1;
        y = rand() % col + 1;
        if (arr[x][y] == '0')
        {
            arr[x][y] = '1';
            count--;
        }
    }
}
void display(char arr[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 ", arr[i][j]);
        }
        printf("\n");
    }
}
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;
        }
    }
}

game.h文件:

#include 
#include 
#include 
#include 
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define mine_number 3
void test(void);
void menu(void);
void test(void);
void game(void);
void initboard(char arr[ROWS][COLS], int rows, int cols, char set);
void display(char arr[ROWS][COLS], int rows, int cols);
void setmine(char arr[ROWS][COLS], int row, int col);
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int getminenumber(char arr[ROWS][COLS], int x, int y);
void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
int get_void(char show[ROWS][COLS]);

改变位置:

        在“void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)”这个函数中,有两句“show[x][y] = getminenumber(mine, x, y) + '0';”和“win++;”,我将这两句删除了,换成了“expand(mine, show, x, y);”和“win = get_void(show);”,即:

expand()函数:

void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
    // 检查坐标是否在有效范围内(1-9)
    if (x < 1 || x > 9 || y < 1 || y > 9)
    {
        return;
    }
    // 如果当前位置已经展开,则不再处理
    if (show[x][y] != '*')
    {
        return;
    }
    // 显示雷的数量
    show[x][y] = getminenumber(mine, x, y) + '0';
    // 只有当周围没有雷时才继续递归展开
    if (getminenumber(mine, x, y) == 0)
    {
        // 使用循环遍历周围的8个方向
        for (int dx = -1; dx <= 1; dx++)
        {
            for (int dy = -1; dy <= 1; dy++)
            {
                if (dx == 0 && dy == 0)
                    continue;
                expand(mine, show, x + dx, y + dy);
            }
        }
    }
}

get_void函数:

expand()函数的代码详细解释:

函数定义和参数

void expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
  • mine:存储地雷位置的二维数组,'1'表示有雷,'0'表示无雷

  • show:显示给玩家的二维数组,初始为全'*',表示未揭开

  • xy:当前要处理的坐标位置

边界检查

if (x < 1 || x > 9 || y < 1 || y > 9)
{
    return;
}
  • 检查坐标是否在有效范围内(1-9)

  • 如果超出范围,直接返回,不进行任何操作

  • 这是递归的终止条件之一,防止访问数组越界

已展开检查

if (show[x][y] != '*')
{
    return;
}
  • 检查当前位置是否已经展开(不是'*')

  • 如果已经展开,直接返回,避免重复处理

  • 这是递归的另一个终止条件,防止无限递归

计算并显示周围雷数

show[x][y] = getminenumber(mine, x, y) + '0';
  • 调用getminenumber函数计算(x,y)周围8个格子的地雷数量

  • 将数字转换为字符(通过加'0')并显示在show数组中

  • 例如,如果周围有3个雷,则show[x][y] = '3'

递归展开条件

if (getminenumber(mine, x, y) == 0)
{
    // 递归展开周围的格子
}
  • 只有当当前位置周围没有雷(雷数为0)时才继续递归展开

  • 如果周围有雷,只显示雷数,不进行递归展开

递归展开周围格子

for (int dx = -1; dx <= 1; dx++)
{
    for (int dy = -1; dy <= 1; dy++)
    {
        if (dx == 0 && dy == 0)
            continue;
        expand(mine, show, x + dx, y + dy);
    }
}
  • 使用双重循环遍历周围的8个方向(左上、上、右上、左、右、左下、下、右下)

  • dxdy表示坐标偏移量,取值范围为-1, 0, 1

  • 跳过当前格子本身(当dx=0且dy=0时)

  • 对周围的每个格子递归调用expand函数

递归过程示例

假设有一个位置(x,y)周围没有雷:

  1. 首先显示该位置为空格(实际上是'0',但通常显示为空格)

  2. 然后递归检查周围的8个位置

  3. 对于每个周围位置,重复上述过程

  4. 当遇到周围有雷的位置时,只显示雷数,停止递归

  5. 当遇到已展开的位置或边界时,直接返回

效果

这个函数实现了扫雷游戏的核心功能:

  • 当玩家点击一个没有雷的格子时,自动展开周围的所有空白区域

  • 直到遇到有雷的边界,显示雷的数量

  • 提供了一种"连锁反应"式的展开效果,大大提升了游戏体验

get_void 函数的代码详细解释

        这段代码实现了一个统计函数,用于计算扫雷游戏中用户可见区域(9×9)中已经展开的非雷位置数量。让我详细解释这段代码:

函数定义和目的

int get_void(char show[ROWS][COLS])
  • 函数名:get_void(可能意为"获取已展开的空格数")

  • 参数:show - 11×11的二维字符数组,表示显示给玩家的棋盘

  • 返回值:整数,表示已展开的非雷位置数量

变量初始化

int count = 0;
  • 初始化计数器为0,用于统计已展开的位置数量

双重循环遍历棋盘

for (int i = 1; i < ROWS - 1; i++)
{
    for (int j = 1; j < COLS - 1; j++)
    {
        // 检查每个位置
    }
}
  • 外层循环遍历行,从索引1开始到ROWS-2结束

  • 内层循环遍历列,从索引1开始到COLS-2结束

  • 这意味着只遍历棋盘的中心9×9区域(索引1-9),忽略外围一圈

检查位置状态

if (show[i][j] != '*')
    count++;
  • 检查当前位置是否不是'*'(即已经展开)

  • 如果已展开,计数器加1

  • 注意:这里统计的是所有已展开的位置,包括显示数字的位置和空白位置

返回值

return count;
  • 返回统计得到的已展开位置数量

实际应用场景

这个函数通常用于判断游戏是否胜利:

// 假设总共有10个雷,棋盘有81个位置(9×9)
int revealed = get_void(show);
if (revealed == 81 - 10) { // 81个位置减去10个雷
    printf("恭喜你,获胜了!\n");
}

注意事项

  1. 数组索引

    • 数组是11×11的,但有效区域是索引1-9(共9行9列)

    • 循环从1开始,到ROWS-1(即10)的前一个索引(9)结束

  2. 统计内容

    • 函数统计所有非'*'的位置,包括:

      • 显示数字的位置('1'-'8')

      • 空白位置(通常是' '或'0')

    • 不统计雷的位置(因为雷不会被展开,而是通过其他方式显示)

示例说明

假设有一个简单的show数组:

行/列 0 1 2 3 4 5 6 7 8 9 10
0     * * * * * * * * * * *
1     * 1 * * * * * * * * *
2     * * * * * * * * * * *
3     * * * * * * * * * * *
4     * * * * * * * * * * *
5     * * * * * * * * * * *
6     * * * * * * * * * * *
7     * * * * * * * * * * *
8     * * * * * * * * * * *
9     * * * * * * * * * * *
10    * * * * * * * * * * *

在这个例子中,只有位置(1,1)是已展开的(显示'1'),所以函数将返回1。

这个函数是扫雷游戏逻辑中的重要组成部分,用于跟踪游戏进度和判断胜利条件。

总结:

        通过对扫雷游戏的代码改进,新增了区域自动展开和游戏进度统计两大功能:通过递归算法实现的expand函数能够在玩家点击空白区域时自动展开相连的安全区域,显著提升了操作效率;而get_void函数则准确统计已展开格子数量,为游戏胜负判断提供了可靠依据。这些改进使游戏操作更加流畅自然,完全符合标准扫雷游戏的核心机制,极大改善了用户体验。

posted @ 2025-10-21 19:17  yjbjingcha  阅读(2)  评论(0)    收藏  举报