こんにちは、あんみんどうふです。
今回はテトリスを動かすためのフィールドを作ります。前回の記事はこちら↓
全編見たい方は「新人プログラマーはテトリスを作れるのか?」というタグにまとめてますのでそちらからどうぞ。
開発環境
開発ソフトは「Visual Studio 2019」を使います。
フォームアプリケーションで作りたいので、言語は「C#.NET」です。私が最も得意で好きな言語です。あ、過度な期待はしないでください・・・。
フレームワークは詳しく知らないのでとりあえず「.NET Core 3.1」を使います。動けばよしということで。
ちなみにテトリスには予め決められた仕様が存在し、商用で作られるテトリスはすべて公式のガイドラインに則ったもので統一されています。
私は個人で作るのでルールなんてどうだっていいのですが、公式に寄せて作りたいのでこのガイドラインになるべく近づけて作りたいと思います。
参考:Tetris Guideline | Tetris Wiki | Fandom
1.フィールドを作ってミノを表示する
まずこれがないと始まらないのがフィールド。ミノを動かしたり置いたりする場所です。これは二次元配列でどうにかしようと思います。
1-1.配列の作成
実際に操作できるフィールドのマス数は縦20マス×横10マス。
とりあえずフィールドを作る分にはこれだけで良いですが、テトリスには外の領域が少しだけあります。
操作の限界となる壁が左右に1マス、床が下に1マス。更に上の空間が2マス必要です。
殆どのミノは回転軸の隣1マスにブロックが存在しますが、Iミノだけ隣2マスに存在します。この空間を作らないとミノの配置や回転ができないので2マス分用意します。
図にするとこんな感じ。これらの仕様をコードにします。
1 2 3 4 5 6 7 8 | /* グローバル変数 */ const int FIELD_HEIGHT = 20; // フィールドの高さ const int FIELD_WIDTH = 10; // フィールドの幅 const int FIELD_WALL = 1; // 壁の厚さ const int FIELD_FLOOR = 1; // 床の厚さ const int FIELD_SPACE = 2; // 上の空間 int[,] field = new int[FIELD_HEIGHT + FIELD_SPACE + FIELD_FLOOR, FIELD_WIDTH + FIELD_WALL * 2]; |
これらを踏まえるとfield配列は[高さ(20)+上の空間(2)+床(1), 幅(10)+壁(1)×2]になります。
とりあえず配列だけできたので、次に壁と床の値を入れます。壁や床の値は8にしたいので、field配列の左端、右端、下端を8で埋めます。(ちなみに8である理由は後述の色の設定で他ミノと干渉しないようにするためです。)
配列宣言時に直接書き込んでもよかったのですが、もし定数の値が変わったとしても柔軟に対応できるテトリスにしたいのであえてコードにしてます。横100マスのテトリスが急にやりたくなることもあるかもしれませんし。
ちなみにこの処理はロード時に行いたいのでLoadイベントで実行してます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | private void mainForm_Load(object sender, EventArgs e) { // 壁と床を作る for(int i = 0; i < field.GetLength(0); i++) { field[i, 0] = 8; // 左壁 field[i, field.GetLength(1) - 1] = 8; // 右壁 } for(int i = 0; i < field.GetLength(1); i++) { field[field.GetLength(0) - 1, i] = 8; // 床 } } |
(余談)
ガイドラインによれば本当は縦40マスあるらしく、視覚領域と外の領域を含めて40マスということみたいです。テトリスの対戦ではお邪魔攻撃として下からミノがせり上がってくるので、それに対応するための40マスでしょう。今回は対戦テトリスを作るわけではないので20マスと操作に最低限必要な2マスの、合計22マスとしています。
1-2.画面の作成
配列の値を出力して数字でテトリス・・・もできないことはないですが、どうせならグラフィカルに遊びたいので、色のついた四角形を並べて画面を作りたいと思います。
ツールボックスからpictureboxを持ってきて適当に配置します。このpictureboxがフィールドになります。nameはview_fieldにします。
四角形の大きさは・・・とりあえず20pxにしますか。後にマスの長さを使ったコードを書くので、予め定数SQUAREを作って20を代入します。グローバル変数の所に追記しましょう。
1 | const int SQUARE = 20; |
フィールドの方で操作できるマスは縦20マス×横10マスとあったので、四角形一辺の長さ×フィールドの長さでpictureboxのheightとwidthを設定します。
heightは20×20=400、widthは20×10=200。プロパティから設定します。
フィールドを配置しましたがこのままだと何もしてくれないので、フィールド内のマス数分四角形を描画する処理を描きます。描画処理はフィールドが更新される度に呼び出すつもりなので、メソッドにして使いまわしが利くようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void draw() { Bitmap canvas = new Bitmap(view_field.Width, view_field.Height); Graphics g = Graphics.FromImage(canvas); for (int i = 0; i < field.GetLength(0); i++) { for (int j = 0; j < field.GetLength(1); j++) { SolidBrush brush = new SolidBrush(Color.FromArgb(0, 0, 0)); // 四角形を描画 g.FillRectangle(brush, j * SQUARE, i * SQUARE, SQUARE, SQUARE); brush.Dispose(); } } g.Dispose(); view_field.Image = canvas; } |
これで実行すると・・・!
・・・仮に色を黒にしていたので、すべての領域が黒く塗りつぶされています。まあ、とりあえずは描画できていることを確認しました。
1-3.色の設定
描画はしてくれましたが真っ黒だと意味がないので、ミノの色を設定します。
ミノの色の判別は配列fieldの値で行います。0なら空白(白)、1~7はミノ、8は壁(黒)。
ミノは全部で7種類あり、それぞれ色が決められています。Iミノは水色、Oミノは黄色、Sミノは緑色、Zミノは赤色、Jミノは青色、Lミノは橙色、Tミノは紫色です。ガイドライン準拠。
これらを順番に数字へ割り当てると、1、2、3、4、5、6、7となります。
この仕様を元に、先程のdrawメソッド内の色の所に追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | for (int i = 0; i < field.GetLength(0); i++) { for (int j = 0; j < field.GetLength(1); j++) { Color color = Color.White; switch(field[i, j]) { case 0: // 空白 color = Color.FromArgb(255, 255, 255); break; case 1: // Iミノ color = Color.FromArgb(0, 255, 255); break; case 2: // Oミノ color = Color.FromArgb(255, 255, 0); break; case 3: // Sミノ color = Color.FromArgb(0, 221, 0); break; case 4: // Zミノ color = Color.FromArgb(255, 0, 0); break; case 5: // Jミノ color = Color.FromArgb(30, 128, 255); break; case 6: // Lミノ color = Color.FromArgb(255, 140, 0); break; case 7: // Tミノ color = Color.FromArgb(255, 0, 255); break; case 8: // 壁 color = Color.FromArgb(0, 0, 0); break; } SolidBrush brush = new SolidBrush(color); // 四角形を描画 g.FillRectangle(brush, j * SQUARE, i * SQUARE, SQUARE, SQUARE); brush.Dispose(); } } |
これで実行してみます。
今度は謎の黒線が。
実はこれ、描画領域がどこを指すかを決めておらず、field配列の一番左上を描画しちゃってます。謎の黒線の正体は左の壁です。上の領域2マス分、左の壁1マス分ずらしてあげないといけないですね。
drawメソッドの一番下の四角形を描画するやつを書き換えます。
1 2 3 4 | // 四角形を描画 // g.FillRectangle(brush, j * SQUARE, i * SQUARE, SQUARE, SQUARE); g.FillRectangle(brush, j * SQUARE - FIELD_WALL * SQUARE, i * SQUARE + FIELD_SPACE * SQUARE, SQUARE, SQUARE); brush.Dispose(); |
後は配列fieldの好きなところに1~7の数値を代入して実行です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | field[19, 1] = 6; field[20, 1] = 6; field[21, 1] = 6; field[21, 2] = 6; field[21, 4] = 1; field[21, 5] = 1; field[21, 6] = 1; field[21, 7] = 1; field[19, 4] = 4; field[19, 5] = 4; field[20, 5] = 4; field[20, 6] = 4; field[19, 7] = 3; field[20, 7] = 3; field[20, 8] = 3; field[21, 8] = 3; field[20, 9] = 2; field[20, 10] = 2; field[21, 9] = 2; field[21, 10] = 2; field[18, 4] = 5; field[18, 5] = 5; field[18, 6] = 5; field[19, 6] = 5; field[14, 3] = 7; field[15, 3] = 7; field[15, 2] = 7; field[16, 3] = 7; |
ちゃんと色分けされてますね。これでフィールド完成です。
ちなみにこの形、テトリスでは「開幕TSD(Tスピンダブル)」と呼ばれています。全7種類のミノを1回ずつ使って組めるので試合開始時に安定して組むことができます。
TスピンダブルというのはTミノを回転入れして2列消すことを指します。Tスピンをすることで相手に大きなダメージを与えることができ、かつ開幕で安定して組めるので先制攻撃を仕掛けるのに適した技です。余談でした。
次回は各種ミノの実装です。今回は手打ちで開幕TSDの形を作りましたが、これを手打ちでなく実際に操作して作るための準備をします。
#2 テトリミノ実装編に続く。