前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用c程序实现扫雷小游戏

用c程序实现扫雷小游戏

原创
作者头像
mariolu
修改2024-02-29 21:22:06
990
修改2024-02-29 21:22:06
举报

一、扫雷UI和玩家交互

定义class Field,用来表明扫雷的面板。以及一系列member func来操作该面板。

声明如下,具体实现见第2部分

代码语言:cpp
复制
class Field
{
  public:
    Field();
    void mineTheField();  // 埋雷
    void markAdjMineCells();  // 统计每个Cells的周围的雷的个数
    void startSweep(int, int);  // 执行扫雷
    void startSweep(int, int, POSOFCELL, DIR_X, DIR_Y);  // 执行扫雷
    void drawField();  // 展示UI画面
    void checkVictoryAndFlagMines();  // 检查是否胜利并展示所有雷
    void getMove();  // 根据用户按键进行响应

  private:
    int l;  // 长度
    int b;  // 宽度
    int m;  // 雷的个数
    int x;  // 当前光标x
    int y;  // 当前光标y
    int flags;
    int hiddenCells;

    bool firstSweep;  // 首次扫雷

    std::vector<std::pair<int, int>> mines; //store the location of mines

    GRID cells;  // using GRID = std::vector<std::vector<Cell>>; 这里GRID网格存储所有单元格的二维数组
};

展示玩家游戏状态:

代码语言:cpp
复制
void dispVictoryOrDefeat()  // 在屏幕打印出游戏状态
{
    writeBuf << endl;
    COLOUR col = gameState == VICTORY ? green_fg : red_fg;

    writeBuf << col << R"(  __   _____  _   _ )" << endl;
    writeBuf << col << R"(  \ \ / / _ \| | | |)" << endl;
    writeBuf << col << R"(   \ V / (_) | |_| |)" << endl;
    writeBuf << col << R"(    |_| \___/ \___/ )" << endl;

    writeBuf << endl;

    if (gameState == VICTORY)
    {

        writeBuf << col << R"(  __      _____ _  _ _ )" << endl;
        writeBuf << col << R"(  \ \    / /_ _| \| | |)" << endl;
        writeBuf << col << R"(   \ \/\/ / | || .` |_|)" << endl;
        writeBuf << col << R"(    \_/\_/ |___|_|\_(_))" << endl;
    }

    else
    {
        writeBuf << col << R"(   _    ___  ___ ___ _ )" << endl;
        writeBuf << col << R"(  | |  / _ \/ __| __| |)" << endl;
        writeBuf << col << R"(  | |_| (_) \__ \ _||_|)" << endl;
        writeBuf << col << R"(  |____\___/|___/___(_))" << endl;
    }
}

那么,我们的主体main函数这样实现

代码语言:cpp
复制

int main()
{
    system("clear"); // 清除屏幕

    Field field;  // 初始化扫雷盘

    while (true)
    {
        system("clear");  // 玩家每进行一步按键后,清屏

        field.drawField();  // 根据当前状态画UI

        if (gameState != RUNNING)
            dispVictoryOrDefeat();  // 游戏结束,显示玩家游戏结果

        writeBuf.disp();  // 打印拼命
        writeBuf.clear();  // 清空写入Buf

        if (gameState == RUNNING)
            field.getMove();  // 根据玩家按键,更新状态
        else
            break;
    }

    std::cout << endl
              << reset;

    return 0;
}

二、Filed实现

2.1 埋雷实现:

代码语言:cpp
复制
void Field::mineTheField()
{
    std::random_device rd;
    std::mt19937 rng(rd());  // 伪随机产生数
    std::uniform_int_distribution<int> x_uni(0, l - 1);  // 符合自然规律的正态分布埋雷坐标
    std::uniform_int_distribution<int> y_uni(0, b - 1);
    auto m_copy = m;  // 总共要埋得雷
    while (m_copy)  // 开始埋雷
    {
        auto i = x_uni(rng);  // 
        auto j = y_uni(rng);
        if ((i >= x - 1 && i <= x + 1) &&  // x和y分别是长度和宽度的中位数。
            (j >= y - 1 && j <= y + 1))
            continue;  // 如果随机到坐标是盘的中心点,那么跳过
        if (cells[i][j].state != MINE)  // 当前区域没有埋过雷
        {
            cells[i][j].setMine();  // 以(x,y)坐标的网格开始埋雷
            mines.push_back(std::make_pair(i, j));  // 储存埋雷位置
            --m_copy;  // 成功埋雷,剩余埋雷数-1
        }
    }
}

2.2 统计周边雷的个数

