★さめがめ 隣接ブロックの選択★


次は、ブロックの選択を再帰呼び出しを使って行います。

インターネット上の「さめがめ」をプレイしていただければ分かると思いますが、マウスのカーソルを動かすだけで選択範囲が変わります。

今まではボタンを押したら何かアクションを起こしていましたが、常に選択を行うよう使います。


準備


「Samegame.h」を開き、ブロック選択に必要な関数や変数を用意します。

class Samegame {
public:

    Samegame();

    //ゲームの初期化
    void Initialize(const int colorNum);

    //ブロックの選択
    void SelectBlock(POINT &refPoint);

    //ブロックの描画
    void DrawBlock(Engine *pEngine);

private:

    const int BLOCK_SOUR_X; //ブロックの転送元左座標
    const int BLOCK_SOUR_Y; //ブロックの転送元上座標
    const int BLOCK_DEST_X; //ブロックの転送先左座標
    const int BLOCK_DEST_Y; //ブロックの転送先上座標
    const int MESH_SOUR_X;  //網掛けの転送元左座標
    const int MESH_SOUR_Y;  //網掛けの転送元左上座標
    const int BLOCK_WIDTH;  //ブロックの幅
    const int BLOCK_HEIGHT; //ブロックの高さ

    //ブロック構造体
    struct Block {
        int m_color;    //ブロックの色
        bool m_bSelect; //選択フラグ
    };

    //ブロック無し定数
    enum { NO_BLOCK };

    //フィールド用定数
    enum { ROW = 15, COL = 20 };

    //フィールド(盤面)
    Block m_field[ROW][COL];

    RECT m_sour;
    RECT m_dest;

    //選択中のブロックの数
    int m_selectBlockNum;

    //範囲外チェック
    bool IsOutsideField(const POINT &refPoint) const;

    //選択フラグのクリア
    void ClearSelectFlag();

    //周りのブロックを調べる
    void CheckAround(const int x, const int y, const int color);

    //選択したブロック数を取得する
    int GetSelectBlockNum() const;
};

まず、publicメンバ関数として「SelectBlock関数」を用意しました。

引数はマウスの座標です。

選択されたブロックの個数を格納するため「m_selectBlockNum」を追加しています。

また、privateメンバ関数として4つの関数を作ります。

「IsOutsideField関数」は「マインスイーパー」でも作った範囲チェック用の関数です。

「ClearSelectFlag関数」は全ての選択フラグをfalseにする関数です。

「CheckAround関数」は再帰的に周りのブロックを調べます。

「GetSelectBlockNum関数」は選択しているブロックの数を取得します。


「Samegame.cpp」を開き、「Initialize関数」で変数の初期値を設定します。

//ゲームの初期化
void Samegame::Initialize(const int colorNum)
{
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            m_field[i][j].m_color = rand() % colorNum + 1;
            m_field[i][j].m_bSelect = false;
        }
    }

    m_selectBlockNum = 0;
}

SelectBlock関数


「Initialize関数」の下に「SelectBlock関数」を追加します。

//ブロックの選択
void Samegame::SelectBlock(POINT &refPoint)
{
    if (IsOutsideField(refPoint)) {
        return;
    }

    int x = (refPoint.x - BLOCK_DEST_X) / BLOCK_WIDTH;
    int y = (refPoint.y - BLOCK_DEST_Y) / BLOCK_HEIGHT;

    if (m_field[y][x].m_color == NO_BLOCK) {
        return;
    }

    ClearSelectFlag();

    CheckAround(x, y, m_field[y][x].m_color);

    m_selectBlockNum = GetSelectBlockNum();

    if (m_selectBlockNum <= 1) {
        ClearSelectFlag();
    }
}

最初に範囲チェックを行います。

マウス座標から2次元配列の添え字を計算し、ブロックの有無を調べます。

選択フラグをクリアし、隣接したブロックを調べ、選択した個数を取得し、個数が1以下であれば選択を解除します。


IsOutsideField関数


「DrawBlock関数」の下に「IsOutsideField関数」を追加しましょう。

//範囲外チェック
bool Samegame::IsOutsideField(const POINT &refPoint) const
{
    const int LIMIT_LEFT = BLOCK_DEST_X;
    const int LIMIT_RIGHT = BLOCK_DEST_X + BLOCK_WIDTH * COL;
    const int LIMIT_TOP = BLOCK_DEST_Y;
    const int LIMIT_BOTTOM = BLOCK_DEST_Y + BLOCK_HEIGHT * ROW;

    return refPoint.x < LIMIT_LEFT || refPoint.x >= LIMIT_RIGHT || refPoint.y < LIMIT_TOP || refPoint.y >= LIMIT_BOTTOM;
}

