【算法】用c#实现德州扑克卡牌游戏规则

发布时间 2023-08-07 10:14:16作者: lanedm

德州扑克是一种牌类游戏,可多人参与,它的玩法是,玩家每人发两张底牌,桌面依次发5张公共牌,玩家用自己的两张底牌和5张公共牌自由组合,按大小决定胜负。

使用c#完成功能Hand()以返回手牌类型和按重要性递减顺序排列的等级列表,用于与同类型的其他手牌进行比较,即最佳手牌。

可能的手牌按价值降序排列:

同花顺(同一套衣服的连续五个等级)。级别越高越好。

四张(四张等级相同的牌)。平局决胜先是等级,然后是剩余牌的等级。

满座(三张等级相同的牌,两张等级相同)。决胜局首先是三张牌的等级,然后是一对牌的等级。

同花顺(五张同花色的牌)。从高到低,级别越高越好。

直(连续五个等级)。级别越高越好。

三张牌(三张等级相同的牌)。决胜局是三张牌中排名第一的,然后是其他排名最高的,然后才是其他排名第二的。

两对(两张相同等级的牌,两张不同等级的牌)。决胜局首先是高牌对的等级,然后是低牌对的级别,然后是剩余牌的等级。

配对(两张等级相同的牌)。平局决胜是先是两张牌的等级,然后是其他三张牌的级别。


