【中級者向け】第六回 メモ帳だけでゲーム作ってみた〈ぷよぷよ編〉

2020年10月13日

今回も、前回までと同様にパソコンに標準装備されているメモ帳だけを使って、あの有名なパズルゲーム「ぷよぷよ」を作っていきたいと思います。

前回の記事をまだ読んでいない方は、是非そちらを先に読んでください!


今回作るもの

前回は、ぷよを置いたときに下に落ちるようなコードを組みましたね。

  1. ぷよぷよのフィールドを作ろう!
  2. ぷよを上から落としてみよう!
  3. ぷよを操作できるようにしてみよう!
  4. ぷよを回転できるようにしてみよう!
  5. ぷよが下に落ちるようにしよう! ←前回
  6. 連鎖をできるようにしよう! ←今回
  7. 次のぷよが何なのかわかるようにしてみよう!

そして今回は、とうとうぷよぷよの醍醐味の「連鎖」のコードを組んでいきたいと思います!

完成イメージは以下の通りです。

連鎖ができるようになると、遊べるようになって面白くなってきますよ~!

ぜひ最後まで読んで、連鎖できるコードを頑張って組んでいきましょう!


コードを書いていこう!

今回も前回に引き続き、メモ帳にコードを書いていきます!

今回は、連鎖のコードだけを書きますが、それでも長くなります。

また、適時図などを用いて解説しますが、それでも難しいと感じるかもしれません。

そんな時は「そんなもんなんだ」程度で進めて、進めて大丈夫です!

自分のペースでしっかり進めていきましょう~!


変数を作る

まず初めに、今回使う2つの変数を作っていきましょう。

…
let rensa_finish = true;

let renketsu = 0;

let flag_field = [
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0]
];

「renketsu」というのは、ぷよが何個連結しているかをチェックするために合って、これが4以上の時にそのぷよを消すという処理を書いていきます。

そして二つ目の「flag_field」には、消すぷよを「2」、消さないぷよを「1」としてここに記憶させたいと思います。

変数は使ってみないとわかりずらいと思うので、ここではささっと書き写しましょう!


コードを書く前に考える

今回の連鎖のコードをいきなり書くというのは難しいです。

なので、いきなり書いていく前にまずはどのようなコードにするかを解説したいと思います。

まず下のようなぷよの配置があったとしましょう。

マス目内の「0」は「flag_field」の「0」です

forループをうまく使って、左上からぷよがどれだけ連結しているかを調べていきます。

先ほども少し書きましたが、4つ以上つながっているぷよがある場所(=ぷよを消す場所)には「2」を、消さない場所には「1」を入れていきます。

なので、左上からチェックしていくと、まず「1」が入っていきますね。

こんな感じ

そして、これがぷよに当たると、まず初めにその場所に「3」を入れます。

「3」を入れて、

・もし連結が4つ以上だったら「2」に入れ替えて消すようにする
・もし連結が3つ以下だったら「1」に入れ替えて残すようにする

という風にします。

なので上の図では次にぷよがどれだけ連結しているかを調べます。

ぷよがどれだけ連結しているか調べる方法は次の項目で書くので、今は省略します。

上の図では5つつながっているので、すべてに「3」を入れた後に「2」に変えます。

こんな感じ

残りのマスも同じように調べていきますが、すでに「0」以外の数字が入っているところはもう調べなくていいです。

なので、if文を使って「0」の時だけ調べるようにすればいいのもわかりますね!

このような形で、右下まで調べることができれば、「2」が入っている部分のぷよを消して、「1」の部分はそのままにすれば1連鎖目の完成です。

もし1連鎖目で1つもぷよが消えなかったら、(=連鎖になっていないとき)前回作った「rensa_finish」という変数を「true」に変えるということを忘れないでおきましょう。

ぷよがいくつくっついているかを調べるコードの解説は以上です。

少し難しいのでわかりずらいかもしれませんが、コードを実際に組んだらわかるかもしれないので、次に進めましょう!


ぷよがいくつくっついているかを判別するコードを考える

では次に先ほど省略したぷよがいくつくっついているかを判別するコードを考えていきましょう。

まずは下の図を見てみましょう!

真ん中(2, 2)の部分をはじめとします

ぷよの連結をチェックするには、そのぷよの隣の上下左右のマスをチェックしないとだめです。

そして隣のぷよの上下左右もまたチェックしなければいけません。

そしてまたその隣のぷよの上下左右をチェック…