これは前にも作りましたので説明は省きます。


ClearSelectFlag関数


「IsOutsideField関数」の下に「ClearSelectFlag関数」を追加します。

//選択フラグのクリア
void Samegame::ClearSelectFlag()
{
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            m_field[i][j].m_bSelect = false;
        }
    }
}

これも単純な内容ですので説明は不要でしょう。


CheckAround関数


「ClearSelectFlag関数」の下に「CheckAround関数」を追加します。

この関数は再帰的に呼び出されます。

引数は調べたいブロックの座標と色です。

同じ色のブロックの個数を調べますので、色は重要な情報です。

//周りのブロックを調べる
void Samegame::CheckAround(const int x, const int y, const int color)
{
    if (m_field[y][x].m_bSelect) {
        return;
    }

    m_field[y][x].m_bSelect = true;

    if (y - 1 >= 0) {
        if (m_field[y - 1][x].m_color == color) {
            CheckAround(x, y - 1, color);
        }
    }

    if (x + 1 < COL) {
        if (m_field[y][x + 1].m_color == color) {
            CheckAround(x + 1, y, color);
        }
    }

    if (y + 1 < ROW) {
        if (m_field[y + 1][x].m_color == color) {
            CheckAround(x, y + 1, color);
        }
    }

    if (x - 1 >= 0) {
        if (m_field[y][x - 1].m_color == color) {
            CheckAround(x - 1, y, color);
        }
    }
}

すでに選択済みのブロックであれば再帰を止めます。

そうでなければ選択フラグをtrueにして、上下左右を調べます。

上下左右のつながりを調べるプログラムは、C言語編の再帰呼び出しのところで説明しています。


GetSelectBlockNum関数


最後に「CheckAround関数」の下に「GetSelectBlockNum関数」を追加します。

//選択したブロック数を取得する
int Samegame::GetSelectBlockNum() const
{
    int counter = 0;

    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            if (m_field[i][j].m_bSelect) {
                counter++;
            }
        }
    }

    return counter;
}

選択フラグが立っているブロックの個数を調べて返しています。


網掛けの描画


選択したブロックを強調するために「網掛け」を重ねて描画するようにします。

「網」は↓のような絵になっています。

灰色のドットと表示されない紫のドットを交互に書いて作りました。

紫の部分は後ろの色が見えるようになりますので、網がかかったように見えます。

「DrawBlock関数」にコードを追加しましょう。

//ブロックの描画
void Samegame::DrawBlock(Engine *pEngine)
{
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {

            if (m_field[i][j].m_color != NO_BLOCK) {

                SetRect(&m_sour,
                    BLOCK_SOUR_X + (m_field[i][j].m_color - 1) * BLOCK_WIDTH,
                    BLOCK_SOUR_Y,
                    BLOCK_SOUR_X + (m_field[i][j].m_color - 1) * BLOCK_WIDTH + BLOCK_WIDTH,
                    BLOCK_SOUR_Y + BLOCK_HEIGHT);

                SetRect(&m_dest,
                    BLOCK_DEST_X + j * BLOCK_WIDTH,
                    BLOCK_DEST_Y + i * BLOCK_HEIGHT,
                    BLOCK_DEST_X + j * BLOCK_WIDTH + BLOCK_WIDTH,
                    BLOCK_DEST_Y + i * BLOCK_HEIGHT + BLOCK_HEIGHT);

                pEngine->Blt(&m_dest, TEXTURE_SAMEGAME, &m_sour);

                if (m_field[i][j].m_bSelect) {

                    SetRect(&m_sour,
                        MESH_SOUR_X,
                        MESH_SOUR_Y,
                        MESH_SOUR_X + BLOCK_WIDTH,
                        MESH_SOUR_Y + BLOCK_HEIGHT);

                    pEngine->Blt(&m_dest, TEXTURE_SAMEGAME, &m_sour);
                }
            }
        }
    }
}

転送したブロックの上に「網」を転送しますので、転送先は同じですから改めて設定する必要はありません。


関数呼び出し


「SceneGame.cpp」を開き「Update関数」にコードを追加しましょう。

//=============================================================================
// シーンの実行時に繰り返し呼び出される更新処理関数
//=============================================================================
void SceneGame::Update()
{
    POINT point = m_pEngine->GetMousePosition();

    m_samegame.SelectBlock(point);
}

これで実行出来るはずです。

実行して確認してみましょう。

<実行結果 クライアント領域のみ>

マウスカーソルのあるブロックに隣接したブロックに「網」がかかっていればOKです。


次へ

戻る

目次へ