ASP.NET Core版 対戦型のマインスイーパーをつくる(4)の続きです。
マインスイーパのなかには「地雷の置かれていないセルを開けたとき、隣接するセルに地雷が置かれていないときは、それらが自動的に開けられる」という機能があるものもあります。
他にも「地雷が置かれていると思われるセルに旗を立て、表示された個数と同じだけの旗を立てたセルで左右のボタンを同時に押下すると、隣接する開けられていないセルを一気に開けることができる」というものもあります。この場合、一気に開けられたセルに地雷がある場合はゲームオーバーとなります。
これらの機能を追加してしまうとゲームの難易度が下がってしまい面白くないのではないと判断してしていないのですが、プログラミング的には面白いと思ったのでベータ版で実装してみることにしました。この機能を使うとゲームは進めやすくなりますが、マスを開くときの緊張感は低下して面白くなくなってしまうかもしれません。
一気にセルを開くための準備
まず以下の処理を多用することになるのでメソッド化してしまいましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class MinesweeperHub : Hub { async Task SendMapToClients() { string names = ""; string scores = ""; GetPlayerInfos(ref names, ref scores); await Clients.All.SendAsync("ReceiveScores", names, scores); await Clients.All.SendAsync("ReceiveUnopenedCellCount", Game.GetUnopenedCellCount()); foreach (var pair in ClientProxyMap) { Player? player0 = null; if (Game.Players.ContainsKey(pair.Key)) player0 = Game.Players[pair.Key]; await ClientProxyMap[pair.Key].SendAsync("ReceiveDraw", Game.RowMax, Game.ColMax, Game.GetStringFromMap(player0)); } } } |
セルを一気に開く処理を追加
既存のOpenCellメソッドを以下のように修正します。
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 |
public class MinesweeperHub : Hub { public async Task OpenCell(string name, int col, int row) { if (!Game.Players.ContainsKey(Context.ConnectionId)) return; Player player = Game.Players[Context.ConnectionId]; player.Name = name; if (col >= 0 && col < Game.ColMax && row >= 0 && row < Game.RowMax) { if (player.Flags.Any(f => f.Column == col && f.Row == row)) return; // 表示された個数と同じだけの旗を立てたセルで隣接するセルを一気に開ける if (Game.IsMapOpen[row, col]) { await OpenMultiCells(player, col, row); // 後述 return; } if (Game.Map[row, col] != -1 && !Game.IsMapOpen[row, col]) { Game.IsMapOpen[row, col] = true; // 普通にセルを開く if (Game.Map[row, col] != 0) { AddScore(player, col, row); await Clients.Caller.SendAsync("ReceiveOpen", player.Score, row, col); await CheckClear(player, col, row); await SendMapToClients(); } // 隣接するセルに地雷が置かれていないとき、それを自動的に開くく if (Game.Map[row, col] == 0) { await OpenCellsAroundZero(player, col, row); // 後述 } return; } // 地雷があるセルを開いてしまった if (Game.Map[row, col] == -1) { SaveHiscore(player); if (Game.Players.ContainsKey(Context.ConnectionId)) Game.Players.Remove(Context.ConnectionId); await Clients.Caller.SendAsync("ReceiveGameOver", row, col); await SendMapToClients(); } } } } |
開いたセルの数字が0の場合
開いたセルの数字が0の場合、地雷がない隣接するセルを自動で開く処理を示します。
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 |
public class MinesweeperHub : Hub { async Task OpenCellsAroundZero(Player player, int col, int row) { AddScore(player, col, row); await Clients.Caller.SendAsync("ReceiveOpen", player.Score, row, col); int[] dx = { 0, 1, 1, 1, 0, -1, -1, -1 }; int[] dy = { -1, -1, 0, 1, 1, 1, 0, -1 }; Queue<int> cols = new Queue<int>(); Queue<int> rows = new Queue<int>(); cols.Enqueue(col); rows.Enqueue(row); int lastRow = row; int lastCol = col; while (true) { int c = cols.Dequeue(); int r = rows.Dequeue(); for (int i = 0; i < 8; i++) { // 隣接するセルの位置を取得 int nrow = r + dy[i]; int ncol = c + dx[i]; // 配列の範囲外である場合はなにもしない if (nrow < 0 || ncol < 0 || nrow >= Game.RowMax || ncol >= Game.ColMax) continue; // 地雷があるセルは無視 if (Game.Map[nrow, ncol] == -1) continue; // すでに開かれているセルは無視 if (Game.IsMapOpen[nrow, ncol]) continue; // 開かれていないセルを見つけたら開いて加点処理をおこない、クライアントサイドに通知 Game.IsMapOpen[nrow, ncol] = true; AddScore(player, ncol, nrow); await Clients.Caller.SendAsync("ReceiveOpen", player.Score, nrow, ncol); // 一気に開こうとしたセルのなかで最後に開いたものを記憶する lastRow = nrow; lastCol = ncol; // セルの数字が0の場合、キューに格納する if (Game.Map[nrow, ncol] == 0) { cols.Enqueue(ncol); rows.Enqueue(nrow); } } // キューが空ならループを抜ける if (cols.Count == 0) break; } // 一気に開いた結果、ステージクリアになっているかもしれない await CheckClear(player, lastCol, lastRow); // 全ユーザーに更新を通知 await SendMapToClients(); } } |
周囲のセルを一気に開く
表示された個数と同じだけの旗を立てたセルで隣接するセルを一気に開ける処理を示します。
一般的には中ボタンをクリック(または左右のボタンを同時に押下)ですが、面倒なので開かれているセルをクリックしたときに表示されている数字と周囲に立てられている旗の数が一致した場合は周囲のセルを一気に開きます。このとき間違えて旗を立てていた場合は地雷があるセルが開かれてしまうのでゲームオーバーとなります。
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 |
public class MinesweeperHub : Hub { async Task OpenMultiCells(Player player, int col, int row) { int mineCount = Game.Map[row, col]; int[] dx = { 0, 1, 1, 1, 0, -1, -1, -1 }; int[] dy = { -1, -1, 0, 1, 1, 1, 0, -1 }; int flagCount = 0; for (int i = 0; i < 8; i++) { // 隣接するセルの位置を取得 int nrow = row + dy[i]; int ncol = col + dx[i]; // 配列の範囲外である場合はなにもしない if (nrow < 0 || ncol < 0 || nrow >= Game.RowMax || ncol >= Game.ColMax) continue; // 旗の数をカウントする if (player.HasFlag(ncol, nrow)) flagCount++; } // 表示されている数字と旗の数が一致するのであれば・・・ if (mineCount == flagCount) { List<Position> positions = new List<Position>(); for (int i = 0; i < 8; i++) { int nrow = row + dy[i]; int ncol = col + dx[i]; if (nrow < 0 || ncol < 0 || nrow >= Game.RowMax || ncol >= Game.ColMax) continue; if (!player.HasFlag(ncol, nrow) && !Game.IsMapOpen[nrow, ncol]) { // 一気に開こうとしたセルに地雷がある場合はゲームオーバーの処理 if (Game.Map[nrow, ncol] == -1) { SaveHiscore(player); if (Game.Players.ContainsKey(Context.ConnectionId)) Game.Players.Remove(Context.ConnectionId); await Clients.Caller.SendAsync("ReceiveGameOver", nrow, ncol); await SendMapToClients(); return; } else { // 一気に開こうとしたセルをリストに格納する positions.Add(new Position(ncol, nrow)); } } } // 一気に開こうとしたセルのなかで最後に開いたものを記憶する int lastRow = row; int lastCol = col; // リストに格納されたセルを実際に開いて加点処理と更新処理をおこなう foreach (Position position in positions) { Game.IsMapOpen[position.Row, position.Column] = true; AddScore(player, position.Column, position.Row); await Clients.Caller.SendAsync("ReceiveOpen", player.Score, position.Row, position.Column); lastRow = position.Row; lastCol = position.Column; // リストに格納されたセルを開いた結果、0が書かれているセルを開いてしまうかもしれない if (Game.Map[position.Row, position.Column] == 0) await OpenCellsAroundZero(player, position.Column, position.Row); } // 一気に開いた結果、ステージクリアになっているかもしれない await CheckClear(player, lastCol, lastRow); // 全ユーザーに更新を通知 await SendMapToClients(); } } } |