とぷよがつながっている限りだいぶ続きますね。

一応わかりやすくまとめると、


初めのマスの色を調べる


上下左右を調べる


上のマスが同じ色なら、左右と上を調べる(下はすでに調べている)
下のマスが同じ色なら、左右と下を調べる(上はすでに調べている)
左のマスが同じ色なら、上下と左を調べる(右はすでに調べている)
右のマスが同じ色なら、上下と右を調べる(左はすでに調べている)


③を繰り返す

これをコードにしようと思うと全部のマスを確認しなければならなくて面倒くさいです。

ですがここで「再起処理」というものを使って簡単にまとめたいと思います。

上を調べるを「checkUp」、下を調べるを「checkDown」、左を調べるを「checkLeft」、右を調べるを「checkRight」とします。

そして「再起処理」を用いて連結を確認するコードはまとめると下のようになります。

function checkUp() {
  ひとつ前のマスと同じ色か調べる処理を書く

  checkUp();
  checkLeft();
  checkRight();
}

function checkDown() {
  ひとつ前のマスと同じ色か調べる処理を書く

  checkDown();
  checkLeft();
  checkRight();
}

function checkLeft() {
  ひとつ前のマスと同じ色か調べる処理を書く

  checkUp();
  checkDown();
  checkRight();
}

function checkRight() {
  ひとつ前のマスと同じ色か調べる処理を書く

  checkUp();
  checkDown();
  checkLeft();
}


連結を調べる実際のコードはここから
checkUp();
checkDown();
checkLeft();
checkRight();

だいぶ簡略化しましたが、このように「checkUp」の関数の中に「checkUp」を入れることができて、自分自身を呼び出すことができます。

これが「再起処理」と呼ばれるものです。

ここのコードが一番難しいと思います。

なので、完全に理解する必要はありません!
「そんなものもあるんだ」ぐらいで覚えるだけでいいと思います!


ぷよをチェックするコードを書く

というわけで解説がすごく長くなりましたが、ここから実際にコードを書いていきます。

一応もう一度書いておきますが、

flag_fieldの中の数字が

・0だったら、まだチェックしていない場所
・1だったら、ぷよを消さない場所
・2だったら、ぷよを消す場所
・3だったら、ぷよを消す処理の途中の場所

といった感じで入れていきます。

さて、連鎖によってぷよを消す処理を書く場所は「erasePuyo」という関数の中です。

まずは、左上から調べていくので、forループをうまく使ったうえで、そのマスが何もない(=fieldが0)の時は消さないので「0」を入れていきましょう。

function erasePuyo() {
  for (let i = 0; i < 12; i++) {
    for (let j = 0; j < 6; j++) {
      if (flag_field[i][j] == 0) {
        if (field[i][j] == 0) {
          flag_field[i][j] = 1;
        }
      }
    }
  }
}

次に、調べたマスが「0」以外の時(=ぷよが置かれているとき)には、そのマスのflag_fieldに「3」をいれて、そのマスの色を取得して、最後にぷよの連結数を1にしましょう!

function erasePuyo() {
  for (let i = 0; i < 12; i++) {
    for (let j = 0; j < 6; j++) {
      if (flag_field[i][j] == 0) {
        if (field[i][j] == 0) {
          flag_field[i][j] = 1;
        } else {
          renketsu = 1;
          flag_field[i][j] = 3;
          let check_color = field[i][j];
        }
      }
    }
  }
}

ぷよの連結の処理はあとで書くので、少し改行で開けておきます。

次にぷよがの連結が4以上の時に、flag_fieldの「3」をすべて「2」に変えて、
ぷよの連結が4未満の時に、flag_fieldの「3」をすべて「1」に変えるようにします。

function erasePuyo() {
  for (let i = 0; i < 12; i++) {
    for (let j = 0; j < 6; j++) {
      if (flag_field[i][j] == 0) {
        if (field[i][j] == 0) {
          flag_field[i][j] = 1;
        } else {
          renketsu = 1;
          flag_field[i][j] = 3;
          let check_color = field[i][j];





          if (renketsu >= 4) {
            for (let i = 0; i < 12; i++) {
              for (let j = 0; j < 6; j++) {
                if (flag_field[i][j] == 3) {
                  flag_field[i][j] = 2;
                }
              }
            }
            rensa_finish = false;
          } else {
            for (let i = 0; i < 12; i++) {
              for (let j = 0; j < 6; j++) {
                if (flag_field[i][j] == 3) {
                  flag_field[i][j] = 1;
                }
              }
            }
          }
        }
      }
    }
  }
}

