今回はプレイヤーが出したカードが正しいものかどうかの判定とコンピュータがカードを出す処理を実装します。
最初に適切なカードが出されたかどうかを判定する処理を実装する前に、カードから素数または合成数を求める処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { int GetNumber(List<Card> cards) { List<int> vs = cards.Select(card => card.Number).ToList(); int ret = 0; foreach (int i in vs) { if (i < 10) ret *= 10; else ret *= 100; ret += i; } return ret; } } |
GetNumberメソッドにはList
決定ボタンが押されたあとの処理
決定ボタンが押されたら出されたカードは素数なのか、または素因数がすべて出されている合成数なのか判定がおこなわれます。ルール上、枚数が間違っていた場合はカードを出すこと自体認められませんが、その条件をクリアしていればカードの正当性が判定され、間違っていた場合はペナルティーを受けることになります。
ペナルティーは間違って出したカードが返されるとともに、出そうとした枚数と同じ数だけ山札からカードを取らなければなりません。いまの段階で全部完璧にはできないので、いまはカードが返されるだけにしています。またグロタンカットやラマヌジャン革命、ジョーカーの存在もいまはカットしています。これはあとで考えます。
途中、await Task.Delay(500)と書かれている部分がありますが、これは出されたカードが正しくなかった場合、すぐにカードをプレイヤーに返されるように見せるためです。これがないとコンピュータがカードを出す処理を終えるまでカードが返されないように見えてしまうのです。
場に出されたカードは自分以外の全員(といっても相手はコンピュータひとりですが……)がパスをしたときに流されますが、相手が素因数場に出したカードは手番が代わると同時に流れます。そのようなルールになっているようです。
自分の手番が終わったらコンピュータの手番になります。ちょっと注意しなければならないこととして、カードを出した直後(パスをした場合も同様)からコンピュータの手番が終了するまでのあいだ、プレイヤーによって他のボタンが押されると困った問題がおきます。そこでボタンを一時的に使用不可にする処理が必要です。これはパスのボタンがクリックされたときも必要です。
これはボタンをまとめて使用可にしたり使用不可にするメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { void EableButtons(bool eable) { ButtonDraw.Enabled = eable; ButtonDecision.Enabled = eable; ButtonPass.Enabled = eable; ButtonClear.Enabled = eable; ButtonGameStart.Enabled = eable; ButtonX.Enabled = eable; } } |
以下が決定ボタンが押されたときにおこなわれる処理です。
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 |
public partial class Form1 : Form { async private void ButtonDecision_Click(object sender, EventArgs e) { // コンピュータが素因数場に出したカードは手番が代わると同時に流れる Cards.AddRange(CardsPutByComp2); CardsPutByComp2.Clear(); if (CountCardsPutMust != -1) { string str = ""; if (CountCardsPutMust != CardsPutByPlayer1.Count) str += $"場に出せるカードは{CountCardsPutMust}枚限定です。"; if (MinValueCards > GetNumber(CardsPutByPlayer1)) str += $"場に出せるカードは{MinValueCards}以上になるようなカードを出してください"; LabelPlayerErrer.Text = str; if (str != "") return; } int cardCount = 0; long minValue = 0; // ボタンをまとめて一時的に使用不可にする EableButtons(false); // 素因数として出されているが確定されていないものがあれば確定させる if (TempPrimefactors.Count > 0) { Primefactors.Add(GetNumber(TempPrimefactors)); TempPrimefactors.Clear(); } if (Primefactors.Count == 0) CheckPlayerCards1(ref cardCount, ref minValue); // 素数として出された else CheckPlayerCards2(ref cardCount, ref minValue); // 合成数として出された Primefactors.Clear(); TempPrimefactors.Clear(); PlayerText1 = ""; PlayerText2 = ""; LabelPlayer.Text = "あなた:"; CheckBoxPrimeFactor.Checked = false; // 判定の結果、カードの状態がどうなったのかを描画する PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); ShowCards(); await Task.Delay(500); // プレイヤーのカードが0になっていないのであればコンピュータの手番に if (PlayerCards.Count == 0) { LabelMessage.Text = "あなたの勝ちです。"; ButtonGameStart.Enabled = true; return; } CompPutCards(cardCount, minValue, ref CountCardsPutMust, ref MinValueCards); if (CompCards.Count == 0) { LabelMessage.Text = "コンピュータの勝ちです。"; ButtonGameStart.Enabled = true; return; } // ボタンをまとめてクリック可能な状態に戻す EableButtons(true); // コンピュータのカードが0になっていないのであれば再びプレイヤーの手番に // どのようなカードを出さなければならないかLabelに表示させる if (CountCardsPutMust != -1) LabelMessage.Text = $"{CountCardsPutMust}枚のカードで{MinValueCards}以上の数を出してください"; else LabelMessage.Text = $"コンピュータはパスをしました。あなたが親です。"; } } |
出されたカードは間違っていないか?
プレイヤーによって出されたカードの正当性をチェックする処理を示します。素数かどうかをみて素数でなければプレイヤーに返されます。素数であれば両者が場に出したカードを一時的に貯めておくCardsPutByAllに格納し、CardsPutByPlayer1内のオブジェクトはクリアしてしまいます。
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 |
public partial class Form1 : Form { void CheckPlayerCards1(ref int cardCount, ref long minValue) { cardCount = this.CardsPutByPlayer1.Count; long number = GetNumber(CardsPutByPlayer1); string mes = ""; if (IsPrimeNumber(number)) { minValue = number + 1; // カードを移動させる CardsPutByAll.AddRange(CardsPutByPlayer1); CardsPutByPlayer1.Clear(); } else { mes = $"{number}は素数ではありません"; // 素数でない場合、カードは返却される PlayerCards.AddRange(CardsPutByPlayer1); CardsPutByPlayer1.Clear(); PlayerCards.OrderBy(card => card.Number).ToList(); ShowCards(); minValue = -1; cardCount = -1; } LabelPlayerErrer.Text = mes; InputDataClear(); } } |
合成数出しの場合の処理を示します。
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 |
public partial class Form1 : Form { void CheckPlayerCards2(ref int cardCount, ref long minValue) { cardCount = CardsPutByPlayer2.Count; long number = GetNumber(CardsPutByPlayer1); int value = 1; foreach (int i in Primefactors) value *= i; string mes = ""; // 素因数の積は出された合成数に一致するか? if (value != GetNumber(CardsPutByPlayer1)) { string mes1 = String.Join(" × ", Primefactors.Select(x => x.ToString()).ToArray()); int compositeNumber = GetNumber(CardsPutByPlayer1); mes += $"{mes1}は{compositeNumber}にはなりません。"; cardCount = -1; minValue = -1; } // 素因数として出された数は本当に素数か? List<int> notPrimeNumbers = Primefactors.Where(x => !IsPrimeNumber(x)).Distinct().ToList(); if (notPrimeNumbers.Count > 0) { mes += String.Join(", ", notPrimeNumbers.Select(num => num.ToString()).ToArray()) + "は素数ではありません。"; cardCount = -1; minValue = -1; } // ここまできて cardCount が -1でないなら正当なカードであるといえる if (cardCount != -1) { minValue = number + 1; // カードを移動させる CardsPutByAll.AddRange(CardsPutByPlayer1); CardsPutByPlayer1.Clear(); // 素因数場のカードはすぐに流す Cards.AddRange(CardsPutByPlayer2); CardsPutByPlayer2.Clear(); } else { LabelPlayerErrer.Text = mes; // 出したカードは返却される PlayerCards.AddRange(CardsPutByPlayer1); PlayerCards.AddRange(CardsPutByPlayer2); CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); } // 出せないカードが出されていた場合はその詳細をLabelに表示する LabelPlayerErrer.Text = mes; } } |
コンピュータの手番における処理
コンピュータの手番の処理はCompPutCardsメソッドで行ないます。SearchNumberメソッドで出せるカードを探してカードを出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public partial class Form1 : Form { void CompPutCards(int cardCount, long minValue, ref int newCardCount, ref long newMinValue) { ResultCards resultCards = SearchNumber(false, cardCount, minValue); if (resultCards == null) resultCards = SearchNumber(true, cardCount, minValue); ShowResult(resultCards); if (resultCards == null) { newCardCount = -1; newMinValue = -1; Pass(); } else { newCardCount = resultCards.CardNumbers1.Count; newMinValue = resultCards.Number + 1; } } } |
出せるカードを探す
SearchNumberメソッドはコンピュータと対戦できるようにする C#で素数大富豪をつくる(5)のものとほとんど同じです。このメソッドのなかで呼び出されているGetCardsCanPutメソッドはこれと完全に同じです。
カードを探すときはスートは関係ないので手持ちのカードのなかから番号部分だけを取り出してリストをつくり、出せるカードを探しています。
それからResultCardsクラスですが、フィールド変数の名前を変えています。ResultCards.Card1とResultCards.Card2ではカードそのもののような名前なので、番号だけであることがわかりやすいようにResultCards.CardNumbers1、ResultCards.CardNumbers2へと変更しています。
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 partial class Form1 : Form { ResultCards SearchNumber(bool isPrimeNumber, int cardCount, long minValue) { // cardCount が -1 のときは親なので好きな枚数を出せる List<int> compCardsDealt = CompCards.Select(card => card.Number).ToList(); List<ResultCards> resultCardsList = new List<ResultCards>(); if(cardCount == 2 || cardCount == -1) resultCardsList.AddRange(GetCardsCanPut(compCardsDealt.ToArray(), 2)); if (cardCount == 3 || cardCount == -1) resultCardsList.AddRange(GetCardsCanPut(compCardsDealt.ToArray(), 3)); if (cardCount == 4 || cardCount == -1) resultCardsList.AddRange(GetCardsCanPut(compCardsDealt.ToArray(), 4)); if (cardCount == 1 || cardCount == -1) resultCardsList.AddRange(GetCardsCanPut(compCardsDealt.ToArray(), 1)); List<ResultCards> rets = new List<ResultCards>(); ResultCards NextBest = null; foreach (ResultCards resultCards in resultCardsList) { if(resultCards.Number < minValue) continue; if (resultCards.IsPrimeNumber != isPrimeNumber) continue; // 次に出せるカードがあるか調べておく List<int> copyCardsDealt = new List<int>(compCardsDealt); foreach (int card in resultCards.CardNumbers1) copyCardsDealt.Remove(card); foreach (int card in resultCards.CardNumbers2) copyCardsDealt.Remove(card); List<ResultCards> nextResultCardsList = new List<ResultCards>(); nextResultCardsList.AddRange(GetCardsCanPut(copyCardsDealt.ToArray(), 2)); nextResultCardsList.AddRange(GetCardsCanPut(copyCardsDealt.ToArray(), 3)); nextResultCardsList.AddRange(GetCardsCanPut(copyCardsDealt.ToArray(), 4)); nextResultCardsList.AddRange(GetCardsCanPut(copyCardsDealt.ToArray(), 1)); // 次に出せるカードがあるならそれをストックしておく if (nextResultCardsList.Count > 0) rets.Add(resultCards); // ないなら次善策として保存しておく else if (NextBest == null) NextBest = resultCards; // コンピュータの手持ちのカードをすべて消費できるならそのまま出す if (copyCardsDealt.Count == 0) return resultCards; } // 次の一手になりうるものが見つからない場合は次善策として保存していたものを使う if (rets.Count == 0 && NextBest != null) return NextBest; // 本当に次の一手が存在しないならnullを返す if (rets.Count == 0 && NextBest == null) return null; // 偶数のカードばかり残るとやりにくいので偶数のカードを使う手を優先する // 消費する偶数のカードがもっとも大きいものを次の一手とする List<ResultCards> rets2 = rets.Where(x => x.CardNumbers1.Where(card => card % 2 == 0 && card != 2).ToList().Count > 0).ToList(); if (rets2.Count > 0) { int max = rets2.Max(x => x.CardNumbers1.Count(card => card % 2 == 0 && card != 2)); ResultCards ret = rets.First(x => x.CardNumbers1.Where(card => card % 2 == 0 && card != 2).ToList().Count == max); return ret; } else return rets[0]; } } |
コンピュータが出すべきカードが見つかったら場に出します。このときCardsPutByComp1になにかが入っていれば前のターンで出したカードなのでCardsPutByAllへ移動させます。出せるカードが見つからなかった場合はコンピュータはカードを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 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 |
public partial class Form1 : Form { void ShowResult(ResultCards resultCards) { CardsPutByAll.AddRange(CardsPutByComp1); CardsPutByComp1.Clear(); List<int> compCardsDealt = CompCards.Select(card => card.Number).ToList(); if (resultCards != null) { // 出すべきカードをCompCardsからCardsPutByComp1、CardsPutByComp2へ移動させる foreach (int number in resultCards.CardNumbers1) { Card card = CompCards.FirstOrDefault(card0 => card0.Number == number); if (card != null) { CardsPutByComp1.Add(card); CompCards.Remove(card); } } foreach (int number in resultCards.CardNumbers2) { Card card = CompCards.FirstOrDefault(card0 => card0.Number == number); if (card != null) { CardsPutByComp2.Add(card); CompCards.Remove(card); } } // コンピュータが出したカードをLabelに表示する CompText2 = String.Join(" × ", resultCards.PrimeNumbers.ToArray()); CompText1 = String.Join("", resultCards.CardNumbers1.ToArray()); string str = ""; if (CompText2 == "") str = CompText1; else str = $"{CompText1} = {CompText2}"; LabelComp.Text = "コンピュータ: " + str; ShowCards(); } else { CompText1 = ""; CompText2 = ""; LabelComp.Text = "コンピュータ: "; // コンピュータの手持ちのカードが0でないのにresultCardsがnullの場合 // 出せるカードがないので山札から1枚とってパスをする if (compCardsDealt.Count > 0) { CompCards.Add(Cards[0]); Cards = Cards.Skip(1).ToList(); } } } } |
カードを出したときプレイに必要なボタンを一時的に操作不能にしましたが、ボタン操作不能時に勝負がついてしまう場合があります。この場合はゲームスタートのボタンがクリックされたときに他のボタンをクリックできるようにしておかなければならないし、ゲーム終了のときにはゲームスタートのボタンをクリックできるようにしておかなければなりません。
ButtonGameStart_Clickメソッドの最後でEableButtons(true)を実行しておけばよいでしょう。