diff --git a/pokercore/findhand.go b/pokercore/findhand.go new file mode 100644 index 0000000..7a69ec8 --- /dev/null +++ b/pokercore/findhand.go @@ -0,0 +1,42 @@ +package pokercore + +import "errors" + +var ErrDuplicateCard = errors.New("cannot score a poker hand out of a set of cards with duplicates") + +// this method makes a n new hands where n is the number of cards in the hand +// each of the new hands has one card removed from the original hand +// then it calls the identifyBestFiveCardPokerHand method on each of the new hands +// and returns the best hand by score. this is recursion. +func (hand Cards) identifyBestFiveCardPokerHand() (Cards, error) { + if hand.containsDuplicates() { + return nil, ErrDuplicateCard + } + + newHands := make([]Cards, len(hand)) + for i := 0; i < len(hand); i++ { + newHand := hand[:i] + newHand = append(newHand, hand[i+1:]...) + newHands[i] = newHand + } + var bestHand Cards + var bestScore HandScore + + for _, h := range newHands { + if len(h) == 5 { + score, _ := h.PokerHandScore() + if score > bestScore { + bestScore = score + bestHand = h + } + } else { + rh, _ := h.identifyBestFiveCardPokerHand() + score, _ := rh.PokerHandScore() + if score > bestScore { + bestScore = score + bestHand = rh + } + } + } + return bestHand, nil +} diff --git a/pokercore/handhelpers.go b/pokercore/handhelpers.go new file mode 100644 index 0000000..fe51826 --- /dev/null +++ b/pokercore/handhelpers.go @@ -0,0 +1,436 @@ +package pokercore + +func (c Cards) scoreStraightFlush() HandScore { + if !c.containsStraightFlush() { + panic("hand must be a straight flush to score it") + } + return ScoreStraightFlush + 1000*c.HighestRank().Score() +} + +func (c Cards) scoreFlush() HandScore { + if !c.containsFlush() { + panic("hand must be a flush to score it") + } + var score HandScore + for _, card := range c { + score += card.Rank.Score() + } + return ScoreFlush + score +} + +func (c Cards) scoreHighCard() HandScore { + if !c.isUnmadeHand() { + panic("hand must be a high card to score it") + } + var score HandScore + for _, card := range c { + score += card.Rank.Score() + } + return ScoreHighCard + score +} + +func (c Cards) pairRank() Rank { + if !c.containsPair() { + panic("hand must have a pair to have a pair rank") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank { + return sorted[0].Rank + } + if sorted[1].Rank == sorted[2].Rank { + return sorted[1].Rank + } + if sorted[2].Rank == sorted[3].Rank { + return sorted[2].Rank + } + if sorted[3].Rank == sorted[4].Rank { + return sorted[3].Rank + } + panic("nope") +} + +func (c Cards) pairFirstKicker() Card { + if !c.containsPair() { + panic("hand must have a pair to have a first kicker") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank { + return sorted[4] + } + if sorted[1].Rank == sorted[2].Rank { + return sorted[4] + } + if sorted[2].Rank == sorted[3].Rank { + return sorted[4] + } + if sorted[3].Rank == sorted[4].Rank { + return sorted[2] + } + panic("nope") +} + +func (c Cards) pairSecondKicker() Card { + if !c.containsPair() { + panic("hand must have a pair to have a second kicker") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank { + // first kicker is [4] + return sorted[3] + } + if sorted[1].Rank == sorted[2].Rank { + // first kicker is [4] + return sorted[3] + } + if sorted[2].Rank == sorted[3].Rank { + // first kicker is [4] + return sorted[1] + } + if sorted[3].Rank == sorted[4].Rank { + // first kicker is [2] + return sorted[1] + } + panic("nope") +} + +func (c Cards) pairThirdKicker() Card { + if !c.containsPair() { + panic("hand must have a pair to have a third kicker") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank { + // first kicker is [4] + // second kicker is [3] + return sorted[2] + } + if sorted[1].Rank == sorted[2].Rank { + // first kicker is [4] + // second kicker is [3] + return sorted[0] + } + if sorted[2].Rank == sorted[3].Rank { + // first kicker is [4] + // second kicker is [1] + return sorted[0] + } + if sorted[3].Rank == sorted[4].Rank { + // first kicker is [2] + // second kicker is [1] + return sorted[0] + } + panic("nope") +} + +func (c Cards) twoPairBiggestPair() Rank { + if !c.containsTwoPair() { + panic("hand must have two pair to have a biggest pair") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank { + return sorted[2].Rank + } + + if sorted[0].Rank == sorted[1].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[0].Rank + } + if sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[1].Rank + } + panic("nope") +} + +func (c Cards) twoPairSmallestPair() Rank { + if !c.containsTwoPair() { + panic("hand must have two pair to have a smallest pair") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank { + return sorted[2].Rank + } + if sorted[0].Rank == sorted[1].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[3].Rank + } + if sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[3].Rank + } + panic("nope") +} + +func (c Cards) twoPairKicker() Card { + if !c.containsTwoPair() { + panic("hand must have two pair to have a twoPairKicker") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank { + return sorted[4] + } + if sorted[0].Rank == sorted[1].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[2] + } + if sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[0] + } + panic("nope") +} + +func (c Cards) threeOfAKindTripsRank() Rank { + if !c.containsThreeOfAKind() { + panic("hand must have three of a kind to have a trips rank") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank { + return sorted[0].Rank + } + if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank { + return sorted[1].Rank + } + if sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[2].Rank + } + panic("nope") +} + +func (c Cards) threeOfAKindKickers() Cards { + if !c.containsThreeOfAKind() { + panic("hand must have three of a kind to have kickers") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank { + return Cards{sorted[3], sorted[4]} + } + if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank { + return Cards{sorted[0], sorted[4]} + } + if sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { + return Cards{sorted[0], sorted[1]} + } + panic("nope") +} + +func (c Cards) threeOfAKindFirstKicker() Card { + x := c.threeOfAKindKickers() + return x[0] +} + +func (c Cards) threeOfAKindSecondKicker() Card { + x := c.threeOfAKindKickers() + return x[1] +} + +func (c Cards) fourOfAKindRank() Rank { + if !c.containsFourOfAKind() { + panic("hand must have four of a kind to have a four of a kind rank") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank { + return sorted[0].Rank + } + if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[1].Rank + } + panic("nope") +} + +func (c Cards) fourOfAKindKicker() Card { + if !c.containsFourOfAKind() { + panic("hand must have four of a kind to have a four of a kind kicker") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank { + return sorted[4] + } + if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[0] + } + panic("nope") +} + +func (c Cards) fullHouseTripsRank() Rank { + if !c.containsFullHouse() { + panic("hand must have a full house to have a trips rank") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[0].Rank + } + if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[4].Rank + } + panic("nope") +} + +func (c Cards) fullHousePairRank() Rank { + if !c.containsFullHouse() { + panic("hand must have a full house to have a pair rank") + } + sorted := c.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[4].Rank + } + if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { + return sorted[0].Rank + } + panic("nope") +} + +func (hand Cards) containsDuplicates() bool { + seen := make(map[Card]bool) + for _, card := range hand { + if _, ok := seen[card]; ok { + return true + } + seen[card] = true + } + return false +} + +func (hand Cards) IsFiveCardPokerHand() bool { + return len(hand) == 5 && !hand.containsDuplicates() +} + +func (hand Cards) containsFlush() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + suit := hand[0].Suit + for i := 1; i < len(hand); i++ { + if hand[i].Suit != suit { + return false + } + } + return true +} + +func (hand Cards) containsStraight() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + sorted := hand.SortByRank() + + if sorted[4].Rank == ACE && sorted[3].Rank == FIVE { + // special case for A-5 straight + if sorted[0].Rank == DEUCE && sorted[1].Rank == THREE && sorted[2].Rank == FOUR { + return true + } else { + return false + } + } + return sorted[0].Rank.Int()+1 == sorted[1].Rank.Int() && + sorted[1].Rank.Int()+1 == sorted[2].Rank.Int() && + sorted[2].Rank.Int()+1 == sorted[3].Rank.Int() && + sorted[3].Rank.Int()+1 == sorted[4].Rank.Int() +} + +func (hand Cards) containsStraightFlush() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + if hand.containsStraight() && hand.containsFlush() { + return true + } + return false +} + +func (hand Cards) containsRoyalFlush() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + if hand.containsStraightFlush() && hand.HighestRank() == ACE { + return true + } + return false +} + +func (hand Cards) containsFourOfAKind() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + sorted := hand.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank { + // the quads precede the kicker + return true + } + if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { + // the kicker is the first card + return true + } + return false +} + +func (hand Cards) containsFullHouse() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + sorted := hand.SortByRank() + + if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { + // the trips precede the pair + return true + } + if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { + // the pair is first + return true + } + return false +} + +func (hand Cards) containsPair() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + sorted := hand.SortByRank() + if sorted[0].Rank == sorted[1].Rank { + return true + } + if sorted[1].Rank == sorted[2].Rank { + return true + } + if sorted[2].Rank == sorted[3].Rank { + return true + } + if sorted[3].Rank == sorted[4].Rank { + return true + } + return false +} + +func (hand Cards) containsThreeOfAKind() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + sorted := hand.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank { + return true + } + if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank { + return true + } + if sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { + return true + } + return false +} + +func (hand Cards) containsTwoPair() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + sorted := hand.SortByRank() + if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank { + return true + } + if sorted[0].Rank == sorted[1].Rank && sorted[3].Rank == sorted[4].Rank { + return true + } + if sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { + return true + } + return false +} + +func (hand Cards) isUnmadeHand() bool { + if !hand.IsFiveCardPokerHand() { + panic("hand must have 5 cards to be scored") + } + return !hand.containsPair() && !hand.containsTwoPair() && !hand.containsThreeOfAKind() && !hand.containsStraight() && !hand.containsFlush() && !hand.containsFullHouse() && !hand.containsFourOfAKind() && !hand.containsStraightFlush() && !hand.containsRoyalFlush() +} diff --git a/pokercore/pokercore.go b/pokercore/pokercore.go index 279b891..f0d8641 100644 --- a/pokercore/pokercore.go +++ b/pokercore/pokercore.go @@ -56,21 +56,19 @@ type Card struct { type Cards []Card -func (r Rank) Int(x AcesHighOrLow) int { - return int(rankToScore(r, x)) +func (r Rank) Int() int { + return int(r.Score()) } -func (r Rank) HandScore(x AcesHighOrLow) HandScore { - return HandScore(r.Int(x)) +func (r Rank) HandScore() HandScore { + return HandScore(r.Int()) } -func (c Cards) SortByRank(x AcesHighOrLow) Cards { - +func (c Cards) SortByRank() Cards { newCards := make(Cards, len(c)) copy(newCards, c) - sort.Slice(newCards, func(i, j int) bool { - return rankToScore(newCards[i].Rank, x) > rankToScore(newCards[j].Rank, x) + return newCards[i].Rank.Score() > newCards[j].Rank.Score() }) return newCards } @@ -125,8 +123,8 @@ func (c *Card) String() string { return fmt.Sprintf("%s%s", string(c.Rank), string(c.Suit)) } -func (c Cards) HighestRank(x AcesHighOrLow) Rank { - c = c.SortByRank(x) +func (c Cards) HighestRank() Rank { + c = c.SortByRank() return c[0].Rank } diff --git a/pokercore/pokerhand.go b/pokercore/pokerhand.go new file mode 100644 index 0000000..f7b384d --- /dev/null +++ b/pokercore/pokerhand.go @@ -0,0 +1,163 @@ +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 +} + +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 + ph.Score = ScoreStraight + 1000*c.HighestRank().Score() + 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 +} + +func (c PokerHand) HighestRank() Rank { + return c.Hand.HighestRank() +} + +func (c PokerHand) String() string { + sortedHand := c.Hand.SortByRank() + if c.Type == RoyalFlush { + return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit) + } + if c.Hand.containsStraightFlush() { + return fmt.Sprintf("%s high straight flush in %s", c.HighestRank().WithArticle(), sortedHand[0].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[0].Suit) + } + if c.Hand.containsStraight() { + 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(), + ) +} diff --git a/pokercore/scoring.go b/pokercore/scoring.go index 8c0ed5b..06ae6ef 100644 --- a/pokercore/scoring.go +++ b/pokercore/scoring.go @@ -5,7 +5,7 @@ import "fmt" type HandScore int const ( - ScoreNoPair = HandScore(iota * 100_000_000_000) + ScoreHighCard = HandScore(iota * 100_000_000_000) ScorePair ScoreTwoPair ScoreThreeOfAKind @@ -14,23 +14,108 @@ const ( ScoreFullHouse ScoreFourOfAKind ScoreStraightFlush + ScoreRoyalFlush ) -type AcesHighOrLow int +func (c Card) Score() HandScore { + return HandScore(c.Rank.Score()) +} -const ( - AcesHigh AcesHighOrLow = iota - AcesLow -) +func (c Cards) PokerHandScore() (HandScore, error) { + if len(c) != 5 { + return 0, fmt.Errorf("hand must have 5 cards to be scored as a poker hand") + } + ph, err := c.PokerHand() + if err != nil { + return 0, err + } -func rankToScore(rank Rank, AcesHighOrLow AcesHighOrLow) HandScore { - switch rank { + fmt.Println(ph) + return ph.Score, nil +} + +/* +func scoreToName(x HandScore) string { + switch x { + case x >= ScoreRoyalFlush: + return "Royal Flush" + case x >= ScoreStraightFlush: + return "Straight Flush" + case x >= ScoreFourOfAKind: + return "Four of a Kind" + case x >= ScoreFullHouse: + return "Full House" + case x >= ScoreFlush: + return "Flush" + case x >= ScoreStraight: + return "Straight" + case x >= ScoreThreeOfAKind: + return "Three of a Kind" + case x >= ScoreTwoPair: + return "Two Pair" + case x >= ScorePair: + return "Pair" + case x >= ScoreHighCard: + return "High Card" + } +} +*/ + +func (r Rank) Article() string { + switch r { case ACE: - if AcesHighOrLow == AcesHigh { - return 14 // Aces are high, so we give them the highest value - } else { - return 1 - } + return "an" + case EIGHT: + return "an" + default: + return "a" + } +} + +func (r Rank) WithArticle() string { + return fmt.Sprintf("%s %s", r.Article(), r) +} + +func (r Rank) Pluralize() string { + switch r { + case ACE: + return "Aces" + case DEUCE: + return "Deuces" + case THREE: + return "Threes" + case FOUR: + return "Fours" + case FIVE: + return "Fives" + case SIX: + return "Sixes" + case SEVEN: + return "Sevens" + case EIGHT: + return "Eights" + case NINE: + return "Nines" + case TEN: + return "Tens" + case JACK: + return "Jacks" + case QUEEN: + return "Queens" + case KING: + return "Kings" + default: + panic("nope") + } +} + +func (x HandScore) String() string { + return fmt.Sprintf("", x) + //return scoreToName(x) +} + +func (r Rank) Score() HandScore { + switch r { case DEUCE: return 2 case THREE: @@ -55,209 +140,8 @@ func rankToScore(rank Rank, AcesHighOrLow AcesHighOrLow) HandScore { return 12 case KING: return 13 - default: - panic("nope") + case ACE: + return 14 } -} - -func (c Cards) ScoreHand() (HandScore, error) { - if !c.IsFiveCardPokerHand() { - return 0, fmt.Errorf("hand must have 5 cards with no duplicates to be scored") - } - if c.containsRoyalFlush() { - return ScoreStraightFlush + 1000*ACE.HandScore(AcesHigh), nil - } - if c.containsStraightFlush() { - return ScoreStraightFlush + 1000*c.HighestRank(AcesHigh).HandScore(AcesHigh), nil - } - - panic("not implemented") - // FIXME finish this - return 0, nil -} - -func (hand Cards) containsDuplicates() bool { - seen := make(map[Card]bool) - for _, card := range hand { - if _, ok := seen[card]; ok { - return true - } - seen[card] = true - } - return false -} - -func (hand Cards) IsFiveCardPokerHand() bool { - return len(hand) == 5 && !hand.containsDuplicates() -} - -func (hand Cards) containsFlush() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - suit := hand[0].Suit - for i := 1; i < len(hand); i++ { - if hand[i].Suit != suit { - return false - } - } - return true -} - -func (hand Cards) containsStraight() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - sorted := hand.SortByRank(AcesHigh) - - if sorted[0].Rank == ACE && sorted[1].Rank == FIVE { - // special case for A-5 straight - if sorted[1].Rank == FIVE && sorted[2].Rank == FOUR && sorted[3].Rank == THREE && sorted[4].Rank == DEUCE { - return true - } - } - return sorted[0].Rank.Int(AcesHigh) == sorted[1].Rank.Int(AcesHigh)+1 && sorted[1].Rank.Int(AcesHigh) == sorted[2].Rank.Int(AcesHigh)+1 && sorted[2].Rank.Int(AcesHigh) == sorted[3].Rank.Int(AcesHigh)+1 && sorted[3].Rank.Int(AcesHigh) == sorted[4].Rank.Int(AcesHigh)+1 -} - -func (hand Cards) containsStraightFlush() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - if hand.containsStraight() && hand.containsFlush() { - return true - } - return false -} - -func (hand Cards) containsRoyalFlush() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - sorted := hand.SortByRank(AcesHigh) - if hand.containsStraightFlush() && sorted[0].Rank == ACE { - return true - } - return false -} - -func (hand Cards) containsFourOfAKind() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - sorted := hand.SortByRank(AcesHigh) - if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank { - return true - } - if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { - return true - } - return false -} - -func (hand Cards) containsFullHouse() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - sorted := hand.SortByRank(AcesHigh) - if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { - return true - } - if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { - return true - } - return false -} - -func (hand Cards) containsPair() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - sorted := hand.SortByRank(AcesHigh) - if sorted[0].Rank == sorted[1].Rank { - return true - } - if sorted[1].Rank == sorted[2].Rank { - return true - } - if sorted[2].Rank == sorted[3].Rank { - return true - } - if sorted[3].Rank == sorted[4].Rank { - return true - } - return false -} - -func (hand Cards) containsThreeOfAKind() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - sorted := hand.SortByRank(AcesHigh) - if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank { - return true - } - if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank { - return true - } - if sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank { - return true - } - return false -} - -func (hand Cards) containsTwoPair() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - sorted := hand.SortByRank(AcesHigh) - if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank { - return true - } - if sorted[0].Rank == sorted[1].Rank && sorted[3].Rank == sorted[4].Rank { - return true - } - if sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { - return true - } - return false -} - -func (hand Cards) isUnmadeHand() bool { - if !hand.IsFiveCardPokerHand() { - panic("hand must have 5 cards to be scored") - } - return !hand.containsPair() && !hand.containsTwoPair() && !hand.containsThreeOfAKind() && !hand.containsStraight() && !hand.containsFlush() && !hand.containsFullHouse() && !hand.containsFourOfAKind() && !hand.containsStraightFlush() && !hand.containsRoyalFlush() -} - -// this method makes a n new hands where n is the number of cards in the hand -// each of the new hands has one card removed from the original hand -// then it calls the identifyBestFiveCardPokerHand method on each of the new hands -// and returns the best hand by score. this is recursion. -func (hand Cards) identifyBestFiveCardPokerHand() (Cards, error) { - newHands := make([]Cards, len(hand)) - for i := 0; i < len(hand); i++ { - newHand := hand[:i] - newHand = append(newHand, hand[i+1:]...) - newHands[i] = newHand - } - var bestHand Cards - var bestScore HandScore - - for _, h := range newHands { - if h.IsFiveCardPokerHand() { - score, _ := h.ScoreHand() - if score > bestScore { - bestScore = score - bestHand = h - } - } else { - rh, _ := h.identifyBestFiveCardPokerHand() - score, _ := rh.ScoreHand() - if score > bestScore { - bestScore = score - bestHand = rh - } - } - } - return bestHand, nil + return 0 } diff --git a/pokercore/scoring_test.go b/pokercore/scoring_test.go index 0a26909..ac26b77 100644 --- a/pokercore/scoring_test.go +++ b/pokercore/scoring_test.go @@ -75,13 +75,13 @@ func TestFlush(t *testing.T) { x := ScoreFlush var multiplier HandScore = 100 - x += multiplier * DEUCE.HandScore(AcesHigh) + x += multiplier * DEUCE.Score() multiplier *= 100 - x += multiplier * THREE.HandScore(AcesHigh) + x += multiplier * THREE.Score() multiplier *= 100 - x += multiplier * FOUR.HandScore(AcesHigh) + x += multiplier * FOUR.Score() multiplier *= 100 - x += multiplier * SIX.HandScore(AcesHigh) + x += multiplier * SIX.Score() fmt.Printf("a-2-3-4-6 flush score should be: %d\n", x) } @@ -103,7 +103,7 @@ func TestStraightFlush(t *testing.T) { assert.False(t, hand.containsTwoPair(), "Expected hand to not be two pair") assert.False(t, hand.containsPair(), "Expected hand to not be a pair") - assert.True(t, hand.HighestRank(AcesHigh) == SIX, "Expected highest rank to be a six") + assert.True(t, hand.HighestRank() == SIX, "Expected highest rank to be a six") nd := NewDeckFromCards(hand) nd.ShuffleDeterministically(123456789) @@ -111,8 +111,8 @@ func TestStraightFlush(t *testing.T) { fmt.Printf("new deck has %d cards\n", nd.Count()) shuffledHand := nd.Deal(5) assert.True(t, shuffledHand.containsStraightFlush(), "Expected hand to still be a straight flush after shuffle") - assert.True(t, shuffledHand.HighestRank(AcesHigh) == SIX, "Expected highest rank to still be a six after shuffle") - assert.True(t, shuffledHand.HighestRank(AcesLow) == SIX, "Expected highest rank to be a six after shuffle even with aces low") + assert.True(t, shuffledHand.HighestRank() == SIX, "Expected highest rank to still be a six after shuffle") + assert.True(t, shuffledHand.HighestRank() == SIX, "Expected highest rank to be a six after shuffle even with aces low") } func TestRoyalFlush(t *testing.T) { @@ -134,8 +134,8 @@ func TestRoyalFlush(t *testing.T) { assert.False(t, hand.containsTwoPair(), "Expected hand to not be two pair") assert.False(t, hand.containsPair(), "Expected hand to not be a pair") - assert.True(t, hand.HighestRank(AcesHigh) == ACE, "Expected highest rank to be an ace") - assert.False(t, hand.HighestRank(AcesHigh) == TEN, "Expected highest rank to not be an ace") + assert.True(t, hand.HighestRank() == ACE, "Expected highest rank to be an ace") + assert.False(t, hand.HighestRank() == TEN, "Expected highest rank to not be an ace") } func TestUnmadeHand(t *testing.T) { @@ -155,7 +155,7 @@ func TestUnmadeHand(t *testing.T) { assert.False(t, hand.containsThreeOfAKind(), "Expected hand to not be three of a kind") assert.False(t, hand.containsTwoPair(), "Expected hand to not be two pair") assert.False(t, hand.containsPair(), "Expected hand to not be a pair") - assert.True(t, hand.HighestRank(AcesHigh) == KING, "Expected highest rank to be a king") + assert.True(t, hand.HighestRank() == KING, "Expected highest rank to be a king") assert.True(t, hand.isUnmadeHand(), "Expected hand to be unmade") } @@ -176,6 +176,24 @@ func TestTwoPair(t *testing.T) { assert.False(t, hand.containsThreeOfAKind(), "Expected hand to not be three of a kind") assert.True(t, hand.containsTwoPair(), "Expected hand to be two pair") assert.True(t, hand.containsPair(), "Expected hand to also be a pair") - assert.True(t, hand.HighestRank(AcesHigh) == KING, "Expected highest rank to be a king") + assert.True(t, hand.HighestRank() == KING, "Expected highest rank to be a king") assert.False(t, hand.isUnmadeHand(), "Expected hand to not be unmade") } + +func TestHandScore(t *testing.T) { + hand := Cards{ + KingOfSpades(), + JackOfDiamonds(), + JackOfSpades(), + KingOfDiamonds(), + TenOfSpades(), + } + + ph, error := hand.PokerHand() + assert.Nil(t, error, "Expected no error") + assert.True(t, ph.Score > 0, "Expected score to be nonzero 0") + assert.True(t, ph.Score < 100000000000000000, "Expected score to be less than 100000000000000000") + + fmt.Printf("PokerHand: %v+\n", ph) + fmt.Printf("PH score: %d\n", ph.Score) +}