pokercore/pokerhand.go

236 lines
6.3 KiB
Go
Raw Normal View History

2024-05-18 09:21:41 +00:00
package pokercore
import (
"errors"
"fmt"
)
2024-05-18 09:21:41 +00:00
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...)
}
2024-05-18 09:21:41 +00:00
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
2024-05-18 09:21:41 +00:00
}
ph := new(PokerHand)
ph.Hand = phc.SortByRankAscending()
if ph.Hand.containsRoyalFlush() {
2024-05-18 09:21:41 +00:00
ph.Type = RoyalFlush
ph.Score = ScoreRoyalFlush
return ph, nil
}
if ph.Hand.containsStraightFlush() {
2024-05-18 09:21:41 +00:00
ph.Type = StraightFlush
ph.Score = ScoreStraightFlush
ph.Score += 1000 * ph.Hand.HighestRank().Score()
2024-05-18 09:21:41 +00:00
return ph, nil
}
if ph.Hand.containsFourOfAKind() {
2024-05-18 09:21:41 +00:00
ph.Type = FourOfAKind
ph.Score = ScoreFourOfAKind
ph.Score += 1000 * ph.Hand.fourOfAKindRank().Score()
ph.Score += 100 * ph.Hand.fourOfAKindKicker().Score()
2024-05-18 09:21:41 +00:00
return ph, nil
}
if ph.Hand.containsFullHouse() {
2024-05-18 09:21:41 +00:00
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()
2024-05-18 09:21:41 +00:00
return ph, nil
}
if ph.Hand.containsFlush() {
2024-05-18 09:21:41 +00:00
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()
2024-05-18 09:21:41 +00:00
return ph, nil
}
if ph.Hand.containsStraight() {
2024-05-18 09:21:41 +00:00
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()
}
2024-05-18 09:21:41 +00:00
return ph, nil
}
if ph.Hand.containsThreeOfAKind() {
2024-05-18 09:21:41 +00:00
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()
2024-05-18 09:21:41 +00:00
return ph, nil
}
if ph.Hand.containsTwoPair() {
2024-05-18 09:21:41 +00:00
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()
2024-05-18 09:21:41 +00:00
return ph, nil
}
if ph.Hand.containsPair() {
2024-05-18 09:21:41 +00:00
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()
2024-05-18 09:21:41 +00:00
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()
2024-05-18 09:21:41 +00:00
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()
2024-05-18 09:21:41 +00:00
}
func (ph PokerHand) String() string {
return fmt.Sprintf("<PokerHand: %s (%s)>", ph.Hand.String(), ph.Description())
}
func (c PokerHand) Description() string {
sortedHand := c.Hand.SortByRankAscending()
2024-05-18 09:21:41 +00:00
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)
2024-05-18 09:21:41 +00:00
}
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)
2024-05-18 09:21:41 +00:00
}
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())
}
2024-05-18 09:21:41 +00:00
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(),
)
}
2024-05-18 09:21:41 +00:00
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(),
)
}