C++でつくったテトリスの完成品

今回はTetrisクラスの実装を行ないます。

ヘッダーファイル

Tetris.hを示します。privateとかpublicを繰り返して見た目は最悪ですが、このような場合はどうすればいいのでしょうか? publicならpublicでまとめる、メンバ変数とメンバ関数をわけて見やすくするなどの考え方がありますが、機能でわけることにしました。そのためprivateとpublicが繰り返し出現するという美しくない書き方になってしまいました。

初期化の処理

コンストラクタを示します。

LeftTopBlockPositionに座標をセットしていますが、これはウィンドウのなかのブロックが表示される部分の左上の座標を(0, 0)ではなく余白を持たせたかったのでこのようにしました。

そのあとInit関数を呼び出してゲームの初期化をしています。

Tetris.cpp

初期化の処理です。ここでやっていることは着地して固定されているブロックの位置を格納する二次元配列をNULLで埋めています。ゲームをしおわってもう一度始めたい場合は前のゲームのデータが残っているのでdeleteしてからNULLで埋めています。

次にm_DeleteLineNumbersですが、ここには横一列にそろった行の番号が入ります。テトリスであれば最大4行を同時に消すことができます。最初は4つとも-1で初期化します。ここに0以上の整数が格納されている場合はラインが消去されたことを意味しています。この場合はタイマーで時間になってもテトリミノを落下させる処理はしません。

実はこんなことをしなくてもラインが消えたらすぐに上の段にあるブロックを下げて空間を詰める処理をしてしまえばいいのですが、ラインが消えたあと一瞬ブロックが浮かんでいるような状態を作りたかったのでこのようにしています。

最後に乱数の初期化をおこないます。これは次におちてくるテトリミノの種類をランダムで決めるための処理です。これをやらないと毎回同じ順番で前回実行時と同じテトリミノが落ちてきて、ゲームが単調になってしまいます。

Tetris.cpp

ResetTimer関数はタイマーをリセットするためのものです。テトリスではテトリミノを移動させたり回転させると一時的にその段で止めることができます。これを無制限にできるようにしてしまうとゲームとしての難易度が下がってしまうので制限値があるのですが、ここでは無制限にできるようにしています。この点はあとで考えます。まずは完成させることを目指します。

描画処理

描画処理をする前に描画対象をどの位置に描画するのかを決めなければなりません。また表示するときに使う色も指定しなければなりません。

まずは表示色取得関連の関数を定義します。

表示色を取得する

テトリミノの種類で表示色は決まっています。そこでガイドラインで定められているとおりの色を指定します。Color::Aquaってどんな色なのかはそのあとの関数で取得します。

GetColor関数はColor列挙体からCOLORREF型でデータを取得する処理をおこないます。CSSで赤だと#FF0000と書きますが、COLORREFの場合は赤が一番右側です。

落下中のテトロミノの位置を取得する

以下は落下中のテトロミノの位置・状態を保存するメンバ変数です。

では落下中のテトロミノの位置を取得するための関数を定義していきます。

GetTetriminosPosition関数は現在落下中のテトロミノの種類と向きからTetriminoPositionの配列を取得します。IミノであればGetInitBlocksPositionI関数を呼び出してテトリミノの左上の行と列が0,0である場合の4つのブロックの位置を取得します。そのあと回転処理をおこない、現在のm_TetriminoPositionXとm_TetriminoPositionYへ平行移動させることでTetriminoPositionの配列を取得しています。

各テトリミノの初期のTetriminoPositionの配列を取得する処理を示します。テトリミノは4つのブロックで構成されているので第二引数は常に4です。念のため4であることをチェックして、そうでない場合は何もしません。

GetInitBlocksPositionX関数でXミノの初期のTetriminoPosition配列を得ることができたらこれに回転処理を加えます。前述のとおり、テトリミノには3×3タイプと4×4タイプがあるので分けて考えます。

