C#でつくる素数大富豪、完成品はこんな感じです。
実際に作成したアプリケーションで遊んでみましたが、合成数だしをするときに「合成数は10、素因数は2×5」のつもりで確定ボタンを押したら「×」を記号を入れるのを忘れていて「10は25にはなりません」というエラーメッセージが返ってくることがありました。
現状では出せないカードを出した場合は出したカードだけ返却されるというルールですが、実際には出したカードだけ返却されるだけでなく、それを同じ枚数を山札から取らなければならないというペナルティーがあります。こんな凡ミス、しかも本物のカードをつかっていれば絶対やらないようなミスでペナルティーを受けたくありません。
そこで合成数出しをするときは本当にそれが自分が出したいカードと一致しているのかの確認画面を出したいと思います。
Contents
カードを出す前の確認機能の実装
MessageBoxを使ってもよいのですが、ちょっと見た目がチープになるのでボタン付きのPanelを表示させます。Panelに確定のボタンがついていてこれをクリックしたら出されたカードの正当性の判定がおこなわれます。最終確認をして出したわけですから、これで間違っていたらプレイヤーの完全なミスです。潔くペナルティーをうけてください。
確認用コントロールの作成
では合成数だしの確認機能を実装します。
まず確認用のコントロールを作成します。LabelひとつとButtonがふたつで構成されています。LabelはAutoSizeをfalseにし、TextAlignはContentAlignment.MiddleCenterにしています。あとは各自のデザイン感覚に任せます。
実行、キャンセルのボタンがクリックされたらForm1クラス側でもこれに対応できるようにClickRunとClickCancelのイベントを定義しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class VerificationControl : UserControl { public VerificationControl() { InitializeComponent(); } public event EventHandler ClickRun; public event EventHandler ClickCancel; private void ButtonRun_Click(object sender, EventArgs e) { ClickRun?.Invoke(this, new EventArgs()); } private void ButtonCancel_Click(object sender, EventArgs e) { ClickCancel?.Invoke(this, new EventArgs()); } } |
これからこんな警告用のコントロールもつくってみました。合成数出しをしようとするとよくやるミス防止のためにつくりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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()); } } |
Form1クラス側の処理
次にForm1クラス側の処理を示します。
初期化に関する処理
作成したVerificationControlクラスとAlertControlクラスのインスタンスを生成し、ボタンがクリックされたときのイベントを処理できるようにイベントハンドラを追加します。
それからアプリケーションが開始された直後はゲームスタートのボタン以外はクリックできないようにすることにしました。ゲームスタート前はカード自体が作られていないので[一枚取る]ボタンをクリックしてしまうと例外が発生してしまいます。
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 { VerificationControl Verification1 = new VerificationControl(); AlertControl AlertControl1 = new AlertControl(); public Form1() { InitializeComponent(); // その他の処理は省略 // 最初はVerification1とAlertControl1は非表示 Verification1.Visible = false; Verification1.ClickRun += Verification1_ClickRun; Verification1.ClickCancel += Verification1_ClickCancel; Verification1.Parent = this; Verification1.Location = new Point(100, 100); AlertControl1.Visible = false; AlertControl1.ClickOK += AlertControl1_ClickOK; AlertControl1.Parent = this; AlertControl1.Location = new Point(100, 100); // アプリケーション開始時はゲームスタートのボタン以外はクリックできないようにする ButtonDraw.Enabled = false; ButtonDecision.Enabled = false; ButtonPass.Enabled = false; ButtonClear.Enabled = false; ButtonX.Enabled = false; } } |
確認メッセージを表示する処理
実行ボタンがクリックされたときに実行される処理はButtonDecision_Clickメソッドで定義されていますが、この中身を別のメソッド(async void DecisionFinal())を作成してそこに移します。そしてButtonDecision_Clickの中を以下のように書き換えます。
素因数が存在する場合は2×5とすべきところを25とやっているとAlertControl1が表示され、素因数をひとつだけでは合成数出しはできない旨を通知します。それ以外のときは指定した合成数と素因数はこれで間違いないか確認するコントロールが表示されます。
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 |
public partial class Form1 : Form { private void ButtonDecision_Click(object sender, EventArgs e) { if (TempPrimefactors.Count > 0) { Primefactors.Add(GetNumber(TempPrimefactors)); TempPrimefactors.Clear(); } int compositeNumber = GetNumber(CardsPutByPlayer1); string str = String.Join(" × ", Primefactors.Select(x => x.ToString()).ToArray()); if (str != "") { if (Primefactors.Count == 1) { AlertControl1.Visible = true; } else { string message = "合成数出しをしようとしています。\n"; message = str + " = " + compositeNumber + "で間違いないですか?"; Verification1.Text = message; Verification1.Visible = true; } EableButtons(false); } else DecisionFinal(); } } |
確認メッセージのボタンが押されたときの処理
確認用のコントロールにあるボタンがクリックされたら出されたカードが正しいかの判定がおこなわれます。素因数が1つしかなかった場合や確認用コントロールのCancelボタンが押されたときは処理は行なわれません。クリアボタンをおして自分で再指定することになります。
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 { private void AlertControl1_ClickOK(object sender, EventArgs e) { // 確認用のコントロールを非表示にして、 // もう一度カードを選択できるようにEableButtons(true)を実行する AlertControl1.Visible = false; EableButtons(true); } private void Verification1_ClickRun(object sender, EventArgs e) { // 確認用のコントロールを非表示にして、判定の処理に移る Verification1.Visible = false; DecisionFinal(); } private void Verification1_ClickCancel(object sender, EventArgs e) { // 確認用のコントロールを非表示にして、 // もう一度カードを選択できるようにEableButtons(true)を実行する Verification1.Visible = false; EableButtons(true); } } |
ペナルティーはありかなしか?
CheckBoxでペナルティーがありかなしかを選択できるようにします。またコンピュータのカードをみることができるかどうかも設定できるようにします。
まずForm1に設定用のCheckBoxをつくります。
コンピュータはミスはしないので、プレイヤーのミスだけ考えます。CheckPlayerCards1メソッドとCheckPlayerCards2メソッドで出されたカードが正しくないと判定されたときに、間違って出されたカードを返却するだけでなく、それと同数のカードを山札から追加します。
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 { void CheckPlayerCards1(ref int cardCount, ref long minValue) { cardCount = CardsPutByPlayer1.Count; long number = GetNumber(CardsPutByPlayer1); string mes = ""; if (IsPrimeNumber(number) || number == 1729) { minValue = 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(); minValue = -1; cardCount = -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 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 |
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; } if (cardCount != -1) { minValue = number + 1; // カードを移動させる 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; } } |
コンピュータのカードを裏向きで表示させる
コンピュータのカードを裏向きで表示させるのは簡単です。[コンピュータのカードをみせる]にチェックが入っていないのであれば
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); } } |