added more tests, cleaned up some code, found another bug

This commit is contained in:
Jeffrey Paul 2024-05-18 22:10:53 -07:00
parent 5f7dba942c
commit 14ffbe4eb4
5 changed files with 250 additions and 132 deletions

View File

@ -7,74 +7,148 @@ import (
"git.eeqj.de/sneak/pokercore" "git.eeqj.de/sneak/pokercore"
) )
// just like on tv
var delayTime = 2 * time.Second
var playerCount = 8
type Player struct {
Hand pokercore.Cards
ScoredHand *pokercore.PokerHand
Position int
CommunityCardsAvailable pokercore.Cards
}
type Game struct {
Deck *pokercore.Deck
Players []*Player
Community pokercore.Cards
Street int
}
func NewGame(seed int) *Game {
g := &Game{}
g.Street = 0
g.Deck = pokercore.NewDeck()
g.SpreadCards()
fmt.Printf("Shuffle up and deal!\n")
time.Sleep(delayTime)
fmt.Printf("Shuffling...\n")
g.Deck.ShuffleDeterministically(3141592653)
return g
}
func (g *Game) StreetAsString() string {
switch g.Street {
case 0:
return "pre-flop"
case 1:
return "flop"
case 2:
return "turn"
case 3:
return "river"
}
return "unknown"
}
func (g *Game) SpreadCards() {
fmt.Printf("deck before shuffling: %s\n", g.Deck.FormatForTerminal())
}
func (g *Game) DealPlayersIn() {
for i := 0; i < playerCount; i++ {
p := Player{Hand: g.Deck.Deal(2), Position: i + 1}
g.Players = append(g.Players, &p)
}
}
func (g *Game) ShowGameStatus() {
fmt.Printf("Street: %s\n", g.StreetAsString())
fmt.Printf("Community cards: %s\n", g.Community.FormatForTerminal())
for _, p := range g.Players {
if p != nil {
fmt.Printf("Player %d: %s\n", p.Position, p.Hand.FormatForTerminal())
if g.Street > 0 {
ac := append(p.Hand, g.Community...)
ph, err := ac.PokerHand()
if err != nil {
panic(err)
}
fmt.Printf("Player %d has %s\n", p.Position, ph.Description())
fmt.Printf("Player %d Score: %d\n", p.Position, ph.Score)
}
}
}
}
func (g *Game) DealFlop() {
g.Community = g.Deck.Deal(3)
g.Street = 1
}
func (g *Game) DealTurn() {
g.Community = append(g.Community, g.Deck.Deal(1)...)
g.Street = 2
}
func (g *Game) DealRiver() {
g.Community = append(g.Community, g.Deck.Deal(1)...)
g.Street = 3
}
func (g *Game) ShowWinner() {
var winner *Player
var winningHand *pokercore.PokerHand
for _, p := range g.Players {
if p != nil {
ac := append(p.Hand, g.Community...)
ph, err := ac.PokerHand()
if err != nil {
panic(err)
}
if winner == nil {
winner = p
winningHand = ph
} else {
if ph.Score > winningHand.Score {
winner = p
winningHand = ph
}
}
}
}
fmt.Printf("Winner: Player %d with %s.\n", winner.Position, winningHand.Description())
}
func main() { func main() {
sleepTime := 2 * time.Second
playerCount := 8
d := pokercore.NewDeck()
fmt.Printf("deck before shuffling: %s\n", d.FormatForTerminal())
// this "randomly chosen" seed somehow deals pocket queens, pocket kings, and pocket aces to three players. // this "randomly chosen" seed somehow deals pocket queens, pocket kings, and pocket aces to three players.
// what are the odds? lol // what are the odds? lol
d.ShuffleDeterministically(1337) //g := NewGame(1337))
fmt.Printf("deck after shuffling: %s\n", d.FormatForTerminal())
var players []pokercore.Cards
for i := 0; i < playerCount; i++ {
players = append(players, d.Deal(2))
}
for i, p := range players {
fmt.Printf("player %d: %s\n", i+1, p.FormatForTerminal())
}
time.Sleep(2 * time.Second)
fmt.Printf("##############################################\n") // nothing up my sleeve:
// deal the flop g := NewGame(3141592653)
var community pokercore.Cards
community = d.Deal(3)
fmt.Printf("flop: %s\n", community.FormatForTerminal())
time.Sleep(sleepTime)
fmt.Printf("##############################################\n") g.ShowGameStatus()
turn := d.Deal(1)
fmt.Printf("turn: %s\n", turn.FormatForTerminal())
community = append(community, turn...)
fmt.Printf("board is now: %s\n", community.FormatForTerminal())
time.Sleep(sleepTime) g.DealPlayersIn()
fmt.Printf("##############################################\n") g.ShowGameStatus()
river := d.Deal(1)
fmt.Printf("river: %s\n", river.FormatForTerminal()) g.DealFlop()
community = append(community, river...)
fmt.Printf("board is now: %s\n", community.FormatForTerminal()) g.ShowGameStatus()
g.DealTurn()
g.ShowGameStatus()
g.DealRiver()
g.ShowGameStatus()
g.ShowWinner()
fmt.Printf("##############################################\n")
var winningPlayer int
var winningHand *pokercore.PokerHand
for i, p := range players {
fmt.Printf("player %d: %s\n", i+1, p.FormatForTerminal())
playerCards, err := p.Append(community).IdentifyBestFiveCardPokerHand()
if err != nil {
panic("error evaluating hand: " + err.Error())
}
ph, err := playerCards.PokerHand()
if err != nil {
panic("error evaluating hand: " + err.Error())
}
fmt.Printf("player %d has a %s\n", i+1, ph.Description())
if winningHand == nil {
winningPlayer = i
winningHand = ph
continue
}
if ph.Score > winningHand.Score {
winningPlayer = i
winningHand = ph
}
}
fmt.Printf("##############################################\n")
fmt.Printf("player %d wins with %s\n", winningPlayer+1, winningHand.Description())
fmt.Printf("##############################################\n")
fmt.Printf("What a strange game. The only winning move is to bet really big.\n") fmt.Printf("What a strange game. The only winning move is to bet really big.\n")
fmt.Printf("Insert coin to play again.\n") fmt.Printf("Insert coin to play again.\n")
} }

