Compare commits

...

12 Commits

16 changed files with 853 additions and 327 deletions

42
.gitignore vendored
View File

@ -5,9 +5,45 @@
*.so *.so
*.dylib *.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
# Go workspace file
go.work
go.work.sum
# Dependency directories
vendor/
# Go build artifacts
bin/
build/
dist/
# Configuration files for editors and tools
.vscode/
.idea/
*.swp
*.swo
# Logs
*.log
# Temporary files
*.tmp
*.temp
*.bak
# Test binary, coverage, and results
*.test
*.cover
*.cov
*.profile
*.prof
# OS-specific files
.DS_Store .DS_Store
Thumbs.db
# i don't know what keeps creating this file
examples/simgame/simgame

View File

@ -1,4 +1,18 @@
default: test .PHONY: examples
default: run
run: simgame
simgame: examples
RANDOM_SHUFFLE=1 ./bin/simgame
clean:
rm -rf bin *.test
test: test:
go test -v ./... go test -count=1 .
examples:
test -d bin || mkdir bin
go build -o bin/ ./examples/...

97
card.go
View File

@ -5,6 +5,7 @@ import (
"slices" "slices"
"sort" "sort"
"strings" "strings"
"unicode/utf8"
"github.com/logrusorgru/aurora/v4" "github.com/logrusorgru/aurora/v4"
) )
@ -14,59 +15,56 @@ type Card struct {
Suit Suit Suit Suit
} }
func NewRankFromString(rank string) Rank {
switch rank {
case "2":
return Rank(DEUCE)
case "3":
return Rank(THREE)
case "4":
return Rank(FOUR)
case "5":
return Rank(FIVE)
case "6":
return Rank(SIX)
case "7":
return Rank(SEVEN)
case "8":
return Rank(EIGHT)
case "9":
return Rank(NINE)
case "T":
return Rank(TEN)
case "J":
return Rank(JACK)
case "Q":
return Rank(QUEEN)
case "K":
return Rank(KING)
case "A":
return Rank(ACE)
}
return Rank(0)
}
func NewSuitFromString(suit string) Suit {
switch suit {
case string(SPADE):
return Suit(SPADE)
case string(CLUB):
return Suit(CLUB)
case string(HEART):
return Suit(HEART)
case string(DIAMOND):
return Suit(DIAMOND)
}
return Suit(0)
}
func NewCardFromString(card string) (Card, error) { func NewCardFromString(card string) (Card, error) {
// FIXME extend this later to common format strings like "9s" // FIXME extend this later to common format strings like "9s"
if len(card) != 2 { length := utf8.RuneCountInString(card)
if length != 2 {
return Card{}, fmt.Errorf("Invalid card string %s", card) return Card{}, fmt.Errorf("Invalid card string %s", card)
} }
rank := NewRankFromString(card[0:1]) var rank Rank
suit := NewSuitFromString(card[1:2]) 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)
}
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)
}
if rank == Rank(0) || suit == Suit(0) { 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)
} }
@ -78,6 +76,7 @@ func NewCardsFromString(cards string) (Cards, error) {
// FIXME extend this later to common format strings like "9c Qh Qd Kc" // FIXME extend this later to common format strings like "9c Qh Qd Kc"
// with or without commas // with or without commas
var newCards Cards var newCards Cards
newCards = make(Cards, 0)
cardStrings := strings.Split(cards, ",") cardStrings := strings.Split(cards, ",")
for _, cardString := range cardStrings { for _, cardString := range cardStrings {
card, err := NewCardFromString(cardString) card, err := NewCardFromString(cardString)

Binary file not shown.

170
examples/simgame/main.go Normal file
View File

@ -0,0 +1,170 @@
package main
import (
"fmt"
"os"
"time"
"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
}
type Game struct {
Deck *pokercore.Deck
Players []*Player
Community pokercore.Cards
Street int
}
func suspense() {
fmt.Printf("########################################\n")
if os.Getenv("NO_LAG") == "" {
time.Sleep(delayTime)
}
}
func NewGame() *Game {
// this "randomly chosen" seed somehow deals pocket queens, pocket kings, and pocket aces to three players.
// what are the odds? lol
//g := NewGame(1337))
// nothing up my sleeve:
seed := 3141592653
g := &Game{}
g.Street = 0
g.Deck = pokercore.NewDeck()
g.SpreadCards()
fmt.Printf("Shuffle up and deal!\n")
suspense()
fmt.Printf("Shuffling...\n")
if os.Getenv("RANDOM_SHUFFLE") != "" {
g.Deck.ShuffleRandomly()
} else {
fmt.Printf("Using deterministic shuffle seed: %d\n", seed)
g.Deck.ShuffleDeterministically(int64(seed))
}
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() {
g := NewGame()
g.ShowGameStatus()
g.DealPlayersIn()
suspense()
g.ShowGameStatus()
g.DealFlop()
suspense()
g.ShowGameStatus()
g.DealTurn()
suspense()
g.ShowGameStatus()
g.DealRiver()
suspense()
g.ShowGameStatus()
g.ShowWinner()
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,35 +0,0 @@
package main
import (
"fmt"
"git.eeqj.de/sneak/pokercore"
)
func main() {
d := pokercore.NewDeck()
fmt.Printf("deck before shuffling: %s\n", d.FormatForTerminal())
d.ShuffleDeterministically(1337 + 1) // 1337 gives too weird a result
fmt.Printf("deck after shuffling: %s\n", d.FormatForTerminal())
var players []pokercore.Cards
for i := 0; i < 6; i++ {
players = append(players, d.Deal(2))
}
for i, p := range players {
fmt.Printf("player %d: %s\n", i, p.FormatForTerminal())
}
// deal the flop
var community pokercore.Cards
community = d.Deal(3)
fmt.Printf("flop: %s\n", community.FormatForTerminal())
// evaluate the hands so far
for i, p := range players {
fmt.Printf("player %d: %s\n", i, p.FormatForTerminal())
thisPlayersHand, err := p.Append(community).PokerHand()
if err != nil {
fmt.Printf("error evaluating hand: %s\n", err)
continue
}
fmt.Printf("player %d: %s\n", i, thisPlayersHand.String())
}
}

Binary file not shown.

View File

@ -17,6 +17,14 @@ func (hand Cards) IdentifyBestFiveCardPokerHand() (Cards, error) {
return nil, ErrDuplicateCard return nil, ErrDuplicateCard
} }
if len(hand) == 5 {
return hand, nil
}
if len(hand) < 5 {
return nil, errors.New("hand must have at least 5 cards to identify the best 5 card poker hand")
}
newHands := make([]Cards, len(hand)) newHands := make([]Cards, len(hand))
for i := 0; i < len(hand); i++ { for i := 0; i < len(hand); i++ {
newHand := make(Cards, len(hand)-1) newHand := make(Cards, len(hand)-1)

23
go.mod
View File

@ -4,14 +4,33 @@ go 1.22.2
require ( require (
github.com/logrusorgru/aurora v2.0.3+incompatible github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/logrusorgru/aurora/v4 v4.0.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
) )
require ( require (
git.eeqj.de/sneak/timingbench v0.0.0-20240519025145-fb13c5c56a02 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/logrusorgru/aurora/v4 v4.0.0 // 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/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

108
go.sum
View File

@ -1,24 +1,132 @@
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 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 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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=
github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -1,38 +1,12 @@
package pokercore package pokercore
func (c Cards) scoreStraightFlush() HandScore { // these helper functions are used in a Cards.PokerHand() constructor and in
if !c.containsStraightFlush() { // the calculateScore() function called from it.
panic("hand must be a straight flush to score it") // they only work with exactly five cards, no duplicates, and no wild cards.
} // they are not intended to be used in any other context, which is why
return ScoreStraightFlush + 1000*c.HighestRank().Score() // they are not exported
}
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() {
panic("hand must have a pair to have a pair rank")
}
sorted := c.SortByRankAscending() sorted := c.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank { if sorted[0].Rank == sorted[1].Rank {
return sorted[0].Rank return sorted[0].Rank
@ -50,9 +24,6 @@ func (c Cards) pairRank() Rank {
} }
func (c Cards) pairFirstKicker() Card { func (c Cards) pairFirstKicker() Card {
if !c.containsPair() {
panic("hand must have a pair to have a first kicker")
}
sorted := c.SortByRankAscending() sorted := c.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank { if sorted[0].Rank == sorted[1].Rank {
return sorted[4] return sorted[4]
@ -70,9 +41,6 @@ func (c Cards) pairFirstKicker() Card {
} }
func (c Cards) pairSecondKicker() Card { func (c Cards) pairSecondKicker() Card {
if !c.containsPair() {
panic("hand must have a pair to have a second kicker")
}
sorted := c.SortByRankAscending() sorted := c.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank { if sorted[0].Rank == sorted[1].Rank {
// first kicker is [4] // first kicker is [4]
@ -94,9 +62,6 @@ func (c Cards) pairSecondKicker() Card {
} }
func (c Cards) pairThirdKicker() Card { func (c Cards) pairThirdKicker() Card {
if !c.containsPair() {
panic("hand must have a pair to have a third kicker")
}
sorted := c.SortByRankAscending() sorted := c.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank { if sorted[0].Rank == sorted[1].Rank {
// first kicker is [4] // first kicker is [4]
@ -122,14 +87,25 @@ func (c Cards) pairThirdKicker() Card {
} }
func (c Cards) twoPairBiggestPair() Rank { func (c Cards) twoPairBiggestPair() Rank {
if !c.containsTwoPair() {
panic("hand must have two pair to have a biggest pair")
}
sorted := c.SortByRankAscending() sorted := c.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank { if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank {
return sorted[2].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) twoPairSmallestPair() Rank {
sorted := c.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank {
return sorted[0].Rank
}
if sorted[0].Rank == sorted[1].Rank && sorted[3].Rank == sorted[4].Rank { if sorted[0].Rank == sorted[1].Rank && sorted[3].Rank == sorted[4].Rank {
return sorted[0].Rank return sorted[0].Rank
} }
@ -139,27 +115,7 @@ func (c Cards) twoPairBiggestPair() Rank {
panic("nope") panic("nope")
} }
func (c Cards) twoPairSmallestPair() Rank {
if !c.containsTwoPair() {
panic("hand must have two pair to have a smallest pair")
}
sorted := c.SortByRankAscending()
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 { func (c Cards) twoPairKicker() Card {
if !c.containsTwoPair() {
panic("hand must have two pair to have a twoPairKicker")
}
sorted := c.SortByRankAscending() sorted := c.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank { if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank {
return sorted[4] return sorted[4]
@ -174,9 +130,6 @@ func (c Cards) twoPairKicker() Card {
} }
func (c Cards) threeOfAKindTripsRank() Rank { func (c Cards) threeOfAKindTripsRank() Rank {
if !c.containsThreeOfAKind() {
panic("hand must have three of a kind to have a trips rank")
}
sorted := c.SortByRankAscending() sorted := c.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank { if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank {
return sorted[0].Rank return sorted[0].Rank
@ -191,9 +144,6 @@ func (c Cards) threeOfAKindTripsRank() Rank {
} }
func (c Cards) threeOfAKindKickers() Cards { func (c Cards) threeOfAKindKickers() Cards {
if !c.containsThreeOfAKind() {
panic("hand must have three of a kind to have kickers")
}
sorted := c.SortByRankAscending() sorted := c.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank { if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank {
return Cards{sorted[3], sorted[4]} return Cards{sorted[3], sorted[4]}
@ -284,14 +234,7 @@ func (hand Cards) containsDuplicates() bool {
return false return false
} }
func (hand Cards) IsFiveCardPokerHand() bool {
return len(hand) == 5 && !hand.containsDuplicates()
}
func (hand Cards) containsFlush() bool { func (hand Cards) containsFlush() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
suit := hand[0].Suit suit := hand[0].Suit
for i := 1; i < len(hand); i++ { for i := 1; i < len(hand); i++ {
if hand[i].Suit != suit { if hand[i].Suit != suit {
@ -302,9 +245,6 @@ func (hand Cards) containsFlush() bool {
} }
func (hand Cards) containsStraight() bool { func (hand Cards) containsStraight() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRankAscending() sorted := hand.SortByRankAscending()
if sorted[4].Rank == ACE && sorted[3].Rank == FIVE { if sorted[4].Rank == ACE && sorted[3].Rank == FIVE {
@ -322,9 +262,8 @@ func (hand Cards) containsStraight() bool {
} }
func (hand Cards) containsStraightFlush() bool { func (hand Cards) containsStraightFlush() bool {
if !hand.IsFiveCardPokerHand() { // this of course only works on five card hands
panic("hand must have 5 cards to be scored") // but these hand helpers are only ever called from a five card hand
}
if hand.containsStraight() && hand.containsFlush() { if hand.containsStraight() && hand.containsFlush() {
return true return true
} }
@ -332,9 +271,6 @@ func (hand Cards) containsStraightFlush() bool {
} }
func (hand Cards) containsRoyalFlush() bool { func (hand Cards) containsRoyalFlush() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
// This seems like it works, but a five-high straight flush is not a royal flush // This seems like it works, but a five-high straight flush is not a royal flush
// and the highest ranked card in five-high straigh flush is an ace. // and the highest ranked card in five-high straigh flush is an ace.
//if hand.containsStraightFlush() && hand.HighestRank() == ACE { //if hand.containsStraightFlush() && hand.HighestRank() == ACE {
@ -348,9 +284,6 @@ func (hand Cards) containsRoyalFlush() bool {
} }
func (hand Cards) containsFourOfAKind() bool { func (hand Cards) containsFourOfAKind() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRankAscending() sorted := hand.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank { if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank {
// the quads precede the kicker // the quads precede the kicker
@ -364,9 +297,6 @@ func (hand Cards) containsFourOfAKind() bool {
} }
func (hand Cards) containsFullHouse() bool { func (hand Cards) containsFullHouse() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRankAscending() sorted := hand.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank { if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank {
@ -381,9 +311,6 @@ func (hand Cards) containsFullHouse() bool {
} }
func (hand Cards) containsPair() bool { func (hand Cards) containsPair() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRankAscending() sorted := hand.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank { if sorted[0].Rank == sorted[1].Rank {
return true return true
@ -401,9 +328,6 @@ func (hand Cards) containsPair() bool {
} }
func (hand Cards) containsThreeOfAKind() bool { func (hand Cards) containsThreeOfAKind() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRankAscending() sorted := hand.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank { if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank {
return true return true
@ -418,9 +342,6 @@ func (hand Cards) containsThreeOfAKind() bool {
} }
func (hand Cards) containsTwoPair() bool { func (hand Cards) containsTwoPair() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRankAscending() sorted := hand.SortByRankAscending()
if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank { if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank {
return true return true
@ -435,8 +356,6 @@ func (hand Cards) containsTwoPair() bool {
} }
func (hand Cards) isUnmadeHand() bool { func (hand Cards) isUnmadeHand() bool {
if !hand.IsFiveCardPokerHand() { // i suspect this is expensive but we use it only in tests
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() return !hand.containsPair() && !hand.containsTwoPair() && !hand.containsThreeOfAKind() && !hand.containsStraight() && !hand.containsFlush() && !hand.containsFullHouse() && !hand.containsFourOfAKind() && !hand.containsStraightFlush() && !hand.containsRoyalFlush()
} }

79
perf_test.go Normal file
View File

@ -0,0 +1,79 @@
package pokercore
import (
"context"
"testing"
"time"
"git.eeqj.de/sneak/timingbench"
"github.com/stretchr/testify/assert"
)
func TestShuffleSpeed(t *testing.T) {
iterations := 1000
t.Logf("Running %d iterations of shuffle speed test", iterations)
// Create a context with a timeout for cancellation.
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// Measure the execution times of the sample function.
d := NewDeck()
result, err := timingbench.TimeFunction(ctx, func() { d.ShuffleRandomly() }, iterations)
if err != nil {
t.Fatalf("Error measuring function: %v", err)
}
// Print the timing results.
t.Logf(result.String())
}
func TestHandFindingSpeedFiveCard(t *testing.T) {
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) {
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) {
iterations := 100
t.Logf("Running %d iterations of hand finding speed test for 9 card hand", iterations)
measureHandFinding(t, iterations, 9)
}
func measureHandFinding(t *testing.T, iterations int, cardCount int) {
// Create a context with a timeout for cancellation.
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// Measure the execution times of the sample function.
result, err := timingbench.TimeFunction(ctx, func() {
findHandInRandomCards(t, int(123456789), cardCount) // check for hand in 10 cards
}, iterations)
if err != nil {
t.Fatalf("Error measuring function: %v", err)
}
t.Logf("Searching %d random cards for a hand takes on average %s", cardCount, result.Mean)
t.Logf("Over %d iterations the min was %s and the max was %s", iterations, result.Min, result.Max)
t.Logf("The standard deviation was %s", result.StdDev)
t.Logf("The median was %s", result.Median)
// Print the timing results.
//t.Logf(result.String())
}
func findHandInRandomCards(t *testing.T, shuffleSeed int, cardCount int) {
d := NewDeck()
d.ShuffleDeterministically(int64(shuffleSeed))
cards := d.Deal(cardCount)
hand, err := cards.IdentifyBestFiveCardPokerHand()
assert.Nil(t, err, "Expected no error")
ph, err := hand.PokerHand()
assert.Nil(t, err, "Expected no error")
//assert.Greater(t, ph.Score, 0, "Expected score to be nonzero 0")
desc := ph.Description()
assert.NotEmpty(t, desc, "Expected description to not be empty")
}

View File

@ -1,7 +1,6 @@
package pokercore package pokercore
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -14,13 +13,13 @@ type ShuffleTestResults []struct {
func TestPokerDeck(t *testing.T) { func TestPokerDeck(t *testing.T) {
d := NewDeck() d := NewDeck()
fmt.Printf("newdeck: %+v\n", d) //fmt.Printf("newdeck: %+v\n", d)
d.ShuffleDeterministically(437) d.ShuffleDeterministically(437)
fmt.Printf("deterministically shuffled deck: %+v\n", d) //fmt.Printf("deterministically shuffled deck: %+v\n", d)
cards := d.Deal(7) cards := d.Deal(7)
expected := "6♥,A♦,7♥,9♣,6♠,9♥,8♣" expected := "6♥,A♦,7♥,9♣,6♠,9♥,8♣"
fmt.Printf("deterministically shuffled deck after dealing: %+v\n", d) //fmt.Printf("deterministically shuffled deck after dealing: %+v\n", d)
fmt.Printf("cards: %+v\n", cards) //fmt.Printf("cards: %+v\n", cards)
assert.Equal(t, expected, cards.String()) assert.Equal(t, expected, cards.String())
x := d.Count() x := d.Count()

View File

@ -1,6 +1,9 @@
package pokercore package pokercore
import "fmt" import (
"errors"
"fmt"
)
type PokerHandType int type PokerHandType int
@ -28,99 +31,41 @@ 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")
} }
ph := new(PokerHand) ph := new(PokerHand)
ph.Hand = c
if c.containsRoyalFlush() {
ph.Type = RoyalFlush
ph.Score = ScoreRoyalFlush
return ph, nil
}
if c.containsStraightFlush() { // IdentifyBestFiveCardPokerHand() calls us to score hands
ph.Type = StraightFlush // but only if the hand is 5 cards exactly which avoids recursion loop
ph.Score = ph.Hand.scoreStraightFlush() if len(c) > 5 {
return ph, nil phc, err := c.IdentifyBestFiveCardPokerHand()
} if err != nil {
// this should in theory never happen
if c.containsFourOfAKind() { return nil, err
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
// Straights are scored by the highest card in the straight
// UNLESS the second highest card is a 5 and the highest card is an Ace
// In that case, the straight is a 5-high straight, not an Ace-high straight
sorted := c.SortByRankAscending()
if sorted[3].Rank == FIVE && sorted[4].Rank == ACE {
// 5-high straight
ph.Score = ScoreStraight + 1000*sorted[3].Score()
} else {
// All other straights
ph.Score = ScoreStraight + 1000*sorted[4].Score()
} }
return ph, nil ph.Hand = phc.SortByRankAscending()
} else if len(c) == 5 {
ph.Hand = c.SortByRankAscending()
} else {
return nil, errors.New("hand must have at least 5 cards to be scored as a poker hand")
} }
if c.containsThreeOfAKind() { // this doesn't return an error because we've already checked for
ph.Type = ThreeOfAKind // duplicates and we have already identified the best 5-card hand
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() { // this code used to be here, but got factored out so it can go in
ph.Type = TwoPair // scoring.go
ph.Score = ScoreTwoPair ph.calculateScore()
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 return ph, nil
} }
func (ph PokerHand) ToSortedCards() Cards { func (ph PokerHand) ToSortedCards() Cards {
sorted := ph.Hand.SortByRankAscending() // I believe ph.Hand is already sorted, but in any case it's only 5
return sorted // cards and sorting it costs ~nothing
return ph.Hand.SortByRankAscending()
} }
func (ph PokerHand) Compare(other PokerHand) int { func (ph PokerHand) Compare(other PokerHand) int {
@ -142,16 +87,15 @@ func (ph PokerHand) String() string {
} }
func (c PokerHand) Description() string { func (c PokerHand) Description() string {
sortedHand := c.Hand.SortByRankAscending()
if c.Type == RoyalFlush { if c.Type == RoyalFlush {
return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit) return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit)
} }
if c.Hand.containsStraightFlush() { if c.Hand.containsStraightFlush() {
if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE { if c.Hand[3].Rank == FIVE && c.Hand[4].Rank == ACE {
// special case for steel wheel // special case for steel wheel
return fmt.Sprintf("%s high straight flush in %s", sortedHand[3].Rank.WithArticle(), sortedHand[4].Suit) return fmt.Sprintf("%s high straight flush in %s", c.Hand[3].Rank.WithArticle(), c.Hand[4].Suit)
} }
return fmt.Sprintf("%s high straight flush in %s", c.HighestRank().WithArticle(), sortedHand[4].Suit) return fmt.Sprintf("%s high straight flush in %s", c.HighestRank().WithArticle(), c.Hand[4].Suit)
} }
if c.Hand.containsFourOfAKind() { if c.Hand.containsFourOfAKind() {
return fmt.Sprintf("four %s with %s", c.Hand.fourOfAKindRank().Pluralize(), c.Hand.fourOfAKindKicker().Rank.WithArticle()) return fmt.Sprintf("four %s with %s", c.Hand.fourOfAKindRank().Pluralize(), c.Hand.fourOfAKindKicker().Rank.WithArticle())
@ -160,12 +104,12 @@ func (c PokerHand) Description() string {
return fmt.Sprintf("a full house, %s full of %s", c.Hand.fullHouseTripsRank().Pluralize(), c.Hand.fullHousePairRank().Pluralize()) return fmt.Sprintf("a full house, %s full of %s", c.Hand.fullHouseTripsRank().Pluralize(), c.Hand.fullHousePairRank().Pluralize())
} }
if c.Hand.containsFlush() { if c.Hand.containsFlush() {
return fmt.Sprintf("%s high flush in %s", c.HighestRank().WithArticle(), sortedHand[4].Suit) return fmt.Sprintf("%s high flush in %s", c.HighestRank().WithArticle(), c.Hand[4].Suit)
} }
if c.Hand.containsStraight() { if c.Hand.containsStraight() {
if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE { if c.Hand[3].Rank == FIVE && c.Hand[4].Rank == ACE {
// special case for wheel straight // special case for wheel straight
return fmt.Sprintf("%s high straight", sortedHand[3].Rank.WithArticle()) return fmt.Sprintf("%s high straight", c.Hand[3].Rank.WithArticle())
} }
return fmt.Sprintf("%s high straight", c.HighestRank().WithArticle()) return fmt.Sprintf("%s high straight", c.HighestRank().WithArticle())
} }
@ -198,10 +142,10 @@ func (c PokerHand) Description() string {
return fmt.Sprintf( return fmt.Sprintf(
// "ace high with an eight, a seven, a six, and a deuce" // "ace high with an eight, a seven, a six, and a deuce"
"%s high with %s, %s, %s, and %s", "%s high with %s, %s, %s, and %s",
sortedHand[4].Rank, c.Hand[4].Rank,
sortedHand[3].Rank.WithArticle(), c.Hand[3].Rank.WithArticle(),
sortedHand[2].Rank.WithArticle(), c.Hand[2].Rank.WithArticle(),
sortedHand[1].Rank.WithArticle(), c.Hand[1].Rank.WithArticle(),
sortedHand[0].Rank.WithArticle(), c.Hand[0].Rank.WithArticle(),
) )
} }

View File

@ -1,6 +1,8 @@
package pokercore package pokercore
import "fmt" import (
"fmt"
)
type HandScore int type HandScore int
@ -22,18 +24,123 @@ func (c Card) Score() HandScore {
} }
func (c Cards) PokerHandScore() (HandScore, error) { 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() 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) }
func (ph *PokerHand) calculateScore() {
// sanity check, we should only be called in the PokerHand() method from
// a Cards, but just in case
if len(ph.Hand) != 5 {
// normally we don't panic in a library but this is a "should never
// happen"
panic("PokerHand.calculateScore() called on a PokerHand with != 5 cards")
}
if ph.Hand.containsRoyalFlush() {
ph.Type = RoyalFlush
ph.Score = ScoreRoyalFlush
return
}
if ph.Hand.containsStraightFlush() {
ph.Type = StraightFlush
ph.Score = ScoreStraightFlush
ph.Score += ph.Hand.HighestRank().Score()
return
}
if ph.Hand.containsFourOfAKind() {
ph.Type = FourOfAKind
ph.Score = ScoreFourOfAKind
ph.Score += 10000 * ph.Hand.fourOfAKindRank().Score()
ph.Score += 100 * ph.Hand.fourOfAKindKicker().Score()
return
}
if ph.Hand.containsFullHouse() {
ph.Type = FullHouse
ph.Score = ScoreFullHouse
ph.Score += 10000 * ph.Hand.fullHouseTripsRank().Score()
ph.Score += 100 * ph.Hand.fullHousePairRank().Score()
return
}
if ph.Hand.containsFlush() {
ph.Type = Flush
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
}
if ph.Hand.containsStraight() {
ph.Type = Straight
ph.Score = ScoreStraight
// note that ph.Hand is already sorted by rank ascending with ace
// high
// 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
if ph.Hand[3].Rank == FIVE && ph.Hand[4].Rank == ACE {
// 5-high straight, scored by the five's rank
ph.Score += ph.Hand[3].Score()
} else {
// All other straights are scored by the highest card in the straight
ph.Score += ph.Hand[4].Score()
}
return
}
if ph.Hand.containsThreeOfAKind() {
ph.Type = ThreeOfAKind
ph.Score = ScoreThreeOfAKind
ph.Score += 10000 * ph.Hand.threeOfAKindTripsRank().Score()
ph.Score += 100 * ph.Hand.threeOfAKindFirstKicker().Score()
ph.Score += ph.Hand.threeOfAKindSecondKicker().Score()
return
}
if ph.Hand.containsTwoPair() {
ph.Type = TwoPair
ph.Score = ScoreTwoPair
ph.Score += 10000 * ph.Hand.twoPairBiggestPair().Score()
ph.Score += 100 * ph.Hand.twoPairSmallestPair().Score()
ph.Score += ph.Hand.twoPairKicker().Score()
return
}
if ph.Hand.containsPair() {
ph.Type = Pair
ph.Score = ScorePair
ph.Score += 10000 * ph.Hand.pairRank().Score()
ph.Score += 100 * ph.Hand.pairFirstKicker().Score()
ph.Score += ph.Hand.pairSecondKicker().Score()
ph.Score += ph.Hand.pairThirdKicker().Score()
return
}
ph.Type = HighCard
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
} }

View File

@ -1,12 +1,72 @@
package pokercore package pokercore
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestHandDescripionBug(t *testing.T) {
playerCount := 8
d := NewDeck()
d.ShuffleDeterministically(1337)
var players []*Cards
players = make([]*Cards, playerCount)
for i := 1; i-1 < playerCount; i++ {
c := d.Deal(2)
players[i-1] = &c
t.Logf("Player %d dealt: %+v\n", i, c)
}
t.Logf("Players: %+v\n", players)
community := d.Deal(5)
t.Logf("Community: %+v\n", community)
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)
hand, err := pc.IdentifyBestFiveCardPokerHand()
assert.Nil(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())
playerResults = append(playerResults, ph)
}
weirdOne := playerResults[7]
t.Logf("Weird one: %v\n", weirdOne)
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")
scoreShouldBe := ScoreTwoPair
scoreShouldBe += 10000 * TEN.Score()
scoreShouldBe += 100 * SEVEN.Score()
scoreShouldBe += NINE.Score()
assert.Equal(t, weirdOne.Score, scoreShouldBe)
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")
sp := cards.twoPairSmallestPair() // returns Rank, because describing a pair
assert.Equal(t, sp, SEVEN, "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")
}
func TestAceLowStraight(t *testing.T) { func TestAceLowStraight(t *testing.T) {
hand := Cards{ hand := Cards{
AceOfSpades(), AceOfSpades(),
@ -18,17 +78,18 @@ 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+100*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") s := hand.SortByRankAscending()
assert.True(t, hand.SortByRankAscending().Last().Rank == ACE, "Expected last card in sorted to be a ace") assert.True(t, s.First().Rank == DEUCE, "Expected first card to be a deuce")
assert.True(t, hand.SortByRankAscending().Second().Rank == THREE, "Expected second card to be a three") assert.True(t, s.Last().Rank == ACE, "Expected last card in sorted to be a ace")
assert.True(t, hand.SortByRankAscending().Third().Rank == FOUR, "Expected third card to be a four") assert.True(t, s.Second().Rank == THREE, "Expected second card to be a three")
assert.True(t, hand.SortByRankAscending().Fourth().Rank == FIVE, "Expected fourth card to be a five") assert.True(t, s.Third().Rank == FOUR, "Expected third card to be a four")
assert.True(t, hand.SortByRankAscending().Fifth().Rank == ACE, "Expected fifth card to be an ace") 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")
} }
func TestAceHighStraight(t *testing.T) { func TestAceHighStraight(t *testing.T) {
@ -66,12 +127,14 @@ func TestOtherStraight(t *testing.T) {
newDeck := NewDeckFromCards(hand) newDeck := NewDeckFromCards(hand)
newDeck.ShuffleDeterministically(123456789) newDeck.ShuffleDeterministically(123456789)
fmt.Printf("Shuffled deck: %s\n", newDeck.String()) //fmt.Printf("Shuffled deck: %s\n", newDeck.String())
fmt.Printf("new deck has %d cards\n", newDeck.Count()) //fmt.Printf("new deck has %d cards\n", newDeck.Count())
shuffledHand := newDeck.Deal(5) shuffledHand := newDeck.Deal(5)
assert.True(t, shuffledHand.containsStraight(), "Expected hand to still be a straight after shuffle") 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.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")
} }
func TestFlush(t *testing.T) { func TestFlush(t *testing.T) {
@ -85,21 +148,24 @@ func TestFlush(t *testing.T) {
assert.True(t, hand.containsFlush(), "Expected hand to be a flush") assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
newDeck := NewDeckFromCards(hand) newDeck := NewDeckFromCards(hand)
newDeck.ShuffleDeterministically(123456789) newDeck.ShuffleDeterministically(123456789)
fmt.Printf("Shuffled deck: %s\n", newDeck.String()) //fmt.Printf("Shuffled deck: %s\n", newDeck.String())
fmt.Printf("new deck has %d cards\n", newDeck.Count()) //fmt.Printf("new deck has %d cards\n", newDeck.Count())
shuffledHand := newDeck.Deal(5) shuffledHand := newDeck.Deal(5)
assert.True(t, shuffledHand.containsFlush(), "Expected hand to still be a flush after shuffle") assert.True(t, shuffledHand.containsFlush(), "Expected hand to still be a flush after shuffle")
// flush value is the sum of the ranks, just like high card
x := ScoreFlush x := ScoreFlush
var multiplier HandScore = 100 x += ACE.Score()
x += multiplier * DEUCE.Score() x += DEUCE.Score()
multiplier *= 100 x += THREE.Score()
x += multiplier * THREE.Score() x += FOUR.Score()
multiplier *= 100 x += SIX.Score()
x += multiplier * FOUR.Score() //fmt.Printf("a-2-3-4-6 flush score should be: %d\n", x)
multiplier *= 100
x += multiplier * SIX.Score() ph, err := shuffledHand.PokerHand()
fmt.Printf("a-2-3-4-6 flush score should be: %d\n", x) assert.Nil(t, err, "Expected no error")
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero 0")
assert.Equal(t, ph.Score, x)
} }
func TestStraightFlush(t *testing.T) { func TestStraightFlush(t *testing.T) {
@ -124,14 +190,43 @@ func TestStraightFlush(t *testing.T) {
nd := NewDeckFromCards(hand) nd := NewDeckFromCards(hand)
nd.ShuffleDeterministically(123456789) nd.ShuffleDeterministically(123456789)
fmt.Printf("Shuffled deck: %s\n", nd.String()) //fmt.Printf("Shuffled deck: %s\n", nd.String())
fmt.Printf("new deck has %d cards\n", nd.Count()) //fmt.Printf("new deck has %d cards\n", nd.Count())
shuffledHand := nd.Deal(5) shuffledHand := nd.Deal(5)
assert.True(t, shuffledHand.containsStraightFlush(), "Expected hand to still be a straight flush after shuffle") 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 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.True(t, shuffledHand.HighestRank() == SIX, "Expected highest rank to be a six after shuffle even with aces low")
} }
func TestFourOfAKind(t *testing.T) {
hand := Cards{
SixOfSpades(),
SixOfHearts(),
SixOfDiamonds(),
SixOfClubs(),
FiveOfSpades(),
}
assert.False(t, hand.containsStraight(), "Expected hand to not be a straight")
assert.False(t, hand.containsFlush(), "Expected hand to not be a flush")
assert.False(t, hand.containsStraightFlush(), "Expected hand to not be a straight flush")
assert.False(t, hand.containsRoyalFlush(), "Expected hand to not be a royal flush")
assert.True(t, hand.containsFourOfAKind(), "Expected hand to be four of a kind")
assert.False(t, hand.containsFullHouse(), "Expected hand to not be a full house")
// note that these are *expected* to be true. the contains* functions
// are used in the PokerHand.calculateScore method to determine the best hand
// and are checked in sequence of descending value, so if a hand is four of a kind
// it will not be checked for full house, three of a kind, etc.
// technically quads *is* two pair also, and a hand with quads does
// indeed contain three of a kind, and contains a pair.
assert.True(t, hand.containsThreeOfAKind(), "Expected hand to contain three of a kind")
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")
}
func TestRoyalFlush(t *testing.T) { func TestRoyalFlush(t *testing.T) {
hand := Cards{ hand := Cards{
TenOfSpades(), TenOfSpades(),
@ -197,6 +292,17 @@ func TestTwoPair(t *testing.T) {
assert.False(t, hand.isUnmadeHand(), "Expected hand to not be unmade") assert.False(t, hand.isUnmadeHand(), "Expected hand to not be unmade")
} }
func TestDetectDuplicates(t *testing.T) {
hand := Cards{
KingOfSpades(),
JackOfDiamonds(),
JackOfSpades(),
KingOfSpades(),
TenOfSpades(),
}
assert.True(t, hand.containsDuplicates(), "Expected hand to contain duplicates")
}
func TestHandScore(t *testing.T) { func TestHandScore(t *testing.T) {
hand := Cards{ hand := Cards{
KingOfSpades(), KingOfSpades(),
@ -211,8 +317,8 @@ func TestHandScore(t *testing.T) {
assert.True(t, ph.Score > 0, "Expected score to be nonzero 0") 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") assert.True(t, ph.Score < 100000000000000000, "Expected score to be less than 100000000000000000")
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 TestTwoPairBug(t *testing.T) { func TestTwoPairBug(t *testing.T) {
@ -220,12 +326,65 @@ func TestTwoPairBug(t *testing.T) {
c, err := NewCardsFromString("9♠,9♣,Q♥,Q♦,K♣") c, err := NewCardsFromString("9♠,9♣,Q♥,Q♦,K♣")
assert.Nil(t, err, "Expected no error") assert.Nil(t, err, "Expected no error")
//fmt.Printf("Cards: %v+\n", c)
ph, err := c.PokerHand() ph, err := c.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 nonzero 0")
desc := ph.Description() desc := ph.Description()
assert.Equal(t, desc, "two pair, queens and nines with a king") assert.Equal(t, desc, "two pair, queens and nines with a king")
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")
} }