合成数出しにおける指数表記にも対応させようとしたらコードを書き直す部分が多くなったので全部を掲載します。
Contents
Form1クラス以外のクラス
Form1クラス以外のクラスを示します。
Permutationクラス
Permutationクラスはコンピュータが出すカードを決めるときに必要な番号の順列を取得するためのものです。
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 Permutation { public IEnumerable<T[]> GetPermutations<T>(IEnumerable<T> items, int length) { return _GetPermutations<T>(new List<T>(), items.ToList(), length); } private IEnumerable<T[]> _GetPermutations<T>(IEnumerable<T> perm, IEnumerable<T> items, int length) { if (items.Count() == 0 || perm.Count() == length) { yield return perm.ToArray(); } else { foreach (var item in items) { var result = _GetPermutations<T>( perm.Concat(new T[] { item }), items.Where(x => x.Equals(item) == false), length); foreach (var xs in result) yield return xs.ToArray(); } } } } |
PrimeNumberFromCardsクラス
PrimeNumberFromCardsクラスはカードによって作られた素因数を表わすためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class PrimeNumberFromCards { public PrimeNumberFromCards(long primeNumber, List<int> cardNumbers) { PrimeNumber = primeNumber; CardNumbers = cardNumbers; } public long PrimeNumber = 0; public List<int> CardNumbers = new List<int>(); public int count = 1; } |
CardsCandidateクラス
CardsCandidateクラスはコンピュータが次に出すカードの候補を扱うためのものです。
1 2 3 4 5 6 7 |
public class CardsCandidate { public bool IsPrimeNumber = true; public long Number = 0; public List<int> CardNumbers = new List<int>(); public List<PrimeNumberFromCards> PrimeNumberCards = new List<PrimeNumberFromCards>(); } |
AlertControlクラス
AlertControlクラスはカードの選択ミスを指摘するメッセージを表示するためのものです。これまでのものと違ってTextプロパティを追加しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class AlertControl : UserControl { public AlertControl() { InitializeComponent(); } public event EventHandler ClickOK; private void ButtonOK_Click(object sender, EventArgs e) { ClickOK?.Invoke(this, new EventArgs()); } public new string Text { get { return label1.Text; } set { label1.Text = value; } } } |
Form1クラスの初期化
フィールド変数
Form1クラスのフィールド変数は以下のとおりです。
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 partial class Form1 : Form { // 山札 List<Card> Cards = new List<Card>(); // プレイヤーとコンピュータが持っているカード List<Card> CompCards = new List<Card>(); List<Card> PlayerCards = new List<Card>(); // プレイヤーが場と素因数場に出したカード List<Card> CardsPutByPlayer1 = new List<Card>(); List<Card> CardsPutByPlayer2 = new List<Card>(); // コンピュータが場と素因数場に出したカード List<Card> CardsPutByComp1 = new List<Card>(); List<Card> CardsPutByComp2 = new List<Card>(); // カードを出すときはCountCardsPutMustと同じ枚数で // MinValueCardsよりも大きな数を出さなければならない int CountCardsPutMust = -1; long MinValueCards = -1; // 確認とエラー表示用のユーザーコントロール VerificationControl VerificationControl1 = new VerificationControl(); AlertControl AlertControl1 = new AlertControl(); // ジョーカーを出すときに値を設定するためのユーザーコントロール JokerControl JokerControl1 = new JokerControl(); // 場に出されて流されていないカード List<Card> CardsPutByAll = new List<Card>(); // 乱数を取得するとき用のRandomオブジェクト Random Random1 = new Random(); } |
コンストラクタにおける処理
コンストラクタにおける処理を示します。ラベルのTextを空文字にしてユーザーコントロールとボタンを初期化します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); LabelMessage.Text = ""; LabelPlayer.Text = ""; LabelComp.Text = ""; LabelPlayerErrer.Text = ""; Size = new Size(700, 650); Ramanujan = false; InitUserControls(); InitButtons(); } } |
ボタン、ユーザーコントロールの初期化
ボタンを初期化します。はじめにクリックできるのは[ゲームスタート]のボタンだけです。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { void InitButtons() { ButtonDraw.Enabled = false; ButtonDecision.Enabled = false; ButtonPass.Enabled = false; ButtonClear.Enabled = false; ButtonX.Enabled = false; ButtonHat.Enabled = false; } } |
各ユーザーコントロールを初期化します。コントロールのボタンをクリックしたときのイベントに対応できるようにします。アプリケーションが開始されたときはどのコントロールも表示されません。必要なときに表示され、ボタンをクリックすると消えます。
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 InitUserControls() { VerificationControl1.Visible = false; VerificationControl1.ClickRun += VerificationOK_Click; VerificationControl1.ClickCancel += VerificationCancel_Click; VerificationControl1.Parent = this; VerificationControl1.Location = new Point(100, 100); AlertControl1.Visible = false; AlertControl1.ClickOK += AlertControlOK_Click; AlertControl1.Parent = this; AlertControl1.Location = new Point(100, 100); JokerControl1.Visible = false; JokerControl1.ClickRun += JokerControlOK_Click; JokerControl1.ClickCancel += JokerControlCancel_Click; JokerControl1.Parent = this; JokerControl1.Location = new Point(100, 100); } } |
プロパティ
革命が起きているかを示すプロパティです。ゲーム開始時は革命は起きていない状態に戻されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { bool _ramanujan = false; bool Ramanujan { get { return _ramanujan; } set { // 革命時は背景色を変える _ramanujan = value; if (_ramanujan) this.BackColor = Color.DarkRed; else this.BackColor = Color.Green; } } } |
プレイヤーが出したカードの数字を表示するプロパティです。PlayerText2に表示されている文字列を解析することで素数がだされているかどうか、合成数出しの計算が合っているかどうかを調べます。
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 { string _playerText1 = ""; string _playerText2 = ""; string PlayerText1 { set { _playerText1 = value; string str = ""; if (_playerText2 == "") str = _playerText1; else str = $"{_playerText1} = {_playerText2}"; LabelPlayer.Text = "あなた: " + str; } get { return _playerText1; } } string PlayerText2 { set { _playerText2 = value; string str = ""; if (_playerText2 == "") str = _playerText1; else str = $"{_playerText1} = {_playerText2}"; LabelPlayer.Text = "あなた: " + str; } get { return _playerText2; } } } |
コンピュータが出したカードの数字を表示するプロパティです。
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 { string _compText1 = ""; string _compText2 = ""; string CompText1 { set { _compText1 = value; string str = ""; if (_compText2 == "") str = _compText1; else str = $"{_compText1} = {_compText2}"; LabelComp.Text = "コンピュータ: " + str; } } string CompText2 { set { _compText2 = value; string str = ""; if (_compText2 == "") str = _compText1; else str = $"{_compText1} = {_compText2}"; LabelComp.Text = "コンピュータ: " + str; } } } |
ゲーム開始時の処理
ゲームの開始時の処理を示します。ゲームをリセットしてカードを新しく生成しシャッフルしたあとカードを配ります。そして最後にShowCardsメソッドでカードをフォーム上に表示させます。
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 { private void ButtonGameStart_Click(object sender, EventArgs e) { GameStart(); } void GameStart() { ResetGame(); CreateCards(); ShuffleCards(); DealCards(); // Labelのテキストをリセットする CompText1 = ""; CompText2 = ""; PlayerText1 = ""; PlayerText2 = ""; // プレイヤーのミスを表示する文字列をリセットする LabelPlayerErrer.Text = ""; CheckBoxPrimeFactor.Checked = false; ShowCards(); LabelMessage.Text = "あなたが親です。"; this.EableButtons(true); } } |
ゲームをリセットする
ゲームの途中でボタンが押されるかもしれないので、いったん両者が持っているカードをクリアします。同時に素因数場に出されているカードの情報もクリアします。
ゲーム開始直後であれば好きな枚数で好きな数を出せるのでフィールド変数 CountCardsPutMustとMinValueCardsには -1を設定します。革命はもとに戻します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public partial class Form1 : Form { void ResetGame() { CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); CardsPutByComp1.Clear(); CardsPutByComp2.Clear(); CompText1 = ""; CompText2 = ""; PlayerText1 = ""; PlayerText2 = ""; CheckBoxPrimeFactor.Checked = false; CountCardsPutMust = -1; MinValueCards = -1; Ramanujan = false; } } |
カードの生成とシャッフル
カードを生成するメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { void CreateCards() { Cards.Clear(); for (int i = 1; i <= 13; i++) { Cards.Add(new Card(Suit.Spade, i, 70, 95)); Cards.Add(new Card(Suit.Heart, i, 70, 95)); Cards.Add(new Card(Suit.Diamond, i, 70, 95)); Cards.Add(new Card(Suit.Club, i, 70, 95)); } for (int i = 0; i < 2; i++) Cards.Add(new Card(Suit.Joker, 0, 70, 95)); } } |
カードをシャッフルするメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public partial class Form1 : Form { void ShuffleCards() { List<Card> ret = new List<Card>(); Random random = new Random(); while (true) { int count = Cards.Count(); if (count == 0) break; int r = random.Next(count); ret.Add(Cards[r]); Cards.RemoveAt(r); } Cards = ret; } } |
カードを配るメソッドです。カードを配り終えたら見やすくするために番号が小さい順に並べます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { void DealCards() { PlayerCards = Cards.Take(13).ToList(); PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); Cards = Cards.Skip(13).ToList(); CompCards = Cards.Take(13).ToList(); CompCards = CompCards.OrderBy(card => card.Number).ToList(); Cards = Cards.Skip(13).ToList(); } } |
カードを表示する
カードを表示するために呼び出されるメソッドです。プレイヤーとコンピュータが持っているカードの位置、場と素因数場に出されているカードの位置を指定します。
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 |
public partial class Form1 : Form { void ShowCards() { int marginTop = 20; int marginLeft = 20; // コンピュータが持っているカードの位置を指定する int i = 0; foreach (Card card in CompCards) { card.X = i * 35 + marginLeft; card.Y = 0 + marginTop; i++; } // プレイヤーが持っているカードの位置を指定する i = 0; foreach (Card card in PlayerCards) { card.X = i * 35 + marginLeft; card.Y = 300 + marginTop; i++; } // コンピュータが場に出しているカードの位置を指定する i = 2; foreach (Card card in CardsPutByComp1) { card.X = i * 20 + marginLeft; card.Y = 100 + marginTop; i++; } // コンピュータが素因数場に出しているカードの位置を指定する i = 10; foreach (Card card in CardsPutByComp2) { card.X = i * 20 + marginLeft; card.Y = 100 + marginTop; i++; } // プレイヤーが場に出しているカードの位置を指定する i = 2; foreach (Card card in CardsPutByPlayer1) { card.X = i * 20 + marginLeft; card.Y = 200 + marginTop; i++; } // プレイヤーが素因数場に出しているカードの位置を指定する i = 10; foreach (Card card in CardsPutByPlayer2) { card.X = i * 20 + marginLeft; card.Y = 200 + marginTop; i++; } Invalidate(); } } |
カードを描画する処理を示します。コンピュータのカードは設定で表向きになるように設定されている場合は数字が見えるように、されていない場合は裏向きで表示されます。
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 partial class Form1 : Form { protected override void OnPaint(PaintEventArgs e) { foreach (Card card in CompCards) { if (CheckBoxShowCompCards.Checked) card.Draw(e.Graphics); else card.DrawBack(e.Graphics); } foreach (Card card in PlayerCards) card.Draw(e.Graphics); foreach (Card card in CardsPutByPlayer1) card.Draw(e.Graphics); foreach (Card card in CardsPutByPlayer2) card.Draw(e.Graphics); foreach (Card card in CardsPutByComp1) card.Draw(e.Graphics); foreach (Card card in CardsPutByComp2) card.Draw(e.Graphics); base.OnPaint(e); } } |
カードを出す処理
プレイヤーのカードがクリックされたときの処理を示します。カードを大きい順に並べ替えたリストを取得することで番号が見える部分をクリックするとそのカードが出された処理がおこなわれます。
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 { protected override void OnMouseClick(MouseEventArgs e) { if (ButtonDecision.Enabled) { List<Card> cards = PlayerCards.OrderByDescending(card => card.X).ToList(); Card firstCard = cards.FirstOrDefault(card => card.X < e.X && e.X < card.X + card.Size.Width && card.Y < e.Y && e.Y < card.Y + card.Size.Height); if (firstCard != null) { if (firstCard.Suit != Suit.Joker) PutCard(firstCard); else { JokerControl1.Visible = true; JokerControl1.Card = firstCard; } } } base.OnMouseClick(e); } } |
カードを出す処理です。カードが出される場所はどこかをCheckBoxの状態から判断してカードの表示位置を変更します。それと同時にPlayerText1プロパティまたはPlayerText2プロパティに数字を追加します。
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 { void PutCard(Card card) { int number = card.Number; if (!CheckBoxPrimeFactor.Checked) { PlayerText1 += number.ToString(); CardsPutByPlayer1.Add(card); PlayerCards.Remove(card); } else { PlayerText2 += number.ToString(); CardsPutByPlayer2.Add(card); PlayerCards.Remove(card); } ShowCards(); } } |
×ボタンが押されたときの処理を示します。PlayerText1プロパティまたはPlayerText2プロパティに文字列” × “を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { private void ButtonX_Click(object sender, EventArgs e) { InsertX(); } void InsertX() { if (!CheckBoxPrimeFactor.Checked) return; PlayerText2 += " × "; } } |
^ボタンが押されたときの処理を示します。PlayerText1プロパティまたはPlayerText2プロパティに文字列” ^ “を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { private void ButtonHat_Click(object sender, EventArgs e) { InsertHat(); } void InsertHat() { if (!CheckBoxPrimeFactor.Checked) return; PlayerText2 += " ^ "; } } |
[一枚取る]ボタンがクリックされたときの処理を示します。出したカードはいったん返却されます。
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 |
public partial class Form1 : Form { private void ButtonDraw_Click(object sender, EventArgs e) { PlayerDrawCard(); } void PlayerDrawCard() { // Enabled = falseにすることで 1回のターンにつき1枚しか取れないようにする ButtonDraw.Enabled = false; PlayerCards.Add(this.Cards[0]); Cards = Cards.Skip(1).ToList(); // 出したカードはいったん返却する PlayerCards.AddRange(CardsPutByPlayer1); PlayerCards.AddRange(CardsPutByPlayer2); CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); // Labelのテキストをクリア PlayerText1 = ""; PlayerText2 = ""; LabelPlayerErrer.Text = ""; CheckBoxPrimeFactor.Checked = false; ShowCards(); } } |
[クリア]ボタンがクリックされたときの処理を示します。場と素因数場に出されているカードをプレイヤーの元に戻します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public partial class Form1 : Form { private void ButtonClear_Click(object sender, EventArgs e) { PlayerClearCards(); } void PlayerClearCards() { LabelPlayerErrer.Text = ""; PlayerText1 = ""; PlayerText2 = ""; PlayerCards.AddRange(CardsPutByPlayer1); PlayerCards.AddRange(CardsPutByPlayer2); CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); CheckBoxPrimeFactor.Checked = false; ShowCards(); } } |
プレイヤーが出したカードは正しいか?
[決定]ボタンがクリックされたときの処理を示します。合成数出しをする場合だけ確認のメッセージを表示します。また明らかに出されているカードが間違っている場合はエラーを示すメッセージを表示させ、プレイヤーにカードの選択のやり直しを要求します。
素数かどうか、合成数出しの計算が合っているかどうかは実際にカードを出す処理のあとおこなわれます。間違っていた場合、設定によってはペナルティーの対象になります。
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 partial class Form1 : Form { private void ButtonDecision_Click(object sender, EventArgs e) { Decision(); } void Decision() { if (!CheckPlayerText1()) return; long compositeNumber = GetNumber(CardsPutByPlayer1); if (PlayerText2 != "") { if (!CheckPlayerText2()) return; string message = "合成数出しをしようとしています。\n"; message = PlayerText2 + " = " + compositeNumber + "で間違いないですか?"; VerificationControl1.Text = message; VerificationControl1.Visible = true; EableButtons(false); } else DecisionFinal(); } } |
事前チェック
プレイヤーが場に出したカードをチェックするメソッドです。[確認]ボタンがクリックされたときなにも出されていない、15桁を超える数になっている(素数の判定に時間がかかるので制限している)、親が出したカードの枚数と違っている場合などはやり直しです。
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 partial class Form1 : Form { bool CheckPlayerText1() { // チェックの結果、不正であると見なされた場合はfalseを返す。 // ShowAlertメソッドはつねにtrueを返すので逆を返している if (PlayerText1 == "") return !ShowAlert("なにかカードを指定してください"); if (PlayerText1.Length > 14) return !ShowAlert("カードがつくる値が巨大すぎます"); if (CountCardsPutMust != -1) { if (CountCardsPutMust != CardsPutByPlayer1.Count) return !ShowAlert($"場に出せるカードは{CountCardsPutMust}枚限定です。"); if (!Ramanujan && MinValueCards >= GetNumber(CardsPutByPlayer1)) return !ShowAlert($"場に出すカードでつくる数は{MinValueCards}より大きくなるようにしてください"); if (Ramanujan && MinValueCards <= GetNumber(CardsPutByPlayer1)) return !ShowAlert($"場に出すカードでつくる数は{MinValueCards}より小さくなるようにしてください"); } return true; } } |
プレイヤーが素因数場に出したカードをチェックするメソッドです。合成数出しなのに複数の素数が指定されていない、たとえば2×5のつもりで25になっていた、×や^の間に数字が入っていないなど実際のゲームではありえないミスの場合はペナルティーではなくやり直しの対象となります。
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 |
public partial class Form1 : Form { bool CheckPlayerText2() { string[] vs = PlayerText2.Replace(" ", "").Split(new string[] { "×" }, StringSplitOptions.None); foreach (string s1 in vs) { if (vs.Length == 1 && s1.IndexOf("^") == -1) return !ShowAlert("合成数出しのときは複数の素数を指定してください"); if (s1 == "") return !ShowAlert("合成数出しで指定した式があきらかに不正です"); if (s1 == "1") return !ShowAlert("1を素因数として用いることはできません"); if (s1.IndexOf("^") != -1) { string[] vs2 = s1.Split(new string[] { "^" }, StringSplitOptions.None); if (vs2[0] == "" || vs2[1] == "") return !ShowAlert("合成数出しで指定した式があきらかに不正です"); if (vs2[1] == "1" || vs2[1] == "0") return !ShowAlert("0と1を指数として用いることはできません"); } } return true; } } |
エラーを表示するユーザーコントロールを表示するメソッドです。実行されると必ずtrueを返します。
1 2 3 4 5 6 7 8 9 |
public partial class Form1 : Form { bool ShowAlert(string alert) { AlertControl1.Text = alert; AlertControl1.Visible = true; return true; } } |
カードの番号をつないで値をつくります。
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 |
public partial class Form1 : Form { long GetNumber(List<Card> cards) { List<int> vs = cards.Select(card => card.Number).ToList(); long ret = 0; foreach (int i in vs) { if (i < 10) ret *= 10; else ret *= 100; ret += i; } return ret; } long GetNumber(int[] numbers) { long ret = 0; foreach (int i in numbers) { if (i < 10) ret *= 10; else ret *= 100; ret += i; } return ret; } } |
確定後の処理
[決断]ボタンがクリックされ、確認のメーセージに対しても[OK]がクリックされたときの処理を示します。ここでは出したカードが正しいかどうかの判定がおこなわれ、そのあとコンピュータの手が示されます。
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 |
public partial class Form1 : Form { async void DecisionFinal() { // コンピュータが素因数場に出したカードは手番が代わると同時に流れる Cards.AddRange(CardsPutByComp2); CardsPutByComp2.Clear(); EableButtons(false); bool isForcePass = false; LabelMessage.Text = "判定中です"; await Task.Delay(200); // プレイヤーが出したカードは正しいかの判定 // isForcePassがtrueになっている場合(プレイヤーが57やジョーカーを出したとき)は // コンピュータは強制的にパスさせられる CheckPlayerCards(ref isForcePass); // プレイヤーの手札がすべてなくなればプレイヤーの勝ち if (CheckPlayerWin()) return; if (!isForcePass) { LabelMessage.Text = "コンピュータが考えています"; await Task.Delay(300); await CompPutCards(); // コンピュータの手札がすべてなくなればコンピュータの勝ち if (CheckCompWin()) return; } EableButtons(true); } } |
ボタンの有効無効の切り替え
EableButtonsメソッドは判定中にプレイヤーによって他のボタンが押されないようにボタンの有効無効を切り替えるためのメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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; ButtonHat.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 |
public partial class Form1 : Form { bool CheckPlayerWin() { if (PlayerCards.Count == 0) { LabelMessage.Text = "あなたの勝ちです。"; ButtonGameStart.Enabled = true; return true; } else return false; } bool CheckCompWin() { if (CompCards.Count == 0) { LabelMessage.Text = "コンピュータの勝ちです。"; ButtonGameStart.Enabled = true; return true; } else return 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 |
public partial class Form1 : Form { void CheckPlayerCards(ref bool isForcePass) { isForcePass = false; // 57が出されている場合はコンピュータは強制的にパスさせられる if (CheckPlayerGrothendieck()) { isForcePass = true; return; } // ジョーカーの1枚出しの場合はコンピュータは強制的にパスさせられる if (CheckSingleJocker()) { isForcePass = true; return; } if (CardsPutByPlayer2.Count == 0) CheckPlayerCards1(); // 素数として出された else CheckPlayerCards2(); // 合成数として出された PlayerText1 = ""; PlayerText2 = ""; CheckBoxPrimeFactor.Checked = false; // 結果を表示する PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); ShowCards(); } } |
特殊な数への対応
グロタンディーク素数切り(グロタンカット)かどうかを判定する処理を示します。57が出されていても合成数として出されている場合はなにもおきません。グロタンカットの場合、強制的に場が流されます。
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 { bool CheckPlayerGrothendieck() { if (CardsPutByPlayer1.Count == 2 && CardsPutByPlayer2.Count == 0 && GetNumber(CardsPutByPlayer1) == 57) { Pass(); // Pass()によって場に出されているカードが流される。 // CountCardsPutMustとMinValueCardsがリセットされる ShowCards(); LabelPlayerErrer.Text = ""; LabelMessage.Text = "グロタンディーク素数を出したのであなたが親です。"; return true; } return false; } } |
ジョーカーが1枚だけ出されたときは場が流れますが、その判定をおこなっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { bool CheckSingleJocker() { if (CardsPutByPlayer1.Count == 1 && CardsPutByPlayer2.Count == 0 && CardsPutByPlayer1[0].Suit == Suit.Joker) { Pass(); ShowCards(); LabelPlayerErrer.Text = ""; LabelMessage.Text = "ジョーカーを1枚出ししたのであなたが親です。"; return true; } return false; } } |
素数として出されたカードの判定
プレイヤーが出したカードが素数として出された場合はCheckPlayerCards1メソッドによって判定がおこなわれます。素数であればカードをCardsPutByAll(非表示なだけでまだ流されていないカード)に移動させます。素数でない場合、カードは返却され、設定によってはペナルティーとして出したカードと同数枚カードを取らされます。
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 partial class Form1 : Form { void CheckPlayerCards1() { CountCardsPutMust = CardsPutByPlayer1.Count; long number = GetNumber(CardsPutByPlayer1); string mes = ""; if (IsPrimeNumber(number) || number == 1729) { MinValueCards = number; // カードを移動させる CardsPutByAll.AddRange(CardsPutByPlayer1); CardsPutByPlayer1.Clear(); if (number == 1729) Ramanujan = Ramanujan != true ? true : false; } else { mes = $"{number}は素数ではありません"; // 素数でない場合、カードは返却される int penalty = CardsPutByPlayer1.Count; PlayerCards.AddRange(CardsPutByPlayer1); CardsPutByPlayer1.Clear(); PlayerCards.OrderBy(card => card.Number).ToList(); if (CheckBoxIsPenalty.Checked) { if (Cards.Count > penalty) { PlayerCards.AddRange(Cards.Take(penalty)); Cards = Cards.Skip(penalty).ToList(); } } ShowCards(); MinValueCards = -1; CountCardsPutMust = -1; } LabelPlayerErrer.Text = mes; } } |
その値が素数かどうかを調べるためのメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public partial class Form1 : Form { bool IsPrimeNumber(long number) { if (number == 0 || number == 1) return false; if (number == 2 || number == 3) return true; if (number % 2 == 0) return false; long lim = (long)Math.Ceiling(Math.Sqrt(number)); if (lim % 2 == 0) lim++; for (long n = 3; n <= lim; n += 2) { if (number % n == 0) return false; } return true; } } |
合成数として出された数の判定
プレイヤーが出したカードが合成数として出された場合はCheckPlayerCards2メソッドによって判定がおこなわれます。CalculatePlayerText2メソッドによってPlayerText2プロパティの文字列が評価され、合成数が生成されます。素因数場に出された素因数のなかに素数でないものが入っていた場合は最初にみつかった非素数が負数として返されます。
CalculatePlayerText2メソッドの戻り値と場に出されているカードの数と一致しない場合は不正な手であると判断され、ペナルティーの対象となります。
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() { CountCardsPutMust = CardsPutByPlayer1.Count; long number = GetNumber(CardsPutByPlayer1); long value = CalculatePlayerText2(); string mes = ""; if (value != GetNumber(CardsPutByPlayer1)) { long compositeNumber = GetNumber(CardsPutByPlayer1); if (value > 0) mes += $"{PlayerText2}は{compositeNumber}にはなりません。{PlayerText2}は{value}です。"; else if (value < 0) mes += $"素因数として指定されている {-value} は素数ではありません。"; MinValueCards = -1; CountCardsPutMust = -1; } if (CountCardsPutMust != -1) { MinValueCards = number; // カードを移動させる CardsPutByAll.AddRange(CardsPutByPlayer1); CardsPutByPlayer1.Clear(); // 素因数場のカードはすぐに流す Cards.AddRange(CardsPutByPlayer2); CardsPutByPlayer2.Clear(); } else { LabelPlayerErrer.Text = mes; // 出したカードは返却される int penalty = CardsPutByPlayer1.Count + CardsPutByPlayer2.Count; PlayerCards.AddRange(CardsPutByPlayer1); PlayerCards.AddRange(CardsPutByPlayer2); CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); if (CheckBoxIsPenalty.Checked) { if (Cards.Count > penalty) { PlayerCards.AddRange(Cards.Take(penalty)); Cards = Cards.Skip(penalty).ToList(); } } } LabelPlayerErrer.Text = mes; } } |
PlayerText2プロパティ文字列の評価
PlayerText2プロパティの文字列を評価して合成数を生成するためのメソッドです。文字列を”×”で分割し、さらに”^”で分割することで計算をしています。不正な値である場合は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 |
public partial class Form1 : Form { long CalculatePlayerText2() { string str = PlayerText2.Replace(" ", ""); string[] vs1 = str.Split(new string[] { "×" }, StringSplitOptions.None); long value = 1; foreach (string s1 in vs1) { // あるべきはずの素因数が存在しない if (s1 == "") return 0; // 素因数が1は不可 if (s1 == "1") return 0; // 指数表記の場合の解析 if (s1.IndexOf("^") == -1) { long value1 = long.Parse(s1); if (IsPrimeNumber(value1)) value *= value1; else return -value1; // 素因数が素数ではない場合は不可 } else { string[] vs2 = s1.Split(new string[] { "^" }, StringSplitOptions.None); if (vs2[0] == "" || vs2[1] == "" || vs2[1] == "1" || vs2[1] == "0") return 0; long value1 = long.Parse(vs2[0]); // 素因数が素数ではない場合は不可 if (!IsPrimeNumber(value1)) return -value1; long value2 = long.Parse(vs2[1]); value *= (int)Math.Pow(value1, value2); } } return value; } } |
コンピュータがカードを出す処理
コンピュータがカードを出す処理を示します。SearchNumberメソッドで候補手を探し、みつからない場合はパスとなります。SearchNumberメソッドの戻り値はShowResultメソッドに渡され、カードの移動がおこなわれます。
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 |
public partial class Form1 : Form { async Task CompPutCards() { if (CheckCompWin()) return; CardsCandidate resultCandidate = SearchNumber(false, CountCardsPutMust, MinValueCards); if (resultCandidate == null) resultCandidate = SearchNumber(true, CountCardsPutMust, MinValueCards); ShowResult(resultCandidate); if (resultCandidate == null) { Pass(); ShowCards(); } else { if (resultCandidate.IsPrimeNumber) { if (resultCandidate.CardNumbers.Count == 1 && GetNumber(resultCandidate.CardNumbers.ToArray()) == 0) { LabelMessage.Text = "コンピュータがジョーカーを出したのでこの場は流れます。"; await Task.Delay(1000); Pass(); await CompPutCards(); return; } if (resultCandidate.CardNumbers.Count == 2 && GetNumber(resultCandidate.CardNumbers.ToArray()) == 57) { LabelMessage.Text = "コンピュータが57を出したのでグロタンカットでこの場は流れます。"; await Task.Delay(1000); Pass(); await CompPutCards(); return; } } if (resultCandidate.Number == 1729 && resultCandidate.IsPrimeNumber) Ramanujan = Ramanujan != true ? true : false; CountCardsPutMust = resultCandidate.CardNumbers.Count; MinValueCards = resultCandidate.Number; } ShowNaviLabelText(); } } |
コンピュータが出したカードを移動させ、結果を表示するためのメソッドです。
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 |
public partial class Form1 : Form { void ShowResult(CardsCandidate candidate) { // CardsPutByComp1にカードが残っている場合は移動させる CardsPutByAll.AddRange(CardsPutByComp1); CardsPutByComp1.Clear(); List<int> compCardsDealt = CompCards.Select(card => card.Number).ToList(); if (candidate != null) { // 素数または合成数として出すカードを場に移動させる foreach (int number in candidate.CardNumbers) { Card card = CompCards.FirstOrDefault(card0 => card0.Number == number); if (card != null) { CardsPutByComp1.Add(card); CompCards.Remove(card); } else { Card joker = CompCards.FirstOrDefault(card0 => card0.Suit == Suit.Joker); CardsPutByComp1.Add(joker); CompCards.Remove(joker); } } // 素因数として出されたカードを素因数場に移動させる foreach (PrimeNumberFromCards cards in candidate.PrimeNumberCards) { foreach (int number in cards.CardNumbers) { Card card = CompCards.FirstOrDefault(card0 => card0.Number == number); if (card != null) { CardsPutByComp2.Add(card); CompCards.Remove(card); } } // 素因数に指数がついている場合は指数も素因数場に移動させる if (cards.count > 1) { Card card2 = CompCards.FirstOrDefault(card0 => card0.Number == cards.count); if (card2 != null) { CardsPutByComp2.Add(card2); CompCards.Remove(card2); } else MessageBox.Show(cards.count.ToString() + "が見つかりません"); } } // 計算式を文字列として表示する List<string> vs = new List<string>(); foreach (PrimeNumberFromCards primeNumberCards in candidate.PrimeNumberCards) { if (primeNumberCards.count < 2) vs.Add(primeNumberCards.PrimeNumber.ToString()); else vs.Add(primeNumberCards.PrimeNumber.ToString() + " ^ " + primeNumberCards.count.ToString()); } CompText2 = String.Join(" × ", vs.ToArray()); CompText1 = String.Join("", candidate.Number); ShowCards(); } else { // 手持ちのカードがあるにも関わらず出せるカードがない場合は山札から1枚取る CompText1 = ""; CompText2 = ""; if (compCardsDealt.Count > 0) { CompCards.Add(Cards[0]); Cards = Cards.Skip(1).ToList(); } } } } |
出せるカードを探す
コンピュータが出せるカードを探す処理を示します。isPrimeNumberがtrueのときは素数として出せるを探し、falseのときは合成数出しできるカードを探します。最初は大量にカードを処理できる合成数出しから探します。ただこれではコンピュータが強くなりすぎるので逆にしたほうがいいかも。
GetCardNumbersPermutationメソッドでカードの組み合わせをできるかぎり探して、そのなかから出せるカードを探し、そのなかから最適に近いものを探すという方法をとっています。
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 partial class Form1 : Form { CardsCandidate SearchNumber(bool isPrimeNumber, int cardCount, long minValue) { List<int> compCardNumbers = CompCards.Select(card => card.Number).ToList(); List<CardsCandidate> candidates = GetCardNumbersPermutation(compCardNumbers, cardCount); List<CardsCandidate> rets = new List<CardsCandidate>(); CardsCandidate NextBest = null; foreach (CardsCandidate candidate in candidates) { if (!Ramanujan && candidate.Number <= minValue) continue; if (Ramanujan && candidate.Number >= minValue) continue; if (candidate.IsPrimeNumber != isPrimeNumber) continue; // コンピュータの手持ちのカードをすべて消費できるならそのまま出す if (CheckWinPutCards(compCardNumbers, candidate)) return candidate; // 次に出せるカードがあるか調べておく // ないなら次善策として保存しておく if (CheckNextPutCards(compCardNumbers, candidate)) rets.Add(candidate); else if (NextBest == null) NextBest = candidate; } // 次の一手になりうるものが見つからない場合は次善策として保存していたものを使う if (rets.Count == 0 && NextBest != null) return NextBest; // 本当に次の一手が存在しないならnullを返す if (rets.Count == 0 && NextBest == null) return null; // 偶数のカードばかり残るとやりにくいので偶数のカードを使う手を優先する // 消費する偶数のカードがもっとも大きいものを次の一手とする return GetBestResultCards(rets); } } |
GetCardNumbersPermutationメソッドとGetCardsCanPutメソッドは第一引数のカードの番号のリストのなかから指定された枚数のカードの組み合わせを候補として返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { List<CardsCandidate> GetCardNumbersPermutation(List<int> compCardNumbers, int cardCount) { // 第二引数が-1なら1枚出しから4枚出しまですべてを取得する List<CardsCandidate> resultCardsList = new List<CardsCandidate>(); if (cardCount == 2 || cardCount == -1) resultCardsList.AddRange(GetCardsCanPut(compCardNumbers.ToArray(), 2)); if (cardCount == 3 || cardCount == -1) resultCardsList.AddRange(GetCardsCanPut(compCardNumbers.ToArray(), 3)); if (cardCount == 4 || cardCount == -1) resultCardsList.AddRange(GetCardsCanPut(compCardNumbers.ToArray(), 4)); if (cardCount == 1 || cardCount == -1) resultCardsList.AddRange(GetCardsCanPut(compCardNumbers.ToArray(), 1)); return resultCardsList; } } |
GetCardsCanPutメソッドは第二引数の枚数でつくることができるカードの番号の順列をつくります。そしてこれを素数と合成数にわけます。合成数の場合は合成数出しができるかどうかをGetPutCompositeNumberメソッドで判断しています。
また生成した順列のなかにはジョーカーが含まれているかもしれません。その場合は0~13までのすべての数を候補として考えます。
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 partial class Form1 : Form { List<CardsCandidate> GetCardsCanPut(int[] compCardNumbers, int cardCount) { // 出せるカードの順列をretのなかに次々と格納していく List<CardsCandidate> ret = new List<CardsCandidate>(); List<CardsCandidate> notPrimeNumbers = new List<CardsCandidate>(); Permutation permutation = new Permutation(); // n枚でつくることができるカードの順列をつくる // ここから生成される数を素数と合成数にわける List<int[]> arrayList1 = permutation.GetPermutations(compCardNumbers, cardCount).ToList(); foreach (int[] array1 in arrayList1) { if (!array1.Any(x => x == 0)) { long num = GetNumber(array1); // 57 と 1729 は素数と見なす if (IsPrimeNumber(num) || num == 57 || num == 1729) ret.Add(new CardsCandidate() { IsPrimeNumber = true, Number = num, CardNumbers = array1.ToList() }); else notPrimeNumbers.Add(new CardsCandidate() { IsPrimeNumber = false, Number = num, CardNumbers = array1.ToList() }); } else { // array1のなかに0(すなわちジョーカー)が存在する List<int> numbers = GetNumbersWithJoker(array1); foreach (int num in numbers) { if (IsPrimeNumber(num) || num == 57 || num == 1729) ret.Add(new CardsCandidate() { IsPrimeNumber = true, Number = num, CardNumbers = array1.ToList() }); } } } // 合成数は「合成数出し」ができるか調べる List<CardsCandidate> numbersCanPrimeFactors = GetPutCompositeNumber(notPrimeNumbers); ret.AddRange(numbersCanPrimeFactors); return ret; } } |
第一引数のなかにジョーカーがあるかどうかを調べてコンピュータが出す素数の候補として返します。
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 |
public partial class Form1 : Form { List<int> GetNumbersWithJoker(int[] numbers) { List<int> rets = new List<int>(); // 1枚出しでは最強なので13より大きい素数17を入れている if (numbers.Length == 1 && numbers[0] == 0) { rets.Add(17); return rets; } // 2枚出し以降では上一桁が0になる組み合わせは考えないので // 先頭がジョーカーであることは考えない if (numbers.Length > 1 && numbers[0] == 0) { return rets; } for (int i = 0; i < 14; i++) rets.Add(0); foreach (int number in numbers) { if (number != 0) { // 第一引数にはジョーカーが含まれるが、この数はジョーカーではない if (number < 10) rets = rets.Select(x => x * 10).ToList(); else rets = rets.Select(x => x * 100).ToList(); rets = rets.Select(x => x + number).ToList(); } else { // この部分がジョーカーである // 0~13まで代入して候補をつくる for (int i = 0; i < 14; i++) { if (i < 10) rets[i] *= 10; else rets[i] *= 100; rets[i] += i; } } } return rets; } } |
候補として生成された数が合成数の場合、「合成数出し」ができるか調べるメソッドです。
まず素因数分解は可能かを調べます。最初の1行でコンピュータが持っているカードをつかって素因数をつくります。これを合成数とともにCanPrimeFactorizationメソッドに渡して素因数分解可能かを調べます。
素因数分解可能である場合はこれをさらにJudgeCompositeNumberメソッドにわたして持っているカードで合成数と素因数の両方をつくることができるかを調べます。両方の条件を満たしている場合だけが最終候補として残ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { List<CardsCandidate> GetPutCompositeNumber(List<CardsCandidate> notPrimeNumbers) { // 素因数分解に使うための手持ちのカードで作れる素数を取得する List<PrimeNumberFromCards> primeNumberFromCards = GetNumbersUseForPrimeFactorization(); List<CardsCandidate> ret = new List<CardsCandidate>(); foreach (CardsCandidate notPrimeNumber in notPrimeNumbers) { // 素因数分解可能か? // 可能である場合は持っているカードで合成数と素因数の両方をつくれるか? if (CanPrimeFactorization(notPrimeNumber, primeNumberFromCards) && JudgeCompositeNumber(notPrimeNumber)) ret.Add(notPrimeNumber); } return ret; } } |
素因数分解は可能か?
コンピュータが持っているカードのなかで素因数分解に使うことができる素数を取得する処理を示します。
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 Form1 : Form { List<PrimeNumberFromCards> GetNumbersUseForPrimeFactorization() { List<PrimeNumberFromCards> primeNumberFromCards = new List<PrimeNumberFromCards>(); Permutation permutation = new Permutation(); for (int i = 1; i <= 4; i++) { List<int> numbers = new List<int>(CompCards.Select(card => card.Number)); List<int[]> arrayList = permutation.GetPermutations(numbers, i).ToList(); foreach (int[] array in arrayList) { long num = GetNumber(array); // 57と1729は素数扱いだが素因数にはなりえない if (IsPrimeNumber(num) && num != 57 && num != 1729) { PrimeNumberFromCards primeNumberCards = new PrimeNumberFromCards(num, array.ToList()); primeNumberFromCards.Add(primeNumberCards); } } } primeNumberFromCards = primeNumberFromCards.OrderBy(x => x.PrimeNumber).ToList(); return primeNumberFromCards; } } |
CanPrimeFactorizationメソッドは第一引数で渡された候補を第二引数の素因数のリストで素因数分解できるかどうかを調べるためのものです。
第二引数の素因数のリストをつかって合成数の候補を割っていき、最終的に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 |
public partial class Form1 : Form { bool CanPrimeFactorization(CardsCandidate notPrimeNumberCandidate, List<PrimeNumberFromCards> primeNumbers) { long notPrimeNumber = notPrimeNumberCandidate.Number; // 1は素因数分解の対象にはならない if (notPrimeNumber == 1) return false; primeNumbers = primeNumbers.OrderBy(x => x.PrimeNumber).ToList(); foreach (PrimeNumberFromCards primeNumberCard in primeNumbers) { bool isFirst = true; while (true) { if (notPrimeNumber % primeNumberCard.PrimeNumber == 0) { notPrimeNumber /= primeNumberCard.PrimeNumber; if (isFirst) { List<int> vs = new List<int>(); foreach (int i in primeNumberCard.CardNumbers) vs.Add(i); PrimeNumberFromCards numberCards = new PrimeNumberFromCards( primeNumberCard.PrimeNumber, primeNumberCard.CardNumbers.ToList() ); notPrimeNumberCandidate.PrimeNumberCards.Add(numberCards); } else { // 同じ素数で2回以上割りきれる場合は指数表記にする PrimeNumberFromCards primeNumberCards = notPrimeNumberCandidate.PrimeNumberCards.Last(); primeNumberCards.count++; } if (notPrimeNumber == 1) return true; isFirst = false; } else break; } } return false; } } |
合成数の生成は可能か?
素因数分解可能であったとしても同じカードを複数回使ってしまい、実際には合成数出しできない場合もあります。JudgeCompositeNumberメソッドはそのような候補を排除して本当に合成数出し可能な候補だけを残すためのメソッドです。
コンピュータが持っているカードの番号のコピーを作り、そこから合成数をつくるために必要な番号と素因数と指数をつくるために必要な番号を取り除いていきます。最後までこの処理ができた場合だけが本当に合成数出し可能な候補です。
8 = 2の3乗ですが、2×2×2でもあります。そこでこの場合は先に2と3があるかどうかでなく、2が3つあるかどうかを調べています。ない場合は2と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 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 { bool JudgeCompositeNumber(CardsCandidate notPrimeNumberCandidate) { // コンピュータが持っているカードの番号のコピーを作る List<int> copyCompCardNumbers = new List<int>(CompCards.Select(card => card.Number)); foreach (int num in notPrimeNumberCandidate.CardNumbers) { if (!copyCompCardNumbers.Remove(num)) return false; } notPrimeNumberCandidate.PrimeNumberCards = notPrimeNumberCandidate.PrimeNumberCards.OrderBy(x => x.PrimeNumber).ToList(); // N枚抜きに成功した素数 List<long> secceedPrimeNumbers = new List<long>(); foreach (PrimeNumberFromCards primeNumberCards in notPrimeNumberCandidate.PrimeNumberCards) { bool complete = true; List<int> vs = new List<int>(); // N枚抜きができるかやってみる for (int i = 0; i < primeNumberCards.count; i++) { foreach (int num in primeNumberCards.CardNumbers) { if (copyCompCardNumbers.Remove(num)) { if(i > 0) vs.Add(num); } else { complete = false; break; } } if (!complete && i == 0) return false; if (!complete) break; } if (complete) secceedPrimeNumbers.Add(primeNumberCards.PrimeNumber); // 1回は抜けた場合は累乗指数を抜けないか追求する if (!complete) { copyCompCardNumbers.AddRange(vs); if (!copyCompCardNumbers.Remove(primeNumberCards.count)) return false; } } // N枚抜きに成功した素数(指数表記から単純なかけ算に変換) foreach (long num in secceedPrimeNumbers) { PrimeNumberFromCards prime = notPrimeNumberCandidate.PrimeNumberCards.First(x => x.PrimeNumber == num); notPrimeNumberCandidate.PrimeNumberCards.Remove(prime); for (int i = 0; i < prime.count; i++) { PrimeNumberFromCards newPrime = new PrimeNumberFromCards(prime.PrimeNumber, prime.CardNumbers.ToList()); notPrimeNumberCandidate.PrimeNumberCards.Add(newPrime); } } return true; } } |
最善手を探す
コンピュータの候補となるカードの出し方ですべてのカードを出せるかどうかを調べています。複数の候補があるならその場で勝てる候補を選ぶべきだからです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { bool CheckWinPutCards(List<int> compCardNumbers, CardsCandidate candidate) { List<int> copyCompCardNumbers = new List<int>(compCardNumbers); foreach (int card in candidate.CardNumbers) copyCompCardNumbers.Remove(card); foreach (PrimeNumberFromCards card in candidate.PrimeNumberCards) { foreach (int num in card.CardNumbers) copyCompCardNumbers.Remove(num); } if (copyCompCardNumbers.Count == 0) return true; else return 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 |
public partial class Form1 : Form { bool CheckNextPutCards(List<int> compCardNumbers, CardsCandidate candidate) { List<int> copyCompCardNumbers = new List<int>(compCardNumbers); foreach (int card in candidate.CardNumbers) copyCompCardNumbers.Remove(card); foreach (PrimeNumberFromCards card in candidate.PrimeNumberCards) { foreach (int num in card.CardNumbers) copyCompCardNumbers.Remove(num); } if (copyCompCardNumbers.Count == 0) return true; else if (ExistNextPutCards(copyCompCardNumbers.ToArray(), 1)) return true; else if (ExistNextPutCards(copyCompCardNumbers.ToArray(), 2)) return true; else if (ExistNextPutCards(copyCompCardNumbers.ToArray(), 3)) return true; else if (ExistNextPutCards(copyCompCardNumbers.ToArray(), 4)) return true; else return false; } } |
ExistNextPutCardsメソッドは第一引数の番号のリストのなかに第二引数の枚数で次に出せるカードがあるか調べるメソッドです。素数がある場合とジョーカーが含まれる場合は次に出せるカードがあると見なしています。
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 partial class Form1 : Form { bool ExistNextPutCards(int[] compCardNumbers, int cardCount) { List<CardsCandidate> ret = new List<CardsCandidate>(); Permutation permutation = new Permutation(); List<int[]> arrayList1 = permutation.GetPermutations(compCardNumbers, cardCount).ToList(); foreach (int[] array1 in arrayList1) { if (!array1.Any(x => x == 0)) { long num = GetNumber(array1); if (IsPrimeNumber(num) || num == 57 || num == 1729) return true; } else { return true; } } return false; } } |
コンピュータの次の一手の候補から一番よさそうなものを選ぶメソッドです。この
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { CardsCandidate GetBestResultCards(List<CardsCandidate> resultCards) { // 偶数を多く含む候補をできるだけ選びたいので最大個数のものを選ぶ List<CardsCandidate> rets = resultCards.Where(x => x.CardNumbers.Where(card => card % 2 == 0 && card != 2).ToList().Count > 0).ToList(); if (rets.Count > 0) { int max = rets.Max(x => x.CardNumbers.Count(card => card % 2 == 0 && card != 2)); rets = resultCards.Where(x => x.CardNumbers.Where(card => card % 2 == 0 && card != 2).ToList().Count == max).ToList(); return rets[Random1.Next(rets.Count)]; } else { // 偶数を多く含む候補がひとつもない場合は乱数を使って適当に選ぶ return resultCards[Random1.Next(resultCards.Count)]; } } } |
下のラベルにつぎにプレイヤーがすべきことを表示させます。いつまでも表示されたままになると見当外れな内容になってしまう場合があるので2秒で消しています。
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 partial class Form1 : Form { void ShowNaviLabelText() { if (CountCardsPutMust != -1) { if (!Ramanujan) LabelMessage.Text = $"{CountCardsPutMust}枚のカードで{MinValueCards}より大きい数を出してください"; else LabelMessage.Text = $"{CountCardsPutMust}枚のカードで{MinValueCards}より小さい数を出してください"; } else LabelMessage.Text = $"コンピュータはパスをしました。あなたが親です。"; Timer timer = new Timer(); timer.Interval = 2000; timer.Tick += Timer_Tick; timer.Start(); void Timer_Tick(object sender, EventArgs e) { Timer t = (Timer)sender; t.Stop(); t.Dispose(); LabelPlayerErrer.Text = ""; } } } |
パスをするときの処理
パスをするときの処理を示します。場にカードが出されている場合は回収してからコンピュータに次のカードを出させます。
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 partial class Form1 : Form { async private void ButtonPass_Click(object sender, EventArgs e) { await PlayerPass(); } async Task PlayerPass() { EableButtons(false); CheckBoxPrimeFactor.Checked = false; LabelPlayerErrer.Text = ""; PlayerText1 = ""; PlayerText2 = ""; PlayerCards.AddRange(CardsPutByPlayer1); PlayerCards.AddRange(CardsPutByPlayer2); Pass(); PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); ShowCards(); await Task.Delay(500); await CompPutCards(); if (CheckCompWin()) return; if (CountCardsPutMust != -1) { if (!Ramanujan) LabelMessage.Text = $"{CountCardsPutMust}枚のカードで{MinValueCards}より大きい数を出してください"; else LabelMessage.Text = $"{CountCardsPutMust}枚のカードで{MinValueCards}より小さい数を出してください"; } else LabelMessage.Text = $"コンピュータもパスをしました。あなたが親です。"; EableButtons(true); } } |
パスをするときは場が流されるのでそのための処理を記述しています。
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 { void Pass() { Cards.AddRange(CardsPutByComp1); Cards.AddRange(CardsPutByComp2); Cards.AddRange(CardsPutByPlayer1); Cards.AddRange(CardsPutByPlayer2); Cards.AddRange(CardsPutByAll); CardsPutByComp1.Clear(); CardsPutByComp2.Clear(); CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); CardsPutByAll.Clear(); CountCardsPutMust = -1; MinValueCards = -1; } } |
確認のメッセージに対する処理
合成数出しをするときに確認のメッセージが表示されますが、[OK]ボタンをクリックするとカードを出したときの処理が行なわれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { private void VerificationOK_Click(object sender, EventArgs e) { VerificationControl1.Visible = false; DecisionFinal(); } private void VerificationCancel_Click(object sender, EventArgs e) { VerificationControl1.Visible = false; EableButtons(true); } } |
カードの指定ミスを指摘するコントロールが表示されているときに[OK]ボタンをクリックするとコントロールが非表示になります。
1 2 3 4 5 6 7 8 |
public partial class Form1 : Form { private void AlertControlOK_Click(object sender, EventArgs e) { AlertControl1.Visible = false; EableButtons(true); } } |
ジョーカーを出すときに数を指定します。[OK]ボタンをクリックすると値が指定されたことになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { private void JokerControlOK_Click(object sender, EventArgs e) { JokerControl1.Visible = false; int num = JokerControl1.Number; Card card = JokerControl1.Card; JokerControl1.Reset(); card.NumberByJoker = num; PutCard(card); } private void JokerControlCancel_Click(object sender, EventArgs e) { JokerControl1.Visible = false; } } |