算法实现:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 
  6 public static class Edm
  7     {
  8         public static (string type, string[] ranks) Hand(string[] holeCards, string[] communityCards)
  9         {
 10             Card[] allCards = holeCards.Concat(communityCards).Select( x=> new Card(x)).OrderByDescending(card =>card.value).ToArray();
 11             
 12             var rulesChain = createChainOfCommand();
 13             var powerhands = rulesChain.Execute(allCards);
 14             return (powerhands.Item1, getReturnlist(allCards, powerhands.Item2));
 15 
 16         }
 17         public static string[] getReturnlist(Card[] cards, Card[] powerhand)
 18         {
 19             var remainderHand = cards.Where(x => !powerhand.Any(y => y.Equals(x))).Take(5-powerhand.Length);
 20             var result = powerhand.Select(x =>x.number).Distinct().Concat(remainderHand.Select(x=>x.number)).Take(5).Select(x => x.ToString()).ToArray();
 21             return result;
 22         }
 23 
 24         public static Rule createChainOfCommand()
 25         {
 26             Rule straightFlush = new StraightFlushRule();
 27             Rule fourOfAKind = new FourOfAKindRule();
 28             Rule fullHouse = new FullHouseRule();
 29             Rule flush = new FlushRule();
 30             Rule straight = new StraightRule();
 31             Rule threeOfAKind = new ThreeOfAKindRule();
 32             Rule pairTwoPair = new PairTwoPairRule();
 33             straightFlush.SetSuccessor(fourOfAKind);
 34             fourOfAKind.SetSuccessor(fullHouse);
 35             fullHouse.SetSuccessor(flush);
 36             flush.SetSuccessor(straight);
 37             straight.SetSuccessor(threeOfAKind);
 38             threeOfAKind.SetSuccessor(pairTwoPair);
 39             return straightFlush;
 40         }
 41     }
 42     public abstract class Rule
 43     {
 44         private Rule nextRule;
 45         public void SetSuccessor(Rule next)
 46         {
 47             nextRule = next;
 48         }
 49         public virtual (string, Card[]) Execute(Card[] cards)
 50         {
 51             if (nextRule != null)
 52             {
 53                 return nextRule.Execute(cards);
 54             }
 55             return ("nothing", cards.Take(5).ToArray());
 56         }
 57     }
 58 
 59     public class PairTwoPairRule : Rule
 60     {
 61         public override (string, Card[]) Execute(Card[] cards)
 62         {
 63             var pairs = cards.GroupBy(x => x.number).Where(g => g.Count() >= 2).SelectMany(card => card).ToList();
 64             if (pairs.Any())
 65             {
 66                 if(pairs.Count() >= 4)
 67                 {
 68                     return ("two pair", pairs.Take(4).ToArray());
 69                 }
 70                 return ("pair", pairs.Take(2).ToArray());
 71             }
 72             return base.Execute(cards);
 73         }
 74     }
 75     public class ThreeOfAKindRule : Rule
 76     {
 77         public override (string, Card[]) Execute(Card[] cards)
 78         {
 79             var triple = cards.GroupBy(x => x.number).Where(g => g.Count() >= 3).SelectMany(card => card).ToList();
 80             if (triple.Any())
 81             {
 82                 return ("three-of-a-kind", triple.Take(3).ToArray());
 83             }
 84             return base.Execute(cards);
 85         }
 86     }
 87     public class StraightRule : Rule
 88     {
 89         public override (string, Card[]) Execute(Card[] cards)
 90         {
 91             for (int i = 0; i < cards.Length - 4; i++)
 92             {
 93                 List<Card> rtnList = new List<Card>() { cards[i] }; //  "A♥","J♦","10♥" "9♠", "9♥", "8♠", "7♣"
 94                 int counter = 4; 
 95                 int j = i; 
 96                 while (counter >= 0 && j < cards.Length - 1)
 97                 {
 98                     if (cards[j].value - cards[j + 1].value == 1)
 99                     {
100                         rtnList.Add(cards[j + 1]);
101 
102                         if (rtnList.Count() == 5)
103                         {
104                             return ("straight", rtnList.ToArray());
105                         }
106                         counter--;
107                         j++;
108                     }
109                     else if (cards[j].value - cards[j + 1].value == 0)
110                     {
111                         j++;
112                     }
113                     else
114                     {
115                         break;
116                     }
117                 }
118             }
119             return base.Execute(cards);
120         }
121     }
122     public class FlushRule : Rule
123     {
124         public override (string, Card[]) Execute(Card[] cards)
125         {
126             var flush = cards.GroupBy(x => x.suit).Where(g => g.Count() >= 5).SelectMany(card => card).ToList(); 
127             if (flush.Any())
128             {
129                 return ("flush", flush.ToArray());
130             }
131             return base.Execute(cards);
132         }
133     }
134 
135     public class FullHouseRule : Rule
136     {
137         public override (string, Card[]) Execute(Card[] cards)
138         {
139             var triple = new ThreeOfAKindRule();
140             var pair = new PairTwoPairRule();
141 
142             var powerhands = triple.Execute(cards);
143             if (!powerhands.Item1.Equals("nothing"))
144             {
145                 if (powerhands.Item2.Count() == 6) // then 2 three of a kind found
146                 {
147                     return ("full house", powerhands.Item2.Take(5).ToArray());
148                 }
149                 var remainderHand = cards.Where(x => !powerhands.Item2.Any(y => y.Equals(x))).ToArray();
150                 var pairHand = pair.Execute(remainderHand);
151                 if (!pairHand.Item1.Equals("nothing"))
152                 {
153                     var fullhouseHand = powerhands.Item2.Concat(pairHand.Item2.Take(2)).ToArray();
154                     return ("full house", fullhouseHand.Take(5).ToArray());
155                 }
156             }
157             return base.Execute(cards);
158         }
159     }
160     public class FourOfAKindRule : Rule
161     {
162         public override (string, Card[]) Execute(Card[] cards)
163         {
164             var fourOfAKind = cards.GroupBy(x => x.number).Where(g => g.Count() >= 4).SelectMany(card => card).ToList();
165             if (fourOfAKind.Any())
166             {
167                 return ("four-of-a-kind", fourOfAKind.Take(4).ToArray());
168             }
169             return base.Execute(cards);
170         }
171     }
172     public class StraightFlushRule : Rule
173     {
174         public override (string, Card[]) Execute(Card[] cards)
175         {
176             var flushRule = new FlushRule();
177             var straightRule = new StraightRule();
178             var flushHand = flushRule.Execute(cards);
179             var straightHand = straightRule.Execute(flushHand.Item2);
180             if (!straightHand.Item1.Equals("nothing") && !flushHand.Item1.Equals("nothing"))
181             {
182                 return ("straight-flush", straightHand.Item2.Take(5).ToArray());
183             }
184             return base.Execute(cards);
185         }
186     }
187 
188     public class Card{
189         public String number { get; set; }
190         public int value { get; set; }
191         public char suit { get; set; }
192         public Dictionary<char, int> mapping = new Dictionary<char, int>()
193         {
194             { 'A',14 },
195             { 'K',13 },
196             { 'Q',12 },
197             { 'J',11 },
198             { '1', 10}
199         };
200         public Card(String s)
201         {
202             number = (s[0] == '1')? "10": Char.ToString(s[0]);
203             value = mapping.ContainsKey(s[0])? mapping[s[0]]: (int) Char.GetNumericValue(s[0]);
204             suit = s[s.Length-1];
205         }
206         public override string ToString()
207         {
208             return number.ToString();
209         }
210 
211         public bool equals(Card s)
212         {
213             return this.value == s.value && this.suit.Equals(s.suit);
214         }
215     }