実際に描画する

描画する位置であるTetriminoPosition配列を取得できたら描画の処理をおこないます。

描画の対象は周囲の灰色のブロックと落下中のテトロミノ、そしてすでに着地して固定されているブロックです。本当はスコアとか次に落ちてくるテトリミノも表示させたいのですが、まずは完成を急ぎます。

DrawBlock関数は列と行と色を指定してブロックを描画するためのものです。輪郭は黒で内部を引数で渡された色で塗りつぶします。メンバ関数 GetColorをつかってCOLORREFを取得しています。

DrawOutsideBlocks関数は周囲の灰色のブロックを描画する処理をおこないます。

DrawTetrimino関数は落下中のテトロミノを描画する処理をおこないます。

DrawFixedBlock関数は着地し固定されているブロックを描画する処理をおこないます。

GetRect関数は再描画の対象になる矩形を取得します。ここでは単純に現在落下中のテトリミノの周辺を切り取ってそのまま返しています。ラインが消えるときでなければ再描画の対象になるのは現在落下中のテトリミノの周辺だけです。

移動関連の処理

けっこうな字数になってきたので次回に回したいのですが、もうちょっと頑張ります。移動関連の処理をおこないます。

移動できるのか確認する

移動させるときはm_TetriminoPositionX と m_TetriminoPositionYを増減させればいいのですが、そのまえに本当に移動させることができるのかどうか確認しなければなりません。

CanMove関数は引数分XY方向に移動させることができるのかどうかを調べます。移動させたら外枠に重なってしまったりすでに固定されているブロックと重なってしまう場合は移動できません。

左右の移動

左右に移動させる処理を示します。移動できる場合はm_TetriminoPositionXとm_TetriminoPositionYを増減させるとともにタイマーをリセットします。

下に移動させる

下に移動させる場合、これ以上下に移動できない場合があります。その場合はテトロミノを落下中の状態から固定された状態に変更します。

それから他にもすることがあって固定されたときに横一列にそろっているかどうかの確認作業、横一列にそろっている場合はラインを消去して上に積まれているブロックを下に1段落とさなければなりません。そして落下中のテトロミノを固定した場合は次のテトリミノを上から落とさなければなりません。

テトリミノを固定する処理はFixBlocks関数、ラインが消去されるかどうかはCheckLine関数でおこない、新しいテトリミノをつくる処理はNewTetrimino関数でおこないます。

FixBlocks関数はテトリミノを固定する処理をおこないます。

NewTetrimino関数は新しいテトリミノをつくる処理をおこないます。新しく作るというよりはメンバ変数をリセットするという表現のほうが正しいかもしれません。Positionを(0, 0)に戻して新しいタイプのテトリミノをセットします。

SetNewBlockType関数は新しいタイプのテトリミノを乱数で決定してm_CurTetriminoTypeにセットします。この単に乱数で決めるというやり方はテトリスのガイドラインに則していません。いまはちょっとスルーさせてください。あとで修正します。

ブロックが消えたときの処理

CheckLine関数はブロックが横一列にそろったかを調べます。そしてそろっている場合はブロックを消去して上の段にあるブロックを下に下げます。

DeleteLines関数は引数で渡された配列のなかに-1でない値があった場合、その行にあるブロックを削除します。このあと上記の関数のなかでInvalidateRect関数が呼び出されるので、この部分は約0.1秒間、空白となり、これより上にあるブロックが宙に浮いているように見えます。

DeleteLines関数でセットされたタイマーによってWM_TIMERメッセージが送られてきた場合はMainWindowクラスのWinProc関数内でOnTimer関数が実行され、これによってDropLines関数が呼び出されます。

MoveDown関数が実行されたときにライン消去の処理がおこなわれている場合、ライン消去の処理を優先しMoveDown関数としては何もしません。ライン消去の処理がおこなわれているかどうかを調べる関数を示します。