ASP.NET Core版 対コンピュータ対戦できるぷよぷよをつくる(4)の続きです。前回までのものはコンピュータ側は適当にぷよを積んでいくだけでした。そこで今回はデタラメに積むのではなくアルゴリズムを考えます。
ぷよは現在落下しているものと次回、そして次々回のものがわかっています。またひとつの組ぷよの置き方はどの列に配置するかと回転量をどうするかで全部で22通り存在します。そのため現在落下している組ぷよと次回、次々回のぷよが配置された結果として22の3乗個の結果が存在します。
今回はこの22の3乗個の結果に対して評価点を与え、そのなかで評価点がもっとも高いものを採用すればよいということになります。今回は基本部分だけ考えます。
AiActionResultクラスの定義
組ぷよを落としたあとの結果を格納するクラスを定義します。現在落下中の組ぷよと次回、次々回分の結果を格納できるように、メンバーはリストにします。右に○回移動して(左移動は負数回として考える)○回回転させたあと(最大で3回)落下させるとどうなるのか(処理後の固定されたぷよの状態、連鎖数、落下数、評価点、窒息していないか)をリストにして格納します。
そしてその複製を生成したり、複数のオブジェクトを合成できるようにしておきます。
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 |
namespace Puyo { public class AiActionResult { public List<Puyo[,]> FixedPuyosList = new List<Puyo[,]>(); public List<int> Rights = new List<int>(); public List<int> RotateCounts = new List<int>(); public List<int> EvaluationValues = new List<int>(); public List<int> Rensas = new List<int>(); public List<int> FallCounts = new List<int>(); public List<bool> IsDeads = new List<bool>(); public AiActionResult Clone() { AiActionResult clone = new AiActionResult(); clone.FixedPuyosList = new List<Puyo[,]>(); foreach (Puyo[,] puyos in FixedPuyosList) { Puyo[,] copiedFixedPuyos = new Puyo[Field.ROW_MAX, Field.COL_MAX]; for (int row = 0; row < Field.ROW_MAX; row++) { for (int col = 0; col < Field.COL_MAX; col++) { copiedFixedPuyos[row, col] = new Puyo(puyos[row, col].Type); } } clone.FixedPuyosList.Add(copiedFixedPuyos); } clone.Rights = new List<int>(Rights); clone.RotateCounts = new List<int>(RotateCounts); clone.Rensas = new List<int>(Rensas); clone.FallCounts = new List<int>(FallCounts); clone.IsDeads = new List<bool>(IsDeads); return clone; } public void Merge(AiActionResult src) { foreach (Puyo[,] puyos in src.FixedPuyosList) { Puyo[,] copiedFixedPuyos = new Puyo[Field.ROW_MAX, Field.COL_MAX]; for (int row = 0; row < Field.ROW_MAX; row++) { for (int col = 0; col < Field.COL_MAX; col++) { copiedFixedPuyos[row, col] = new Puyo(puyos[row, col].Type); } } FixedPuyosList.Add(copiedFixedPuyos); } Rights.AddRange(src.Rights); RotateCounts.AddRange(src.RotateCounts); Rensas.AddRange(src.Rensas); FallCounts.AddRange(src.FallCounts); IsDeads.AddRange(src.IsDeads); } } } |
AiBasicクラスの定義
AIの実装の基本になる動作を定義したクラスを作成します。
1 2 3 4 5 6 |
namespace Puyo { public class AiBasic { } } |
以降は名前空間を省略します。
1 2 3 |
public class AiBasic { } |
ぷよを落とした結果を取得する
これはすでに固定されたぷよを示す二次元配列のコピーを返すメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class AiBasic { public static Puyo[,] CopyArray(Puyo[,] fixedPuys) { Puyo[,] puyos = new Puyo[Field.ROW_MAX, Field.COL_MAX]; for (int row = 0; row < Field.ROW_MAX; row++) { for (int col = 0; col < Field.COL_MAX; col++) { Puyo puyo = new Puyo(fixedPuys[row, col].Type); puyos[row, col] = puyo; } } return puyos; } } |
以下は固定されたぷよの二次元配列から消えるぷよのリストを返すメソッドです。
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 |
public class AiBasic { List<Puyo> Check(Puyo[,] field, int col, int row) { List<Puyo> ret = new List<Puyo>(); bool[,] fieldForCheck = new bool[Field.ROW_MAX, Field.COL_MAX]; if (field[row, col].Type == PuyoType.None || field[row, col].Type == PuyoType.Ojama) return ret; Puyo s = field[row, col]; fieldForCheck[row, col] = true; Queue<int> cols = new Queue<int>(); Queue<int> rows = new Queue<int>(); List<int> resultXs = new List<int>(); List<int> resultYs = new List<int>(); cols.Enqueue(col); rows.Enqueue(row); resultXs.Add(col); resultYs.Add(row); while (true) { int col0 = cols.Dequeue(); int row0 = rows.Dequeue(); int[] dxs = { 0, 0, 1, -1 }; int[] dys = { 1, -1, 0, 0 }; for (int i = 0; i < 4; i++) { int nextCol = col0 + dxs[i]; int nextRow = row0 + dys[i]; if (nextCol < 0 || nextRow < 0 || nextCol >= Field.COL_MAX || nextRow >= Field.ROW_MAX) continue; if (fieldForCheck[nextRow, nextCol]) continue; if(field[nextRow, nextCol].Type == PuyoType.Ojama) continue; fieldForCheck[nextRow, nextCol] = true; if (field[nextRow, nextCol].Type == s.Type) { cols.Enqueue(nextCol); rows.Enqueue(nextRow); resultXs.Add(nextCol); resultYs.Add(nextRow); } } if (cols.Count == 0) break; } if (resultXs.Count < 4) return ret; for (int k = 0; k < resultXs.Count; k++) ret.Add(field[resultYs[k], resultXs[k]]); return ret; } } |
以下はぷよが消えたときにそのぷよの上にあるぷよを一段下げるメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class AiBasic { bool DownByDelete(Puyo[,] field) { bool isDowned = false; for (int row = Field.ROW_MAX - 1; row > 0; row--) { for (int col = 0; col < Field.COL_MAX; col++) { if (field[row, col].Type == PuyoType.None && field[row - 1, col].Type != PuyoType.None) { Puyo s = field[row - 1, col]; field[row, col] = s; field[row - 1, col] = new Puyo(PuyoType.None); isDowned = true; } } } return isDowned; } } |
以下は組ぷよのそれぞれを落下できるところまで落下させるメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class AiBasic { void FallPuyo(FallingPuyo puyo, Puyo[,] copiedFixedPuyos) { int row = puyo.Row; int col = puyo.Col; if (copiedFixedPuyos[row, col].Type != PuyoType.None) return; while (true) { if (row >= Field.ROW_MAX || copiedFixedPuyos[row, col].Type != PuyoType.None) { row--; puyo.Row = row; return; } else row++; } } } |
以下はこの位置で組ぷよを落としたあと固定されたぷよがどうなるのか(連鎖発生後の状態)を取得するメソッドです。
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 |
public class AiBasic { Puyo[,] GetFalledPuyos(Puyo[,] copiedFixedPuyos, FallingPuyo[] copiedFallingPuyo, ref int refRensa, ref int fallCount) { if (copiedFallingPuyo[0].Col == copiedFallingPuyo[1].Col && copiedFallingPuyo[1].Row > copiedFallingPuyo[0].Row) { FallPuyo(copiedFallingPuyo[1], copiedFixedPuyos); FallPuyo(copiedFallingPuyo[0], copiedFixedPuyos); } else { FallPuyo(copiedFallingPuyo[0], copiedFixedPuyos); FallPuyo(copiedFallingPuyo[1], copiedFixedPuyos); } fallCount = copiedFallingPuyo[0].Row + copiedFallingPuyo[1].Row; copiedFixedPuyos[copiedFallingPuyo[1].Row, copiedFallingPuyo[1].Col] = new Puyo(copiedFallingPuyo[1].Type); copiedFixedPuyos[copiedFallingPuyo[0].Row, copiedFallingPuyo[0].Col] = new Puyo(copiedFallingPuyo[0].Type); int rensa = 0; while (true) { List<Puyo> deletingPuyos = new List<Puyo>(); for (int row = 0; row < Field.ROW_MAX; row++) { for (int col = 0; col < Field.COL_MAX; col++) deletingPuyos.AddRange(Check(copiedFixedPuyos, col, row)); } // ぷよが消えないなら抜ける if (deletingPuyos.Count == 0) break; rensa++; foreach (Puyo puyo in deletingPuyos) puyo.Delete(); // ぷよが消えるなら連鎖がおきるかもしれない // 空白地点のうえにぷよがあるなら1段下げる // この処理が必要なくなるまで繰り返す while (true) { if (!DownByDelete(copiedFixedPuyos)) break; } } refRensa = rensa; return copiedFixedPuyos; } } |
以下は組ぷよを落としたときの結果が格納されているAiActionResultオブジェクトのリストを取得するメソッドです。落下させるために組ぷよを各列に移動させ回転させます。各列に移動する処理は6とおり、回転は4とおりあります。ただし端に移動させた場合は回転できない場合があるので考えるのは全部で22とおりです。これらをすべて総当たりで取得します。
GetFalledPuyosメソッドに引数をそのまま渡すと組ぷよの高さが変わってしまうので、引数をそのまま渡すのではなくコピーを渡します。あとは取得されたもののなかから
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 |
public class AiBasic { public List<AiActionResult> GetAiActionResults(Puyo[,] fixedPuyos, FallingPuyo[] fallingPuyos) { bool isDead = false; if (fixedPuyos[Field.INIT_MAIN_ROW, Field.INIT_MAIN_COL].Type != PuyoType.None) isDead = true; List<AiActionResult> results = new List<AiActionResult>(); int[] mainCols = { 5, 4, 3, 0, 1, 2 }; int[] mainRows = { 1, 1, 1, 1, 1, 1 }; int[] subCols = { 0, 1, 0, -1 }; int[] subRows = { -1, 0, 1, 0 }; for (int i = 0; i < mainCols.Length; i++) { // 初期位置から0列には移動できない if (i == 0 && fixedPuyos != null && (fixedPuyos[1, 0].Type != PuyoType.None || fixedPuyos[1, 1].Type != PuyoType.None)) continue; // 初期位置から1列には移動できない if (i == 1 && fixedPuyos != null && fixedPuyos[1, 1].Type != PuyoType.None) continue; // 初期位置から3列には移動できない(4列と5列にも当然移動できない) if (i == 3 && fixedPuyos != null && fixedPuyos[1, 3].Type != PuyoType.None) continue; // 初期位置から4列には移動できない(5列にも当然移動できない) if (i == 4 && fixedPuyos != null && fixedPuyos[1, 4].Type != PuyoType.None) continue; // 初期位置から5列には移動できない if (i == 5 && fixedPuyos != null && fixedPuyos[1, 5].Type != PuyoType.None) continue; for (int k = 0; k < 4; k++) { Puyo[,] copiedPuyos = CopyArray(fixedPuyos); FallingPuyo[] copiedFallingPuyos = new FallingPuyo[2]; copiedFallingPuyos[0] = fallingPuyos[0].Clone(); copiedFallingPuyos[1] = fallingPuyos[1].Clone(); copiedFallingPuyos[0].Row = mainRows[i]; copiedFallingPuyos[0].Col = mainCols[i]; copiedFallingPuyos[1].Row = mainRows[i] + subRows[k]; copiedFallingPuyos[1].Col = mainCols[i] + subCols[k]; // 回転できない(回転するとフィールドの外に出てしまう) if (copiedFallingPuyos[0].Col < 0) continue; if (copiedFallingPuyos[0].Col >= Field.COL_MAX) continue; if (copiedFallingPuyos[1].Col < 0) continue; if (copiedFallingPuyos[1].Col >= Field.COL_MAX) continue; // 回転できない(回転するとすでに固定されているぷよとぶつかってしまう) if (fixedPuyos == null) continue; if (fixedPuyos[copiedFallingPuyos[0].Row, copiedFallingPuyos[0].Col].Type != PuyoType.None) continue; if (fixedPuyos[copiedFallingPuyos[1].Row, copiedFallingPuyos[1].Col].Type != PuyoType.None) continue; AiActionResult result = new AiActionResult(); result.Rights.Add(copiedFallingPuyos[0].Col - 2); result.RotateCounts.Add(k); int rensa = 0; int fallCount = 0; Puyo[,] puyos = GetFalledPuyos(copiedPuyos, copiedFallingPuyos, ref rensa, ref fallCount); if (puyos == null) continue; result.FixedPuyosList.Add(puyos); result.Rensas.Add(rensa); result.FallCounts.Add(fallCount); result.IsDeads.Add(isDead); results.Add(result); } } return results; } } |