测试用例:

  1 namespace Solution
  2 {
  3     using NUnit.Framework;
  4     using System;
  5     using System.Collections.Generic;
  6     using System.Diagnostics;
  7     using System.Linq;
  8     using System.Text;
  9   
 10     [TestFixture]
 11     public class SolutionTest
 12     {
 13         #region Sample Tests
 14     
 15         [Test(Description = "Fixed Tests")]
 16         public void FixedTests()
 17         {
 18             SampleTest(("nothing",         new[] { "A", "K", "Q", "J", "9" }),  new[] { "K♠", "A♦" },  new[] { "J♣", "Q♥", "9♥", "2♥", "3♦" });
 19             SampleTest(("pair",            new[] { "Q", "K", "J", "9" }),       new[] { "K♠", "Q♦" },  new[] { "J♣", "Q♥", "9♥", "2♥", "3♦" });
 20             SampleTest(("two pair",        new[] { "K", "J", "9" }),            new[] { "K♠", "J♦" },  new[] { "J♣", "K♥", "9♥", "2♥", "3♦" });
 21             SampleTest(("three-of-a-kind", new[] { "Q", "J", "9" }),            new[] { "4♠", "9♦" },  new[] { "J♣", "Q♥", "Q♠", "2♥", "Q♦" });
 22             SampleTest(("straight",        new[] { "K", "Q", "J", "10", "9" }), new[] { "Q♠", "2♦" },  new[] { "J♣", "10♥", "9♥", "K♥", "3♦" });
 23             SampleTest(("flush",           new[] { "Q", "J", "10", "5", "3" }), new[] { "A♠", "K♦" },  new[] { "J♥", "5♥", "10♥", "Q♥", "3♥" });
 24             SampleTest(("full house",      new[] { "A", "K" }),                 new[] { "A♠", "A♦" },  new[] { "K♣", "K♥", "A♥", "Q♥", "3♦" });
 25             SampleTest(("four-of-a-kind",  new[] { "2", "3" }),                 new[] { "2♠", "3♦" },  new[] { "2♣", "2♥", "3♠", "3♥", "2♦" });
 26             SampleTest(("straight-flush",  new[] { "J", "10", "9", "8", "7" }), new[] { "8♠", "6♠" },  new[] { "7♠", "5♠", "9♠", "J♠", "10♠" });
 27         }
 28     
 29         private static void SampleTest((string type, string[] ranks) expected, string[] holeCards, string[] communityCards)
 30         {
 31             var actual = Act(holeCards, communityCards);
 32             Verify(expected, actual, holeCards, communityCards);
 33         }
 34     
 35         #endregion
 36     
 37         private static readonly StringBuilder template = new StringBuilder();
 38         private static readonly StringBuilder buffer = new StringBuilder();
 39         private static readonly string[] ranks = new string[] { "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3", "2" };
 40         private static readonly string[] types = new string[] { "straight-flush", "four-of-a-kind", "full house", "flush", "straight", "three-of-a-kind", "two pair", "pair", "nothing" };
 41         private static readonly Dictionary<string, int> ranksLookup = ranks.ToDictionary(x => x, x => Array.FindIndex(ranks, y => y == x));
 42         private static string Show(string str) => $@"""{str}""";
 43         private static string ShowSeq(IEnumerable<string> seq) => $"[{string.Join(", ", seq.Select(Show))}]";
 44         private static (string type, string[] ranks) Act(string[] holeCards, string[] communityCards) => Edm.Hand(holeCards.Select(m=>m).ToArray(), communityCards.Select(m=>m).ToArray());
 45     
 46         private static string Error(string message)
 47         {
 48             buffer.Clear();
 49             buffer.Append(template.ToString());
 50             buffer.AppendLine($"Error: {message}");
 51             return buffer.ToString();
 52         }
 53     
 54         private static void Verify(
 55             (string type, string[] ranks) expected, (string type, string[] ranks) actual, string[] holeCards, string[] communityCards)
 56         {
 57             Debug.Assert(holeCards.Concat(communityCards).Distinct().Count() == 7, "Invalid input");
 58             template.Clear();
 59             template.AppendLine($"\tHole cards: {ShowSeq(holeCards)}");
 60             template.AppendLine($"\tCommunity cards: {ShowSeq(communityCards)}");
 61             template.AppendLine($"Expected: (type: {Show(expected.type)}, ranks: {ShowSeq(expected.ranks)})");
 62             Assert.IsNotNull(actual.type, Error("Type must not be null"));
 63             Assert.IsNotNull(actual.ranks, Error("Ranks must not be null"));
 64             template.AppendLine($"Actual: (type: {Show(actual.type)}, ranks: {ShowSeq(actual.ranks)})");
 65             Assert.IsTrue(types.Any(x => string.Equals(x, actual.type)), 
 66                 Error($"{Show(actual.type)} is not valid, valid options are: {ShowSeq(types)}"));
 67             Assert.AreEqual(expected.type, actual.type, Error("Type is incorrect"));
 68             Assert.AreEqual(expected.ranks.Length, actual.ranks.Length, Error("Number of ranks is incorrect"));
 69             for (var i = 0; i < expected.ranks.Length; i++)
 70                 Assert.IsTrue(ranks.Any(x => string.Equals(x, actual.ranks[i])),
 71                     Error($"{Show(actual.ranks[i])} is not valid, valid options are: {ShowSeq(ranks)}"));
 72             for (var i = 0; i < expected.ranks.Length; i++) 
 73                 Assert.AreEqual(expected.ranks[i], actual.ranks[i], Error($"Rank at position {i} is incorrect"));
 74         }
 75     
 76         #region Test Cases
 77     
 78         private static readonly string[] suits = new string[] { "", "", "", "" };
 79         private static Dictionary<string, int> stats = new Dictionary<string, int>();
 80     
 81         [Test(Description = "Fixed Edge Case Tests")]
 82         public void FixedEdgeCaseTests()
 83         {
 84             // ace low straight invalidated 
 85             SampleTest(("nothing", new[] { "A", "8", "7", "5", "4" }), new[] { "A♠", "2♦" }, new[] { "3♣", "4♥", "5♥", "7♥", "8♦" });
 86             // non straight around
 87             SampleTest(("nothing", new[] { "A", "K", "8", "7", "4" }), new[] { "A♠", "K♦" }, new[] { "3♣", "4♥", "2♥", "7♥", "8♦" });
 88           
 89             // pair on board
 90             SampleTest(("pair", new[] { "4", "A", "9", "7" }), new[] { "A♠", "2♦" }, new[] { "3♣", "4♥", "9♥", "7♥", "4♦" });
 91             // pair made with 1 hole card
 92             SampleTest(("pair", new[] { "4", "A", "10", "9" }), new[] { "A♠", "4♦" }, new[] { "3♣", "4♥", "9♥", "7♥", "10♦" });
 93             // pair made with 2 hole cards
 94             SampleTest(("pair", new[] { "4", "A", "10", "9" }), new[] { "4♠", "4♦" }, new[] { "3♣", "A♥", "9♥", "7♥", "10♦" });
 95 
 96             // two pair on board
 97             SampleTest(("two pair", new[] { "Q", "2", "K" }), new[] { "K♠", "J♦" }, new[] { "Q♣", "Q♥", "9♥", "2♥", "2♦" });
 98             // two pair made with 1 hole card and 1 pair on board
 99             SampleTest(("two pair", new[] { "Q", "2", "K" }), new[] { "K♠", "Q♦" }, new[] { "J♣", "Q♥", "9♥", "2♥", "2♦" });
100             // two pair made with 2 hole cards
101             SampleTest(("two pair", new[] { "Q", "2", "K" }), new[] { "2♠", "Q♦" }, new[] { "J♣", "Q♥", "9♥", "2♥", "K♦" });
102             // two pair made with pair in hole cards and 1 pair on board
103             SampleTest(("two pair", new[] { "Q", "2", "K" }), new[] { "Q♠", "Q♦" }, new[] { "K♣", "J♥", "9♥", "2♥", "2♦" });
104             // two pair made with 2 hole cards, invalidating a 3th pair on board
105             SampleTest(("two pair", new[] { "K", "J", "9" }), new[] { "K♠", "J♦" }, new[] { "J♣", "K♥", "9♥", "2♥", "2♦" });
106 
107             // three-of-a-kind on board
108             SampleTest(("three-of-a-kind", new[] { "Q", "K", "J" }), new[] { "K♠", "J♦" }, new[] { "Q♣", "Q♥", "9♥", "2♥", "Q♦" });
109             // three-of-a-kind made with 1 hole card and 1 pair on board
110             SampleTest(("three-of-a-kind", new[] { "Q", "K", "J" }), new[] { "K♠", "Q♦" }, new[] { "Q♣", "Q♥", "9♥", "2♥", "J♦" });
111             // three-of-a-kind made with 2 hole cards
112             SampleTest(("three-of-a-kind", new[] { "Q", "K", "J" }), new[] { "Q♣", "Q♦" }, new[] { "K♠", "Q♥", "9♥", "2♥", "J♦" });
113 
114             // board straight cancels out pocket aces
115             SampleTest(("straight", new[] { "A", "K", "Q", "J", "10" }), new[] { "A♥", "A♠" }, new[] { "A♣", "K♥", "Q♥", "J♥", "10♦" });
116             // super straight
117             SampleTest(("straight", new[] { "A", "K", "Q", "J", "10" }), new[] { "A♠", "Q♥" }, new[] { "K♥", "10♠", "J♠", "9♠", "8♦" });
118             // high straight
119             SampleTest(("straight", new[] { "7", "6", "5", "4", "3" }), new[] { "6♠", "7♥" }, new[] { "3♥", "4♠", "5♠", "10♠", "10♦" });
120             // low straight
121             SampleTest(("straight", new[] { "6", "5", "4", "3", "2" }), new[] { "2♠", "3♥" }, new[] { "4♥", "5♠", "6♠", "10♠", "10♦" });
122             // outside straight
123             SampleTest(("straight", new[] { "6", "5", "4", "3", "2" }), new[] { "2♠", "6♥" }, new[] { "4♥", "5♠", "3♠", "10♠", "10♦" });
124             // inside straight
125             SampleTest(("straight", new[] { "6", "5", "4", "3", "2" }), new[] { "4♠", "3♥" }, new[] { "2♥", "5♠", "6♠", "10♠", "10♦" });
126             // interspersed straight
127             SampleTest(("straight", new[] { "6", "5", "4", "3", "2" }), new[] { "4♠", "2♥" }, new[] { "3♥", "5♠", "6♠", "10♠", "10♦" });
128 
129             // seven deuce runner runner
130             SampleTest(("full house", new[] { "2", "7" }), new[] { "7♥", "2♠" }, new[] { "A♣", "K♥", "2♦", "7♣", "2♥" });
131             // full house with 2 pairs on board where pockets make the triple
132             SampleTest(("full house", new[] { "A", "K" }), new[] { "A♠", "A♦" }, new[] { "K♣", "K♥", "A♥", "Q♥", "Q♦" });
133             // full house with 1 pair on board where pockets make the triple
134             SampleTest(("full house", new[] { "A", "K" }), new[] { "A♠", "A♦" }, new[] { "K♣", "K♥", "A♥", "J♥", "Q♦" });
135             // full house with 1 hole card making triple and other making pair
136             SampleTest(("full house", new[] { "K", "A" }), new[] { "A♠", "K♦" }, new[] { "K♣", "K♥", "A♥", "J♥", "Q♦" });
137             // full house with better triple than board
138             SampleTest(("full house", new[] { "A", "K" }), new[] { "A♠", "A♦" }, new[] { "K♣", "K♥", "A♥", "Q♥", "K♦" });
139 
140             // flush and straight combo
141             SampleTest(("flush", new[] { "J", "10", "9", "8", "6" }), new[] { "8♠", "6♠" }, new[] { "7♦", "5♠", "9♠", "J♠", "10♠" });
142             // power flush
143             SampleTest(("flush", new[] { "A", "K", "Q", "J", "9" }), new[] { "A♠", "Q♠" }, new[] { "K♠", "4♠", "J♠", "9♠", "3♠" });
144 
145             // four-of-a-kind on board
146             SampleTest(("four-of-a-kind", new[] { "A", "K" }), new[] { "K♠", "9♥" }, new[] { "A♥", "A♣", "A♠", "A♦", "3♥" });
147             // four-of-a-kind with 1 hole card and triple on board
148             SampleTest(("four-of-a-kind", new[] { "A", "K" }), new[] { "K♠", "A♥" }, new[] { "9♥", "A♣", "A♠", "A♦", "3♥" });
149             // carré
150             SampleTest(("four-of-a-kind", new[] { "A", "K" }), new[] { "A♠", "A♦" }, new[] { "A♥", "A♣", "K♠", "9♥", "3♥" });
151 
152             // royal flush
153             SampleTest(("straight-flush", new[] { "A", "K", "Q", "J", "10" }), new[] { "A♠", "Q♠" }, new[] { "K♠", "10♠", "J♠", "9♠", "3♦" });
154           
155             // regression tests
156             SampleTest(("straight", new[] { "6", "5", "4", "3", "2" }), new[] { "3♠", "4♥" }, new[] { "6♣", "5♠", "2♣", "2♦", "3♦" });
157             SampleTest(("straight", new[] { "10", "9", "8", "7", "6" }), new[] { "6♣", "10♠" }, new[] { "9♠", "8♦", "5♦", "7♥", "9♦" });
158             SampleTest(("straight", new[] { "K", "Q", "J", "10", "9" }), new[] { "2♦", "J♦" }, new[] { "Q♥", "9♠", "K♥", "10♥", "J♥" });
159         }
160     
161         [Test(Description = "Random Tests (Batch #1)")]
162         public void RandomBatch1Tests()
163         {
164             var rand = new Random((int)(DateTime.Now.Ticks % int.MaxValue));
165             var bulkSize = 500;
166             for (var i = 0; i < bulkSize; i++)
167             {
168                 var hand = GenerateRandomHand(rand);
169                 var holeCards = hand.Take(2).ToArray();
170                 var communityCards = hand.Skip(2).ToArray();
171                 Test(holeCards, communityCards);
172             }
173         }
174 
175         [Test(Description = "Random Tests (Batch #2)")]
176         public void RandomBatch2Tests()
177         {
178             var rand = new Random((int)(DateTime.Now.Ticks % int.MaxValue));
179             var bulkSize = 500;
180             for (var i = 0; i < bulkSize; i++)
181             {
182                 do
183                 {
184                     var hand = GenerateRandomHand(rand);
185                     var holeCards = hand.Take(2).ToArray();
186                     var communityCards = hand.Skip(2).ToArray();
187                     var expected = Expect(holeCards, communityCards);
188 
189                     if (new[] { "nothing", "pair", "two pair", "three-of-a-kind" }.Contains(expected.type))
190                     {
191                         continue;
192                     }
193                     else
194                     {
195                         Test(holeCards, communityCards);
196                         break;
197                     }
198                 } while (true);
199             }
200         }
201 
202         [Test(Description = "Random Tests (Batch #3)")]
203         public void RandomBatch3Tests()
204         {
205             var rand = new Random((int)(DateTime.Now.Ticks % int.MaxValue));
206             var hands = new List<string[]>();
207             var batchSize = 100;
208             for (var i = 0; i < batchSize; i++) hands.Add(GenerateStraightFlush(rand));
209             for (var i = 0; i < batchSize; i++) hands.Add(GenerateFourOfAKind(rand));
210             for (var i = 0; i < batchSize; i++) hands.Add(GenerateFullHouse(rand));
211             for (var i = 0; i < batchSize; i++) hands.Add(GenerateFlush(rand));
212             for (var i = 0; i < batchSize; i++) hands.Add(GenerateStraight(rand));
213             hands = hands.Select(x => x.OrderBy(y => rand.Next()).ToArray()).OrderBy(x => rand.Next()).ToList();
214             foreach (var hand in hands)
215             {
216                 var holeCards = hand.Take(2).ToArray();
217                 var communityCards = hand.Skip(2).ToArray();
218                 Test(holeCards, communityCards);
219             }
220         }
221 
222         private static Dictionary<int, (string rank, string suit, int id)> Deck()
223         {
224             var id = 0;
225             var hand = new List<string>();
226             return (from suit in suits
227                     from rank in ranks
228                     select (rank, suit, id: id++)).ToDictionary(x => x.id);
229         }
230 
231         private static void RemoveSuit(Dictionary<int, (string rank, string suit, int id)> deck, int suit)
232         {
233             var list = deck.Values.Where(card => card.id / ranks.Length == suit).ToList();
234             foreach (var card in list)
235             {
236                 deck.Remove(card.id);
237             }
238         }
239 
240         private static void RemoveRank(Dictionary<int, (string rank, string suit, int id)> deck, int rank)
241         {
242             var list = deck.Values.Where(card => card.id % ranks.Length == rank).ToList();
243             foreach (var card in list)
244             {
245                 deck.Remove(card.id);
246             }
247         }
248 
249         private static (string rank, string suit, int id) RandomCard(Dictionary<int, (string rank, string suit, int id)> deck, Random rand)
250         {
251             return deck.Skip(rand.Next(0, deck.Count)).First().Value;
252         }
253 
254         private static string[] GenerateRandomHand(Random rand)
255         {
256             var hand = new List<string>();
257             var deck = Deck();
258 
259             while (hand.Count < 7)
260             {
261                 var next = RandomCard(deck, rand);
262                 deck.Remove(next.id);
263                 hand.Add($"{next.rank}{next.suit}");
264             }
265 
266             return hand.ToArray();
267         }
268 
269         private static string[] GenerateStraightFlush(Random rand)
270         {
271             var hand = new List<string>();
272             var deck = Deck();
273             var suit = rand.Next(0, suits.Length);
274             var rank = rand.Next(0, ranks.Length - 5);
275             var head = suit * ranks.Length + rank;
276             // 5 cards make the straight flush
277             for (var i = 0; i < 5; i++)
278             {
279                 var current = head + i;
280                 var card = deck[current];
281                 deck.Remove(current);
282                 hand.Add($"{card.rank}{card.suit}");
283             }
284             // any 2 other cards may be added
285             for (var i = 0; i < 2; i++)
286             {
287                 var card = RandomCard(deck, rand);
288                 deck.Remove(card.id);
289                 hand.Add($"{card.rank}{card.suit}");
290             }
291             return hand.ToArray();
292         }
293 
294         private static string[] GenerateFourOfAKind(Random rand)
295         {
296             var hand = new List<string>();
297             var deck = Deck();
298             var rank = rand.Next(0, ranks.Length);
299             var head = rank;
300             // 4 cards make the four-of-a-kind
301             for (var i = 0; i < 4; i++)
302             {
303                 var current = head + i * ranks.Length;
304                 var card = deck[current];
305                 deck.Remove(current);
306                 hand.Add($"{card.rank}{card.suit}");
307             }
308             // any 3 other cards may be added
309             for (var i = 0; i < 3; i++)
310             {
311                 var card = RandomCard(deck, rand);
312                 deck.Remove(card.id);
313                 hand.Add($"{card.rank}{card.suit}");
314             }
315             return hand.ToArray();
316         }
317 
318         private static string[] GenerateFullHouse(Random rand)
319         {
320             var hand = new List<string>();
321             var deck = Deck();
322             var rank = rand.Next(0, ranks.Length);
323             var head = rank;
324             // 3 cards make the triple
325             for (var i = 0; i < 3; i++)
326             {
327                 var current = head + i * ranks.Length;
328                 var card = deck[current];
329                 deck.Remove(current);
330                 hand.Add($"{card.rank}{card.suit}");
331             }
332             // remaining rank would result in a four-of-a-kind
333             RemoveRank(deck, rank);
334             // 2 cards make a pair
335             var rank2 = Array.IndexOf(ranks, RandomCard(deck, rand).rank);
336             var head2 = rank2;
337             for (var i = 0; i < 2; i++)
338             {
339                 var current = head2 + i * ranks.Length;
340                 var card = deck[current];
341                 deck.Remove(current);
342                 hand.Add($"{card.rank}{card.suit}");
343             }
344             // remaining rank would result in a three-of-a-kind
345             RemoveRank(deck, rank2);
346             // any 2 other cards may be added
347             for (var i = 0; i < 2; i++)
348             {
349                 var card = RandomCard(deck, rand);
350                 deck.Remove(card.id);
351                 hand.Add($"{card.rank}{card.suit}");
352             }
353             return hand.ToArray();
354         }
355 
356         private static string[] GenerateFlush(Random rand)
357         {
358             var hand = new List<string>();
359             var deck = Deck();
360             var primaryDeck = Deck();
361             var suit = rand.Next(0, suits.Length);
362             for (var i = 0; i < 4; i++)
363             {
364                 if (i != suit) RemoveSuit(primaryDeck, i);
365             }
366             // 5 cards make a flush
367             for (var i = 0; i < 5; i++)
368             {
369                 var card = RandomCard(primaryDeck, rand);
370                 primaryDeck.Remove(card.id);
371                 deck.Remove(card.id);
372                 hand.Add($"{card.rank}{card.suit}");
373             }
374             // any 2 other cards may be added
375             // small chance on straight flush, but that's ok
376             for (var i = 0; i < 2; i++)
377             {
378                 var card = RandomCard(deck, rand);
379                 deck.Remove(card.id);
380                 hand.Add($"{card.rank}{card.suit}");
381             }
382             return hand.ToArray();
383         }
384 
385         private static string[] GenerateStraight(Random rand)
386         {
387             var hand = new List<string>();
388             var deck = Deck();
389             var rank = rand.Next(0, ranks.Length - 5);
390             var head = rank;
391             // 5 cards make the straight
392             for (var i = 0; i < 5; i++)
393             {
394                 var suit = rand.Next(0, suits.Length);
395                 var current = head + i + suit * ranks.Length;
396                 var card = deck[current];
397                 deck.Remove(current);
398                 hand.Add($"{card.rank}{card.suit}");
399             }
400             // any 2 other cards may be added
401             // small chance on straight flush, but that's ok
402             for (var i = 0; i < 2; i++)
403             {
404                 var card = RandomCard(deck, rand);
405                 deck.Remove(card.id);
406                 hand.Add($"{card.rank}{card.suit}");
407             }
408             return hand.ToArray();
409         }
410 
411         private static void Test(string[] holeCards, string[] communityCards)
412         {
413             var expected = Expect(holeCards, communityCards);
414             var actual = Act(holeCards, communityCards);
415             Verify(expected, actual, holeCards, communityCards);
416             if (!stats.TryGetValue(expected.type, out var cnt)) cnt = 0;
417             stats[expected.type] = cnt + 1;
418         }
419     
420         private static (string type, string[] ranks) Expect(string[] holeCards, string[] communityCards)
421         {
422             var cards = holeCards.Concat(communityCards).Select(Parse).OrderBy(x => ranksLookup[x.rank]).ToArray();
423             var cardsByRank = cards.ToLookup(x => x.rank);
424             var cardsBySuit = cards.ToLookup(x => x.suit);
425             var ans = findStraightFlush();
426             if (ans == null) ans = findFourOfAKind();
427             if (ans == null) ans = findFullHouse();
428             if (ans == null) ans = findFlush();
429             if (ans == null) ans = findStraight();
430             if (ans == null) ans = findThreeOfAKind();
431             if (ans == null) ans = findTwoPair();
432             if (ans == null) ans = findPair();
433             if (ans == null) ans = findNothing();
434             return ans.GetValueOrDefault(default);
435     
436             (string rank, string suit) Parse(string card) => (card.Substring(0, card.Length - 1), card.Substring(card.Length - 1, 1));
437     
438             (string type, string[] ranks)? findStraightFlush()
439             {
440                 var flush = cardsBySuit.SingleOrDefault(x => x.Count() >= 5)?.ToArray();
441                 if (flush == null) return null;
442                 for (var i = 0; i + 4 < flush.Length; i++)
443                 {
444                     var match = true;
445                     for (var j = 1; j <= 4; j++)
446                     {
447                         if (!flush.Any(card => ranksLookup[card.rank] == ranksLookup[flush[i].rank] + j))
448                         {
449                             match = false;
450                             break;
451                         }
452                     }
453                     if (match) return ("straight-flush", Enumerable.Range(0, 5).Select(k => ranks[k + ranksLookup[flush[i].rank]]).ToArray());
454                 }
455                 return null;
456             }
457     
458             (string type, string[] ranks)? findFourOfAKind()
459             {
460                 var t4_cards = cardsByRank.SingleOrDefault(x => x.Count() == 4);
461                 if (t4_cards == null) return null;
462                 var t4 = t4_cards.First().rank;
463                 var h1 = cardsByRank.First(x => x.Key != t4).Key;
464                 return ("four-of-a-kind", new[] { t4, h1 });
465             }
466     
467             (string type, string[] ranks)? findFullHouse()
468             {
469                 var t3_set = cardsByRank.Where(x => x.Count() == 3);
470                 if (!t3_set.Any()) return null;
471                 var t3 = t3_set.First().First().rank;
472                 var t2_ranks = cardsByRank.Where(x => x.Count() == 2).Select(x => x.Key).ToList();
473                 if (t3_set.Count() > 1) t2_ranks.Add(t3_set.Skip(1).First().Key);
474                 if (!t2_ranks.Any()) return null;
475                 var t2 = t2_ranks.OrderBy(x => ranksLookup[x]).First();
476                 return ("full house", new[] { t3, t2 });
477             }
478     
479             (string type, string[] ranks)? findFlush()
480             {
481                 var flush = cardsBySuit.SingleOrDefault(x => x.Count() >= 5)?.ToArray();
482                 if (flush == null) return null;
483                 return ("flush", flush.Take(5).Select(x => x.rank).ToArray());
484             }
485     
486             (string type, string[] ranks)? findStraight()
487             {
488                 for (var i = 0; i + 4 < cards.Length; i++)
489                 {
490                     var match = true;
491                     for (var j = 1; j <= 4; j++)
492                     {
493                         if (!cards.Any(card => ranksLookup[card.rank] == ranksLookup[cards[i].rank] + j))
494                         {
495                             match = false;
496                             break;
497                         }
498                     }
499                     if (match) return ("straight", Enumerable.Range(0, 5).Select(k => ranks[k + ranksLookup[cards[i].rank]]).ToArray());
500                 }
501                 return null;
502             }
503     
504             (string type, string[] ranks)? findThreeOfAKind()
505             {
506                 var t3_cards = cardsByRank.SingleOrDefault(x => x.Count() == 3);
507                 if (t3_cards == null) return null;
508                 var t3 = t3_cards.First().rank;
509                 var h1 = cardsByRank.First(x => x.Key != t3).Key;
510                 var h2 = cardsByRank.First(x => x.Key != t3 && x.Key != h1).Key;
511                 return ("three-of-a-kind", new[] { t3, h1, h2 });
512             }
513     
514             (string type, string[] ranks)? findTwoPair()
515             {
516                 var t2_set = cardsByRank.Where(x => x.Count() == 2);
517                 if (t2_set.Count() < 2) return null;
518                 var t2_high = t2_set.First().First().rank;
519                 var t2_low = t2_set.Skip(1).First().First().rank;
520                 var h1 = cardsByRank.First(x => x.Key != t2_high && x.Key != t2_low).Key;
521                 return ("two pair", new[] { t2_high, t2_low, h1 });
522             }
523     
524             (string type, string[] ranks)? findPair()
525             {
526                 var t2_cards = cardsByRank.SingleOrDefault(x => x.Count() == 2);
527                 if (t2_cards == null) return null;
528                 var t2 = t2_cards.First().rank;
529                 var h1 = cardsByRank.First(x => x.Key != t2).Key;
530                 var h2 = cardsByRank.First(x => x.Key != t2 && x.Key != h1).Key;
531                 var h3 = cardsByRank.First(x => x.Key != t2 && x.Key != h1 && x.Key != h2).Key;
532                 return ("pair", new[] { t2, h1, h2, h3 });
533             }
534     
535             (string type, string[] ranks) findNothing()
536             {
537                 return ("nothing", cards.Take(5).Select(x => x.rank).ToArray());
538             }
539         }
540     
541         #endregion
542     }
543 }