Compare commits

...

4 Commits

Author SHA1 Message Date
8dbfc45ebb latest 2026-01-06 10:13:38 -08:00
e26b79751f cleanups 2024-05-18 23:55:15 -07:00
6c66adf8d3 linting and formatting updates 2024-05-18 23:50:19 -07:00
ab92f29a88 latest. passes all tests, no known bugs, has examples. 2024-05-18 23:25:55 -07:00
12 changed files with 788 additions and 519 deletions

View File

@@ -16,3 +16,9 @@ test:
examples:
test -d bin || mkdir bin
go build -o bin/ ./examples/...
lint:
golangci-lint run
fmt:
go fmt ./...

160
card.go
View File

@@ -7,7 +7,7 @@ import (
"strings"
"unicode/utf8"
"github.com/logrusorgru/aurora/v4"
aurora "github.com/logrusorgru/aurora/v4"
)
type Card struct {
@@ -19,55 +19,52 @@ func NewCardFromString(card string) (Card, error) {
// FIXME extend this later to common format strings like "9s"
length := utf8.RuneCountInString(card)
if length != 2 {
return Card{}, fmt.Errorf("Invalid card string %s", card)
}
var rank Rank
var suit Suit
if strings.ContainsRune(card, rune(SPADE)) {
suit = Suit(SPADE)
} else if strings.ContainsRune(card, rune(HEART)) {
suit = Suit(HEART)
} else if strings.ContainsRune(card, rune(DIAMOND)) {
suit = Suit(DIAMOND)
} else if strings.ContainsRune(card, rune(CLUB)) {
suit = Suit(CLUB)
} else {
return Card{}, fmt.Errorf("Invalid card string %s", card)
return Card{}, fmt.Errorf("invalid card string %s, must be 2 characters", card)
}
if strings.ContainsRune(card, rune(DEUCE)) {
rank = Rank(DEUCE)
} else if strings.ContainsRune(card, rune(THREE)) {
rank = Rank(THREE)
} else if strings.ContainsRune(card, rune(FOUR)) {
rank = Rank(FOUR)
} else if strings.ContainsRune(card, rune(FIVE)) {
rank = Rank(FIVE)
} else if strings.ContainsRune(card, rune(SIX)) {
rank = Rank(SIX)
} else if strings.ContainsRune(card, rune(SEVEN)) {
rank = Rank(SEVEN)
} else if strings.ContainsRune(card, rune(EIGHT)) {
rank = Rank(EIGHT)
} else if strings.ContainsRune(card, rune(NINE)) {
rank = Rank(NINE)
} else if strings.ContainsRune(card, rune(TEN)) {
rank = Rank(TEN)
} else if strings.ContainsRune(card, rune(JACK)) {
rank = Rank(JACK)
} else if strings.ContainsRune(card, rune(QUEEN)) {
rank = Rank(QUEEN)
} else if strings.ContainsRune(card, rune(KING)) {
rank = Rank(KING)
} else if strings.ContainsRune(card, rune(ACE)) {
rank = Rank(ACE)
} else {
return Card{}, fmt.Errorf("Invalid card string %s", card)
rankMap := map[rune]Rank{
rune(DEUCE): DEUCE,
rune(THREE): THREE,
rune(FOUR): FOUR,
rune(FIVE): FIVE,
rune(SIX): SIX,
rune(SEVEN): SEVEN,
rune(EIGHT): EIGHT,
rune(NINE): NINE,
rune(TEN): TEN,
rune(JACK): JACK,
rune(QUEEN): QUEEN,
rune(KING): KING,
rune(ACE): ACE,
}
suitMap := map[rune]Suit{
rune(SPADE): SPADE,
rune(HEART): HEART,
rune(DIAMOND): DIAMOND,
rune(CLUB): CLUB,
}
var rank Rank
var suit Suit
for r := range rankMap {
if strings.ContainsRune(card, r) {
rank = rankMap[r]
break
}
}
for s := range suitMap {
if strings.ContainsRune(card, s) {
suit = suitMap[s]
break
}
}
if rank == Rank(0) || suit == Suit(0) {
return Card{}, fmt.Errorf("Invalid card string %s", card)
return Card{}, fmt.Errorf("invalid card string %s", card)
}
return Card{Rank: rank, Suit: suit}, nil
}
@@ -75,18 +72,17 @@ func NewCardsFromString(cards string) (Cards, error) {
// supports a string like 9♠,9♣,Q♥,Q♦,K♣
// FIXME extend this later to common format strings like "9c Qh Qd Kc"
// with or without commas
var newCards Cards
newCards = make(Cards, 0)
cardStrings := strings.Split(cards, ",")
for _, cardString := range cardStrings {
newCards := make(Cards, len(cardStrings))
for i, cardString := range cardStrings {
card, err := NewCardFromString(cardString)
if err != nil {
return Cards{}, err
return nil, err
}
newCards = append(newCards, card)
newCards[i] = card
}
if len(newCards) == 0 {
return Cards{}, fmt.Errorf("No cards found in string %s", cards)
return nil, fmt.Errorf("no cards found in string %s", cards)
}
return newCards, nil
}
@@ -97,42 +93,41 @@ func (c *Card) String() string {
type Cards []Card
func (c Cards) First() Card {
return c[0]
func (cards Cards) First() Card {
return cards[0]
}
func (c Cards) Second() Card {
return c[1]
func (cards Cards) Second() Card {
return cards[1]
}
func (c Cards) Third() Card {
return c[2]
func (cards Cards) Third() Card {
return cards[2]
}
func (c Cards) Fourth() Card {
return c[3]
func (cards Cards) Fourth() Card {
return cards[3]
}
func (c Cards) Fifth() Card {
return c[4]
func (cards Cards) Fifth() Card {
return cards[4]
}
func (c Cards) Last() Card {
return c[len(c)-1]
func (cards Cards) Last() Card {
return cards[len(cards)-1]
}
func (c Cards) SortByRankAscending() Cards {
newCards := make(Cards, len(c))
copy(newCards, c)
sort.Slice(newCards, func(i, j int) bool {
return newCards[i].Rank.Score() < newCards[j].Rank.Score()
func (cards Cards) SortByRankAscending() Cards {
sortedCards := make(Cards, len(cards))
copy(sortedCards, cards)
sort.Slice(sortedCards, func(i, j int) bool {
return sortedCards[i].Rank.Score() < sortedCards[j].Rank.Score()
})
return newCards
return sortedCards
}
func (c Cards) PrintToTerminal() {
fmt.Printf("%s", c.FormatForTerminal())
func (cards Cards) PrintToTerminal() {
fmt.Printf("%s", cards.FormatForTerminal())
}
type SortOrder int
@@ -142,18 +137,18 @@ const (
AceHighDescending
)
func (c Cards) FormatForTerminalSorted(order SortOrder) string {
sorted := c.SortByRankAscending() // this is ascending
func (cards Cards) FormatForTerminalSorted(order SortOrder) string {
sorted := cards.SortByRankAscending() // this is ascending
if order == AceHighDescending {
slices.Reverse(sorted)
}
return sorted.FormatForTerminal()
}
func (c Cards) FormatForTerminal() string {
func (cards Cards) FormatForTerminal() string {
var cardstrings []string
for i := 0; i < len(c); i++ {
cardstrings = append(cardstrings, c[i].FormatForTerminal())
for i := 0; i < len(cards); i++ {
cardstrings = append(cardstrings, cards[i].FormatForTerminal())
}
return strings.Join(cardstrings, ",")
}
@@ -178,16 +173,15 @@ func (c Card) FormatForTerminal() string {
return fmt.Sprintf("%s%s", rank, suit)
}
func (c Cards) HighestRank() Rank {
sorted := c.SortByRankAscending()
func (cards Cards) HighestRank() Rank {
sorted := cards.SortByRankAscending()
return sorted[len(sorted)-1].Rank
}
func (s Cards) String() (output string) {
func (cards Cards) String() string {
var cardstrings []string
for i := 0; i < len(s); i++ {
cardstrings = append(cardstrings, s[i].String())
for i := 0; i < len(cards); i++ {
cardstrings = append(cardstrings, cards[i].String())
}
output = strings.Join(cardstrings, ",")
return output
return strings.Join(cardstrings, ",")
}

119
examples/flip/main.go Normal file
View File

@@ -0,0 +1,119 @@
package main
import (
"fmt"
"git.eeqj.de/sneak/pokercore"
)
var playerCount = 2
type Player struct {
Hand pokercore.Cards
ScoredHand *pokercore.PokerHand
Position int
}
type Game struct {
Deck *pokercore.Deck
Players []*Player
Community pokercore.Cards
Street int
}
func NewGame() *Game {
g := &Game{}
g.Street = 0
g.Deck = pokercore.NewDeck()
g.Deck.ShuffleRandomly()
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) 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() {
g := NewGame()
g.DealPlayersIn()
g.DealFlop()
g.DealTurn()
g.DealRiver()
g.ShowGameStatus()
g.ShowWinner()
}

155
examples/montecarlo/main.go Normal file
View File

@@ -0,0 +1,155 @@
package main
import (
"fmt"
"time"
"github.com/rcrowley/go-metrics"
"github.com/schollz/progressbar/v3"
"sneak.berlin/go/pokercore"
)
var gameCount = 50_000
var playerCount = 2
type Player struct {
Hand pokercore.Cards
ScoredHand *pokercore.PokerHand
Position int
}
type Game struct {
Deck *pokercore.Deck
Players []*Player
Community pokercore.Cards
Street int
}
func NewGame() *Game {
g := &Game{}
g.Street = 0
g.Deck = pokercore.NewDeck()
g.Deck.ShuffleRandomly()
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) 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) 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() int {
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
}
}
}
}
return winner.Position
}
func main() {
oneWins := 0
twoWins := 0
registry := metrics.NewRegistry()
timer := metrics.NewTimer()
registry.Register("pokerGame", timer)
bar := progressbar.NewOptions(
gameCount,
progressbar.OptionSetDescription("Gambling..."),
progressbar.OptionShowCount(),
progressbar.OptionShowIts(),
progressbar.OptionSetPredictTime(true),
progressbar.OptionClearOnFinish(),
progressbar.OptionThrottle(
100*time.Millisecond,
), // Update every 100ms
)
for i := 0; i < gameCount; i++ {
start := time.Now()
w := runFlip()
duration := time.Since(start)
timer.Update(duration)
bar.Add(1)
if w == 1 {
oneWins++
} else {
twoWins++
}
}
fmt.Printf("%d games played\n", gameCount)
fmt.Printf("Min: %d ns\n", timer.Min())
fmt.Printf("Max: %d ns\n", timer.Max())
fmt.Printf("Mean: %0.2f ns\n", timer.Mean())
fmt.Printf("StdDev: %0.2f ns\n", timer.StdDev())
fmt.Printf(
"Percentiles: 50%%: %0.2f ns, 75%%: %0.2f ns, 95%%: %0.2f ns, 99%%: %0.2f ns\n",
timer.Percentile(0.50),
timer.Percentile(0.75),
timer.Percentile(0.95),
timer.Percentile(0.99),
)
oneWinPercentage := float64(oneWins) / float64(gameCount) * 100
twoWinPercentage := float64(twoWins) / float64(gameCount) * 100
fmt.Printf("Player 1 won: %d (%0.2f%%)\n", oneWins, oneWinPercentage)
fmt.Printf("Player 2 won: %d (%0.2f%%)\n", twoWins, twoWinPercentage)
}
func runFlip() int {
g := NewGame()
g.DealPlayersIn()
g.DealFlop()
g.DealTurn()
g.DealRiver()
winnerPosition := g.ShowWinner()
return winnerPosition
}

56
examples/sf/main.go Normal file
View File

@@ -0,0 +1,56 @@
package main
import (
"fmt"
"sneak.berlin/go/pokercore"
)
func main() {
var sfp bool
var tries int
var found int
const maxTries = 100_000_000
for i := 0; i < maxTries; i++ {
sfp = searchStraightFlush()
if sfp {
found++
}
tries++
if tries%1000 == 0 {
fmt.Printf("Tries: %d, Found: %d\n", tries, found)
}
}
fmt.Printf("Tries: %d, Found: %d\n", tries, found)
}
func searchStraightFlush() bool {
d := pokercore.NewDeck()
d.ShuffleRandomly()
var hand pokercore.Cards
hand = d.Deal(7)
ph, err := hand.PokerHand()
if err != nil {
fmt.Println("Error: ", err)
return false
}
if ph.Type == pokercore.StraightFlush ||
ph.Type == pokercore.RoyalFlush {
fmt.Println("straight flush found")
fmt.Println("Hand: ", hand.FormatForTerminal())
fmt.Println("PokerHand: ", ph)
return true
}
return false
}

21
go.mod
View File

@@ -1,36 +1,23 @@
module git.eeqj.de/sneak/pokercore
module sneak.berlin/go/pokercore
go 1.22.2
require (
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/logrusorgru/aurora/v4 v4.0.0
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
github.com/schollz/progressbar/v3 v3.14.2
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.0
sneak.berlin/go/timingbench v0.0.0-20240522212031-a6243a470213
)
require (
git.eeqj.de/sneak/timingbench v0.0.0-20240519025145-fb13c5c56a02 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/k0kubun/pp/v3 v3.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.33.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/schollz/progressbar/v3 v3.14.2 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.20.0 // indirect
gonum.org/v1/gonum v0.15.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

102
go.sum
View File

@@ -1,53 +1,16 @@
git.eeqj.de/sneak/timingbench v0.0.0-20240519025145-fb13c5c56a02 h1:b/v1EDAlsfvINIeV4znI/vH7SY7mUJOO1KWeBD+IW90=
git.eeqj.de/sneak/timingbench v0.0.0-20240519025145-fb13c5c56a02/go.mod h1:iKAlgt/liDtXifmn7fPJK+KYMr0c4lXYFJ+j5d3gfEQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs=
github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyMFIEa99ks=
@@ -57,77 +20,26 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sneak.berlin/go/timingbench v0.0.0-20240522212031-a6243a470213 h1:jgfwL2lUUp6aII87vgkgFenfKftsbKvUR3jlsRdS2yo=
sneak.berlin/go/timingbench v0.0.0-20240522212031-a6243a470213/go.mod h1:W+0S+VhiuNIU/06KPhWJCmNhMaCztg2MuHitNEVEFG0=

View File

@@ -5,11 +5,12 @@ import (
"testing"
"time"
"git.eeqj.de/sneak/timingbench"
"github.com/stretchr/testify/assert"
"sneak.berlin/go/timingbench"
)
func TestShuffleSpeed(t *testing.T) {
t.Parallel()
iterations := 1000
t.Logf("Running %d iterations of shuffle speed test", iterations)
// Create a context with a timeout for cancellation.
@@ -27,18 +28,21 @@ func TestShuffleSpeed(t *testing.T) {
}
func TestHandFindingSpeedFiveCard(t *testing.T) {
t.Parallel()
iterations := 1000
t.Logf("Running %d iterations of hand finding speed test for 5 card hand", iterations)
measureHandFinding(t, iterations, 5)
}
func TestHandFindingSpeedSevenCard(t *testing.T) {
t.Parallel()
iterations := 1000
t.Logf("Running %d iterations of hand finding speed test for 7 card hand", iterations)
measureHandFinding(t, iterations, 7)
}
func TestHandFindingSpeedNineCard(t *testing.T) {
t.Parallel()
iterations := 100
t.Logf("Running %d iterations of hand finding speed test for 9 card hand", iterations)
measureHandFinding(t, iterations, 9)

View File

@@ -12,6 +12,7 @@ type ShuffleTestResults []struct {
}
func TestPokerDeck(t *testing.T) {
t.Parallel()
d := NewDeck()
//fmt.Printf("newdeck: %+v\n", d)
d.ShuffleDeterministically(437)
@@ -35,6 +36,7 @@ func TestPokerDeck(t *testing.T) {
}
func TestDealing(t *testing.T) {
t.Parallel()
d := NewDeckFromCards(Cards{
Card{Rank: ACE, Suit: HEART},
Card{Rank: DEUCE, Suit: HEART},
@@ -50,6 +52,7 @@ func TestDealing(t *testing.T) {
}
func TestSpecialCaseOfFiveHighStraightFlush(t *testing.T) {
t.Parallel()
// actual bug from first implementation
d := NewDeckFromCards(Cards{
Card{Rank: ACE, Suit: HEART},
@@ -68,6 +71,7 @@ func TestSpecialCaseOfFiveHighStraightFlush(t *testing.T) {
}
func TestSpecialCaseOfFiveHighStraight(t *testing.T) {
t.Parallel()
// actual bug from first implementation
d := NewDeckFromCards(Cards{
Card{Rank: ACE, Suit: HEART},

View File

@@ -62,13 +62,13 @@ func (c Cards) PokerHand() (*PokerHand, error) {
return ph, nil
}
func (ph PokerHand) ToSortedCards() Cards {
func (ph *PokerHand) ToSortedCards() Cards {
// I believe ph.Hand is already sorted, but in any case it's only 5
// cards and sorting it costs ~nothing
return ph.Hand.SortByRankAscending()
}
func (ph PokerHand) Compare(other PokerHand) int {
func (ph *PokerHand) Compare(other PokerHand) int {
if ph.Score > other.Score {
return 1
}
@@ -78,74 +78,74 @@ func (ph PokerHand) Compare(other PokerHand) int {
return 0
}
func (ph PokerHand) HighestRank() Rank {
func (ph *PokerHand) HighestRank() Rank {
return ph.Hand.HighestRank()
}
func (ph PokerHand) String() string {
func (ph *PokerHand) String() string {
return fmt.Sprintf("<PokerHand: %s (%s)>", ph.Hand.String(), ph.Description())
}
func (c PokerHand) Description() string {
if c.Type == RoyalFlush {
return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit)
func (ph *PokerHand) Description() string {
if ph.Type == RoyalFlush {
return fmt.Sprintf("a royal flush in %s", ph.Hand[0].Suit)
}
if c.Hand.containsStraightFlush() {
if c.Hand[3].Rank == FIVE && c.Hand[4].Rank == ACE {
if ph.Hand.containsStraightFlush() {
if ph.Hand[3].Rank == FIVE && ph.Hand[4].Rank == ACE {
// special case for steel wheel
return fmt.Sprintf("%s high straight flush in %s", c.Hand[3].Rank.WithArticle(), c.Hand[4].Suit)
return fmt.Sprintf("%s high straight flush in %s", ph.Hand[3].Rank.WithArticle(), ph.Hand[4].Suit)
}
return fmt.Sprintf("%s high straight flush in %s", c.HighestRank().WithArticle(), c.Hand[4].Suit)
return fmt.Sprintf("%s high straight flush in %s", ph.HighestRank().WithArticle(), ph.Hand[4].Suit)
}
if c.Hand.containsFourOfAKind() {
return fmt.Sprintf("four %s with %s", c.Hand.fourOfAKindRank().Pluralize(), c.Hand.fourOfAKindKicker().Rank.WithArticle())
if ph.Hand.containsFourOfAKind() {
return fmt.Sprintf("four %s with %s", ph.Hand.fourOfAKindRank().Pluralize(), ph.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 ph.Hand.containsFullHouse() {
return fmt.Sprintf("a full house, %s full of %s", ph.Hand.fullHouseTripsRank().Pluralize(), ph.Hand.fullHousePairRank().Pluralize())
}
if c.Hand.containsFlush() {
return fmt.Sprintf("%s high flush in %s", c.HighestRank().WithArticle(), c.Hand[4].Suit)
if ph.Hand.containsFlush() {
return fmt.Sprintf("%s high flush in %s", ph.HighestRank().WithArticle(), ph.Hand[4].Suit)
}
if c.Hand.containsStraight() {
if c.Hand[3].Rank == FIVE && c.Hand[4].Rank == ACE {
if ph.Hand.containsStraight() {
if ph.Hand[3].Rank == FIVE && ph.Hand[4].Rank == ACE {
// special case for wheel straight
return fmt.Sprintf("%s high straight", c.Hand[3].Rank.WithArticle())
return fmt.Sprintf("%s high straight", ph.Hand[3].Rank.WithArticle())
}
return fmt.Sprintf("%s high straight", c.HighestRank().WithArticle())
return fmt.Sprintf("%s high straight", ph.HighestRank().WithArticle())
}
if c.Hand.containsThreeOfAKind() {
if ph.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(),
ph.Hand.threeOfAKindTripsRank().Pluralize(),
ph.Hand.threeOfAKindFirstKicker().Rank.WithArticle(),
ph.Hand.threeOfAKindSecondKicker().Rank.WithArticle(),
)
}
if c.Hand.containsTwoPair() {
if ph.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(),
ph.Hand.twoPairBiggestPair().Pluralize(),
ph.Hand.twoPairSmallestPair().Pluralize(),
ph.Hand.twoPairKicker().Rank.WithArticle(),
)
}
if c.Hand.containsPair() {
if ph.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(),
ph.Hand.pairRank().Pluralize(),
ph.Hand.pairFirstKicker().Rank.WithArticle(),
ph.Hand.pairSecondKicker().Rank.WithArticle(),
ph.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",
c.Hand[4].Rank,
c.Hand[3].Rank.WithArticle(),
c.Hand[2].Rank.WithArticle(),
c.Hand[1].Rank.WithArticle(),
c.Hand[0].Rank.WithArticle(),
ph.Hand[4].Rank,
ph.Hand[3].Rank.WithArticle(),
ph.Hand[2].Rank.WithArticle(),
ph.Hand[1].Rank.WithArticle(),
ph.Hand[0].Rank.WithArticle(),
)
}

View File

@@ -142,5 +142,4 @@ func (ph *PokerHand) calculateScore() {
ph.Score += ph.Hand[2].Score()
ph.Score += ph.Hand[3].Score()
ph.Score += ph.Hand[4].Score()
return
}

View File

@@ -6,16 +6,16 @@ import (
"github.com/stretchr/testify/assert"
)
func TestHandDescripionBug(t *testing.T) {
func TestHandDescriptionBug(t *testing.T) {
t.Parallel()
playerCount := 8
d := NewDeck()
d.ShuffleDeterministically(1337)
var players []*Cards
players = make([]*Cards, playerCount)
for i := 1; i-1 < playerCount; i++ {
players := make([]*Cards, playerCount)
for i := 0; i < playerCount; i++ {
c := d.Deal(2)
players[i-1] = &c
t.Logf("Player %d dealt: %+v\n", i, c)
players[i] = &c
t.Logf("Player %d dealt: %+v\n", i+1, c)
}
t.Logf("Players: %+v\n", players)
@@ -24,17 +24,17 @@ func TestHandDescripionBug(t *testing.T) {
var playerResults []*PokerHand
for i := 1; i-1 < playerCount; i++ {
t.Logf("Player %d hole cards: %+v\n", i, *players[i-1])
pc := append(*players[i-1], community...)
t.Logf("Player %d cards available: %+v\n", i, pc)
for i := 0; i < playerCount; i++ {
t.Logf("Player %d hole cards: %+v\n", i+1, *players[i])
pc := append(*players[i], community...)
t.Logf("Player %d cards available: %+v\n", i+1, pc)
hand, err := pc.IdentifyBestFiveCardPokerHand()
assert.Nil(t, err, "Expected no error")
assert.NoError(t, err, "Expected no error")
ph, err := hand.PokerHand()
assert.Nil(t, err, "Expected no error")
t.Logf("Player %d five cards used: %+v\n", i, hand)
t.Logf("Player %d poker hand: %+v\n", i, ph)
t.Logf("Player %d best hand description: %s\n", i, ph.Description())
assert.NoError(t, err, "Expected no error")
t.Logf("Player %d five cards used: %+v\n", i+1, hand)
t.Logf("Player %d poker hand: %+v\n", i+1, ph)
t.Logf("Player %d best hand description: %s\n", i+1, ph.Description())
playerResults = append(playerResults, ph)
}
@@ -44,30 +44,31 @@ func TestHandDescripionBug(t *testing.T) {
t.Logf("Weird one description: %s\n", weirdOne.Description())
// T♠,7♠,9♦,7♣,T♥
assert.Equal(t, weirdOne.Description(), "two pair, tens and sevens with a nine")
assert.Equal(t, "two pair, tens and sevens with a nine", weirdOne.Description())
scoreShouldBe := ScoreTwoPair
scoreShouldBe += 10000 * TEN.Score()
scoreShouldBe += 100 * SEVEN.Score()
scoreShouldBe += NINE.Score()
assert.Equal(t, weirdOne.Score, scoreShouldBe)
assert.Equal(t, scoreShouldBe, weirdOne.Score)
cards := weirdOne.Hand
assert.True(t, cards.containsTwoPair(), "Expected hand to be two pair")
bp := cards.twoPairBiggestPair() // returns Rank, because describing a pair
assert.Equal(t, bp, TEN, "Expected biggest pair to be a ten")
assert.Equal(t, TEN, bp, "Expected biggest pair to be a ten")
sp := cards.twoPairSmallestPair() // returns Rank, because describing a pair
assert.Equal(t, sp, SEVEN, "Expected smallest pair to be a seven")
assert.Equal(t, SEVEN, sp, "Expected smallest pair to be a seven")
k := cards.twoPairKicker() // returns Card, because describing a single card
assert.Equal(t, k.Rank, NINE, "Expected kicker to be a nine")
assert.Equal(t, NINE, k.Rank, "Expected kicker to be a nine")
}
func TestAceLowStraight(t *testing.T) {
t.Parallel()
t.Run("Test Ace-Low Straight", func(t *testing.T) {
hand := Cards{
AceOfSpades(),
DeuceOfHearts(),
@@ -77,22 +78,25 @@ 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.NoError(t, err, "Expected no error")
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+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.Equal(t, "a five high straight", ph.Description())
assert.Equal(t, ACE, hand.HighestRank(), "Expected highest rank to be an ace")
s := hand.SortByRankAscending()
assert.True(t, s.First().Rank == DEUCE, "Expected first card to be a deuce")
assert.True(t, s.Last().Rank == ACE, "Expected last card in sorted to be a ace")
assert.True(t, s.Second().Rank == THREE, "Expected second card to be a three")
assert.True(t, s.Third().Rank == FOUR, "Expected third card to be a four")
assert.True(t, s.Fourth().Rank == FIVE, "Expected fourth card to be a five")
assert.True(t, s.Fifth().Rank == ACE, "Expected fifth card to be an ace")
assert.Equal(t, DEUCE, s.First().Rank, "Expected first card to be a deuce")
assert.Equal(t, ACE, s.Last().Rank, "Expected last card in sorted to be an ace")
assert.Equal(t, THREE, s.Second().Rank, "Expected second card to be a three")
assert.Equal(t, FOUR, s.Third().Rank, "Expected third card to be a four")
assert.Equal(t, FIVE, s.Fourth().Rank, "Expected fourth card to be a five")
assert.Equal(t, ACE, s.Fifth().Rank, "Expected fifth card to be an ace")
})
}
func TestAceHighStraight(t *testing.T) {
t.Parallel()
t.Run("Test Ace-High Straight", func(t *testing.T) {
hand := Cards{
TenOfSpades(),
JackOfHearts(),
@@ -106,16 +110,19 @@ func TestAceHighStraight(t *testing.T) {
newDeck.ShuffleDeterministically(123456789)
shuffledHand := newDeck.Deal(5)
assert.True(t, shuffledHand.containsStraight(), "Expected hand to still be a straight after shuffle")
assert.True(t, shuffledHand.HighestRank() == ACE, "Expected highest rank to be an ace")
assert.Equal(t, ACE, shuffledHand.HighestRank(), "Expected highest rank to be an ace")
sortedHand := shuffledHand.SortByRankAscending()
assert.True(t, sortedHand[0].Rank == TEN, "Expected lowest rank to be a ten")
assert.True(t, sortedHand[1].Rank == JACK, "Expected second lowest rank to be a jack")
assert.True(t, sortedHand[2].Rank == QUEEN, "Expected third lowest rank to be a queen")
assert.True(t, sortedHand[3].Rank == KING, "Expected fourth lowest rank to be a king")
assert.True(t, sortedHand[4].Rank == ACE, "Expected highest rank to be an ace")
assert.Equal(t, TEN, sortedHand[0].Rank, "Expected lowest rank to be a ten")
assert.Equal(t, JACK, sortedHand[1].Rank, "Expected second lowest rank to be a jack")
assert.Equal(t, QUEEN, sortedHand[2].Rank, "Expected third lowest rank to be a queen")
assert.Equal(t, KING, sortedHand[3].Rank, "Expected fourth lowest rank to be a king")
assert.Equal(t, ACE, sortedHand[4].Rank, "Expected highest rank to be an ace")
})
}
func TestOtherStraight(t *testing.T) {
t.Parallel()
t.Run("Test Other Straight", func(t *testing.T) {
hand := Cards{
DeuceOfSpades(),
ThreeOfHearts(),
@@ -127,17 +134,18 @@ func TestOtherStraight(t *testing.T) {
newDeck := NewDeckFromCards(hand)
newDeck.ShuffleDeterministically(123456789)
//fmt.Printf("Shuffled deck: %s\n", newDeck.String())
//fmt.Printf("new deck has %d cards\n", newDeck.Count())
shuffledHand := newDeck.Deal(5)
assert.True(t, shuffledHand.containsStraight(), "Expected hand to still be a straight after shuffle")
assert.False(t, shuffledHand.containsTwoPair(), "Expected hand to not be two pair")
assert.False(t, shuffledHand.containsPair(), "Expected hand to not be a pair")
assert.True(t, shuffledHand.HighestRank() == SIX, "Expected highest rank to be a six")
assert.True(t, shuffledHand.SortByRankAscending().First().Rank == DEUCE, "Expected first card to be a deuce")
assert.Equal(t, SIX, shuffledHand.HighestRank(), "Expected highest rank to be a six")
assert.Equal(t, DEUCE, shuffledHand.SortByRankAscending().First().Rank, "Expected first card to be a deuce")
})
}
func TestFlush(t *testing.T) {
t.Parallel()
t.Run("Test Flush", func(t *testing.T) {
hand := Cards{
AceOfSpades(),
DeuceOfSpades(),
@@ -148,8 +156,6 @@ func TestFlush(t *testing.T) {
assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
newDeck := NewDeckFromCards(hand)
newDeck.ShuffleDeterministically(123456789)
//fmt.Printf("Shuffled deck: %s\n", newDeck.String())
//fmt.Printf("new deck has %d cards\n", newDeck.Count())
shuffledHand := newDeck.Deal(5)
assert.True(t, shuffledHand.containsFlush(), "Expected hand to still be a flush after shuffle")
@@ -160,15 +166,17 @@ func TestFlush(t *testing.T) {
x += THREE.Score()
x += FOUR.Score()
x += SIX.Score()
//fmt.Printf("a-2-3-4-6 flush score should be: %d\n", x)
ph, err := shuffledHand.PokerHand()
assert.Nil(t, err, "Expected no error")
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero 0")
assert.NoError(t, err, "Expected no error")
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero")
assert.Equal(t, ph.Score, x)
})
}
func TestStraightFlush(t *testing.T) {
t.Parallel()
t.Run("Test Straight Flush", func(t *testing.T) {
hand := Cards{
SixOfSpades(),
DeuceOfSpades(),
@@ -186,19 +194,20 @@ 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() == SIX, "Expected highest rank to be a six")
assert.Equal(t, SIX, hand.HighestRank(), "Expected highest rank to be a six")
nd := NewDeckFromCards(hand)
nd.ShuffleDeterministically(123456789)
//fmt.Printf("Shuffled deck: %s\n", nd.String())
//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() == 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")
assert.Equal(t, SIX, shuffledHand.HighestRank(), "Expected highest rank to still be a six after shuffle")
assert.Equal(t, SIX, shuffledHand.HighestRank(), "Expected highest rank to be a six after shuffle even with aces low")
})
}
func TestFourOfAKind(t *testing.T) {
t.Parallel()
t.Run("Test Four of a Kind", func(t *testing.T) {
hand := Cards{
SixOfSpades(),
SixOfHearts(),
@@ -224,10 +233,13 @@ func TestFourOfAKind(t *testing.T) {
assert.True(t, hand.containsTwoPair(), "Expected hand to contain two pair")
assert.True(t, hand.containsPair(), "Expected hand to contain a pair")
assert.True(t, hand.HighestRank() == SIX, "Expected highest rank to be a six")
assert.Equal(t, SIX, hand.HighestRank(), "Expected highest rank to be a six")
})
}
func TestRoyalFlush(t *testing.T) {
t.Parallel()
t.Run("Test Royal Flush", func(t *testing.T) {
hand := Cards{
TenOfSpades(),
JackOfSpades(),
@@ -246,11 +258,14 @@ 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() == ACE, "Expected highest rank to be an ace")
assert.False(t, hand.HighestRank() == TEN, "Expected highest rank to not be an ace")
assert.Equal(t, ACE, hand.HighestRank(), "Expected highest rank to be an ace")
assert.NotEqual(t, TEN, hand.HighestRank(), "Expected highest rank to not be a ten")
})
}
func TestUnmadeHand(t *testing.T) {
t.Parallel()
t.Run("Test Unmade Hand", func(t *testing.T) {
hand := Cards{
TenOfSpades(),
JackOfDiamonds(),
@@ -267,11 +282,14 @@ 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() == KING, "Expected highest rank to be a king")
assert.Equal(t, KING, hand.HighestRank(), "Expected highest rank to be a king")
assert.True(t, hand.isUnmadeHand(), "Expected hand to be unmade")
})
}
func TestTwoPair(t *testing.T) {
t.Parallel()
t.Run("Test Two Pair", func(t *testing.T) {
hand := Cards{
KingOfSpades(),
JackOfDiamonds(),
@@ -288,11 +306,14 @@ 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() == KING, "Expected highest rank to be a king")
assert.Equal(t, KING, hand.HighestRank(), "Expected highest rank to be a king")
assert.False(t, hand.isUnmadeHand(), "Expected hand to not be unmade")
})
}
func TestDetectDuplicates(t *testing.T) {
t.Parallel()
t.Run("Test Detect Duplicates", func(t *testing.T) {
hand := Cards{
KingOfSpades(),
JackOfDiamonds(),
@@ -301,9 +322,12 @@ func TestDetectDuplicates(t *testing.T) {
TenOfSpades(),
}
assert.True(t, hand.containsDuplicates(), "Expected hand to contain duplicates")
})
}
func TestHandScore(t *testing.T) {
t.Parallel()
t.Run("Test Hand Score", func(t *testing.T) {
hand := Cards{
KingOfSpades(),
JackOfDiamonds(),
@@ -312,33 +336,33 @@ func TestHandScore(t *testing.T) {
TenOfSpades(),
}
ph, error := hand.PokerHand()
assert.Nil(t, error, "Expected no error")
assert.True(t, ph.Score > 0, "Expected score to be nonzero 0")
ph, err := hand.PokerHand()
assert.NoError(t, err, "Expected no error")
assert.True(t, ph.Score > 0, "Expected score to be nonzero")
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)
// write more assertions FIXME
})
}
func TestTwoPairBug(t *testing.T) {
t.Parallel()
t.Run("Test Two Pair Bug", func(t *testing.T) {
// this is an actual bug in the first implementation
c, err := NewCardsFromString("9♠,9♣,Q♥,Q♦,K♣")
assert.Nil(t, err, "Expected no error")
//fmt.Printf("Cards: %v+\n", c)
assert.NoError(t, err, "Expected no error")
ph, err := c.PokerHand()
assert.Nil(t, err, "Expected no error")
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero 0")
assert.NoError(t, err, "Expected no error")
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero")
desc := ph.Description()
assert.Equal(t, desc, "two pair, queens and nines with a king")
//fmt.Printf("PokerHand: %v+\n", ph)
//fmt.Printf("PH score: %d\n", ph.Score)
assert.Equal(t, "two pair, queens and nines with a king", desc)
})
}
func TestScoringStructureQuads(t *testing.T) {
t.Parallel()
t.Run("Test Scoring Structure Quads", func(t *testing.T) {
// this test case was for a bug, but is now fixed
handA := Cards{
DeuceOfSpades(),
DeuceOfHearts(),
@@ -356,13 +380,21 @@ func TestScoringStructureQuads(t *testing.T) {
}
phA, err := handA.PokerHand()
assert.Nil(t, err, "Expected no error")
assert.NoError(t, err, "Expected no error")
phB, err := handB.PokerHand()
assert.Nil(t, err, "Expected no error")
assert.NoError(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) {
t.Parallel()
t.Run("Test Scoring Structure Full House", func(t *testing.T) {
// this test case documents an actual bug i found in my scoring code
// related to the fact that i was multiplying by 100 then by 1000,
// instead of by 100 then by 10000 in the scoring. because the ranks
// are 2-14, the different levels of kickers (or in the case of a full
// house, the pair) were not distinguishing sufficiently.
handA := Cards{
DeuceOfSpades(),
DeuceOfHearts(),
@@ -372,7 +404,7 @@ func TestScoringStructureFullHouse(t *testing.T) {
}
phA, err := handA.PokerHand()
assert.Nil(t, err, "Expected no error")
assert.NoError(t, err, "Expected no error")
deucesFullOfAcesScore := phA.Score
handB := Cards{
@@ -383,8 +415,9 @@ func TestScoringStructureFullHouse(t *testing.T) {
DeuceOfHearts(),
}
phB, err := handB.PokerHand()
assert.Nil(t, err, "Expected no error")
assert.NoError(t, err, "Expected no error")
threesFullOfDeucesScore := phB.Score
assert.Greater(t, threesFullOfDeucesScore, deucesFullOfAcesScore, "Expected Threes full of deuces to beat deuces full of aces")
})
}