*連結数が4以上の時は連鎖があるということなので、「rensa_finish」(=連鎖がないときに「true」にする変数)を「false」に変えましょう。

さて、最後にflag_fieldの「2」の部分は消すので、fleldのその場所を「0」に変えましょう。

function erasePuyo() {
  for (let i = 0; i < 12; i++) {
    for (let j = 0; j < 6; j++) {
      if (flag_field[i][j] == 0) {
        if (field[i][j] == 0) {
          flag_field[i][j] = 1;
        } else {
          renketsu = 1;
          flag_field[i][j] = 3;
          let check_color = field[i][j];





          if (renketsu >= 4) {
            for (let i = 0; i < 12; i++) {
              for (let j = 0; j < 6; j++) {
                if (flag_field[i][j] == 3) {
                  flag_field[i][j] = 2;
                }
              }
            }
            rensa_finish = false;
          } else {
            for (let i = 0; i < 12; i++) {
              for (let j = 0; j < 6; j++) {
                if (flag_field[i][j] == 3) {
                  flag_field[i][j] = 1;
                }
              }
            }
          }
        }
      }
    }
  }
  for (let i = 0; i < 12; i++) {
    for (let j = 0; j < 6; j++) {
      if (flag_field[i][j] == 2) {
        field[i][j] = 0;
      }
    }
  }
}

「{}」がたくさんあって本当に間違いやすいと思うので気を付けましょう!

最後に、

・最初にflag_fieldをすべて「0」にする
・rensa_finishを「true」にする
・「checkUp」などを書き足す

この3つを追加します。

function erasePuyo() {
  rensa_finish = true;

  for (let i = 0; i < 12; i++) {
    for (let j = 0; j < 6; j++) {
      flag_field[i][j] = 0;
    }
  }

  for (let i = 0; i < 12; i++) {
    for (let j = 0; j < 6; j++) {
      if (flag_field[i][j] == 0) {
        if (field[i][j] == 0) {
          flag_field[i][j] = 1;
        } else {
          renketsu = 1;
          flag_field[i][j] = 3;
          let check_color = field[i][j];

          checkUp(j, i - 1, check_color);
          checkDown(j, i + 1, check_color);
          checkLeft(j - 1, i, check_color);
          checkRight(j + 1, i, check_color);

          if (renketsu >= 4) {
            for (let i = 0; i < 12; i++) {
              for (let j = 0; j < 6; j++) {
                if (flag_field[i][j] == 3) {
                  flag_field[i][j] = 2;
                }
              }
            }
            rensa_finish = false;
          } else {
            for (let i = 0; i < 12; i++) {
              for (let j = 0; j < 6; j++) {
                if (flag_field[i][j] == 3) {
                  flag_field[i][j] = 1;
                }
              }
            }
          }
        }
      }
    }
  }

  for (let i = 0; i < 12; i++) {
    for (let j = 0; j < 6; j++) {
      if (flag_field[i][j] == 2) {
        field[i][j] = 0;
      }
    }
  }
}

これで「erasePuyo」のコードは完成ですね!

あとは、「checkUp」などの関数を作っていきます。


ぷよの連結をチェックするコードを書く

というわけで、最後に「checkUp」などの4つの関数を作っていきましょう。

先ほどコードを書いたときに気づいた方もいるかもしれませんが、引数が3つ設定されていますね。

checkUp(調べるx座標, 調べるy座標, ぷよの色);

といった感じで引数を設定しました。

まずは関数を書いていきましょう。

function erasePuyo(){
…省略…
}

function checkUp(check_x, check_y, check_color) {

}

function checkDown(check_x, check_y, check_color) {

}

function checkLeft(check_x, check_y, check_color) {

}

function checkRight(check_x, check_y, check_color) {

}

