爆弾の配置が完了したので、その他のパネルには「周りにある爆弾の個数」を入れなければいけません。
基本的な考え方を図で示します。
フィールのの左上が↑のような状態だったとします。
☆の部分をチェックしようとした場合、
赤い枠の部分(パネル8個分)を調べ、爆弾の個数を数えます。
赤い枠の部分には1個の爆弾がありましたので、フィールドに1を格納します。
次に隣のパネルを調べます。
赤い枠の部分の爆弾の個数を数えます。
2個の爆弾があったので、フィールドに2を格納します。
さらに隣のパネルを調べます。
赤い枠の部分の爆弾の個数を数えます。
3個の爆弾があったので、フィールドに3を格納します。
これを、爆弾以外の全てのパネルで行います。
当然、↓のようにフィールドの端を調べる場合もあります。
この場合は、↓のようにフィールドをはみ出さないように範囲チェックも行う必要があります。
爆弾以外のパネルに周りの爆弾の個数をセットしなければゲームは始められません。
これから作る関数も「Initialize関数」から呼ばれる、privateメンバ関数として作ります。
「Mine.h」を開き、プロトタイプ宣言を追加します。
class Mine { public: Mine(); ~Mine(); //フィールドの初期化 void Initialize(const int row, const int col, const int bombMax); //フィールドの描画 void Draw(Engine *pEngine); private: const int ADJUST_X; //調整用左座標 const int ADJUST_Y; //調整用上座標 const int PANEL_SOUR_X; //パネルの転送元左座標 const int PANEL_SOUR_Y; //パネルの転送元上座標 const int PANEL_WIDTH; //パネルの幅 const int PANEL_HEIGHT; //パネルの高さ enum { NONE, //爆弾なし BOMB = 9, //爆弾 }; struct Field { int m_number; //周りにある爆弾の数(0〜8)、爆弾は9 bool m_bOpen; //オープンフラグ }; Field *m_pField; //フィールド int m_row; //縦方向のパネル数 int m_col; //横方向のパネル数 int m_bombMax; //爆弾の最大数 RECT m_sour; RECT m_dest; //フィールドのクリア void ClearField(); //爆弾のセット void SetBomb(); //爆弾の個数のセット void SetNumber(); }; |
「Mine.cpp」を開き、「SetBomb関数」の下に「SetNumber関数」の本体を追加します。
//爆弾の個数のセット
void Mine::SetNumber()
{
const int FIELD_MAX = m_row * m_col;
for (int i = 0; i < FIELD_MAX; i++) {
if (m_pField[i].m_number != BOMB) {
}
}
}
|
この関数はまだ中身が完成していません、複雑になるので少しずつ作ります。
今の状態は、全てのパネルを調べ「爆弾ではない」パネルを選んでいます。
このループの中で、選ばれたパネルの周りにある爆弾の個数を数えなければなりません。
下の表の「黒い文字」の部分が調べるべき領域です。
左上 上 右上 左 基準 右 左下 下 右下
このように2次元配列的に書くと分かりやすいのですが、フィールドは1次元配列です。
1次元配列を2次元配列のように使う方法は説明しましたので、基準となる添え字を2次元に変換します。
//爆弾の個数のセット void Mine::SetNumber() { const int FIELD_MAX = m_row * m_col; for (int i = 0; i < FIELD_MAX; i++) { if (m_pField[i].m_number != BOMB) { int x = i % m_col; int y = i / m_col; } } } |
このx、yを使って表を書き直します。
x - 1, y - 1 x, y - 1 x + 1, y - 1 x - 1, y x, y x + 1, y x - 1, y + 1 x, y + 1 x + 1, y + 1
基準となる要素から、それぞれx、yに±1する事で回りの座標を示しています。
もう1段階書き換えてみます。
x - 1, y - 1 x + 0, y - 1 x + 1, y - 1 x - 1, y + 0 x + 0, y + 0 x + 1, y + 0 x - 1, y + 1 x + 0, y + 1 x + 1, y + 1
見た目が変わりましたが、計算結果は変わりません。
さらにもう1段階、式を変えてみます。
x + -1, y + -1 x + 0, y + -1 x + 1, y + -1 x + -1, y + 0 x + 0, y + 0 x + 1, y + 0 x + -1, y + 1 x + 0, y + 1 x + 1, y + 1
文章にするのは難しいですが、x方向は「−1、0、1」を加算する事で左、中央、右と位置を変化させています。
y方向も「−1、0、1」を加算する事で上、中央、下と位置を変化させています。
これをプログラムにしてみると、
for (int i = -1; i < 2; i++) { for (int j = -1; j < 2; j++) { int wx = x + j; int wy = y + i; } } |
このような感じになります。
xを5、yを3としてトレースしてみると、
i j wx wy -1 -1 4 2 -1 0 4 3 -1 1 4 4 0 -1 5 2 0 0 5 3 0 1 5 4 1 -1 6 2 1 0 6 3 1 1 6 4
このwx、wyが「5、3」を中心とした周りの要素を指してくれます。
では、この仕組みを「SetNumber関数」に組み込みたいと思います。
が、今回の「マインスイーパー」は関数分割を意識して作ろうとしています。
この「周りの爆弾の個数を調べる」関数は別に作って、「SetNumber関数」から呼び出す事にします。
「Mine.h」を開き、プロトタイプ宣言を追加します。
class Mine { public: Mine(); ~Mine(); //フィールドの初期化 void Initialize(const int row, const int col, const int bombMax); //フィールドの描画 void Draw(Engine *pEngine); private: const int ADJUST_X; //調整用左座標 const int ADJUST_Y; //調整用上座標 const int PANEL_SOUR_X; //パネルの転送元左座標 const int PANEL_SOUR_Y; //パネルの転送元上座標 const int PANEL_WIDTH; //パネルの幅 const int PANEL_HEIGHT; //パネルの高さ enum { NONE, //爆弾なし BOMB = 9, //爆弾 }; struct Field { int m_number; //周りにある爆弾の数(0〜8)、爆弾は9 bool m_bOpen; //オープンフラグ }; Field *m_pField; //フィールド int m_row; //縦方向のパネル数 int m_col; //横方向のパネル数 int m_bombMax; //爆弾の最大数 RECT m_sour; RECT m_dest; //フィールドのクリア void ClearField(); //爆弾のセット void SetBomb(); //爆弾の個数のセット void SetNumber(); //周りにある爆弾の数を数える int GetAroundBombNum(const int x, const int y); }; |
引数として、基準となる座標を受け取ります。
戻り値は「爆弾の個数」です。
「Mine.cpp」を開き、「SetNumber関数」の下に「GetAroundBombNum関数」の本体を追加しましょう。
//周りにある爆弾の数を数える
int Mine::GetAroundBombNum(const int x, const int y)
{
int counter = 0;
for (int i = -1; i < 2; i++) {
for (int j = -1; j < 2; j++) {
int wx = x + j;
int wy = y + i;
if (wx >= 0 && wx < m_col && wy >= 0 && wy < m_row) {
if (m_pField[wy * m_col + wx].m_number == BOMB) {
counter++;
}
}
}
}
return counter;
}
|
きちんと範囲チェックも行っています。
この関数を「SetNumber関数」から呼び出します。
//爆弾の個数のセット void Mine::SetNumber() { const int FIELD_MAX = m_row * m_col; for (int i = 0; i < FIELD_MAX; i++) { if (m_pField[i].m_number != BOMB) { int x = i % m_col; int y = i / m_col; m_pField[i].m_number = GetAroundBombNum(x, y); } } } |
「GetAroundBombNum関数」の戻り値をフィールドの該当箇所に代入しています。
これで、爆弾の個数をセットするための仕組みが出来上がりました。
「Mine.cpp」の「Initialize関数」に「SetNumber関数」の呼び出しを追加しましょう。
//フィールドの初期化 void Mine::Initialize(const int row, const int col, const int bombMax) { m_row = row; m_col = col; m_pField = new Field[m_row * m_col]; m_bombMax = bombMax; ClearField(); SetBomb(); SetNumber(); } |
それでは実行してみましょう。
<実行結果 クライアント領域のみ>
どうでしょう、上手くいっていますか?
間違っていないか、よく確かめてから次へ行きましょう。