ASP.NET Core版 対戦型のマインスイーパーをつくる(3)の続きです。
前回はスルーした以下の課題に応えます。
(3)最後のマスを開いたプレイヤーにはボーナスポイントを与えてはどうか。
(8)終盤になってミスをすると高得点は見込めないので、難しいところは配点を上げるとか、参加人数や残りのセル数で配点を変えてほしい。
(9)クリア時に成績優秀者を表示して欲しい。
Contents
配点を見直す
開けたセルの数字が0の場合、その周囲は絶対に安全なので点数を下げます。それ以外の部分は残りのセル数が少なくなるごとに配点を1点ずつ上げていきます。最初に開けたときは1点、その次は2点と1点ずつあげていき、最後のセルを開けたプレイヤーには通常の点数とは別に1000点を追加します。
これは既存のスコアを加算するメソッドです。
1 2 3 4 5 6 7 |
public class MinesweeperHub : Hub { void AddScore(Player player, int col, int row) { player.Score += (Game.Map[row, col] + 1) * 10; } } |
これを変更すればよいのですが、その前にGameクラスにメソッドを追加します。
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 |
public class Game { static int _openCount = 0; public static int GetAddScore(int col, int row) { _openCount++; int[] dx = { 0, -1, -1, -1, 0, 1, 1, 1 }; int[] dy = { 1, 1, 0, -1, -1, -1, 0, 1 }; for(int i=0; i<8; i++) { int nrow = row + dy[i]; int ncol = col + dx[i]; if (nrow < 0 || ncol < 0 || nrow >= RowMax || ncol >= ColMax) continue; // すでに開いている0が書かれたセルの隣が安全なのは簡単にわかるので配点はつねに1 if (IsMapOpen[nrow, ncol] && Map[nrow, ncol] == 0) return 1; } return _openCount; } } |
またステージクリアした場合は_openCountを0に戻さなければなりません。
1 2 3 4 5 6 7 8 9 |
public class Game { public static void Init() { _openCount = 0; // 以降は既存のものと同じ } } |
あとは新しく定義したGame.GetAddScoreメソッドで追加すべき点数を取得して加点処理をおこないます。
1 2 3 4 5 6 7 8 9 10 |
public class MinesweeperHub : Hub { void AddScore(Player player, int col, int row) { int add = Game.GetAddScore(col, row); player.Score += add; Console.WriteLine(add); // デバッグ用 } } |
最後のマスを開いたプレイヤーにボーナスポイント
最後のセルを開いたプレイヤーにボーナスポイントを追加するのであればMinesweeperHub.CheckClearメソッドを修正します。
スコアを追加するだけではプレイヤー自身のスコアの表示は変わらないので、1000点を追加したあともう一度ReceiveOpenを送信します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class MinesweeperHub : Hub { async Task CheckClear(Player player, int col, int row) { if (Game.GetUnopenedCellCount() == 0) { await Clients.All.SendAsync("ReceiveCleared"); System.Timers.Timer timer = new System.Timers.Timer(); timer.Interval = 3000; timer.Elapsed += Timer_Elapsed; timer.Start(); // 以下2行を追加 player.Score += 1000; await Clients.Caller.SendAsync("ReceiveOpen", player.Score, row, col); } } } |
クリア時に成績優秀者を表示
クリア時に成績優秀者を表示する機能ですが、成績優秀者はクリア時にゲームオーバーになっていない、そのステージで多くのセルを開いたプレイヤーと定義します。
サーバーサイドにおける処理
そのステージにおいてどれだけのセルを開いたのかを記憶しておかなければなりません。そこで以下のプロパティとメソッドを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Player { public int OpenCount { set; get; } public int ScoreOnStage { set; get; } // 次のステージが開始されるときにリセットする public void AllClear() { OpenCount = 0; ScoreOnStage = 0; _flags.Clear(); } } |
MinesweeperHubクラスのAddScoreメソッドが実行されたときに、プレイヤーが開いたセルとそのステージの得点を記憶させます。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class MinesweeperHub : Hub { void AddScore(Player player, int col, int row) { int add = Game.GetAddScore(col, row); player.Score += add; // そのステージで何枚開いたか、得点を記録する player.OpenCount++; player.ScoreOnStage += add; } } |
あとはステージクリアになったときにプレイヤーのなかから上位3名を探してボーナスポイントを手にしたプレイヤー名と上位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 |
public class MinesweeperHub : Hub { async Task CheckClear(Player player, int col, int row) { if (Game.GetUnopenedCellCount() == 0) { await Clients.All.SendAsync("ReceiveCleared"); System.Timers.Timer timer = new System.Timers.Timer(); timer.Interval = 3000; timer.Elapsed += Timer_Elapsed; timer.Start(); player.Score += 1000; await Clients.Caller.SendAsync("ReceiveOpen", player.Score, row, col); // 上位3名を取得 List<Player> players = Game.Players.Values.OrderByDescending(p => p.OpenCount).ToList(); if (players.Count > 3) players = players.Take(3).ToList(); List<string> vs = new List<string>(); // 1000点追加するプレイヤー名を表示 vs.Add(String.Format("{0} に BONUS POINT 1000点", player.Name.Replace(",", "_"))); // 上位3名を表示 foreach (Player p in players) vs.Add(String.Format("{0} ({1} - {2})", p.Name.Replace(",", "_"), p.OpenCount, p.ScoreOnStage)); string str = String.Join(",", vs.ToArray()); await Clients.All.SendAsync("ReceiveShowRanking", str); } } } |
クライアントサイドにおける処理
クライアントサイドではReceiveShowRankingを受信したらカンマ区切りの文字列を配列に格納します。そしてReceiveDrawを受信したときにこれをcanvas上に描画します。
wwwroot\mine-sweeper\app.js
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 |
let rankingArray = []; connection.on("ReceiveShowRanking", function (rankingText) { if(rankingText != '') rankingArray = rankingText.split(','); else rankingArray = []; }); connection.on("ReceiveDraw", function (rowMax, colMax, str) { if(cellsOpenedYourself.length == 0) InitCellsOpenedYourself(rowMax, colMax); ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); let arr = str.split(',') let index = 0; for(let row=0; row<rowMax; row++) { for(let col=0; col<colMax; col++) { DrawCell(col, row, arr[index]); index++; } } oldCellArray = arr; oldRowMax = rowMax; oldColMax = colMax; // これを追加 ShowRanking(); }); function ShowRanking(){ if(rankingArray.length == 0) return; ctx.font="bold 30px MS ゴシック"; for(let i =0; i<rankingArray.length; i++) { // 文字がみえにくいので矩形で囲む const mesure = ctx.measureText(rankingArray[i]); let w = mesure.width; let h = mesure.actualBoundingBoxAscent + mesure.actualBoundingBoxDescent; ctx.fillStyle="#000000"; ctx.fillRect(10,50 + i * 50 - h, w, h + 10); ctx.fillStyle="#ffffff"; ctx.fillText(rankingArray[i],10, 50 + i * 50); } } |
ランキングの消去
次のステージが開始されたらcanvas上のランキングは消します。クライアントサイドにReceiveHideRankingを送信します。
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 class MinesweeperHub : Hub { private void Timer_Elapsed(object? sender, ElapsedEventArgs e) { if (sender == null) return; System.Timers.Timer t = (System.Timers.Timer)sender; t.Stop(); t.Dispose(); Game.Init(); foreach (Player player in Game.Players.Values) player.AllClear(); Task.Run(async() => { foreach (var client in ClientProxyMap.Values) { await client.SendAsync("ReceiveHideRanking"); await client.SendAsync("ReceiveDraw", Game.RowMax, Game.ColMax, Game.GetStringFromMap(null)); await client.SendAsync("ReceiveUnopenedCellCount", Game.GetUnopenedCellCount()); } }); } } |
クライアントサイドではrankingArray配列を空にします。
wwwroot\mine-sweeper\app.js
1 2 3 |
connection.on("ReceiveHideRanking", function () { rankingArray = []; }); |