こんにちは、あんみんどうふです。
今回はより直感的に回転できる仕様、スーパーローテーションを実装します。多分ここが一番のつまづきポイント。
前回の記事はこちら。
全編見たい方はタグからどうぞ。
6.スーパーローテーションの実装
6-0.おさらい
スーパーローテーションを実装するとこれができるようになります。画像はTミノを操作中、Oミノが屋根になったくぼみで右回転することで回転入れをしています。
どういうことかというと、現代のテトリスは画像のように「屋根を作る」のと、「下の穴が回転後の形と合う」ことでミノが回転して中に入る仕組みになっており、これを公式ではスーパーローテーション、テトリスプレイヤーからは回転入れと呼ばれています。Tミノを使って回転入れをすると「Tスピン」になり、高スコアがもらえたり対戦であれば高火力をぶつけることができます。
ちなみに画像ではTスピンで3列消してるので「Tスピントリプル」になりますね。
もちろん、TミノだけでなくOミノ以外のすべてのミノで回転入れができます。(Oミノはいくら回転しても正方形のまま変わらないので…)
6-1.準備(重複チェック)
まず、準備として新たなメソッドminoDuplicateCheckを作ります。ミノが壁や既に設置したミノと重複しているかチェックするメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * ミノの重複チェック */ bool minoDuplicateCheck(int srsy = 0, int srsx = 0) { for(int i = 0; i < 4; i++) { for(int j = 0; j < 4; j++) { // 現在位置と操作中のミノが重複しないかチェック if (field[i + y + FIELD_SPACE - 1 + srsy, j + x + FIELD_WALL + srsx] != 0 && mino[next[0], dir, i, j] != 0) { return false; } } } return true; } |
引数であるsrsy、srsxにはスーパーローテーションをした時の移動量を入れます。いわゆる、「オフセット」というやつです。
現在位置にオフセットを加えた4×4マスをチェックし、現在操作しているミノと1マスも重複していなければtrue、重複していればループを終了し即座にfalseを返します。
これを使ってスーパーローテーション時に次のパターンに行くかどうかを決めます。
そして、ミノの回転を行うminoTurnメソッドの後半に追記します。
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 | int dirTmp = dir; // 以前のdirを控えておく switch (turn) { case 0: // 左回転 dir--; break; case 1: // 右回転 dir++; break; } if (dir < 0) dir = 3; if (dir > 3) dir = 0; // 通常の回転ができるかチェック if (!minoDuplicateCheck()) { // できないならスーパーローテーションを試す if(!minoSuperRotation(dirTmp)) { // それでもできないなら回転前の状態に戻す dir = dirTmp; } } // 描画 minoDrawing(next[0], dir); } |
通常の回転ができない場合、スーパーローテーションを試します。
スーパーローテーションもできない場合は回転自体が失敗したということなので、回転前の状態に戻します。
6-2.Iミノ以外のスーパーローテーション
スーパーローテーションを正しく実装すると回転入れが可能になるので、それを目標にして作ります。細かい仕様があるので、今回もTetrisちゃんねる様を参考にして作ります。
スーパーローテーションには法則があり、4つの条件のうちいずれかが満たされたら回転が可能ということになります。また、Iミノだけ形状が特殊(他のミノは横2~3マス使うが、Iミノは4マス使う)なので、「Iミノ以外」「Iミノ用」と分けられています。
まずはIミノ以外の法則から。
1.軸を左右に動かす
- 0が90度(B)の場合は左,-90度(D)の場合は右へ移動
- 0が0度(A),180度(C)の場合は回転した方向の逆へ移動
2.その状態から軸を上下に動かす
- 0が90度(B),-90度(D)の場合は上へ移動
- 0が0度(A),180度(C)の場合は下へ移動
3.元に戻し,軸を上下に2マス動かす
- 0が90度(B),-90度(D)の場合は下へ移動
- 0が0度(A),180度(C)の場合は上へ移動
4.その状態から軸を左右に動かす
- 0が90度(B)の場合は左,-90度(D)の場合は右へ移動
- 0が0度(A),180度(C)の場合は回転した方向の逆へ移動
「Tetrisちゃんねる」より引用
A、B、C、Dは向きのことですね。ここでいうdir変数の0、1、2、3と同じです。
普通に回転してみて、何かしら障害があれば1の内容で移動して、移動できなければ2、それもダメなら3、それもダメなら4といったように、順番に条件と比較していきます。4もダメなら移動も回転もしません。
これらをコードにします。結構長いです。
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | /** * スーパーローテーションシステム(SRS) * ------------------------------ * dirOld 回転前の向き */ bool minoSuperRotation(int dirOld) { int movex = 0; // X座標移動量 int movey = 0; // Y座標移動量 // Iミノ以外 if (next[0] != 1) { // 1. 軸を左右に動かす // 0が90度(B)の場合は左,-90度(D)の場合は右へ移動 // 0が0度(A),180度(C)の場合は回転した方向の逆へ移動 switch (dir) { case 1: // 右向き movex = -1; break; case 3: // 左向き movex = 1; break; case 0: // 上向き case 2: // 下向き switch (dirOld) { case 1: // 回転前が右向き movex = 1; break; case 3: // 回転前が左向き movex = -1; break; } break; } if (!minoDuplicateCheck(movey, movex)) { // 2.その状態から軸を上下に動かす // 0が90度(B),-90度(D)の場合は上へ移動 // 0が0度(A),180度(C)の場合は下へ移動 switch (dir) { case 1: case 3: movey = -1; break; case 0: case 2: movey = 1; break; } if (!minoDuplicateCheck(movey, movex)) { // 3.元に戻し、軸を上下に2マス動かす // 0が90度(B),-90度(D)の場合は下へ移動 // 0が0度(A),180度(C)の場合は上へ移動 movex = 0; movey = 0; switch (dir) { case 1: case 3: movey = 2; break; case 0: case 2: movey = -2; break; } if (!minoDuplicateCheck(movey, movex)) { // 4.その状態から軸を左右に動かす // 0が90度(B)の場合は左,-90度(D)の場合は右へ移動 // 0が0度(A),180度(C)の場合は回転した方向の逆へ移動 switch (dir) { case 1: movex = -1; break; case 3: movex = 1; break; case 0: case 2: switch (dirOld) { case 1: // 回転前が右向き movex = 1; break; case 3: // 回転前が左向き movex = -1; break; } break; } if(!minoDuplicateCheck(movey, movex)) { return false; } } } } } |
とりあえずここまで書けば、Iミノ以外のスーパーローテーションは完成です。movexとmoveyはIミノ用のコードを書いた後に座標へ統合します。
ちなみに作ってて思ったのですが・・・法則に書いてある「回転した方向の逆へ移動」ですが、これ左回転だと右へ移動すると思うじゃないですか。実はそういうことではないらしく、回転前の方向の逆方向という意味みたいです。例えば、回転前が左向きなら右へ移動する、みたいな。
私は一回ここでつまづきました・・・。
6-3.Iミノのスーパーローテーション
Iミノだけは特殊な形状をしているので、先程とは少し違う仕様になっています。
1.軸を左右に動かす
- 0が90度(B)の場合は右,-90度(D)の場合は左へ移動(枠にくっつく)
- 0が0度(A),180度(C)の場合は回転した方向の逆へ移動 0度は2マス移動
2.軸を左右に動かす
- 0が90度(B)の場合は左,-90度(D)の場合は右へ移動(枠にくっつく)
- 0が0度(A),180度(C)の場合は回転した方向へ移動 180度は2マス移動
3.軸を上下に動かす
- 0が90度(B)の場合は1を下,-90度(D)の場合は1を上へ移動
- 0が0度(A),180度(C)の場合は
回転前のミノが右半分にある(B)なら1を上へ
回転前のミノが左半分にある(D)なら2を下へ移動- 左回転なら2マス動かす
4.軸を上下に動かす
- 0が90度(B)の場合は2を上,-90度(D)の場合は2を下へ移動
- 0が0度(A),180度(C)の場合は
回転前のミノが右半分にある(B)なら2を下へ
回転前のミノが左半分にある(D)なら1を上へ移動- 右回転なら2マス動かす
「Tetrisちゃんねる」より引用
かなり複雑ですね・・・。これらをif文のelseの方に書き起こします。
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | // Iミノの場合 else { int pt1x; // 1のX移動量 int pt2x; // 2のX移動量 // 1. 軸を左右に動かす // 0が90度(B)の場合は右,-90度(D)の場合は左へ移動(枠にくっつく) // 0が0度(A),180度(C)の場合は回転した方向の逆へ移動 0度は2マス移動 switch (dir) { case 1: movex = 1; break; case 3: movex = -1; break; case 0: case 2: switch (dirOld) { case 1: movex = -1; break; case 3: movex = 1; break; } if (dir == 0) movex *= 2; // 0度は2マス移動 break; } pt1x = movex; if(!minoDuplicateCheck(movey, movex)) { // 2. 軸を左右に動かす // 0が90度(B)の場合は左,-90度(D)の場合は右へ移動(枠にくっつく) // 0が0度(A),180度(C)の場合は回転した方向へ移動 180度は2マス移動 switch (dir) { case 1: movex = -1; break; case 3: movex = 1; break; case 0: case 2: switch (dirOld) { case 1: movex = 1; break; case 3: movex = -1; break; } if (dir == 2) movex *= 2; // 180度は2マス移動 break; } pt2x = movex; if(!minoDuplicateCheck(movey, movex)) { // 3. 軸を上下に動かす // 0が90度(B)の場合は1を下,-90度(D)の場合は1を上へ移動 // 0が0度(A),180度(C)の場合は // 回転前のミノが右半分にある(B)なら1を上へ // 回転前のミノが左半分にある(D)なら2を下へ移動 // 左回転なら2マス動かす switch (dir) { case 1: movex = pt1x; movey = 1; break; case 3: movex = pt1x; movey = -1; break; case 0: case 2: switch(dirOld) { case 1: movex = pt1x; movey = -1; break; case 3: movex = pt2x; movey = 1; break; } break; } // 左回転 if(dirOld == 0 && dir == 3 || dirOld == 3 && dir == 2 || dirOld == 2 && dir == 1 || dirOld == 1 && dir == 0) { movey *= 2; } if(!minoDuplicateCheck(movey, movex)) { // 4. 軸を上下に動かす // 0が90度(B)の場合は2を上,-90度(D)の場合は2を下へ移動 // 0が0度(A),180度(C)の場合は // 回転前のミノが右半分にある(B)なら2を下へ // 回転前のミノが左半分にある(D)なら1を上へ移動 // 右回転なら2マス動かす switch (dir) { case 1: movex = pt2x; movey = -1; break; case 3: movex = pt2x; movey = 1; break; case 0: case 2: switch (dirOld) { case 1: movex = pt2x; movey = 1; break; case 3: movex = pt1x; movey = -1; break; } break; } // 右回転 if (dirOld == 3 && dir == 0 || dirOld == 0 && dir == 1 || dirOld == 1 && dir == 2 || dirOld == 2 && dir == 3) { movey *= 2; } if (!minoDuplicateCheck(movey, movex)) { return false; } } } } } x += movex; y += movey; return true; } |
あまりうまくできてる自信は無いですが、とりあえず完成です。流石にここはミスってる可能性が高いので、気づき次第修正します・・・。
※7月2日追記
やはりミスってました。上向きのIミノを床にくっつけて左回転し、再度右回転して元に戻すと1マス横にずれちゃってます・・・。修正中。
実際にできるか試します。#2の最後にあった「DT砲」を組んでみます。Tスピンダブル→Tスピントリプルの順に消せる優秀なテンプレ技です。
できてますね。この技は初心者から上級者まで使える便利で強力な技なので、テトリスに興味のある方は是非覚えましょう。さあ、あなたもテトリスデビューだ!
ただ、ホールド無しだと成功率はかなり低いです。安定して組むためにも、次回はハードドロップとホールドの実装をします。これらの実装により、積み上げるスピードが格段に上がったり、組みたい形を楽に組むことができます!
#7 ハードドロップ・ホールド編に続く。