ゲームプログラミング:C++でマインスイーパを作ってみた


概要

コンソールゲームプログラミングは5つ目!
2Dでフィールドが綺麗に用意されるタイプのゲームは、もう作り方が分かってきますね。
今回はマインスイーパです。
こちらは昔やったことあったなと思い出しましたが、当時は「地雷を爆破させたら勝ち」だと思っていました。
「どれだけ早く地雷を爆破するのか」競うゲームと解釈していて、なんかおもんないわと思っていました。
え?みなさんもそうですか?

いやー、推論を駆使して楽しむゲームだったとは・・・いいですね、面白い!!

以下を参考にしながらマインスイーパを作成しました。
僕個人が考慮した仕様で、フラグを立てた箇所の鉱石を取り除くことはできないようにしました。
館長、ありがとう!!!
 https://youtu.be/CHdkoeb-8oQ

とりあえずうまく動作していそうです。

お、ここは明らかに地雷!

じゃあここにフラグを立ててと・・・

楽勝楽勝~

ぎゃあああああああああああ!!

コード

せっかくなので、コードを公開します。
ご自由にお試し下さい。
ホントはコメントとかネーミングとかもうちょっと考えて、初学者の方が分かるようにしたいと思っていますが・・・

相変わらず不適切なコーディングがあるので、ちょこちょこ修正していこうと思います。
回帰テストせなアカンで!!!

※コードレビューコメント頂けますと幸いです

#include <iostream>
#include <stdlib.h>
#include <conio.h>
#include <time.h>

static const int FIELD_WIDTH = 9;
static const int FIELD_HEIGHT = 9;

static const int BOMB_COUNT = 10;

int cursorX;
int cursorY;

struct Cell
{
    bool isExistBomb;
    bool isHiddenByMine;
    bool isOnFlag;
};

Cell cells[FIELD_WIDTH][FIELD_HEIGHT];

int getAdjacentBombsCount(int x, int y);
void autoRemoveMines(int argX, int argY);

int main()
{
    // initialize. 
    srand(static_cast<unsigned int>(time(NULL)));
    {
        int count = 0;
        while(count < BOMB_COUNT)
        {
            int x = rand() % FIELD_WIDTH;
            int y = rand() % FIELD_HEIGHT;
            if (false == cells[x][y].isExistBomb)
            {
                cells[x][y].isExistBomb = true;
                ++count;
            }
        }
    }

    for (int y = 0; y < FIELD_HEIGHT; ++y)
    {
        for (int x = 0; x < FIELD_WIDTH; ++x)
        {
            cells[x][y].isHiddenByMine = true;
        }
    }

    bool isExplosion = false;
    bool isClear = false;

    while (1)
    {
        system("cls");
        for (int y = 0; y < FIELD_HEIGHT; ++y)
        {
            for (int x = 0; x < FIELD_WIDTH; ++x)
            {
                if ((cursorX == x) && (cursorY == y))
                {
                    if (true == isExplosion)
                    {
                        std::cout << "※";
                    }
                    else
                    {
                        std::cout << "◎";
                    }
                }
                else if (true == cells[x][y].isOnFlag)
                {
                    std::cout << "▲";
                }
                else if (true == cells[x][y].isHiddenByMine)
                {
                    std::cout << "■";
                }
                else if (true == cells[x][y].isExistBomb)
                {
                    std::cout << "●";
                }
                else
                {
                    int AdjacentBombs = getAdjacentBombsCount(x, y);
                    if (AdjacentBombs > 0)
                    {
                        char str[] = "0";
                        str[1] += AdjacentBombs;
                        std::cout << str;
                    }
                    else
                    {
                        std::cout << "・";
                    }
                }
            }
            std::cout << std::endl;
        }

        // Game Over. 
        if (true == isExplosion)
        {
            std::cout << "Game Over...";
            std::cout << "\a";
            _getch();
            break;
        }

        // Game Clear. 
        if (true == isClear)
        {
            std::cout << "Game Clear!";
            std::cout << "\a";
            _getch();
            break;
        }

        switch (_getch())
        {
        case 'w':
            --cursorY;
            break;
        case 's':
            ++cursorY;
            break;
        case 'a':
            --cursorX;
            break;
        case 'd':
            ++cursorX;
            break;
        /* DEBUG
        case 'b':
            cells[cursorX][cursorY].isExistBomb = !cells[cursorX][cursorY].isExistBomb;
            break;
        case 'm':
            cells[cursorX][cursorY].isHiddenByMine = !cells[cursorX][cursorY].isHiddenByMine;
            break;
        */
        case 'f':
            cells[cursorX][cursorY].isOnFlag = !cells[cursorX][cursorY].isOnFlag;
            break;
        default:
            // disable to remove mine. 
            if (true == cells[cursorX][cursorY].isOnFlag)
            {
                break;
            }
            cells[cursorX][cursorY].isHiddenByMine = false;

            // Judge Game Over. 
            if (true == cells[cursorX][cursorY].isExistBomb)
            {
                isExplosion = true;
                for (int y = 0; y < FIELD_HEIGHT; ++y)
                {
                    for (int x = 0; x < FIELD_WIDTH; ++x)
                    {
                        cells[x][y].isHiddenByMine = false;
                        cells[x][y].isOnFlag = false;
                    }
                }
                break;
            }

            // Judge Game Clear. 
            {
                isClear = true;
                for (int y = 0; y < FIELD_HEIGHT; ++y)
                {
                    for (int x = 0; x < FIELD_WIDTH; ++x)
                    {
                        if ((false == cells[x][y].isExistBomb) && (true == cells[x][y].isHiddenByMine))
                        {
                            isClear = false;
                        }
                    }
                }
            }

            // Remove Adjacent Mines Automatically. 
            for (int y = -1; y <= 1; ++y)
            {
                for (int x = -1; x <= 1; ++x)
                {
                    if ((0 == x) && (0 == y))
                    {
                        continue;
                    }

                    autoRemoveMines(cursorX + x, cursorY + y);
                }
            }
            break;
        }
    }
    return 0;
}

int getAdjacentBombsCount(int argX, int argY)
{
    int count = 0;

    for (int y = -1; y <= 1; ++y)
    {
        for (int x = -1; x <= 1; ++x)
        {
            if ((0 == x) && (0 == y))
            {
                continue;
            }
            if ((argX + x < 0) || (argX + x >= FIELD_WIDTH) || (argY + y < 0) || (argY + y >= FIELD_HEIGHT))
            {
                continue;
            }
            if (true == cells[argX + x][argY + y].isExistBomb)
            {
                ++count;
            }
        }
    }
    return count;
}

void autoRemoveMines(int argX, int argY)
{
    if ((true == cells[argX][argY].isExistBomb) 
        || (false == cells[argX][argY].isHiddenByMine)
        || (argX < 0) || (argX >= FIELD_WIDTH) || (argY < 0) || (argY >= FIELD_HEIGHT)
        || (getAdjacentBombsCount(argX, argY) > 0))
    {
        return;
    }

    cells[argX][argY].isHiddenByMine = false;

    for (int y = -1; y <= 1; ++y)
    {
        for (int x = -1; x <= 1; ++x)
        {
            if ((0 == x) && (0 == y))
            {
                continue;
            }
            autoRemoveMines(argX + x, argY + y);
        }
    }
}