ASP.NET Coreで対コンピュータ対戦できるぷよぷよをつくります。

「ぷよぷよ」(Puyo Puyo)とは?

「ぷよぷよ」(Puyo Puyo)は落ち物パズルゲームのひとつです。縦12マス×横6マスのフィールドに2つ1組で上から落下してくるぷよを置いていきます。ぷよが4個以上くっつくと消滅します。ぷよが消滅することでその上にあるぷよが落下し、連鎖が起きることがあります。連鎖をつくって高得点を狙うのがこのゲームの目的です。

以前、ぷよぷよに似たゲームを作成しましたが、これは対戦型のゲームではなくお一人様用でした。今回は対コンピュータ戦ができるものとしてつくります。

主な仕様

PCで操作する場合は左右の移動は←キーと→ キー、急速落下は↓キー、回転は左がZキー、右はXキーとします。

ではさっそく作成することにします。名前空間はPuyoとします。C#で書かれたソースはPages\Puyoに置きます。それからNET 6.0をエックスサーバーにインストールするで示している手順が完了していることを前提としています。

PuyoクラスとFallingPuyoクラスの定義

ぷよには種類があります。そこで列挙体を定義します。ぷよが配置されていないときはPuyoType.Noneです。ぷよは4種類とし、それとはべつに「おじゃまぷよ」をつくります。

「おじゃまぷよ」は通常のぷよと異なり4つ以上くっついても消滅しません。おじゃまぷよを消すためには通常の色ぷよを同色4つ以上つなげて消滅させなければなりません。そうすることで消滅する色ぷよの上下左右に隣接するおじゃまぷよも一緒に消すことができます。

フィールドは縦12マス×横6マスなので二次元配列を定義してそこに配置します。最初にフィールド上に固定されているPuyoクラスを示します。

固定されていない落下中のぷよの状態を管理するためのFallingPuyoクラスを定義します。Cloneメソッドは同じプロパティをもつ別のオブジェクトを返します。

Fieldクラスの定義

これまでMicrosoft.AspNetCore.SignalR.Hubクラスを継承して、そのなかに静的なGameクラスのリストを生成していましたが、対戦型のぷよぷよではフィールドが自分と相手の2つ必要です。そこでGameクラス(仮称)のなかで使われるオブジェクトを生成するためのFieldクラスを先に定義します。

以降は名前空間を省略して以下のように書きます。

定数部分

まず定数部分を示します。

コンストラクタの定義

ここでは二次元配列を初期化してなにもないぷよで埋め尽くしています。またIsPlayerプロパティはプレイヤーであればtrue、コンピュータであるならfalseです。

更新処理

データが更新されたときは他のクラスにそれを伝えることができるようにします。

Updateメソッドを呼び出すことでUpdatedイベントを発生させ、データが更新されたことを通知できるようにします。イベントハンドラの引数はすでにフィールド上に固定されているぷよと落下中のぷよ、そして落下中のおじゃまぷよです。

おじゃまぷよを降らせる

落下中の組ぷよが固定されたら新しい組ぷよを落下させるのですが、そのときにおじゃまぷよを降らせる必要がある場合はさきにおじゃまぷよを落下させます。30個以上のおじゃまぷよを降らせる場合は2回にわけます。その処理を示します。

おじゃまぷよの位置ですが、6個にみたない場合は6列のどれかを乱数で選んでその列におじゃまぷよを仮置きします。それ以上の場合は1段6個で端数は左の列から配置します。そのあと仮置きしたおじゃまぷよがすべて最上段より上になるように平行移動させます。この位置がおじゃまぷよの初期位置となります。

初期位置が決まったらおじゃまぷよを0.05秒おきに1段ずつ落下させます。これを繰り返してすべてのおじゃまぷよが下に移動できなくなったら落下処理は完了したことになります。

前述の引数ありの同名メソッドを呼び出しておじゃまぷよを降らせる処理を示します。降らせるおじゃまぷよの数は_nextOjamaPuyoCountsのなかに格納されています。

30個以上のおじゃまぷよを降らせる場合は2回にわけたいので、_nextOjamaPuyoCountsの最初の要素が30より大きい場合は引数を30にして引数ありのFallOjamaPuyoメソッドを呼び出して最初の要素の値を差し引きます。30以下の場合はその値を引数にしてFallOjamaPuyoメソッドを呼び出したあと_nextOjamaPuyoCountsの最初の要素を削除します。

おじゃまぷよが降るタイミングで効果音を鳴らしたい(個数で音を変えたい)のでOjamaFalledイベントを定義しています。

新しい色ぷよを落下させる

おじゃまぷよを落下させたら、次に新しい色ぷよを落下させる処理をおこないます。

ゲーム開始時の処理

ゲーム開始時や次のステージが開始されるまえのフィールドの初期化の処理を示します。

移動の処理

左右の移動の処理を示します。

落下中の色ぷよが壁にめり込んでしまう場合は移動できません。またすでにゲームオーバーになっている場合や_isAllowedMoveフラグがfalseの場合(急速落下中やステージとステージの間)は移動処理をおこないません。移動可能な場合は_fallingPuyoMainと_fallingPuyoSubのColプロパティを増減させます。

移動処理が行なわれた場合はtrueを返します。移動不能の場合はfalseを返します。

組ぷよを落下させる

組ぷよの片方(もう片方はこれを軸に周囲を回転する)を落下させる処理を示します。

