前回までにババ抜きの大枠完成させました。
ただ不満な点も残っています。
Contents
大枠はできたが不満な点も
相手のカードをクリックしたらすぐにつぎの順番がまわってきます。
また実際に人とババ抜きをするのであれば相手はカードを何枚もっているのか、相手がカードを引いた時カードはそろったのかといった情報も知ることができます。相手がカードをひいて自分に順番が回ってくるまでに一定の時間がかかります。
そこでもっと実際のババ抜きらしいアプリケーションを作成することにします。
相手が持っているカードの数を表示させる
まずは相手がどれだけのカードを持っているのかを表示させましょう。
そこでデザイナで以下のようなユーザーコントロールをつくります。
ユーザーコントロールのサイズは(500, 30)、ラベルを3枚(高さは23ピクセル)貼り付けています。ユーザーコントロールのなまえはUserControlPlayerInfoにしました。
またForm1の下部にpanel3を追加しました。この部分にプレイヤーの数だけユーザーコントロールが追加されます。
CreatePlayerメソッドを以下のように変更します。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); CreateCards(); CreatePlayer(); this.BackColor = Color.Green; } List<Player> Players = new List<Player>(); void CreatePlayer() { Players.Add(new Player("あなた")); Players.Add(new Player("佐藤さん")); Players.Add(new Player("鈴木さん")); Players.Add(new Player("高橋さん")); Players.Add(new Player("田中さん")); ShowPlayerInfo(); // これを追加 } void ShowPlayerInfo() { int posY = 20; foreach(Player player in Players) { UserControlPlayerInfo info = new UserControlPlayerInfo(); info.Location = new Point(0, posY); info.Parent = panel3; posY += info.Height; } // 自分のものだけ他とは距離をとる Players[0].PlayerInfo.Location = new Point(0, 0); } } |
実行結果はこうなります。
プレイヤーからこれに対応するユーザーコントロール UserControlPlayerInfoを取得できるように、Playerクラスに以下のプロパティを加えます。
1 2 3 4 5 6 7 8 |
public class Player { public UserControlPlayerInfo PlayerInfo { set; get; } } |
先述のShowPlayerInfoメソッドを以下のように変更すれば、これでプレイヤーの名前と残り枚数、その他の情報を表示させることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class Form1 : Form { void ShowPlayerInfo() { int posY = 0; foreach(Player player in Players) { UserControlPlayerInfo info = new UserControlPlayerInfo(); info.Location = new Point(0, posY); info.Parent = panel3; posY += info.Height; player.PlayerInfo = info; // これを追加 } } } |
またゲームが開始される以前であればなにも表示させる必要はないのでPlayerInfoにはなにも表示されていない状態にしたほうがよいでしょう。
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 UserControlPlayerInfo : UserControl { public UserControlPlayerInfo() { InitializeComponent(); // なにも表示させない labelName.Text = ""; labelCount.Text = ""; labelInfo.Text = ""; // ラベルの周囲の境界線を消す labelName.BorderStyle = BorderStyle.None; labelCount.BorderStyle = BorderStyle.None; labelInfo.BorderStyle = BorderStyle.None; // フォントの設定 Font font = new Font("MS ゴシック", 12, FontStyle.Bold); labelName.Font = font; labelCount.Font = font; labelInfo.Font = font; labelName.ForeColor = Color.White; labelCount.ForeColor = Color.White; labelInfo.ForeColor = Color.White; } } |
それから表示させるものはプレイヤーの名前、カードの残数、その他情報なので、以下のメソッドも追加しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class UserControlPlayerInfo : UserControl { public void ShowCardsCount(int a) { labelCount.Text = "残り " + a.ToString() + "枚"; } public void ShowPlayerName(string str) { labelName.Text = str; } public void ShowPlayerInfo(string str) { labelInfo.Text = str; } } |
GameStartメソッドに以下を追加するとプレイヤーに関するデータが表示されます。
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 |
public partial class Form1 : Form { void GameStart() { if(isRivalsTurn) { // この部分は後述 MessageBox.Show("処理が終わるまでお待ちください"); return; } // 表示内容をいったんクリア foreach(Player player in Players) { player.IsFinished = false; player.PlayerInfo.ShowPlayerName(player.Name); player.PlayerInfo.ShowCardsCount(0); player.PlayerInfo.ShowPlayerInfo(""); } // カードを配る HandoutCards(); foreach(Player player in Players) { player.PlayerInfo.ShowCardsCount(player.Cards.Count); // カード残数を表示 } // ここから下は以前とかわらず panel1.Invalidate(); panel2.Invalidate(); IsMyTurn = true; rank = 1; finishedPlayers.Clear(); } } |
すると実行結果はこんな感じになります。
次にゲームの進行によってカード残数がどのように変更するかを表示させます。
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 |
public partial class Form1 : Form { int RivalsTurn1() { List<Player> players1 = Players.Where(x => !x.IsFinished).ToList(); int playersCount = players1.Count; for(int i = 1; i < playersCount; i++) { if(players1[i].Cards.Count != 0) { int rand = random.Next(0, players1[i - 1].Cards.Count); players1[i].PullCard(players1[i - 1], rand); // カードを引いたり引かせることであがってしまうかもしれない if(players1[i].Cards.Count == 0) { players1[i].IsFinished = true; PlayerFinished(players1[i]); } else { // あがっていないのであればカードの残数を表示させる players1[i].PlayerInfo.ShowCardsCount(players1[i].Cards.Count); } if(players1[i - 1].Cards.Count == 0) { players1[i - 1].IsFinished = true; PlayerFinished(players1[i - 1]); } else { // あがっていないのであればカードの残数を表示させる players1[i - 1].PlayerInfo.ShowCardsCount(players1[i - 1].Cards.Count); } if(i-1 == 0) panel2.Invalidate(); } } return Players.Count(x => !x.IsFinished); } } |
全プレイヤーのカード残数が一気にかわる問題点を改善
これで一応、カードの残数は表示されます。しかし全プレイヤーのカード残数が一気にかわります。できればひとりがカードをひくごとに結果がかわるようにしたいものです。そのほうがゲームの進行が自然にみえます。そこでこのように変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { int RivalsTurn1() { List<Player> players1 = Players.Where(x => !x.IsFinished).ToList(); int playersCount = players1.Count; for(int i = 1; i < playersCount; i++) { if(players1[i].Cards.Count != 0) { // 追加したのはこれだけ! System.Threading.Thread.Sleep(1000); } } return Players.Count(x => !x.IsFinished); } } |
カードを引くたび1秒間停止します。しかしこれだけではうまくいきません。RivalsTurn1の呼び出し元にも変更を加える必要があります。
asyncをつけました。それから
1 2 |
Task<int> task = Task.Run<int>(() => RivalsTurn1()); int result = await task; |
とすることで1秒ごとに結果を表示してくれるようになります。
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 |
bool isRivalsTurn = false; public partial class Form1 : Form { async void RivalsTurn() { isRivalsTurn = true; IsMyTurn = false; Task<int> task = Task.Run<int>(() => RivalsTurn1()); int result = await task; if(Players[0].IsFinished) { while(RivalsTurn2() >= 2) ; } IsMyTurn = true; panel1.Invalidate(); panel2.Invalidate(); isRivalsTurn = false; } } |
非同期処理をすると「有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール‘XXXX’がアクセスされました」という例外が発生することがあります。そこでその対策が必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class UserControlPlayerInfo : UserControl { public void ShowCardsCount(int a) { Invoke((Action)(() => { labelCount.Text = "残り " + a.ToString() + "枚"; })); } public void ShowPlayerInfo(string str) { Invoke((Action)(() => { labelInfo.Text = str; })); } } |
処理中に新しいゲームを開始しようとするとどうなる?
isRivalsTurnというフィールド変数を使用していますが、これはこの処理が行なわれているあいだに[ゲームスタート]をクリックすると、処理が停止されることなく新しくカードが配られてしまうという問題がおきるからです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { void GameStart() { if(isRivalsTurn) { MessageBox.Show("処理が終わるまでお待ちください"); return; } // これより下はこれまでと同じ // 省略 } } |