function nextPuyo() {
…

これまでの関数を含めてたくさんの関数がありますね。

ここでも「{}」を忘れないように気を付けましょう。

では最初に「checkUp」の処理を考えていきましょう。

まずはぷよを動かしたときのコードと同じで、調べる部分が画面外だといけないので、if文を1つ書きましょう。

function checkUp(check_x, check_y, check_color) {
  if (check_y >= 0) {

  }
}

上をチェックするので、画面外かどうかは上にはみ出ていないかだけを調べればOKですね。

では次にその場所がすでに調べられていないかをチェックします。

これはflag_fieldのその場所が「0」だったらいいですね。

なのでこのようになります。

function checkUp(check_x, check_y, check_color) {
  if (check_y >= 0) {
    if (flag_field[check_y][check_x] == 0) {

    }
  }
}

そして次に、その調べるマスが元のマスの色と同じなら、ぷよの連結数を指す「renketsusu」を1つ増やして、flag_fieldのその場所を「3」にしましょう。

function checkUp(check_x, check_y, check_color) {
  if (check_y >= 0) {
    if (flag_field[check_y][check_x] == 0) {
      if (field[check_y][check_x] == check_color) {
        renketsu++;
        flag_field[check_y][check_x] = 3;
      }
    }
  }
}

最後に、次に上と左右を調べないといけないので、

function checkUp(check_x, check_y, check_color) {
  if (check_y >= 0) {
    if (flag_field[check_y][check_x] == 0) {
      if (field[check_y][check_x] == check_color) {
        renketsu++;
        flag_field[check_y][check_x] = 3;

        checkUp(check_x, check_y - 1, check_color);
        checkLeft(check_x - 1, check_y, check_color);
        checkRight(check_x + 1, check_y, check_color);
      }
    }
  }
}

このようになりますね!

これで「checkUp」は完成です!

あとはこれをほかの3つにも対応させましょう。

いきなりコードを書くので、少し考えたい方はスクロールを止めて考えましょう!

function checkUp(check_x, check_y, check_color) {
  if (check_y >= 0) {
    if (flag_field[check_y][check_x] == 0) {
      if (field[check_y][check_x] == check_color) {
        renketsu++;
        flag_field[check_y][check_x] = 3;

        checkUp(check_x, check_y - 1, check_color);
        checkLeft(check_x - 1, check_y, check_color);
        checkRight(check_x + 1, check_y, check_color);
      }
    }
  }
}

function checkDown(check_x, check_y, check_color) {
  if (check_y <= 11) {
    if (flag_field[check_y][check_x] == 0) {
      if (field[check_y][check_x] == check_color) {
        renketsu++;
        flag_field[check_y][check_x] = 3;

        checkDown(check_x, check_y + 1, check_color);
        checkLeft(check_x - 1, check_y, check_color);
        checkRight(check_x + 1, check_y, check_color);
      }
    }
  }
}

function checkLeft(check_x, check_y, check_color) {
  if (check_x >= 0) {
    if (flag_field[check_y][check_x] == 0) {
      if (field[check_y][check_x] == check_color) {
        renketsu++;
        flag_field[check_y][check_x] = 3;

        checkUp(check_x, check_y - 1, check_color);
        checkDown(check_x, check_y + 1, check_color);
        checkLeft(check_x - 1, check_y, check_color);
      }
    }
  }
}

function checkRight(check_x, check_y, check_color) {
  if (check_x <= 11) {
    if (flag_field[check_y][check_x] == 0) {
      if (field[check_y][check_x] == check_color) {
        renketsu++;
        flag_field[check_y][check_x] = 3;

        checkUp(check_x, check_y - 1, check_color);
        checkDown(check_x, check_y + 1, check_color);
        checkRight(check_x + 1, check_y, check_color);
      }
    }
  }
}

長かったですが、これで完成です!

これで連鎖ができるようになったはずです!

本当に長かったですね…
お疲れ様です!


うまくいかなかった方向け

うまくいかなかった方は、まずエラーが出ていないか確認しましょう!

エラーの確認方法については、別の記事で詳しくまとめたので、ぜひそちらを参考にしてください!

【初心者向け】 JavaScriptの エラーの直し方

自分が作ったコードがうまく動かないってこと、プログラミングで本当によく起きますよね?私なんて、10回トライして9回エラーでうまくいかないなんてこともよくあります。 でもそんなときどこが間違っているか、自分の目ですべて探す […]

それでもうまくいかなければ、下のサンプルコードをダウンロードして、自分のコードを比較してみましょう!


今回はここまで!

というわけで第六回はここまでとしたいと思います!

説明もコードも非常に長くなってしまいましたが、無事連鎖ができるようになりましたね!

次回は次のぷよが見える「ネクスト」というシステムというものを入れて、もっと遊びやすくします!

また、次が最終回ですね!

ここまで六回までやってきて、次が最後です!
次も頑張っていきましょう!

というわけで長くなってしまいましたが、

最後まで読んでいただきありがとうございました!

ご感想などあればコメントやTwitterにどしどしお寄せください!

第七回はこちらから!