package pokercore import ( "errors" "fmt" ) type PokerHandType int const ( HighCard PokerHandType = iota Pair TwoPair ThreeOfAKind Straight Flush FullHouse FourOfAKind StraightFlush RoyalFlush ) type PokerHand struct { Hand Cards Type PokerHandType Score HandScore } func (c Cards) Append(other Cards) Cards { return append(c, other...) } func (c Cards) PokerHand() (*PokerHand, error) { if c.containsDuplicates() { return nil, errors.New("hand must have no duplicates to be scored as a poker hand") } if len(c) < 5 { return nil, errors.New("hand must have at least 5 cards to be scored as a poker hand") } phc, err := c.IdentifyBestFiveCardPokerHand() if err != nil { // this should in theory never happen return nil, err } ph := new(PokerHand) ph.Hand = phc.SortByRankAscending() if ph.Hand.containsRoyalFlush() { ph.Type = RoyalFlush ph.Score = ScoreRoyalFlush return ph, nil } if ph.Hand.containsStraightFlush() { ph.Type = StraightFlush ph.Score = ScoreStraightFlush ph.Score += 1000 * ph.Hand.HighestRank().Score() return ph, nil } if ph.Hand.containsFourOfAKind() { ph.Type = FourOfAKind ph.Score = ScoreFourOfAKind ph.Score += 1000 * ph.Hand.fourOfAKindRank().Score() ph.Score += 100 * ph.Hand.fourOfAKindKicker().Score() return ph, nil } if ph.Hand.containsFullHouse() { ph.Type = FullHouse ph.Score = ScoreFullHouse // FIXME write a good test for this ph.Score += 1000 * ph.Hand.fullHouseTripsRank().Score() ph.Score += 100 * ph.Hand.fullHousePairRank().Score() return ph, nil } if ph.Hand.containsFlush() { ph.Type = Flush ph.Score = ScoreFlush // flush base score plus sum of card ranks ph.Score += ph.Hand[0].Score() ph.Score += ph.Hand[1].Score() ph.Score += ph.Hand[2].Score() ph.Score += ph.Hand[3].Score() ph.Score += ph.Hand[4].Score() return ph, nil } if ph.Hand.containsStraight() { ph.Type = Straight ph.Score = ScoreStraight // Straights are scored by the highest card in the straight // UNLESS the second highest card is a 5 and the highest card is an Ace // In that case, the straight is a 5-high straight, not an Ace-high straight sorted := ph.Hand.SortByRankAscending() if sorted[3].Rank == FIVE && sorted[4].Rank == ACE { // 5-high straight, scored by the five's rank ph.Score += sorted[3].Score() } else { // All other straights are scored by the highest card in the straight ph.Score += sorted[4].Score() } return ph, nil } if ph.Hand.containsThreeOfAKind() { ph.Type = ThreeOfAKind ph.Score = ScoreThreeOfAKind ph.Score += 1000 * ph.Hand.threeOfAKindTripsRank().Score() ph.Score += 100 * ph.Hand.threeOfAKindFirstKicker().Score() ph.Score += 10 * ph.Hand.threeOfAKindSecondKicker().Score() return ph, nil } if ph.Hand.containsTwoPair() { ph.Type = TwoPair ph.Score = ScoreTwoPair ph.Score += 1000 * ph.Hand.twoPairBiggestPair().Score() ph.Score += 100 * ph.Hand.twoPairSmallestPair().Score() ph.Score += 10 * ph.Hand.twoPairKicker().Score() return ph, nil } if ph.Hand.containsPair() { ph.Type = Pair ph.Score = ScorePair ph.Score += 1000 * ph.Hand.pairRank().Score() ph.Score += 100 * ph.Hand.pairFirstKicker().Score() ph.Score += 10 * ph.Hand.pairSecondKicker().Score() ph.Score += ph.Hand.pairThirdKicker().Score() return ph, nil } ph.Type = HighCard ph.Score = ScoreHighCard // base score // unmade hands are scored like flushes, just add up the values ph.Score += ph.Hand[0].Score() ph.Score += ph.Hand[1].Score() ph.Score += ph.Hand[2].Score() ph.Score += ph.Hand[3].Score() ph.Score += ph.Hand[4].Score() return ph, nil } func (ph PokerHand) ToSortedCards() Cards { sorted := ph.Hand.SortByRankAscending() return sorted } func (ph PokerHand) Compare(other PokerHand) int { if ph.Score > other.Score { return 1 } if ph.Score < other.Score { return -1 } return 0 } func (ph PokerHand) HighestRank() Rank { return ph.Hand.HighestRank() } func (ph PokerHand) String() string { return fmt.Sprintf("", ph.Hand.String(), ph.Description()) } func (c PokerHand) Description() string { sortedHand := c.Hand.SortByRankAscending() if c.Type == RoyalFlush { return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit) } if c.Hand.containsStraightFlush() { if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE { // special case for steel wheel return fmt.Sprintf("%s high straight flush in %s", sortedHand[3].Rank.WithArticle(), sortedHand[4].Suit) } return fmt.Sprintf("%s high straight flush in %s", c.HighestRank().WithArticle(), sortedHand[4].Suit) } if c.Hand.containsFourOfAKind() { return fmt.Sprintf("four %s with %s", c.Hand.fourOfAKindRank().Pluralize(), c.Hand.fourOfAKindKicker().Rank.WithArticle()) } if c.Hand.containsFullHouse() { return fmt.Sprintf("a full house, %s full of %s", c.Hand.fullHouseTripsRank().Pluralize(), c.Hand.fullHousePairRank().Pluralize()) } if c.Hand.containsFlush() { return fmt.Sprintf("%s high flush in %s", c.HighestRank().WithArticle(), sortedHand[4].Suit) } if c.Hand.containsStraight() { if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE { // special case for wheel straight return fmt.Sprintf("%s high straight", sortedHand[3].Rank.WithArticle()) } return fmt.Sprintf("%s high straight", c.HighestRank().WithArticle()) } if c.Hand.containsThreeOfAKind() { return fmt.Sprintf( "three %s with %s and %s", c.Hand.threeOfAKindTripsRank().Pluralize(), c.Hand.threeOfAKindFirstKicker().Rank.WithArticle(), c.Hand.threeOfAKindSecondKicker().Rank.WithArticle(), ) } if c.Hand.containsTwoPair() { return fmt.Sprintf( "two pair, %s and %s with %s", c.Hand.twoPairBiggestPair().Pluralize(), c.Hand.twoPairSmallestPair().Pluralize(), c.Hand.twoPairKicker().Rank.WithArticle(), ) } if c.Hand.containsPair() { return fmt.Sprintf( "a pair of %s with %s, %s, and %s", c.Hand.pairRank().Pluralize(), c.Hand.pairFirstKicker().Rank.WithArticle(), c.Hand.pairSecondKicker().Rank.WithArticle(), c.Hand.pairThirdKicker().Rank.WithArticle(), ) } return fmt.Sprintf( // "ace high with an eight, a seven, a six, and a deuce" "%s high with %s, %s, %s, and %s", sortedHand[4].Rank, sortedHand[3].Rank.WithArticle(), sortedHand[2].Rank.WithArticle(), sortedHand[1].Rank.WithArticle(), sortedHand[0].Rank.WithArticle(), ) }