代码语言:cpp
复制
void Field::markAdjMineCells()
{
    for (auto mine : mines)  // 遍历所有雷
    {
        int x_pos = mine.first, y_pos = mine.second;  // 雷的坐标

        for (int i = x_pos - 1; i < x_pos + 2; ++i)  // 雷的x坐标范围[x_pos-1, x_pos+1]
        {
            if (i < 0 || i > l - 1)  // 无效区域,不统计超出扫雷盘x坐标的无效区间
                continue;

            for (int j = y_pos - 1; j < y_pos + 2; ++j)  // 雷的y坐标范围[y_pos-1, y_pos+1]
            {
                if (j < 0 || j > b - 1) // 无效区域,不统计超出扫雷盘y坐标的无效区间
                    continue;
                if (cells[i][j].state == MINE)  // 如果当前位置就是雷,那么不记录周边雷的个数
                    continue;
                int mineCount = 0;  // 正式开始统计

                for (int c = i - 1; c < i + 2; ++c)  // 以当前位置(i,j)为中心,检查周围的8个点的区域
                {
                    if (c < 0 || c > l - 1)  // 无效区域
                        continue;
                    for (int d = j - 1; d < j + 2; ++d)
                    {
                        if (d < 0 || d > b - 1)  // 无效区域
                            continue;
                        if (cells[c][d].state == MINE)  // 周围存在1个雷,进行加1操作
                            ++mineCount;
                    }
                }
                if (mineCount)  // 如果有雷,进行标注
                    cells[i][j].markAdjMine(mineCount);
            }
        }
    }
}

2.3 开始扫雷

代码语言:cpp
复制
void Field::startSweep(int x, int y)
{
    if (gameState != RUNNING)
        return;
    if (cells[x][y].flagged)  // 玩家标记的雷区
        return;
    if (!cells[x][y].hidden && !QUICKCLEAR)  // 当前区域已经扫过
        return;

    switch (cells[x][y].state)
    {
    case EMPTY:  // 没有扫过雷区
        --hiddenCells;
        cells[x][y].reveal();  // 展示当前网格状态
        checkVictoryAndFlagMines();  // 检查是否踩到雷
        break;

    case ADJ_TO_MINE:  // 是个周边雷的数字
        if (cells[x][y].hidden)  //  展示
        {
            --hiddenCells;
            cells[x][y].reveal();
            checkVictoryAndFlagMines();
            return;
        }

        else if (QUICKCLEAR)
        {
            bool isValid = checkValidityOfQuickClear();
            checkVictoryAndFlagMines();
            if(isValid)
                break;
            else
                return;
        }

    case MINE:  // 踩到雷
        gameState = DEFEAT;  // 游戏失败
        for (auto mine : mines)  //所有雷展示出来
            cells[mine.first][mine.second].reveal();
        return;
    }

    // 四周8个位置扫雷
    startSweep(x - 1, y - 1, CORNER, LEFT      , UP        );
    startSweep(x    , y - 1, EDGE  , NULL_DIR_X, UP        );
    startSweep(x + 1, y - 1, CORNER, RIGHT     , UP        );
    startSweep(x - 1, y    , EDGE  , LEFT      , NULL_DIR_Y);
    startSweep(x + 1, y    , EDGE  , RIGHT     , NULL_DIR_Y);
    startSweep(x - 1, y + 1, CORNER, LEFT      , DOWN      );
    startSweep(x    , y + 1, EDGE  , NULL_DIR_X, DOWN      );
    startSweep(x + 1, y + 1, CORNER, RIGHT     , DOWN      );
    checkVictoryAndFlagMines();
}

开始扫雷

代码语言:cpp
复制
void Field::startSweep(int x, int y, POSOFCELL pos, DIR_X x_dir, DIR_Y y_dir)
{
    checkVictoryAndFlagMines();
    if (x < 0 || x > l - 1 || y < 0 || y > b - 1)  // 超出扫雷面板
        return;
    if (gameState != RUNNING)
        return;
    if (cells[x][y].flagged)  // 当前位置已经被玩家标注
        return;
    if (!cells[x][y].hidden)  // 当前位置已经扫过
        return;

    switch (cells[x][y].state)
    {
    case EMPTY:  // 空白区域
        cells[x][y].reveal();  // 显示空白
        --hiddenCells;
        if (pos == CORNER)  // 如果该网格位置是角落,那么需要下一次递归是2个边和3个角
        {
            startSweep(x + x_dir, y + y_dir, CORNER, x_dir          , y_dir          );
            startSweep(x + x_dir, y        , EDGE  , x_dir          , NULL_DIR_Y     );
            startSweep(x        , y + y_dir, EDGE  , NULL_DIR_X     , y_dir          );
            startSweep(x + x_dir, y - y_dir, CORNER, x_dir          , (DIR_Y)(-y_dir));
            startSweep(x - x_dir, y + y_dir, CORNER, (DIR_X)(-x_dir), y_dir          );
        }

        else  // 如果该网格位置是边缘
        {
            startSweep(x + x_dir, y + y_dir, EDGE , x_dir, y_dir);  // 扫雷当前位置

            if (y_dir == NULL_DIR_Y)  // 
            {
                startSweep(x + x_dir, y - 1, CORNER, x_dir     , UP  );
                startSweep(x        , y - 1, EDGE  , NULL_DIR_X, UP  );
                startSweep(x        , y + 1, EDGE  , NULL_DIR_X, DOWN);
                startSweep(x + x_dir, y + 1, CORNER, x_dir     , DOWN);
            }
            else
            {
                startSweep(x - 1, y + y_dir, CORNER, LEFT , y_dir     );
                startSweep(x - 1, y        , EDGE  , LEFT , NULL_DIR_Y);
                startSweep(x + 1, y        , EDGE  , RIGHT, NULL_DIR_Y);
                startSweep(x + 1, y + y_dir, CORNER, RIGHT, y_dir     );
            }
        }

        break;

    case ADJ_TO_MINE:  // 位置是周边雷的数字,展示出来
        cells[x][y].reveal();
        --hiddenCells;
        break;
    }
}

