【算法】用c#实现德州扑克卡牌游戏规则
德州扑克是一种牌类游戏,可多人参与,它的玩法是,玩家每人发两张底牌,桌面依次发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 }