CodeZine(コードジン)

特集ページ一覧

落ち物ゲームの作り方 第2回:「聖夜の落とし物」編

隣接する同じ種類のブロックを走査する方法

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/12/16 12:00

本稿では、第1回で解説した「TETRA」とは別の落ち物ゲーム「聖夜の落とし物」を作ります。「聖夜の落とし物」も、ある有名な落ち物ゲームを模しています。「聖夜の落とし物」に特有な処理として、4個以上つながっているかの判定、妨害ブロックの取り扱い、などがあります。

目次
図1 サンプルプログラム「聖夜の落とし物」の完成図
図1 サンプルプログラム「聖夜の落とし物」の完成図

はじめに

 本稿では、第1回で解説した「TETRA」とは別の落ち物ゲーム「聖夜の落とし物」を作ります。「聖夜の落とし物」も、ある有名な落ち物ゲームを模しています。ルールを簡単に説明すると、次の通りです。

 このゲームでは、落ちてくるブロックを積み重ねて、同じ種類のブロックを4個以上つなげることを目標とします。縦横に4個以上つながるとブロックが消え、点数が入ります。上まで積み重ねてしまうと、ゲームオーバーです。

 他に「TETRA」と違うところは、浮いているブロックが個別に落下すること(ピース単位ではなく)、それによって続けざまにブロックが消える「連鎖」という動作が起こりうること、です。連鎖するほど得られる点数は増えるので、大連鎖を狙う楽しみがあります。

 本稿のプログラムは、第1回のサンプルプログラム「TETRA」を基にしています。「聖夜の落とし物」に特有な処理として、4個以上つながっているかの判定、妨害ブロック(後述)の取り扱い、などがあります。

対象読者

 前作『落ち物ゲームの作り方 第1回:「TETRA」編』を読んだ方。重複する内容は解説しません。

必要な環境

 Visual C++ .NET 2002で開発し、Windows XP/98で動作確認しています。

データ構造の諸変更

 PIECE構造体とFIELD構造体にあった、ブロックの有無を表す変数bool block[][]の代わりに、BYTE image[][]メンバ変数を利用します。ブロックが無いときはNONEを代入します。他にも、ここではわかりませんが、フィールドの最上段は表示しないように変更しています。

// ピースのセル数
#define PW 3
#define PH 3

// フィールドのセル数
#define FW 6
#define FH 13

// セルのピクセル数
#define CW 32
#define CH 32

// ピース管理構造体
typedef struct tagPIECE
{
    BYTE image[PH][PW];    // ブロックのビットマップ番号
    char x,y;              // 左上のセル座標
}PIECE;

// フィールド管理構造体
typedef struct tagFIELD
{
    BYTE image[FH][FW];     // ブロックのビットマップ番号
    bool vanish[FH][FW];    // 消滅フラグ
}FIELD;

// ブロックのビットマップ番号
enum
{
    COLOR1,COLOR2,COLOR3,COLOR4,COLOR5,COLOR6,JAMA,KATA,NONE
};

ピースを作る

 ピースの形は次に示す1種類だけですが、ブロックの色はランダムです。この色ブロックはCOLOR1からCOLOR6までの最大6種類を用意していますが、実際に出現させる種類はg_colorsグローバル変数で調節します。

図2 ピース
図2 ピース
BYTE g_colors=6;    // 色ブロックの種類

// ピース作成
static void CreatePiece(PIECE *piece)
{
    for(BYTE y=0;y<PH;y++)
        for(BYTE x=0;x<PW;x++)
            piece->image[y][x]=NONE;

    piece->image[1][1]=(BYTE)(rand()%g_colors);
    piece->image[2][1]=(BYTE)(rand()%g_colors);

    piece->x=1; piece->y=-2;
}

いくつ繋がっているかを調べる

 このゲームの肝である、同じ色のブロックが一定数(例えば4個)以上つながったら消す、という処理の下請けとして、いくつ繋がっているのかを調べます。調べる方向は上下左右の四方です。これは再帰処理で簡単に書くことができます。

// 四方に隣接している同色ブロックの個数を調べる
// TETRA から追加
static void Count(FIELD *pField,int y,int x,DWORD *n)

{
    BYTE c=pField->image[y][x];    // 自分の色
    pField->image[y][x]=NONE;      // 調べたブロックは消す
    (*n)++;                        // 個数

    if(x+1<FW && pField->image[y][x+1]==c) Count(pField,y,x+1,n); // 右
    if(y+1<FH && pField->image[y+1][x]==c) Count(pField,y+1,x,n); // 下
    if(x-1>=0 && pField->image[y][x-1]==c) Count(pField,y,x-1,n); // 左
    if(y-1>=0 && pField->image[y-1][x]==c) Count(pField,y-1,x,n); // 上
}

 これだけです。でも本当にこれで大丈夫なの、と感じた方もいると思うので、図3に示す例で、同じ色のブロックを追跡する軌跡をたどってみましょう。○は空セル、●は色ブロック、は現在の座標です。

図3 同じ色のブロックを追跡する
図3 同じ色のブロックを追跡する
  1. 初期状態です。
  2. を起点に走査を開始します。右を調べると色ブロックがあるので移動します。
  3. 右を調べると色ブロックがあるので移動します。
  4. 右、下、左、上の順に調べますが、色ブロックがないので、再帰を抜けて、3.の座標に戻ります。そして下を調べると色ブロックがあるので移動します。
  5. 右下左上に色ブロックがないので、3.の座標に戻ります。そして左、上の順に調べますが、色ブロックがないので、2.の座標に戻ります。下、左の順に調べると、左に色ブロックがあるので移動します。

 以下同様です。

消滅フラグを立てる

 Count関数で一定数以上つながっていることがわかったら、そのセル座標をVanish関数に渡して、隣接しているブロックに消滅フラグを立てます。

 ところで、このゲームには色ブロックの他に、妨害ブロックというものがあります。妨害ブロックは、いくつ繋げても消えることが無いやっかいな存在です。JAMAKATAの2種類があり、これらが消える条件は、「一定数以上つながった色ブロックに隣接している」です。JAMAKATAの違いは後で説明します。

// 四方に隣接している同色ブロックと
// その四方に隣接している妨害ブロックに消滅フラグを立てる
static void Vanish(int y,int x)    // TETRA から追加
{
    if(g_field.vanish[y][x]) return;

    BYTE c;

    // 妨害ブロックに消滅フラグを立てる
    if(x+1<FW)
    {
        c=g_field.image[y][x+1];
        if(c==JAMA || c==KATA) g_field.vanish[y][x+1]=true;
    }
    if(y+1<FH)
    {
        c=g_field.image[y+1][x];
        if(c==JAMA || c==KATA) g_field.vanish[y+1][x]=true;
    }
    if(x-1>=0)
    {
        c=g_field.image[y][x-1];
        if(c==JAMA || c==KATA) g_field.vanish[y][x-1]=true;
    }
    if(y-1>=0)
    {
        c=g_field.image[y-1][x];
        if(c==JAMA || c==KATA) g_field.vanish[y-1][x]=true;
    }

    // 同色ブロックに消滅フラグを立てる
    g_field.vanish[y][x]=true;

    c=g_field.image[y][x];

    if(x+1<FW && g_field.image[y][x+1]==c) Vanish(y,x+1);
    if(y+1<FH && g_field.image[y+1][x]==c) Vanish(y+1,x);
    if(x-1>=0 && g_field.image[y][x-1]==c) Vanish(y,x-1);
    if(y-1>=0 && g_field.image[y-1][x]==c) Vanish(y-1,x);
}

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

バックナンバー

連載:ゲームプログラミング入門
All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5