[C语言]扫雷游戏

一、前言

对于编程初学者而言,掌握指针与数组的基础知识是构建复杂程序不可或缺的基石。若您对这些核心概念尚存疑虑,强烈推荐您通过以下链接进行深入学习,以巩固基础:传送门

若您已对指针与数组有了较好的理解,但对于如何生成随机数仍感陌生,那么探索以下猜数字游戏的实现过程将是极佳的学习机会:传送门


二、游戏思路

回溯至我们童年时期的经典游戏 —— 扫雷:游戏原型

  • 游戏的核心目标在于找出并准确标记所有不含地雷的格子,同时确保自身安全,避免触雷。
  • 游戏区域内的每个格子均藏有玄机,其显示的数字代表该格子周围8个相邻格子中地雷的总数。
  • 特别规则:玩家的首次点击将确保安全,绝不会遭遇地雷。
  • 若不幸选中了地雷格子,则宣告游戏立即结束。
  • 若玩家选中的是无地雷且未显示数字的格子,则系统会根据规则自动展开其周围的安全区域,直至遇到含数字的格子为止。

三、游戏方法

1、初始化游戏区域:

  • 创建一个二维数组作为游戏区域的模型,初始时,所有格子均处于未探索状态。
  • 等待玩家首次操作完成后,系统将在游戏区域内随机布置地雷,增添游戏挑战性。

2、用户交互:

  • 当玩家在游戏区域内选择某个格子时,系统将即时响应其操作。
  • 若所选格子暗藏地雷,则立即显示“游戏结束”的提示信息,并高亮显示所有地雷位置,让玩家一目了然。
  • 若所选格子安全无虞,则显示其周围地雷的数量,并依据游戏规则自动展开周围的安全区域,提升游戏体验。

3、输赢条件:

  • 胜利条件:当游戏区域内所有非地雷格子均被玩家成功翻开时,即宣布玩家取得胜利。
  • 失败条件:一旦玩家不慎翻开地雷格子,则游戏立即结束,宣告玩家失败。

四、核心方法说明

1、初始化游戏

/*
 * 初始化扫雷游戏
 * @param mine 指向存储游戏棋盘的指针的指针
 * @param row 棋盘的行数(不包括边界)
 * @param col 棋盘的列数(不包括边界)
 */
void init_mine(char*** mine, int row, int col)
{
    //分配内存
    *mine = (char**)malloc((row + 2) * sizeof(char*));//多开两个,防止越界
    if (*mine == NULL)
    {
        fprintf(stderr, "内存分配失败\n");
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < (row + 2); i++)//多开两个,防止越界
    {
        (*mine)[i] = (char*)malloc((col + 2) * sizeof(char));
        if ((*mine)[i] == NULL)
        {
            fprintf(stderr, "内存分配失败\n");
            //释放之前已分配的内存
            for (int j = 0; j < i; j++)
            {
                free((*mine)[j]);
            }
            free(*mine);
            exit(EXIT_FAILURE);
        }
    }

    //初始化
    for (int i = 0; i < (row + 2); i++)
    {
        for (int j = 0; j < (col + 2); j++)
        {
            if (i == 0 || i == row + 1 || j == 0 || j == col + 1)
                (*mine)[i][j] = '0';
            else
                (*mine)[i][j] = '#';
        }
    }
}

2、销毁棋盘

/*
 * 销毁扫雷游戏棋盘
 * @param mine 指向存储游戏棋盘的指针的指针
 * @param row 棋盘的行数(不包括边界)
 * @param col 棋盘的列数(不包括边界)
 */
void destory_mine(char*** mine, int row, int col)
{
    assert(mine != NULL);

    for (int i = 0; i < (row + 2); i++)
    {
        free((*mine)[i]);
    }
    free(*mine);
    *mine = NULL;
}

3、展示游戏

/*
 * 显示扫雷游戏棋盘
 * @param mine 游戏棋盘
 * @param row 棋盘的行数(不包括边界)
 * @param col 棋盘的列数(不包括边界)
 * @param flag 游戏当前状态(0表示游戏未结束,非0表示游戏已结束)
 */
void show_mine(char** mine, int row, int col, int flag)
{
    assert(mine != NULL);

    for (int i = 0; i <= col; i++)//列号
    {
        printf("%02d ", i);
    }
    printf("\n");

    for (int i = 1; i <= row; i++)
    {
        printf("%02d ", i);//行号

        for (int j = 1; j <= col; j++)
        {
            if (mine[i][j] == '*' && flag == 0)//雷
            {
                printf("## ");
            }
            else if (mine[i][j] == '*' && flag != 0)//雷
            {
                printf("\033[31m** \033[0m");
            }
            else if (mine[i][j] == '0')//已点击周围无雷
            {
                printf("\033[30;1m## \033[0m");
            }
            else if (mine[i][j] >= '1' && mine[i][j] <= '8')//已点击周围有雷
            {
                printf("\033[30;1m%02c \033[0m", mine[i][j]);
            }
            else//未点击
            {
                printf("## ");
            }

        }
        printf("\n\033[0m");
    }
    printf("\n");
}

4、扩展非雷区

/*
 * 计算指定位置周围的地雷数量
 * @param mine 游戏棋盘
 * @param row 棋盘行数(不包括边界)
 * @param col 棋盘列数(不包括边界)
 * @param x 当前位置的行号
 * @param y 当前位置的列号
 * @return 指定位置周围的地雷数量
 */