View File

@ -1,34 +1,5 @@
package pokercore 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 { func (c Cards) pairRank() Rank {
if !c.containsPair() { if !c.containsPair() {
panic("hand must have a pair to have a pair rank") panic("hand must have a pair to have a pair rank")

View File

@ -1,6 +1,9 @@
package pokercore package pokercore
import "fmt" import (
"errors"
"fmt"
)
type PokerHandType int type PokerHandType int
@ -28,93 +31,118 @@ func (c Cards) Append(other Cards) Cards {
} }
func (c Cards) PokerHand() (*PokerHand, error) { 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() { if c.containsDuplicates() {
return nil, fmt.Errorf("hand must have no duplicates to be scored as a poker hand") 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 := new(PokerHand)
ph.Hand = c ph.Hand = phc.SortByRankAscending()
if c.containsRoyalFlush() { if ph.Hand.containsRoyalFlush() {
ph.Type = RoyalFlush ph.Type = RoyalFlush
ph.Score = ScoreRoyalFlush ph.Score = ScoreRoyalFlush
return ph, nil return ph, nil
} }
if c.containsStraightFlush() { if ph.Hand.containsStraightFlush() {
ph.Type = StraightFlush ph.Type = StraightFlush
ph.Score = ph.Hand.scoreStraightFlush() ph.Score = ScoreStraightFlush
ph.Score += 1000 * ph.Hand.HighestRank().Score()
return ph, nil return ph, nil
} }
if c.containsFourOfAKind() { if ph.Hand.containsFourOfAKind() {
ph.Type = FourOfAKind ph.Type = FourOfAKind
ph.Score = ScoreFourOfAKind + 1000*c.fourOfAKindRank().Score() + c.fourOfAKindKicker().Score() ph.Score = ScoreFourOfAKind
ph.Score += 1000 * ph.Hand.fourOfAKindRank().Score()
ph.Score += 100 * ph.Hand.fourOfAKindKicker().Score()
return ph, nil return ph, nil
} }
if c.containsFullHouse() { if ph.Hand.containsFullHouse() {
ph.Type = FullHouse ph.Type = FullHouse
ph.Score = ScoreFullHouse + 1000*c.fullHouseTripsRank().Score() + c.fullHousePairRank().Score() 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 return ph, nil
} }
if c.containsFlush() { if ph.Hand.containsFlush() {
ph.Type = Flush ph.Type = Flush
ph.Score = c.scoreFlush() 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 return ph, nil
} }
if c.containsStraight() { if ph.Hand.containsStraight() {
ph.Type = Straight ph.Type = Straight
ph.Score = ScoreStraight
// Straights are scored by the highest card in the straight // 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 // 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 // In that case, the straight is a 5-high straight, not an Ace-high straight
sorted := c.SortByRankAscending() sorted := ph.Hand.SortByRankAscending()
if sorted[3].Rank == FIVE && sorted[4].Rank == ACE { if sorted[3].Rank == FIVE && sorted[4].Rank == ACE {
// 5-high straight // 5-high straight, scored by the five's rank
ph.Score = ScoreStraight + 1000*sorted[3].Score() ph.Score += sorted[3].Score()
} else { } else {
// All other straights // All other straights are scored by the highest card in the straight
ph.Score = ScoreStraight + 1000*sorted[4].Score() ph.Score += sorted[4].Score()
} }
return ph, nil return ph, nil
} }
if c.containsThreeOfAKind() { if ph.Hand.containsThreeOfAKind() {
ph.Type = ThreeOfAKind ph.Type = ThreeOfAKind
ph.Score = ScoreThreeOfAKind ph.Score = ScoreThreeOfAKind
ph.Score += 1000 * c.threeOfAKindTripsRank().Score() ph.Score += 1000 * ph.Hand.threeOfAKindTripsRank().Score()
ph.Score += 100 * c.threeOfAKindFirstKicker().Score() ph.Score += 100 * ph.Hand.threeOfAKindFirstKicker().Score()
ph.Score += 10 * c.threeOfAKindSecondKicker().Score() ph.Score += 10 * ph.Hand.threeOfAKindSecondKicker().Score()
return ph, nil return ph, nil
} }
if c.containsTwoPair() { if ph.Hand.containsTwoPair() {
ph.Type = TwoPair ph.Type = TwoPair
ph.Score = ScoreTwoPair ph.Score = ScoreTwoPair
ph.Score += 1000 * c.twoPairBiggestPair().Score() ph.Score += 1000 * ph.Hand.twoPairBiggestPair().Score()
ph.Score += 100 * c.twoPairSmallestPair().Score() ph.Score += 100 * ph.Hand.twoPairSmallestPair().Score()
ph.Score += 10 * c.twoPairKicker().Score() ph.Score += 10 * ph.Hand.twoPairKicker().Score()
return ph, nil return ph, nil
} }
if c.containsPair() { if ph.Hand.containsPair() {
ph.Type = Pair ph.Type = Pair
ph.Score = ScorePair ph.Score = ScorePair
ph.Score += 1000 * c.pairRank().Score() ph.Score += 1000 * ph.Hand.pairRank().Score()
ph.Score += 100 * c.pairFirstKicker().Score() ph.Score += 100 * ph.Hand.pairFirstKicker().Score()
ph.Score += 10 * c.pairSecondKicker().Score() ph.Score += 10 * ph.Hand.pairSecondKicker().Score()
ph.Score += c.pairThirdKicker().Score() ph.Score += ph.Hand.pairThirdKicker().Score()
return ph, nil return ph, nil
} }
ph.Type = HighCard ph.Type = HighCard
ph.Score = c.scoreHighCard() 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 return ph, nil
} }

View File

@ -1,7 +1,6 @@
package pokercore package pokercore
import ( import (
"errors"
"fmt" "fmt"
) )
@ -25,18 +24,13 @@ func (c Card) Score() HandScore {
} }
func (c Cards) PokerHandScore() (HandScore, error) { func (c Cards) PokerHandScore() (HandScore, error) {
if len(c) != 5 {
return 0, errors.New("hand must have 5 cards to be scored as a poker hand")
}
ph, err := c.PokerHand() ph, err := c.PokerHand()
if err != nil { if err != nil {
return 0, err return 0, err
} }
//fmt.Println(ph)
return ph.Score, nil return ph.Score, nil
} }
func (x HandScore) String() string { func (x HandScore) String() string {
return fmt.Sprintf("<HandScore %d>", x) return fmt.Sprintf("<HandScore %d>", x)
//return scoreToName(x)
} }

View File

@ -78,9 +78,9 @@ func TestAceLowStraight(t *testing.T) {
assert.True(t, hand.containsStraight(), "Expected hand to be a straight") assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
ph, err := hand.PokerHand() ph, err := hand.PokerHand()
assert.Nil(t, err, "Expected no error") assert.Nil(t, err, "Expected no error")
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero 0") assert.Greater(t, ph.Score, 0, "Expected score to be greater than 0")
assert.Less(t, ph.Score, 100000000000000000, "Expected score to be less than 100000000000000000") assert.Less(t, ph.Score, 100000000000000000, "Expected score to be less than 100000000000000000")
assert.Equal(t, ph.Score, ScoreStraight+1000*FIVE.Score()) assert.Equal(t, ph.Score, ScoreStraight+FIVE.Score())
assert.Equal(t, ph.Description(), "a five high straight") assert.Equal(t, ph.Description(), "a five high straight")
assert.True(t, hand.HighestRank() == ACE, "Expected highest rank to be an ace") assert.True(t, hand.HighestRank() == ACE, "Expected highest rank to be an ace")
assert.True(t, hand.SortByRankAscending().First().Rank == DEUCE, "Expected first card to be a deuce") assert.True(t, hand.SortByRankAscending().First().Rank == DEUCE, "Expected first card to be a deuce")
@ -296,3 +296,54 @@ func TestTwoPairBug(t *testing.T) {
//fmt.Printf("PokerHand: %v+\n", ph) //fmt.Printf("PokerHand: %v+\n", ph)
//fmt.Printf("PH score: %d\n", ph.Score) //fmt.Printf("PH score: %d\n", ph.Score)
} }
func TestScoringStructureQuads(t *testing.T) {
handA := Cards{
DeuceOfSpades(),
DeuceOfHearts(),
DeuceOfDiamonds(),
DeuceOfClubs(),
AceOfSpades(),
}
handB := Cards{
ThreeOfSpades(),
ThreeOfHearts(),
ThreeOfDiamonds(),
ThreeOfClubs(),
DeuceOfSpades(),
}
phA, err := handA.PokerHand()
assert.Nil(t, err, "Expected no error")
phB, err := handB.PokerHand()
assert.Nil(t, err, "Expected no error")
assert.Greater(t, phB.Score, phA.Score, "Expected hand B to be higher than hand A")
}
func TestScoringStructureFullHouse(t *testing.T) {
handA := Cards{
DeuceOfSpades(),
DeuceOfHearts(),
DeuceOfDiamonds(),
AceOfSpades(),
AceOfHearts(),
}
phA, err := handA.PokerHand()
assert.Nil(t, err, "Expected no error")
deucesFullOfAcesScore := phA.Score
handB := Cards{
ThreeOfSpades(),
ThreeOfHearts(),
ThreeOfDiamonds(),
DeuceOfSpades(),
DeuceOfHearts(),
}
phB, err := handB.PokerHand()
assert.Nil(t, err, "Expected no error")
threesFullOfDeucesScore := phB.Score
assert.Greater(t, threesFullOfDeucesScore, deucesFullOfAcesScore, "Expected Threes full of deuces to beat deuces full of aces")
}