すでに固定されたぷよが存在する場合でないなら落下させると判断します。この場合は_fallingPuyoMainのRowプロパティを1増加させてtrueを返します。できない場合は落下中のぷよをフィールド上に固定し、falseを返します。

組ぷよのもう片方(前述のぷよを軸に周囲を回転する)を落下させる処理を示します。

すでに固定されたぷよが存在する場合でないなら落下させると判断します。この場合は_fallingPuyoSubのRowプロパティを1増加させてtrueを返します。できない場合は落下中のぷよをフィールド上に固定し、falseを返します。

組ぷよをセットで1段だけ落下させる処理を示します。

組ぷよが存在しない場合はなにもしません。前述のFallMainメソッドとFallSubメソッドでぷよを下に移動させてみます。このとき組ぷよが縦に並んでいる場合は両方の位置関係に注意します。下にあるほうを先に移動させます。

両方ともfalseが返される場合は組ぷよのふたつが同時に着地したことを意味します。この場合はFixPuyoメソッド(後述)でフィールド上に落下していた組ぷよを固定します。

片方だけfalseを返した場合は片方だけ着地していることを意味します。この場合は組ぷよはちぎれて他方が着地するまで落下し続けます。この場合じゃ着地していないぷよが着地するまで(FallMainメソッドまたはFallSubメソッドがfalseを返すまで)呼び出し続けます。そしてFixPuyoメソッドを呼び出してフィールド上にぷよを固定します。

ぷよがちぎれて落下しているときにキー操作をすると動作がおかしくなるので、処理中は_isAllowedMoveフラグをfalseにしてキー操作できなくしています。

これはクラスの外部からFallメソッドを呼び出せるようにしたものです。ゲームオーバー時や_isAllowedMoveフラグがfalseの場合以外は上記のFallメソッドを呼び出します。

FallAllメソッドは急速落下です。組ぷよを落下できる位置まで一気に落下させます。この場合も処理中に他の移動操作ができてしまうと困るので_isAllowedMoveフラグで制御しています。

組ぷよを回転させる

組ぷよを回転させる処理を示します。_fallingPuyoMainと_fallingPuyoSubの位置関係から回転処理をしたときの_fallingPuyoSubの位置を取得して、その位置に移動可能であれば回転処理をしています。組ぷよが存在しない場合や_fallingPuyoSubの移動先に固定されたぷよが存在する場合、やフィールドの外にでてしまう場合、ゲームオーバー時や_isAllowedMoveがfalseのはfalseを返します。

組ぷよをフィールド上に固定する

組ぷよをフィールド上に固定したら4つ以上つながっている場合はぷよを消す処理と連鎖するときは連鎖する処理をおこなわなければなりません。最初に固定されたぷよが4つ以上つながっているかどうかを調べる処理を示します。

IsSpaceメソッドはそこはぷよが存在しない、または存在しないとみなせる部分かを調べるためのものです。その地点がPuyoType.NoneであったりRensaプロパティが1以上(これから消えようとしているぷよが存在する)の場合はtrueを返します。

Checkメソッドでは引数で渡された地点の上下左右の同じ色の部分を取得し、さらにそれと上下左右の同じ色の部分を取得する処理を繰り返しています。このとき一度調べた場所を何度も調査して無限ループにならないようにチェック済みの部分を二重チェックしないようにしています。またIsSpaceメソッドがtrueを返すPuyo(ぷよが存在しないか、すでに消去するぷよとしてマークされている地点)やTypeプロパティがPuyoType.OjamaのPuyoは対象外です。

取得されたPuyoの数が4つ以上であるならそれは消去されるぷよです。この場合はそのPuyoのRensaプロパティに_rensaCountをセットしてそのリストを返します。

4つ以上同じ色でつながっているぷよを消す処理を示します。

DownByDeleteメソッドは各ぷよの下が空洞の場合、ぷよを1段下げる処理をおこないます。

前述のCheckメソッドで消えるPuyoを取得します。これが存在しない場合はなにもしないでfalseを返します。

落下し終わった組ぷよをフィールド上に固定する処理を示します。

フィールド変数 _addScoreを0で、_rensaCountを1で初期化して、前述のCheckDeleteメソッドをfalseを返すまで呼び出し続けます。CheckDeleteメソッドが true を返す限り連鎖が進行していることを意味するので、trueを返した場合は_rensaCountをインクリメントしてCheckDeleteメソッドを呼び出す処理を繰り返します。処理がおわったら新しい組ぷよを落下させる処理をおこないます。

またこの処理が終了したときに_addScoreが0よりも大きな値になっている場合はスコアが変動することを意味するのでイベントを発生させます。

敵側におじゃまぷよを送り込む

得点によっては敵側におじゃまぷよを送り込む処理が必要になります。送り込まれるおじゃまぷよの数は追加された点数によって決まります。計算の結果求められた数のおじゃまぷよが、敵側が現在落下させているぷよが固定されたあとに出現するぷよが固定されたときに降ってきます(1ターン分の猶予がある)。

そこで_nextOjamaPuyoCountsが空の場合は0を格納したあと、その個数を格納します。要素数が1の場合はその次に、要素数が2のときは最後の要素の値に加算します。要素数が3以上になることはないはずですが、その場合は3番目以降を要素数を2にして2番目の要素を調整します。

点数が加算されたときに敵側からすでに送り込まれようとしているおじゃまぷよがある場合は相殺の処理がおこなわれます。その処理を示します。

これから落下しようとするおじゃまぷよがある場合はその数を表示させます。その数を取得する処理を示します。