これまでは上から落ちてきたテトリミノが床やブロックに接すると機械的に固定させていました。今回はこの部分を変更させます。
テトリス(tetris)のガイドラインを理解する – Qiitaのロックダウン(設置判定)によると
上から落ちてきたテトリミノは底面が床やブロックと触れても直ちに設置されるわけではありません。0.5秒間なにも無ければ設置されます。言い方を変えると0.5秒のあいだに回転や移動をさせることが可能なのです。しかも0.5秒のあいだに回転や移動をするとそのときからさらに0.5秒の猶予が生まれます。
これだけだと回転させ続けることで無限に設置しないこともできるので回転や移動には制限があります。回転と移動が計15回行われると猶予なしでそのまま設置されます。底面が触れてない場合であれば回転・移動の回数に制限はありません。
15回という制限はミノが1ブロック落下するとリセットされます。ただし回転で上昇しても位置の最低値が更新されないとリセットされません。
このようになっています。
そこでこれに対応させます。
ミノが下に移動したら着地しているかどうか調べます。着地しているのであればタイマーをセットして0.5秒後に固定させます。タイマーがセットされている最中に移動や回転がおこなわれたらタイマーをセットしなおします。
これまではミノを下に移動させるときはこのようにしていました。移動させようとしたときにすでに着地しているのであれば固定させる処理をしていました。
これを移動させたあと着地しているかどうかしらべて別のタイマーをセットして500ms経過後にTetoroDownedメソッドを実行することにします。
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 |
public partial class Form1 : Form { void MoveDownTetoro() { if(MovingTetoros.Max(x => x.Row) >= FIELD_INNER_HEIGHT - 1) { TetoroDowned(); return; } var newPos = MovingTetoros.Select(x => Field[x.Colum, x.Row + 1]).ToList(); if(newPos.Intersect(FixedTetoro).Count() != 0) { TetoroDowned(); return; } Color tetoroColor = MovingTetoros[0].BackColor; ClearOldTetoro(); MovingTetoros = newPos; foreach(Block block in newPos) block.BackColor = tetoroColor; if(isDroping) dropPoint++; } } |
変更されたMoveDownTetoroメソッドを示します。
着地しているか調べて着地しているのであれば自作メソッド SetSubTimerIfTetoroDownedでタイマーをセットします。また移動や回転させることで着地していないものが着地することになるので、基本は下に移動させてからタイマーをセットするのですが、移動前と移動後に2回調べています。
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 |
public partial class Form1 : Form { int rotateCount = 0; // 15回ルールがあるので void MoveDownTetoro() { rotateCount = 0; // 着地しているか調べる if(IsTetoroDowned()) { Timer.Stop(); SetSubTimerIfTetoroDowned(); } else { var newPos = MovingTetoros.Select(x => Field[x.Colum, x.Row + 1]).ToList(); Color tetoroColor = MovingTetoros[0].BackColor; ClearOldTetoro(); MovingTetoros = newPos; foreach(Block block in newPos) block.BackColor = tetoroColor; if(isDroping) dropPoint++; // 移動の結果着地しているなら・・・ if(IsTetoroDowned()) { Timer.Stop(); SetSubTimerIfTetoroDowned(); } } } } |
IsTetoroDownedメソッドは着地しているかどうか調べるためのメソッドです。下に移動させてみて他のブロックに当たったりフィールドの外に出る場合は移動不可ということになります。CanRotateメソッドをつかって判定していますが、CanRotate・・・名前の付け方がよくない・・・
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { bool IsTetoroDowned() { List<BlockPosition> tempPos = MovingTetoros.Select(x => new BlockPosition(x.Colum, x.Row)).ToList(); List<BlockPosition> newPos = tempPos.Select(x => new BlockPosition(x.Colum, x.Row + 1)).ToList(); if(CanRotate(newPos)) return false; else return true; } } |
SetSubTimerIfTetoroDownedはタイマーをセットするためのメソッドです。「着地してから500ms、ただしそのあいだに移動や回転がおこなわれた場合はリセットされる」というルールなので、すでにTimerオブジェクトがあるときは停止させてからセットし直しています。
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 |
public partial class Form1 : Form { Timer subTimer = null; void SetSubTimerIfTetoroDowned() { if(!IsTetoroDowned()) return; // 15回の回数制限を超えたときはかまわず設置する if(rotateCount > 15) { TetoroDowned(); Timer.Start(); return; } if(subTimer != null) { subTimer.Stop(); subTimer.Dispose(); } subTimer = new Timer(); subTimer.Tick += SubTimer_Tick; subTimer.Interval = 500; subTimer.Start(); rotateCount++; } } |
500ms間なにもなければイベントハンドラSubTimer_Tickが実行されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { private void SubTimer_Tick(object sender, EventArgs e) { subTimer.Stop(); subTimer.Dispose(); subTimer = null; if(IsTetoroDowned()) TetoroDowned(); Timer.Start(); } } |
移動や回転のときは500msの猶予がリセットされるのでSetSubTimerIfTetoroDownedメソッドを呼び出しています。メソッドのなかでミノが着地しているかどうか調べてタイマーをリセットします。
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 |
public partial class Form1 : Form { void MoveLeftTetoro() { if(MovingTetoros.Min(x => x.Colum) <= 0) return; var newPos = MovingTetoros.Select(x => Field[x.Colum-1, x.Row]).ToList(); if(newPos.Intersect(FixedTetoro).Count() != 0) return; Color tetoroColor = MovingTetoros[0].BackColor; ClearOldTetoro(); MovingTetoros = newPos; foreach(Block block in newPos) block.BackColor = tetoroColor; ShowGohst(); SetSubTimerIfTetoroDowned(); // 最後に // void MoveRightTetoro() // void RotateTetoro() // void RotateTetoro2() // も同様にする } } |
subTimerを破棄する時、subTimer.Tickイベントも解除しないとメモリリークしませんか?
たぶん問題ないと思います。