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

  • 【算法】用c#实现德州扑克卡牌游戏规则已关闭评论
  • 81 次浏览
  • A+
所属分类:.NET技术
摘要

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

德州扑克是一种牌类游戏,可多人参与,它的玩法是,玩家每人发两张底牌,桌面依次发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展开]: (int) Char.GetNumericValue(s[0]); 204             suit = s展开; 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 }