2024-05-18 09:21:41 +00:00
|
|
|
package pokercore
|
|
|
|
|
|
|
|
import "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
|
|
|
|
}
|
|
|
|
|
2024-05-19 00:33:15 +00:00
|
|
|
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 len(c) != 5 {
|
|
|
|
return nil, fmt.Errorf("hand must have 5 cards to be scored as a poker hand")
|
|
|
|
}
|
|
|
|
if c.containsDuplicates() {
|
|
|
|
return nil, fmt.Errorf("hand must have no duplicates to be scored as a poker hand")
|
|
|
|
}
|
|
|
|
|
|
|
|
ph := new(PokerHand)
|
|
|
|
ph.Hand = c
|
|
|
|
if c.containsRoyalFlush() {
|
|
|
|
ph.Type = RoyalFlush
|
|
|
|
ph.Score = ScoreRoyalFlush
|
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.containsStraightFlush() {
|
|
|
|
ph.Type = StraightFlush
|
|
|
|
ph.Score = ph.Hand.scoreStraightFlush()
|
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.containsFourOfAKind() {
|
|
|
|
ph.Type = FourOfAKind
|
|
|
|
ph.Score = ScoreFourOfAKind + 1000*c.fourOfAKindRank().Score() + c.fourOfAKindKicker().Score()
|
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.containsFullHouse() {
|
|
|
|
ph.Type = FullHouse
|
|
|
|
ph.Score = ScoreFullHouse + 1000*c.fullHouseTripsRank().Score() + c.fullHousePairRank().Score()
|
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.containsFlush() {
|
|
|
|
ph.Type = Flush
|
|
|
|
ph.Score = c.scoreFlush()
|
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.containsStraight() {
|
|
|
|
ph.Type = Straight
|
2024-05-19 00:33:15 +00:00
|
|
|
|
|
|
|
// 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 := c.SortByRankAscending()
|
|
|
|
|
|
|
|
if sorted[3].Rank == FIVE && sorted[4].Rank == ACE {
|
|
|
|
// 5-high straight
|
|
|
|
ph.Score = ScoreStraight + 1000*sorted[3].Score()
|
|
|
|
} else {
|
|
|
|
// All other straights
|
|
|
|
ph.Score = ScoreStraight + 1000*sorted[4].Score()
|
|
|
|
}
|
2024-05-18 09:21:41 +00:00
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.containsThreeOfAKind() {
|
|
|
|
ph.Type = ThreeOfAKind
|
|
|
|
ph.Score = ScoreThreeOfAKind
|
|
|
|
ph.Score += 1000 * c.threeOfAKindTripsRank().Score()
|
|
|
|
ph.Score += 100 * c.threeOfAKindFirstKicker().Score()
|
|
|
|
ph.Score += 10 * c.threeOfAKindSecondKicker().Score()
|
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.containsTwoPair() {
|
|
|
|
ph.Type = TwoPair
|
|
|
|
ph.Score = ScoreTwoPair
|
|
|
|
ph.Score += 1000 * c.twoPairBiggestPair().Score()
|
|
|
|
ph.Score += 100 * c.twoPairSmallestPair().Score()
|
|
|
|
ph.Score += 10 * c.twoPairKicker().Score()
|
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.containsPair() {
|
|
|
|
ph.Type = Pair
|
|
|
|
ph.Score = ScorePair
|
|
|
|
ph.Score += 1000 * c.pairRank().Score()
|
|
|
|
ph.Score += 100 * c.pairFirstKicker().Score()
|
|
|
|
ph.Score += 10 * c.pairSecondKicker().Score()
|
|
|
|
ph.Score += c.pairThirdKicker().Score()
|
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ph.Type = HighCard
|
|
|
|
ph.Score = c.scoreHighCard()
|
|
|
|
return ph, nil
|
|
|
|
}
|
|
|
|
|
2024-05-19 00:33:15 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2024-05-19 00:33:15 +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() {
|
2024-05-19 00:33:15 +00:00
|
|
|
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() {
|
2024-05-19 00:33:15 +00:00
|
|
|
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() {
|
2024-05-19 00:33:15 +00:00
|
|
|
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-19 00:33:15 +00:00
|
|
|
|
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(),
|
|
|
|
)
|
|
|
|
}
|