これまでは対コンピュータ対戦型のぷよぷよもどきのゲームを作ってきましたが、対人対戦型のものをつくります。動画は対コンピュータ対戦のものですが、リンク先は対人対戦型のものです。
これまで対人対戦型のゲームはいくつか作成してきましたが、基本的に途中から参加でき、途中で抜けることができるタイプのものでした。最初に自分の対戦相手を決めるタイプのものは作ってこなかったので、これを機に作ってみることにします。
その方法ですが、ページにアクセスしてプレイヤーとして登録したときにすでに登録されている人がいればその人との対戦がはじまり、誰も登録されていなければ誰から登録するまで待ち続けることにします。ふたりを1セットにしてGameクラスのインスタンスを作成してそこでゲームに関する処理をおこないます。対コンピュータ戦のようなアルゴリズムを考える必要はないと安易に考えていたら、けっこうハマりどころがありました。
ではさっそく作成することにします。名前空間はPuyoMatchとします。C#で書かれたソースはPages\PuyoMatchに置きます。それからNET 6.0をエックスサーバーにインストールするで示している手順が完了していることを前提としています。
Contents
流用するクラス
PuyoType列挙体とPuyoクラス、FallingPuyoクラス、Fieldクラスは対コンピュータ対戦型でつかっていたものをそのまま使います。ASP.NET Core版 対コンピュータ対戦できるぷよぷよをつくる(1)を参照してください。
PuyoMatchGameクラスの定義
PuyoMatchGameクラスを定義してそこでFieldクラスのインスタンスを2つ生成します。キー操作によるぷよの移動処理や送り込まれるおじゃまぷよの数の計算はここでおこないます。落下させた場合の結果や実際におじゃまぷよを落下させる処理はPuyoクラスでおこなわれます。
プレイヤーがふたりいるので、それぞれの状態を管理するためのPlayerInfoクラスを定義します。ここに格納されるのはプレイヤーの名前、AspNetCore.SignalRで接続したときのIDです。またHTMLでプレイヤー名が表示されるので名前のなかに <や>があったときにこれをエスケープした文字列を取得するためのプロパティも定義しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
namespace PuyoMatch { public class PlayerInfo { public PlayerInfo(string CconnectionId, string playerName) { ConnectionId = CconnectionId; PlayerName = playerName; } public string ConnectionId { get; } public string PlayerName { get; } public string EscapedPlayerName { get { return PlayerName.Replace("<", "<").Replace(">", ">"); } } } |
次にPuyoMatchGameクラスの本体の定義ですが、名前空間は省略します。
1 2 3 4 5 6 7 8 9 |
using System; using System.Data; namespace PuyoMatch { public class PuyoMatchGame { } } |
と書くべきところを以下のように省略して表記します。
1 2 3 |
public class PuyoMatchGame { } |
プロパティ
PuyoMatchGameクラスで定義されているプロパティを示します。
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 |
public class PuyoMatchGame { // 現在プレイ中か? public bool IsPlaying { private set; get; } // プレイしている2人のAspNetCore.SignalR接続時のID public string[] ConnectionIds { get; } // プレイしている2人のプレイヤー名 public string[] PlayerNames { get; } public string[] EscapedPlayerName { get; } // フィールド public Field[] Fields { get; } // プレイしている両者の←キーは押下されているか? public bool[] IsLeftKeyDowns { set; get; } // プレイしている両者の→キーは押下されているか? public bool[] IsRightKeyDowns { set; get; } // プレイしている両者の↓キーは押下されているか? public bool[] IsDownKeyDowns { set; get; } // 1秒ごとに自然落下するが、trueの場合は無視する bool[] IgnoreTimerForPuyoDowns { set; get; } // 1つのぷよが固定されるまでに何回キー操作がおこなわれたか? int[] MoveCounts { set; get; } // 両者のスコア public int[] Scores { private set; get; } // 両者の予告ぷよ public int[] OjamaCounts { private set; get; } // クライアントサイドの送信するぷよの状態(カンマ区切りの文字列) // 両者の固定されているぷよの行(上から何行目?) public string[] FixedPuyoRowsTexts { private set; get; } // 両者の固定されているぷよの列(左から何列目?) public string[] FixedPuyoColsTexts { private set; get; } // 両者の固定されているぷよの種類 public string[] FixedPuyoTypesTexts { private set; get; } // 両者の固定されているぷよに表示される連鎖数 public string[] FixedPuyoRensasTexts { private set; get; } // 両者の落下中のぷよの行(上から何行目?) public string[] FallingPuyoRowsTexts { private set; get; } // 両者の落下中のぷよの列(左から何列目?) public string[] FallingPuyoColsTexts { private set; get; } // 両者の落下中のぷよの種類 public string[] FallingPuyoTypesTexts { private set; get; } // 両者のPing値 public int[] Pings { private set; get; } } |
コンストラクタ
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 |
public class PuyoMatchGame { System.Timers.Timer _timerForPuyoDown = new System.Timers.Timer(); const int OJAMA_RATE = 70; public PuyoMatchGame(PlayerInfo[] playerInfos) { PlayerInfos = playerInfos; ConnectionIds = new string[2]; PlayerNames = new string[2]; EscapedPlayerNames = new string[2]; Fields = new Field[2]; IsLeftKeyDowns = new bool[2]; IsRightKeyDowns = new bool[2]; IsDownKeyDowns = new bool[2]; IgnoreTimerForPuyoDowns = new bool[2]; MoveCounts = new int[2]; Scores = new int[2]; OjamaCounts = new int[2]; FixedPuyoRowsTexts = new string[2]; FixedPuyoColsTexts = new string[2]; FixedPuyoTypesTexts = new string[2]; FixedPuyoRensasTexts = new string[2]; FallingPuyoRowsTexts = new string[2]; FallingPuyoColsTexts = new string[2]; FallingPuyoTypesTexts = new string[2]; Pings = new int[2]; ConnectionIds[0] = playerInfos[0].ConnectionId; ConnectionIds[1] = playerInfos[1].ConnectionId; PlayerNames[0] = playerInfos[0].PlayerName; PlayerNames[1] = playerInfos[1].PlayerName; EscapedPlayerNames[0] = playerInfos[0].EscapedPlayerName; EscapedPlayerNames[1] = playerInfos[1].EscapedPlayerName; Fields[0] = new Field(); Fields[1] = new Field(); Fields[0].Updated += FieldPlayer0_Updated; Fields[1].Updated += FieldPlayer1_Updated; Fields[0].Fixed += FieldPlayer0_Fixed; Fields[1].Fixed += FieldPlayer1_Fixed; Fields[0].BeginRensa += FieldPlayer0_BeginRensa; Fields[1].BeginRensa += FieldPlayer1_BeginRensa; Fields[0].Rensa += FieldPlayer0_Rensa; Fields[1].Rensa += FieldPlayer1_Rensa; Fields[0].FinishRensa += FieldPlayer0_FinishRensa; Fields[1].FinishRensa += FieldPlayer1_FinishRensa; Fields[0].OjamaFalling += FieldPlayer0_OjamaFalling; Fields[1].OjamaFalling += FieldPlayer1_OjamaFalling; Fields[0].SendNext += FieldPlayer0_SendNext; Fields[1].SendNext += FieldPlayer1_SendNext; Fields[0].GameOvered += FieldPlayer0_GameOvered; Fields[1].GameOvered += FieldPlayer1_GameOvered; Scores[0] = 0; Scores[1] = 0; _timerForPuyoDown.Interval = 1000; _timerForPuyoDown.Elapsed += Timer_Elapsed; } } |
終了処理
ゲームが終わったらそれ以上の処理が行なわれないようにイベントハンドラをRemoveします。
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 |
public class PuyoMatchGame { public void Uninitialize() { Fields[0].Fixed -= FieldPlayer0_Fixed; Fields[1].Fixed -= FieldPlayer1_Fixed; Fields[0].BeginRensa -= FieldPlayer0_BeginRensa; Fields[1].BeginRensa -= FieldPlayer1_BeginRensa; Fields[0].FinishRensa -= FieldPlayer0_FinishRensa; Fields[1].FinishRensa -= FieldPlayer1_FinishRensa; Fields[0].Rensa -= FieldPlayer0_Rensa; Fields[1].Rensa -= FieldPlayer1_Rensa; Fields[0].Updated -= FieldPlayer0_Updated; Fields[1].Updated -= FieldPlayer1_Updated; Fields[0].SendNext -= FieldPlayer0_SendNext; Fields[1].SendNext -= FieldPlayer1_SendNext; Fields[0].GameOvered -= FieldPlayer0_GameOvered; Fields[1].GameOvered -= FieldPlayer1_GameOvered; Fields[0].OjamaFalling -= FieldPlayer0_OjamaFalling; Fields[1].OjamaFalling -= FieldPlayer1_OjamaFalling; IsPlaying = false; _timerForPuyoDown.Elapsed -= Timer_Elapsed; _timerForPuyoDown.Stop(); } } |
ゲーム開始時の処理
Field.Initメソッドでフィールドを初期化して各プロパティを初期化します。そして組ぷよを自然落下させるためのタイマーをStartさせます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class PuyoMatchGame { public void GameStart() { Task.Run(async () => { await Fields[0].Init(); await Fields[1].Init(); Scores[0] = 0; Scores[1] = 0; OjamaCounts[0] = 0; OjamaCounts[1] = 0; IsPlaying = true; _timerForPuyoDown.Start(); }); } } |
キー操作時の処理
キー操作時の処理を示します。引数でどちらのプレイヤーによる操作なのかを区別しています。
キーが押されたらフラグをセットしてキーが押されているあいだ処理を繰り返します。キーが離されたときは外部からフラグをクリアする処理がおこなわれます。またキー操作をするとIgnoreTimerForPuyoDowns[n]フラグをセットします。これがtrueだと次のタイマーによる落下を1回分パスします。いつまでもキー操作で時間稼ぎができないように上限値(16回まで)を設定しています。
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 |
public class PuyoMatchGame { public async void OnKeydownLeftKey(bool isFirstPlayers) { if (!_timerForPuyoDown.Enabled) return; if (isFirstPlayers) { IsLeftKeyDowns[0] = true; if (MoveCounts[0]++ < 16) IgnoreTimerForPuyoDowns[0] = true; Fields[0].MoveLeft(); await Task.Delay(300); while (IsLeftKeyDowns[0]) { Fields[0].MoveLeft(); await Task.Delay(50); } } else { IsLeftKeyDowns[1] = true; if (MoveCounts[1]++ < 16) IgnoreTimerForPuyoDowns[1] = true; Fields[1].MoveLeft(); await Task.Delay(300); while (IsLeftKeyDowns[1]) { Fields[1].MoveLeft(); await Task.Delay(50); } } } public async void OnKeydownRightKey(bool isFirstPlayers) { if (!_timerForPuyoDown.Enabled) return; if (isFirstPlayers) { IsRightKeyDowns[0] = true; if (MoveCounts[0]++ < 16) IgnoreTimerForPuyoDowns[0] = true; Fields[0].MoveRight(); await Task.Delay(300); while (IsRightKeyDowns[0]) { Fields[0].MoveRight(); await Task.Delay(50); } } else { IsRightKeyDowns[1] = true; if (MoveCounts[1]++ < 16) IgnoreTimerForPuyoDowns[1] = true; Fields[1].MoveRight(); await Task.Delay(300); while (IsRightKeyDowns[1]) { Fields[1].MoveRight(); await Task.Delay(50); } } } public async void OnKeydownDownKey(bool isFirstPlayers) { if (isFirstPlayers) { IsDownKeyDowns[0] = true; if (MoveCounts[0]++ < 16) IgnoreTimerForPuyoDowns[0] = true; while (IsDownKeyDowns[0]) { await Fields[0].FallOne(); await Task.Delay(50); } } else { IsDownKeyDowns[1] = true; if (MoveCounts[1]++ < 16) IgnoreTimerForPuyoDowns[1] = true; while (IsDownKeyDowns[1]) { await Fields[1].FallOne(); await Task.Delay(50); } } } } |
回転時の処理
ZキーやXキーが押下されたら回転の処理をおこないます。
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 |
public class PuyoMatchGame { public delegate void GameHandler(PuyoMatchGame game, bool isFirstPlayer); public event GameHandler? Rotated; public void RotateLeft(bool isFirstPlayers) { if (isFirstPlayers) { Field field = Fields[0]; if (MoveCounts[0]++ < 16) IgnoreTimerForPuyoDowns[0] = true; if (Fields[0].Rotate(false)) Rotated?.Invoke(this, true); } else { Field field = Fields[1]; if (MoveCounts[1]++ < 16) IgnoreTimerForPuyoDowns[1] = true; if (Fields[1].Rotate(false)) Rotated?.Invoke(this, false); } } public void RotateRight(bool isFirstPlayers) { if (isFirstPlayers) { Field field = Fields[0]; if (MoveCounts[0]++ < 16) IgnoreTimerForPuyoDowns[0] = true; if (Fields[0].Rotate(true)) Rotated?.Invoke(this, true); } else { Field field = Fields[1]; if (MoveCounts[1]++ < 16) IgnoreTimerForPuyoDowns[1] = true; if (Fields[1].Rotate(true)) Rotated?.Invoke(this, false); } } } |
Timer.Elapsedイベントが発生したら落下中の組ぷよを自然落下させます。このときにIgnoreTimerForPuyoDowns[n]フラグがセットされているときはなにもしません。この場合は1回分だけパスしたいのでIgnoreTimerForPuyoDowns[n]フラグは常にセットします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class PuyoMatchGame { private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { Task.Run(async () => { if (!IgnoreTimerForPuyoDowns[0]) await Fields[0].FallOne(); IgnoreTimerForPuyoDowns[0] = false; }); Task.Run(async () => { if (!IgnoreTimerForPuyoDowns[1]) await Fields[1].FallOne(); IgnoreTimerForPuyoDowns[1] = false; }); } } |
データ更新時の処理
ぷよの位置が変更された場合などデータが更新されたときの処理を示します。
ぷよの状態をクライアントサイドにカンマ区切りの文字列として送信したいので、その文字列を生成する処理を最初に示します。
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 |
public class PuyoMatchGame { // それぞれの固定されたぷよは上から何段目? string CreateFixedPuyoRowsText(Puyo[,]? field) { if (field == null) return ""; int rowMax = field.GetLength(0); int colMax = field.GetLength(1); List<int> rows = new List<int>(); for (int row = 0; row < rowMax; row++) { for (int col = 0; col < colMax; col++) rows.Add(row); } return string.Join(",", rows); } // それぞれの固定されたぷよは左から何列目? string CreateFixedPuyoColsText(Puyo[,]? field) { if (field == null) return ""; int rowMax = field.GetLength(0); int colMax = field.GetLength(1); List<int> cols = new List<int>(); for (int row = 0; row < rowMax; row++) { for (int col = 0; col < colMax; col++) cols.Add(col); } return String.Join(",", cols); } // Puyoオブジェクトから0~4または-1の整数値を得る int GetNumberFromPuyo(Puyo puyo) { if (puyo.Type == PuyoType.None) return 0; else if (puyo.Type == PuyoType.Type1) return 1; else if (puyo.Type == PuyoType.Type2) return 2; else if (puyo.Type == PuyoType.Type3) return 3; else if (puyo.Type == PuyoType.Type4) return 4; else if (puyo.Type == PuyoType.Ojama) return -1; else return 0; } // それぞれの固定されたぷよの種類 string CreateFixedPuyoTypesText(Puyo[,]? field) { if (field == null) return ""; int rowMax = field.GetLength(0); int colMax = field.GetLength(1); List<int> types = new List<int>(); for (int row = 0; row < rowMax; row++) { for (int col = 0; col < colMax; col++) { types.Add(GetNumberFromPuyo(field[row, col])); } } return String.Join(",", types); } // それぞれの固定されたぷよに表示される連鎖数 string CreateFixedPuyoRensasText(Puyo[,]? field) { if (field == null) return ""; int rowMax = field.GetLength(0); int colMax = field.GetLength(1); List<int> rensas = new List<int>(); for (int row = 0; row < rowMax; row++) { for (int col = 0; col < colMax; col++) rensas.Add(field[row, col].Rensa); } return String.Join(",", rensas); } } |
落下中のぷよについても同様に文字列を取得する処理を定義します。
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 |
public class PuyoMatchGame { // それぞれの落下中のぷよは上から何段目? string CreateFallingPuyoRowsText(FallingPuyo? mainPuyo, FallingPuyo? subPuyo, List<FallingPuyo> ojamaPuyos) { List<int> fallingRows = new List<int>(); fallingRows.AddRange(ojamaPuyos.Select(_ => _.Row)); if (mainPuyo != null && subPuyo != null) { fallingRows.Add(mainPuyo.Row); fallingRows.Add(subPuyo.Row); } return String.Join(",", fallingRows); } // それぞれの落下中のぷよは左から何列目? string CreateFallingPuyoColsText(FallingPuyo? mainPuyo, FallingPuyo? subPuyo, List<FallingPuyo> ojamaPuyos) { List<int> fallingCols = new List<int>(); fallingCols.AddRange(ojamaPuyos.Select(_ => _.Col)); if (mainPuyo != null && subPuyo != null) { fallingCols.Add(mainPuyo.Col); fallingCols.Add(subPuyo.Col); } return String.Join(",", fallingCols); } // それぞれの落下中のぷよの種類 string CreateFallingPuyoTypesText(FallingPuyo? mainPuyo, FallingPuyo? subPuyo, List<FallingPuyo> ojamaPuyos) { List<int> fallingTypes = new List<int>(); fallingTypes.AddRange(ojamaPuyos.Select(_ => GetNumberFromPuyo(_))); if (mainPuyo != null && subPuyo != null) { fallingTypes.Add(GetNumberFromPuyo(mainPuyo)); fallingTypes.Add(GetNumberFromPuyo(subPuyo)); } return String.Join(",", fallingTypes); } } |
前述のメソッドでそれぞれのフィールドに存在するぷよの状態を文字列に変換して、各プロパティに格納します。
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 PuyoMatchGame { private void FieldPlayer0_Updated(Puyo[,] field, FallingPuyo? mainPuyo, FallingPuyo? subPuyo, List<FallingPuyo> fallingOjamaPuyos) { FixedPuyoRowsTexts[0] = CreateFixedPuyoRowsText(field); FixedPuyoColsTexts[0] = CreateFixedPuyoColsText(field); FixedPuyoTypesTexts[0] = CreateFixedPuyoTypesText(field); FixedPuyoRensasTexts[0] = CreateFixedPuyoRensasText(field); FallingPuyoRowsTexts[0] = CreateFallingPuyoRowsText(mainPuyo, subPuyo, fallingOjamaPuyos); FallingPuyoColsTexts[0] = CreateFallingPuyoColsText(mainPuyo, subPuyo, fallingOjamaPuyos); FallingPuyoTypesTexts[0] = CreateFallingPuyoTypesText(mainPuyo, subPuyo, fallingOjamaPuyos); } private void FieldPlayer1_Updated(Puyo[,] field, FallingPuyo? mainPuyo, FallingPuyo? subPuyo, List<FallingPuyo> fallingOjamaPuyos) { FixedPuyoRowsTexts[1] = CreateFixedPuyoRowsText(field); FixedPuyoColsTexts[1] = CreateFixedPuyoColsText(field); FixedPuyoTypesTexts[1] = CreateFixedPuyoTypesText(field); FixedPuyoRensasTexts[1] = CreateFixedPuyoRensasText(field); FallingPuyoRowsTexts[1] = CreateFallingPuyoRowsText(mainPuyo, subPuyo, fallingOjamaPuyos); FallingPuyoColsTexts[1] = CreateFallingPuyoColsText(mainPuyo, subPuyo, fallingOjamaPuyos); FallingPuyoTypesTexts[1] = CreateFallingPuyoTypesText(mainPuyo, subPuyo, fallingOjamaPuyos); } } |
ぷよが固定されたときの処理
ぷよが固定されたときはイベントを発生させるとともにIsDownKeyDowns[n]フラグをクリアするとともにMoveCounts[n]をリセットします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class PuyoMatchGame { public delegate void FixedHandler(PuyoMatchGame game, bool isPlayer); public event FixedHandler? Fixed; private void FieldPlayer0_Fixed(object? sender, EventArgs e) { IsDownKeyDowns[0] = false; MoveCounts[0] = 0; Fixed?.Invoke(this, true); } private void FieldPlayer1_Fixed(object? sender, EventArgs e) { IsDownKeyDowns[1] = false; MoveCounts[1] = 0; Fixed?.Invoke(this, false); } } |
連鎖発生時の処理
連鎖発生時はおじゃまぷよをどれだけ敵側に送り込めるか?その前に相殺はあるのかを調べます。
1 2 3 4 5 6 7 8 9 |
public class PuyoMatchGame { // 連鎖発生から終了するまでに加算された点数の合計 int _addScore0 = 0; int _addScore1 = 0; // 連鎖発生から終了するまでに仮に確定したおじゃまぷよの数 int[] _tempOjamaCounts = new int[2]; } |
連鎖発生時の処理を示します。上記のフィールド変数を初期化します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class PuyoMatchGame { private void FieldPlayer0_BeginRensa(object? sender, EventArgs e) { _tempOjamaCounts[0] = 0; _addScore0 = 0; } private void FieldPlayer1_BeginRensa(object? sender, EventArgs e) { _tempOjamaCounts[1] = 0; _addScore1 = 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 72 |
public class PuyoMatchGame { public delegate void RensaHandler(PuyoMatchGame game, bool isPlayer, int rensa); public event RensaHandler? Rensa; public delegate void GameOffsetedHandler(PuyoMatchGame game, bool isPlayer, int offsetCount); public event GameOffsetedHandler? Offseted; void CalculateOjamapuyo(int rensa, int addScore, bool isPlayer0) { int index = 0; if(!isPlayer0) index = 1; Scores[index] += addScore; // 現段階の追加点の総計とおじゃまレートから送り込める数と前回の連鎖で確定している分との増分を計算する int puyoCount; if (isPlayer0) { _addScore0 += addScore; puyoCount = _addScore0 / OJAMA_RATE; } else { _addScore1 += addScore; puyoCount = _addScore1 / OJAMA_RATE; } int incremental = puyoCount - _tempOjamaCounts[index]; _tempOjamaCounts[index] = puyoCount; // 増分があるなら相殺があるなら相殺する // 相殺後、敵側に送り込めるおじゃまぷよの数が増えたのであれば加算する if (incremental > 0) { if (OjamaCounts[index] > 0) { // 相殺の結果、すべて相殺された。自分の側の予告ぷよを0にし、あまりの部分を敵側におくりこむ if (OjamaCounts[index] < incremental) { Fields[index].RemoveOjamaPuyo(OjamaCounts[index]); Offseted?.Invoke(this, isPlayer0, OjamaCounts[index]); incremental -= OjamaCounts[index]; OjamaCounts[index] = 0; } else // 相殺の結果、相殺しきれなかった。敵側にはおくりこまない。相殺分の予告ぷよを減らす。 { Fields[index].RemoveOjamaPuyo(incremental); Offseted?.Invoke(this, isPlayer0, incremental); OjamaCounts[index] -= incremental; incremental = 0; } } if(isPlayer0) OjamaCounts[1] += incremental; else OjamaCounts[0] += incremental; } Rensa?.Invoke(this, isPlayer0, rensa); } private void FieldPlayer0_Rensa(int rensa, int addScore) { CalculateOjamapuyo(rensa, addScore, true); } private void FieldPlayer1_Rensa(int rensa, int addScore) { CalculateOjamapuyo(rensa, addScore, false); } } |
連鎖終了時の処理を示します。
ここでは確定したおじゃまぷよを送り込みます。連鎖発生の確定していた予告ぷよの数と比較して増えていれば増分を送り込みます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class PuyoMatchGame { private void FieldPlayer0_FinishRensa(object? sender, EventArgs e) { // 連鎖発生の確定していた予告ぷよの数と比較して増えていれば増分を送り込む if(Fields[1].GetYokokuCount() < OjamaCounts[1]) { Fields[1].SetOjamaPuyo(OjamaCounts[1] - Fields[1].GetYokokuCount()); } } private void FieldPlayer1_FinishRensa(object? sender, EventArgs e) { if (Fields[0].GetYokokuCount() < OjamaCounts[0]) { Fields[0].SetOjamaPuyo(OjamaCounts[0] - Fields[0].GetYokokuCount()); } } } |
おじゃまぷよ落下時の処理
おじゃまぷよを落下させるときはイベントを発生させるとともに、落下した分だけ予告ぷよの数を減らします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class PuyoMatchGame { public event OjamaFalledHandler? OjamaFalling; public delegate void OjamaFalledHandler(PuyoMatchGame game, bool isPlayer, int ojamaCount); private void FieldPlayer0_OjamaFalling(int ojamaCount) { OjamaCounts[0] -= ojamaCount; OjamaFalling?.Invoke(this, true, ojamaCount); } private void FieldPlayer1_OjamaFalling(int ojamaCount) { OjamaCounts[1] -= ojamaCount; OjamaFalling?.Invoke(this, false, ojamaCount); } } |
次のぷよの確定時の処理
落下中のぷよが固定されて次のぷよが確定したらクライアントサイドにぷよの種類を示す文字列を送信するのですが、その文字列を生成する処理を先に示します。
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 |
public class PuyoMatchGame { int GetNumberFromPuyo(FallingPuyo puyo) { if (puyo.Type == PuyoType.None) return 0; else if (puyo.Type == PuyoType.Type1) return 1; else if (puyo.Type == PuyoType.Type2) return 2; else if (puyo.Type == PuyoType.Type3) return 3; else if (puyo.Type == PuyoType.Type4) return 4; else if (puyo.Type == PuyoType.Ojama) return -1; else return 0; } string CreateTextFromNextPuyo(FallingPuyo main, FallingPuyo sub) { int[] ints = new int[2]; ints[0] = GetNumberFromPuyo(main); ints[1] = GetNumberFromPuyo(sub); return String.Join(",", ints); } } |
つぎのぷよが確定したら、SendNextイベントを発生させ、引数として上記メソッドで生成した文字列を送ります。
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 PuyoMatchGame { public delegate void SendNextHandler(PuyoMatchGame game, bool isPlayer, string[] nextPuyoTexts); public event SendNextHandler? SendNext; private void FieldPlayer0_SendNext(FallingPuyo[] nextMainSub1, FallingPuyo[] nextMainSub2) { IgnoreTimerForPuyoDowns[0] = true; string nextMainSubText1 = CreateTextFromNextPuyo(nextMainSub1[0], nextMainSub1[1]); string nextMainSubText2 = CreateTextFromNextPuyo(nextMainSub2[0], nextMainSub2[1]); string[] nextPuyoTexts = new string[2] { nextMainSubText1, nextMainSubText2 }; SendNext?.Invoke(this, true, nextPuyoTexts); } private void FieldPlayer1_SendNext(FallingPuyo[] nextMainSub1, FallingPuyo[] nextMainSub2) { IgnoreTimerForPuyoDowns[1] = true; string nextMainSubText1 = CreateTextFromNextPuyo(nextMainSub1[0], nextMainSub1[1]); string nextMainSubText2 = CreateTextFromNextPuyo(nextMainSub2[0], nextMainSub2[1]); string[] nextPuyoTexts = new string[2] { nextMainSubText1, nextMainSubText2 }; SendNext?.Invoke(this, false, nextPuyoTexts); } } |
ゲームオーバー時の処理
片方がゲームオーバーになったらそのプレイヤーの負けです。IsPlayingフラグをクリアしてタイマーを停止します。LostGameイベントを発生させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class PuyoMatchGame { public delegate void GameOveredHandler(PuyoMatchGame game, bool isPlayer); public event GameOveredHandler? LostGame; private void FieldPlayer0_GameOvered(object? sender, EventArgs e) { IsPlaying = false; _timerForPuyoDown.Stop(); LostGame?.Invoke(this, true); } private void FieldPlayer1_GameOvered(object? sender, EventArgs e) { IsPlaying = false; _timerForPuyoDown.Stop(); LostGame?.Invoke(this, false); } } |