C#でつくる素数大富豪、完成品はこんな感じです。
前回のコンピュータによる合成数出しが本当に出せるカードなのか調べる C#で素数大富豪をつくる(3)ではコンピュータが出せるカードの組み合わせを取得しましたが、そのなかからランダムに選ぶだけでは簡単に行き詰まってしまいます。最善とはいかなくてもそれに近そうなコンピュータの次の一手を考えます。
Contents
カードを配布する
Button1を押すとコンピュータにカードが13枚配られます。配られたカードはテキストボックスに表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { List<int> CardsDealt = new List<int>(); // 配られたカード List<int> shuffledCards = null; // 山札 private void button1_Click(object sender, EventArgs e) { shuffledCards = ShuffleCards(); CardsDealt = shuffledCards.Take(13).ToList(); shuffledCards = shuffledCards.Skip(13).ToList(); CardsDealt = CardsDealt.OrderBy(x => x).ToList(); TextBoxCompCards.Text = String.Join(", ", CardsDealt); } } |
カードの生成とシャッフル
CreateCardsメソッドはゲームを開始するにあたって52枚のカードをつくるためのものです。
1 2 3 4 5 6 7 8 9 10 |
public partial class Form1 : Form { List<int> CreateCards() { List<int> vs = new List<int>(); for (int i = 1; i <= 13; i++) vs.AddRange(new int[] { i, i, i, i }); return vs; } } |
ShuffleCardsメソッドは生成された52枚のカードをシャッフルします。この先頭の13枚がコンピュータに配られます。
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 { List<int> ShuffleCards() { List<int> ret = new List<int>(); List<int> vs = CreateCards(); Random random = new Random(); while (true) { int count = vs.Count; if (count == 0) break; int r = random.Next(count); ret.Add(vs[r]); vs.RemoveAt(r); } return ret; } } |
なにも考えずにカードを出して大破産
カードが配られたらButton2をクリックしてみましょう。場に出すことができるカードのうち最初にみつけたものを出します。そのとき残りのカード、場に出されたカード、素因数場に出されたカード、合成数出しの場合は素因数、カードによって作られた数がテキストボックスに表示されます。
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 { private void button2_Click(object sender, EventArgs e) { List<ResultCards> resultCardsList = GetCardsCanPut(CardsDealt.ToArray(), 2); // なにも考えずに最初に見つけたカードを出す(合成数出しが優先) ResultCards resultCards = resultCardsList.FirstOrDefault(x => x.IsPrimeNumber == false); if(resultCards == null) resultCards = resultCardsList.FirstOrDefault(x => x.IsPrimeNumber == true); if (resultCards != null) { foreach (int card in resultCards.Cards1) CardsDealt.Remove(card); foreach (int card in resultCards.Cards2) CardsDealt.Remove(card); TextBoxCompCards.Text = "残りのカード:" + String.Join(", ", CardsDealt); TextCardsPut.Text = "場に出されたカード:" + String.Join(", ", resultCards.Cards1.ToArray()); TextCardsPutAsPrimeFactor.Text = "素因数場に出されたカード:" + String.Join(", ", resultCards.Cards2.ToArray()); TextResultsPrimeFactorization.Text = "素因数分解:" + String.Join(" × ", resultCards.PrimeNumbers.ToArray()); string isPrimeNumber = resultCards.IsPrimeNumber ? "素数:" : "合成数:"; TextNumberMadeWithCards.Text = isPrimeNumber + String.Join("", resultCards.Cards1.ToArray()); } else { TextCardsPut.Text = ""; TextCardsPutAsPrimeFactor.Text = ""; TextResultsPrimeFactorization.Text = ""; TextNumberMadeWithCards.Text = ""; if (CardsDealt.Count > 0) TextNumberMadeWithCards.Text = "カードが出せない"; else TextNumberMadeWithCards.Text = "すべてのカードが出された"; } } } |
実際にButton2を押し続けてみると以下のような結果になります。乱数を用いてカードを配っているので、必ずこうなるというわけではなく、結果の一例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# なにも考えなかった結果 最初に配られたカード 1, 2, 2, 3, 3, 4, 4, 5, 6, 8, 8, 9, 11 場に出されたカード:1, 8 素因数場に出されたカード:2, 3, 3 素因数分解:2 × 3 × 3 残りのカード:2, 4, 4, 5, 6, 8, 9, 11 場に出されたカード:11, 8 素因数場に出されたカード:2, 5, 9 素因数分解:2 × 59 残りのカード:4, 4, 6 残りのカード:4, 4, 6ではカードは出せない! |
これだと途中で出せるカードがなくなってしまいます。素数大富豪も大富豪と同じで出せるカードだからといって何も考えずに出してしまうと後半で行き詰まってしまうのです。カードを出したら次の順番が回ってきたときにどうするかを考えながら出すカードを選ばなければなりません。
次に出せるカードがあるか確認してから出す
改善点は他にも多数あるはずですが、カードを出したとして次に出せるカードが存在しない場合はそのカードを出すのをやめて他のカードを考えさせるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { private void button2_Click(object sender, EventArgs e) { // 出せるカードのなかで次の手番のことも考えてカードを選ぶ // 最初は合成数出しを追求する ResultCards resultCards = SearchNumber(false); if(resultCards == null) resultCards = SearchNumber(true); ShowResult(resultCards); } } |
偶数のカードはできるだけ早めに処分したい
SearchNumberメソッドはコンピュータが出すことができるカードを探します。
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) { // ここではコンピュータが親であるという前提になっている // そのため1枚から4枚まで好きな枚数を出せる List<ResultCards> resultCardsList = new List<ResultCards>(); resultCardsList.AddRange(GetCardsCanPut(CardsDealt.ToArray(), 2)); resultCardsList.AddRange(GetCardsCanPut(CardsDealt.ToArray(), 3)); resultCardsList.AddRange(GetCardsCanPut(CardsDealt.ToArray(), 4)); resultCardsList.AddRange(GetCardsCanPut(CardsDealt.ToArray(), 1)); List<ResultCards> rets = new List<ResultCards>(); ResultCards NextBest = null; foreach (ResultCards resultCards in resultCardsList) { if (resultCards.IsPrimeNumber != isPrimeNumber) continue; // 次に出せるカードがあるか調べておく List<int> copyCardsDealt = new List<int>(CardsDealt); foreach (int card in resultCards.Cards1) copyCardsDealt.Remove(card); foreach (int card in resultCards.Cards2) 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) { textBox1.Text = "次善策"; return NextBest; } // 本当に次の一手が存在しないならnullを返す if (rets.Count == 0 && NextBest == null) return null; // 偶数のカードばかり残るとやりにくいので偶数のカードを使う手を優先する // 消費する偶数のカードがもっとも大きいものを次の一手とする List<ResultCards> rets2 = rets.Where(x => x.Cards1.Where(card => card % 2 == 0 && card != 2).ToList().Count > 0).ToList(); if (rets2.Count > 0) { int max = rets2.Max(x => x.Cards1.Count(card => card % 2 == 0 && card != 2)); textBox1.Text = "偶数を含むものがある" + max; ResultCards ret = rets.First(x => x.Cards1.Where(card => card % 2 == 0 && card != 2).ToList().Count == max); return ret; } else { textBox1.Text = "偶数を含むものはない"; return rets[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 |
public partial class Form1 : Form { void ShowResult(ResultCards resultCards) { if (resultCards != null) { // 場と素因数場に出されたカードは手持ちのカードから取り除く foreach (int card in resultCards.Cards1) CardsDealt.Remove(card); foreach (int card in resultCards.Cards2) CardsDealt.Remove(card); // 結果を表示 TextBoxCompCards.Text = "残りのカード:" + String.Join(", ", CardsDealt); TextCardsPut.Text = "場に出されたカード:" + String.Join(", ", resultCards.Cards1.ToArray()); TextCardsPutAsPrimeFactor.Text = "素因数場に出されたカード:" + String.Join(", ", resultCards.Cards2.ToArray()); TextResultsPrimeFactorization.Text = "素因数分解:" + String.Join(" × ", resultCards.PrimeNumbers.ToArray()); string isPrimeNumber = resultCards.IsPrimeNumber ? "素数:" : "合成数:"; TextNumberMadeWithCards.Text = isPrimeNumber + String.Join("", resultCards.Cards1.ToArray()); } else { TextCardsPut.Text = ""; TextCardsPutAsPrimeFactor.Text = ""; TextResultsPrimeFactorization.Text = ""; TextNumberMadeWithCards.Text = ""; if (CardsDealt.Count > 0) { // カードが出せないなら山札から1枚取る CardsDealt.Add(shuffledCards[0]); shuffledCards = shuffledCards.Skip(1).ToList(); TextNumberMadeWithCards.Text = "カードが出せない"; TextBoxCompCards.Text = "残りのカード:" + String.Join(", ", CardsDealt); } else TextNumberMadeWithCards.Text = "すべてのカードが出された"; } } } |
動作チェックをしてみると、こんな感じになります。