int count_mine(char** mine, int row, int col, int x, int y)
{
    assert(mine != NULL);
    assert(x >= 1 && x <= row && y >= 1 && y <= col);

    return  (mine[x-1][y] == '*') +
        (mine[x+1][y] == '*') + 
        (mine[x][y+1] == '*') + 
        (mine[x][y-1] == '*') +
        (mine[x-1][y-1] == '*') +
        (mine[x+1][y+1] == '*') +
        (mine[x+1][y-1] == '*') +
        (mine[x-1][y+1] == '*');
}

/*
 * 扩展安全区域(递归地显示周围无雷的格子)
 * @param mine 游戏棋盘
 * @param row 棋盘行数(不包括边界)
 * @param col 棋盘列数(不包括边界)
 * @param x 当前位置的行号
 * @param y 当前位置的列号
 */
void expand_safe(char** mine, int row, int col, int x, int y)
{
    assert(mine != NULL);
    assert(x >= 1 && x <= row && y >= 1 && y <= col);

    int count = count_mine(mine, row, col, x, y);
    if (mine[x][y] != '#')
    {
        return;
    }
    else if (count != 0)
    {
        mine[x][y] = count + '0';
        return;
    }
    else if (count == 0)
    {
        mine[x][y] = '0';
    }

    if (x - 1 >= 1)
        expand_safe(mine, row, col, x - 1, y);
    if (y - 1 >= 1)
        expand_safe(mine, row, col, x, y - 1);
    if (x - 1 >= 1 && y - 1 >= 1)
        expand_safe(mine, row, col, x - 1, y - 1);
    if (x + 1 <= row && y - 1 >= 1)
        expand_safe(mine, row, col, x + 1, y - 1);
    if (x + 1 <= row)
        expand_safe(mine, row, col, x + 1, y);
    if (y + 1 <= col)
        expand_safe(mine, row, col, x, y + 1);
    if (x + 1 <= row && y + 1 <= col)
        expand_safe(mine, row, col, x + 1, y + 1);
    if (x - 1 >= 1 && y + 1 <= col)
        expand_safe(mine, row, col, x - 1, y + 1);
    return;
}

5、判断游戏结束

/*
 * 判断游戏是否结束
 * @param mine 游戏棋盘
 * @param row 棋盘行数(不包括边界)
 * @param col 棋盘列数(不包括边界)
 * @param x 当前选择的行号
 * @param y 当前选择的列号
 * @return -1 表示玩家踩雷,游戏结束;0 表示还有未探索的格子,游戏继续;1 表示所有无雷格子都已探索,玩家获胜。
 */
int is_win(char** mine, int row, int col, int x, int y)
{
    assert(mine != NULL);
    assert(x >= 1 && x <= row && y >= 1 && y <= col);

    if (mine[x][y] == '*')
        return -1;
    else
        expand_safe(mine, row, col, x, y);

    for (int i = 1; i <= row; i++)
    {
        for (int j = 1; j <= col; j++)
        {
            if (mine[i][j] == '#')
                return 0;
        }
    }

    return 1;
}

6、游戏交互

/*
 * 清除标准输入缓冲区中直到换行符的所有字符,并返回一个标志位。
 * @return 如果缓冲区中有字符被清除,则返回1;否则返回0。
 */
int clear_stdin(void)
{
    int flag = 0;
    while (getchar() != '\n')
        flag = 1;
    return flag;
}

/*
 * 运行扫雷游戏的主逻辑。
 * @param mine 指向游戏棋盘的指针的指针。
 * @param row 棋盘行数(不包括边界)。
 * @param col 棋盘列数(不包括边界)。
 * @param mine_num 雷的个数。
 */
void game(char*** mine, int row, int col, int mine_num)
{
    assert(mine_num > 0 && mine_num < (row * col));

    srand(time(NULL));
    init_mine(mine, row, col);
    int win = 0;
    int count = 1;
    while (1)
    {
        system("cls");
        show_mine(*mine, row, col, win);
        int x = 0, y = 0;
        printf("请输入排雷点(x, y):");
        scanf("%d,%d", &x, &y);
        if (clear_stdin() != 0)
            x = y = -1;

        if (x < 1 || x > row || y < 1 || y > col)
        {
            system("cls");
            printf("输入错误!\n");
            clear_stdin();
            system("cls");
            continue;
        }
        
        if (count == 1)
            set_mine(*mine, mine_num, row, col, x, y);

        count++;

        win = is_win(*mine, row, col, x, y);
        if (win == -1)
        {
            system("cls");
            printf("游戏结束!\n");
            show_mine(*mine, row, col, win);
            destory_mine(mine, row, col);
            clear_stdin();
            system("cls");
            break;
        }
        else if (win == 1)
        {
            system("cls");
            printf("游戏胜利!\n");
            show_mine(*mine, row, col, win);
            destory_mine(mine, row, col);
            clear_stdin();
            system("cls");
            break;
        }
    }
}

五、游戏成果

1、效果展示

以下是我精心打造的扫雷游戏的效果展示:

请添加图片描述

从图中可以看到,游戏的基本功能已经全部实现,玩家可以愉快地进行扫雷挑战啦!🎮

2、源代码

想要更深入地了解这款扫雷游戏的实现细节吗?没问题!我已经将游戏的源代码完整地开源在了Gitee上:传送门

欢迎大家前来查看和学习,你可以在这里找到游戏的完整实现代码,包括棋盘初始化、地雷布置、游戏交互逻辑等各个模块的代码实现。无论你是对扫雷游戏的算法感兴趣,还是想学习如何用C语言实现一个完整的游戏项目,这里都是你不容错过的好去处。快来一起探索和学习吧!📚👨‍💻


评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

疑惑的杰瑞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值