2.5 画UI

代码语言:cpp
复制
void Field::drawField()
{
    writeBuf << reset;
    for (int s = 0; s <= l * 4; ++s)  // 用5个位置画一个格子Cell
        writeBuf << " ";  // 先画第1行空白行
    writeBuf << endl;

    writeBuf << reset;
    writeBuf << "    ";  // 第一列1的左边空白区域
    if (cells[0][0].hidden)   // 如果当前区域未扫过,用比较粗的线  
        writeBuf << "┏";
    else
        writeBuf << reset << "┌";

    for (int i = 0; i < l - 1; ++i)  // 先画第1行轮廓
    {
        if (cells[i][0].hidden)
        {
            writeBuf << "━━━";
            if (cells[i + 1][0].hidden)
                writeBuf << "┳";
            else
                writeBuf << "┱";
        }
        else
        {
            writeBuf << "───";
            if (cells[i + 1][0].hidden)
                writeBuf << "┲";
            else
                writeBuf << "┬";
        }
    }

    if (cells[l - 1][0].hidden)
        writeBuf << "━━━┓";
    else
        writeBuf << "───┐";
    writeBuf << endl;

    writeBuf << reset;
    for (int j = 0; j < b; ++j)
    {
        if (cells[0][j].hidden)
            writeBuf << "    ┃";
        else
            writeBuf << reset << "    │";

        for (int i = 0; i < l; ++i)
        {
            if (cells[i][j].state != MINE || cells[i][j].hidden)
                writeBuf << " ";
            else
                writeBuf << "?"; //implement different whitespace char here

            if (i == x && j == y)
            {
                if ((cells[i][j].hidden || cells[i][j].state == EMPTY) &&
                    (!cells[i][j].flagged))
                    writeBuf << blue_bg << " ";

                else
                    writeBuf << blue_bg << cells[i][j].sym;
            }
            else
                writeBuf << cells[i][j].sym;

            if (cells[i][j].hidden || (i != l - 1 && cells[i + 1][j].hidden))
                writeBuf << reset << " ┃";
            else
                writeBuf << reset << " │";
        }

        if (j != b - 1)
        {
            writeBuf << endl;
            writeBuf << reset;
            writeBuf << "    ";

            if (cells[0][j].hidden)
            {
                if (cells[0][j + 1].hidden)
                    writeBuf << "┣";
                else
                    writeBuf << "┡";
            }

            else
            {
                if (cells[0][j + 1].hidden)
                    writeBuf << "┢";
                else
                    writeBuf << "├";
            }

            for (int k = 0; k < l - 1; ++k)
            {
                if (cells[k][j].hidden)
                {
                    writeBuf << "━━━";

                    if (cells[k + 1][j].hidden && !cells[k][j + 1].hidden && !cells[k + 1][j + 1].hidden)
                    {
                        writeBuf << "╇";
                    }

                    else if (!cells[k + 1][j].hidden && cells[k][j + 1].hidden && !cells[k + 1][j + 1].hidden)
                    {
                        writeBuf << "╉";
                    }

                    else if (!cells[k + 1][j].hidden && !cells[k][j + 1].hidden && !cells[k + 1][j + 1].hidden)
                    {
                        writeBuf << "╃";
                    }

                    else
                    {
                        writeBuf << "╋";
                    }
                }
                else
                {
                    if (cells[k][j + 1].hidden)
                        writeBuf << "━━━";
                    else
                        writeBuf << "───";

                    if (cells[k + 1][j].hidden && !cells[k][j + 1].hidden && !cells[k + 1][j + 1].hidden)
                    {
                        writeBuf << "╄";
                    }

                    else if (!cells[k + 1][j].hidden && cells[k][j + 1].hidden && !cells[k + 1][j + 1].hidden)
                    {
                        writeBuf << "╅";
                    }

                    else if (!cells[k + 1][j].hidden && !cells[k][j + 1].hidden && cells[k + 1][j + 1].hidden)
                    {
                        writeBuf << "╆";
                    }

                    else if (cells[k + 1][j].hidden && !cells[k][j + 1].hidden && cells[k + 1][j + 1].hidden)
                    {
                        writeBuf << "╊";
                    }

                    else if (!cells[k + 1][j].hidden && cells[k][j + 1].hidden && cells[k + 1][j + 1].hidden)
                    {
                        writeBuf << "╈";
                    }

                    else if (!cells[k + 1][j].hidden && !cells[k][j + 1].hidden && !cells[k + 1][j + 1].hidden)
                    {
                        writeBuf << "┼";
                    }

                    else
                    {
                        writeBuf << "╋";
                    }
                }
            }

            if (cells[l - 1][j].hidden)
            {
                writeBuf << "━━━";
                if (cells[l - 1][j + 1].hidden)
                    writeBuf << "┫";
                else
                    writeBuf << "┩";
            }

            else
            {
                if (cells[l - 1][j + 1].hidden)
                    writeBuf << "━━━┪";
                else
                    writeBuf << "───┤";
            }
        }

        writeBuf << endl;
        writeBuf << reset;
    }

    writeBuf << "    ";
    if (cells[0][b - 1].hidden)
        writeBuf << "┗";
    else
        writeBuf << "└";

    for (int i = 0; i < l - 1; ++i)
    {
        if (cells[i][b - 1].hidden)
        {
            writeBuf << "━━━";
            if (cells[i + 1][b - 1].hidden)
                writeBuf << "┻";
            else
                writeBuf << "┹";
        }
        else
        {
            writeBuf << "───";
            if (cells[i + 1][b - 1].hidden)
                writeBuf << "┺";
            else
                writeBuf << "┴";
        }
    }

    if (cells[l - 1][b - 1].hidden)
        writeBuf << "━━━┛";
    else
        writeBuf << "───┘";

    writeBuf << endl;

    writeBuf.goToLine(0);  // 开始绘画
}

