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"
)
// 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() {
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.
// what are the odds? lol
d.ShuffleDeterministically(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)
//g := NewGame(1337))
fmt.Printf("##############################################\n")
// deal the flop
var community pokercore.Cards
community = d.Deal(3)
fmt.Printf("flop: %s\n", community.FormatForTerminal())
time.Sleep(sleepTime)
// nothing up my sleeve:
g := NewGame(3141592653)
fmt.Printf("##############################################\n")
turn := d.Deal(1)
fmt.Printf("turn: %s\n", turn.FormatForTerminal())
community = append(community, turn...)
fmt.Printf("board is now: %s\n", community.FormatForTerminal())
g.ShowGameStatus()
time.Sleep(sleepTime)
g.DealPlayersIn()
fmt.Printf("##############################################\n")
river := d.Deal(1)
fmt.Printf("river: %s\n", river.FormatForTerminal())
community = append(community, river...)
fmt.Printf("board is now: %s\n", community.FormatForTerminal())
g.ShowGameStatus()
g.DealFlop()
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("Insert coin to play again.\n")
}

View File

@ -1,34 +1,5 @@
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")

View File

@ -1,6 +1,9 @@
package pokercore
import "fmt"
import (
"errors"
"fmt"
)
type PokerHandType int
@ -28,93 +31,118 @@ func (c Cards) Append(other Cards) Cards {
}
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")
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 = c
if c.containsRoyalFlush() {
ph.Hand = phc.SortByRankAscending()
if ph.Hand.containsRoyalFlush() {
ph.Type = RoyalFlush
ph.Score = ScoreRoyalFlush
return ph, nil
}
if c.containsStraightFlush() {
if ph.Hand.containsStraightFlush() {
ph.Type = StraightFlush
ph.Score = ph.Hand.scoreStraightFlush()
ph.Score = ScoreStraightFlush
ph.Score += 1000 * ph.Hand.HighestRank().Score()
return ph, nil
}
if c.containsFourOfAKind() {
if ph.Hand.containsFourOfAKind() {
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
}
if c.containsFullHouse() {
if ph.Hand.containsFullHouse() {
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
}
if c.containsFlush() {
if ph.Hand.containsFlush() {
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
}
if c.containsStraight() {
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 := c.SortByRankAscending()
sorted := ph.Hand.SortByRankAscending()
if sorted[3].Rank == FIVE && sorted[4].Rank == ACE {
// 5-high straight
ph.Score = ScoreStraight + 1000*sorted[3].Score()
// 5-high straight, scored by the five's rank
ph.Score += sorted[3].Score()
} else {
// All other straights
ph.Score = ScoreStraight + 1000*sorted[4].Score()
// All other straights are scored by the highest card in the straight
ph.Score += sorted[4].Score()
}
return ph, nil
}
if c.containsThreeOfAKind() {
if ph.Hand.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()
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 c.containsTwoPair() {
if ph.Hand.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()
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 c.containsPair() {
if ph.Hand.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()
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 = 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
}

View File

@ -1,7 +1,6 @@
package pokercore
import (
"errors"
"fmt"
)
@ -25,18 +24,13 @@ func (c Card) Score() HandScore {
}
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()
if err != nil {
return 0, err
}
//fmt.Println(ph)
return ph.Score, nil
}
func (x HandScore) String() string {
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")
ph, err := hand.PokerHand()
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.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.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")
@ -296,3 +296,54 @@ func TestTwoPairBug(t *testing.T) {
//fmt.Printf("PokerHand: %v+\n", ph)
//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")
}