あんみんどうふです。
前回は手札を配る所まで作りました。
 今回はカードを場に出して次の番へ進められる所まで作ります。
前回の記事はこちら。
UI
まず画面が無いと始まらないので適当にコントロールを配置します。
とりあえずこんな感じになりました。
 各種コントロールは以下の通りです。これからもう少し増える予定ですが、暫定としてとりあえず。
Label
labelRemainCards・・・自身の手札残り枚数。
 labelMsg・・・現在の状況をテキストとして表示する領域。
Button
buttonStart・・・ゲーム開始ボタン。ゲーム中は表示されない。
 buttonConfirm・・・手札を選んで確定するボタン。
 buttonPassMe・・・カードを出せない時にパスをして次の番へ進めるボタン。
PictureBox
pictureBoxCardNow1・・・現在場に出ているカードの1枚目。
 pictureBoxCardNow2・・・現在場に出ているカードの2枚目。
 pictureBoxCardNow3・・・現在場に出ているカードの3枚目。
 pictureBoxCardNow4・・・現在場に出ているカードの4枚目。
CheckedListBox
checkedListBoxMyCards・・・自身の手札。出すカードにチェックを入れる。
フォームのBackColor(背景色)はテーブルゲームっぽい色にしています。SeaGreen(#2e8b57)です。
関数もろもろ
コントロールを用意したら次に細かい関数をいろいろ用意していきます。
メッセージ出力
前回コメントアウトしていた関数その1、「WriteMsg」です。labelMsgにテキストを表示させる関数を作ります。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /// <summary> /// メッセージを書き込む(改行を含む)。 /// </summary> /// <param name="message">ログ内容</param> /// <param name="postscript">Trueなら改行して追記</param> void WriteMsg(string message, bool postscript = false) {     if (postscript)     {         string msg = "";         if (labelMsg.Text != "") msg = Environment.NewLine;         labelMsg.Text += msg + message;     }     else labelMsg.Text = message; } | 
WriteMsg(“メッセージの内容”)と打つだけでlabelMsgに反映されます。
 第2引数のboolはTrueを入れると改行して追記するようにしてます(デフォルトはFalse)。
関数化すると”いざコントロールがまるっと変わることになった時”に関数内3箇所の「labelMsg.Text」を変えるだけでよくなります。
 最初はこの部分をLabelではなくTextBoxで作っていたので一度助けられました。
プレイヤーの名前を取得する
プレイヤーIDから名前を取得して返します。
 自身のIDと一致する場合「あなた」を返し、それ以外は「CPU {ID}」の文字列を返しています。
| 1 2 3 4 5 6 7 8 9 10 11 12 | /// <summary> /// プレイヤー名を返す。 /// </summary> /// <param name="playerId">プレイヤーID</param> /// <returns>プレイヤー名</returns> string GetPlayerName(int playerId) {     string result;     if (playerId == playerYou) result = "あなた";     else result = "CPU" + playerId;     return result; } | 
カード画像一括読込
前回の記事でコメントアウトしていた関数その2、「ReadCardImage」です。
 実行ファイルと同フォルダ内の「img」フォルダから「{種類ID}-{カード番号}.png」(例:♥の6→「1-6.png」)を読み込んで配列cardImgに格納しています。
| 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 | public Image[] cardImg; // カード画像 /// <summary> /// カード画像を読み込む。 /// </summary> void ReadCardImage() {     // 画像配列を初期化     cardImg = new Image[53];     // imgフォルダの(マークID)-(数字).pngを順に読み込む     for (int i = 0; i < 5; i++)     {         for (int j = 0; (i < 4 && j < 13) || (i == 4 && j == 0); j++)         {             string location = $@"img\{i}-{j + 1}.png";             if (File.Exists(location) && cardImg[i * 13 + j] == null)             {                 //Debug.WriteLine($"Reading {location}");                 cardImg[i * 13 + j] = Image.FromFile(location);             }             else             {                 ErrorMessage($@"画像の読み込みに失敗しました。{"\n"}参照したソース: {location}");                 return;             }         }     } } | 
ちなみにカードの画像はチコデザ様より拝借してます。
 トランプのイラスト – オリジナル全53枚のカード無料素材
場に出ているカードを表示
カードを出したり消したりした時に呼び出す関数「UpdateViewCardNow」です。
 cardsNow(場に出ているカード)が1枚でもあれば先程のReadCardImageで読み込んだ画像を各PictureBoxに表示します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /// <summary> /// 現在場に出ているカードを表示(更新)する。 /// </summary> void UpdateViewCardNow() {     PictureBox[] picCardsNow =         { pictureBoxCardsNow1, pictureBoxCardsNow2, pictureBoxCardsNow3, pictureBoxCardsNow4 };     for(int i = 0; i < cardsNow.Length; i++)     {         if (cardsNow[i] != null && cardsNow[i].Id > 0)             picCardsNow[i].Image = cardImg[cardsNow[i].Id - 1];         else             picCardsNow[i].Image = null;     } } | 
場に出ているカードをすべて消す
カードをリセットします。1巡分終わる時に使います。
 コードは至って単純で、cardsNow内全てにID0のカード(空)を入れてるだけ。
| 1 2 3 4 5 6 7 8 | /// <summary> /// 場に出ているカードを全て消す(リセットする)。 /// </summary> void CardsReset() {     for(int i = 0; i < cardsNow.Length; i++)         cardsNow[i] = new Card(0); } | 
自身の所持するカードのリストを更新
checkedListBoxMyCardsの項目を更新します。
 Listから自身の手札を取得してソートし、データソースとして格納しています。
 ついでに残り枚数のテキストも更新しています。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /// <summary> /// 自身の所持カード更新 /// </summary> void UpdateCardList() {     // 全ての項目のチェックを外す     for (int i = 0; i < checkedListBoxMyCards.Items.Count; i++)         checkedListBoxMyCards.SetItemChecked(i, false);     // 独自クラス型のListに所持カードを入れる     List<Card> cmb = new List<Card>();     foreach(Card card in cards[playerYou]) cmb.Add(card);     // 強さで昇順ソートし、CheckedListBoxに格納     cmb = cmb.OrderBy(x => x.Power).ThenBy(x => x.Id).ToList();     checkedListBoxMyCards.DataSource = cmb;     labelRemainCards.Text = $"残り枚数: {checkedListBoxMyCards.Items.Count}"; } | 
カードを場に出す
プレイヤーとカードIDを指定してカードを場に出します。
| 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 | /// <summary> /// 指定したカードを場に出す。 /// </summary> /// <param name="targetPlayer">対象プレイヤー</param> /// <param name="cardId1">カード(1枚目) ※必須</param> /// <param name="cardId2">カード(2枚目)</param> /// <param name="cardId3">カード(3枚目)</param> /// <param name="cardId4">カード(4枚目)</param> void CardsPlay(int targetPlayer, int cardId1, int cardId2 = 0, int cardId3 = 0, int cardId4 = 0) {     bool[] judge = new bool[4];     int[] cardIds = { cardId1, cardId2, cardId3, cardId4 };     Debug.WriteLine($"CardsPlay: 場に出す枚数・・・ {cardIds.Count(x => x != 0)}枚");     // 各カードがプレイヤーの手札に存在するかチェック     // 存在する => True, 存在しない => False     for (int i = 0; i < cardIds.Length; i++)     {         Debug.WriteLine(cards[targetPlayer].Any(x => x.Id == cardIds[i]));         if (cards[targetPlayer].Any(x => x.Id == cardIds[i]) || cardIds[i] == 0)             judge[i] = true;     }     // 全てTrueならカードを場に出す     if(judge[0] && judge[1] && judge[2] && judge[3])     {         for(int i = 0; i < cardIds.Length && cardIds[i] != 0; i++)         {             // カードを場に出す             cardsNow[i] = new Card(cardIds[i]);             // 手札から削除する             cards[targetPlayer].RemoveAll(x => x.Id== cardIds[i]);         }         Debug.WriteLine($"CardsPlay[成功]: {cardId1}-{cardId2}-{cardId3}-{cardId4}");     }     else     {         Debug.WriteLine($"CardsPlay[失敗]: {cardId1}:{judge[0]}, {cardId2}:{judge[1]}, {cardId3}:{judge[2]}, {cardId4}:{judge[3]}");     } } | 
引数で対象プレイヤー、対象カード1~4枚を指定し、そのプレイヤーがカードを所持しているかチェックします。
 カードを持っている or 指定無し(IDが0)ならフラグをTrueにし、4枚分すべてのフラグがTrueならカードを場に出し、手札から削除するようにしています。
Debug~から始まる部分は各値をコンソールに出力していますが、デバッグ用なので無視していいです。
次の番へ進める
ここまで紹介した関数を使い、次の番へ進める関数「NextPlayer」を書いていきます。
playerNow(現在のプレイヤーID)を増やして番を進める
 ↓
 必要ならリセット処理をし、メッセージ表示
 ↓
 次のプレイヤーへ
みたいな流れです。
| 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 | /// <summary> /// 番を進める。 /// </summary> async void NextPlayer() {     // 現在の番のプレイヤーを進める     playerNow++;     // プレイヤー数を超えたら0に戻す     if (playerNow >= playerCount) playerNow = 0;     // 1周したら場に出ているカードをリセットする     if (playerParent == playerNow)     {         WriteMsg("1周したので場のカードは回収されます。");         await Task.Delay(speed);         CardsReset();         UpdateViewCardNow();         WriteMsg($"親プレイヤー {GetPlayerName(playerParent)} の番です。", true);     }     else     {         WriteMsg($"現在のカードは {cardsNow[0].MsgName} です。");         WriteMsg($"{GetPlayerName(playerNow)} の番です。", true);     }     // 現在のプレイヤーが自身以外ならCPUのAI処理実行     if (DEBUG_AUTO || playerNow != playerYou)     {         checkedListBoxMyCards.Enabled = false;         buttonConfirm.Enabled = false;         buttonPassMe.Enabled = false;         //CpuAI();     }     else     {         checkedListBoxMyCards.Enabled = true;         buttonConfirm.Enabled = true;         buttonPassMe.Enabled = true;     } } | 
32行目の関数「CpuAI」をコメントアウトしていますが、これはCPUのAIに関するものです。長くなるのでこれは次回に回します。
ゲーム開始ボタン
buttonStartのClickイベントを作ります。これが無いと始まりません。
| 1 2 3 4 5 6 7 8 9 | private void buttonStart_Click(object sender, EventArgs e) {     NextPlayer();           // 番を進める     UpdateCardList();       // 手持ちカードリスト更新     UpdateViewCardNow();    // 場に出ているカード表示     buttonStart.Visible = false;     buttonConfirm.Visible = true;     buttonPassMe.Visible = true; } | 
初期化処理ちょっと修正
ReadCardImageとWriteMsgを作ったので、前回作った関数「Initialize」の97、98行目のコメントアウトを削除します。後ろから2行分です。
| 1 2 3 4 5 |     ...     ReadCardImage();        // カード画像読み込み     WriteMsg($"今回の親プレイヤーは {GetPlayerName(playerParent)} です。"); } | 
また、こんなルールがありましたね。(Wikipedia引用)
ゲームはダイヤの3から始める。最初の親が手札から最初のカードを出し、以降順番に次のプレイヤーがカードを出し重ねていく。
前回はカードを配る部分しか作っていなかったので、♢の3を持つプレイヤーが親となる所までは出来ていましたが、場に出す処理は入れていないので昇順ソートの次の行に関数CardsPlayを追記します。
 前回の記事で言うと84行目辺り。
| 1 2 3 4 5 6 7 | // 各プレイヤーの手持ちのカードを昇順ソートする for (int i = 0; i < playerCount; i++)     cards[i] = cards[i].OrderBy(x => x.Power).ThenBy(x => x.Id).ToList(); // ダイヤの3(29)を場に出して次のプレイヤーの番へ CardsPlay(playerNow, 29); ... | 
実行してみる
ここまで書いたら実行します。
今回は私が♢の3を持っているようなので親プレイヤーになりました。
 Startをクリックします。
♢の3が場に出され、手札が表示され、メッセージも表示されました。
 番がCPU1へと進んでいます。
 また、自身の手札の内容がリストに表示されていることも確認できます。
ごちゃごちゃと書いてますが、まだこのくらいしかできていません。次回はプレイヤーが選んだカードを場に出したり、CPUに選ばせたりできるようにします。
つづく、かも。





