2.6 检查是否扫完雷区

代码语言:cpp
复制

void Field::checkVictoryAndFlagMines()
{
    if (hiddenCells == m && gameState != DEFEAT)
    {
        gameState = VICTORY;
        //flag all mines that weren't flagged
        for (auto mine : mines)  // 把所有雷的都展示出来
        {
            if (!cells[mine.first][mine.second].flagged)
            {
                cells[mine.first][mine.second].toggleflag();
            }
        }
    }
}

2.7 根据玩家按键进行下一步动作

玩家有效的操作按键包括:

  • ↑, ←, ↓, → 或者H, J, K, L
  • 扫雷 字母S或者回车键
  • 标记雷区 字母F或者空格键
代码语言:cpp
复制
void Field::getMove()
{
    KEY k = getKey();
    switch (k)
    {
    case K_K:
    case K_k:
    case K_UP:  // 玩家按键是向上键
        if (y)
            --y;
        return;

    case K_J:
    case K_j:
    case K_DOWN:  // 玩家按键是向下键
        if (y != b - 1)
            ++y;
        return;

    case K_H:
    case K_h:
    case K_LEFT:  // 玩家按键是向左键
        if (x)
            --x;
        return;

    case K_L:
    case K_l:
    case K_RIGHT:  // 玩家按键是向右键
        if (x != l - 1)
            ++x;
        return;

    case K_SPACE:  // 玩家按键是空格,标记雷区
    case K_F:
        if (cells[x][y].flagged)
        {
            ++flags;
            ++flagDisp;
            cells[x][y].toggleflag();
        }
        else if (flags && cells[x][y].hidden)
        {
            --flags;
            --flagDisp;
            cells[x][y].toggleflag();
        }
        return;

    case K_ENTER:  // 玩家按键是Enter键,执行扫雷
    case K_S:
        if (firstSweep)
        {
            mineTheField();
            markAdjMineCells();
            firstSweep = false;
        }
        startSweep(x, y);
        return;
    }
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、扫雷UI和玩家交互
  • 二、Filed实现
    • 2.1 埋雷实现:
      • 2.2 统计周边雷的个数
        • 2.3 开始扫雷
          • 2.5 画UI
            • 2.6 检查是否扫完雷区
              • 2.7 根据玩家按键进行下一步动作
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
              http://www.vxiaotou.com