Compare commits
16 Commits
eb85e1d36e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dbfc45ebb | |||
| e26b79751f | |||
| 6c66adf8d3 | |||
| ab92f29a88 | |||
| f135f480be | |||
| 0757f12dfa | |||
| 79cf2fd9aa | |||
| f527c6eb07 | |||
| f217a95e19 | |||
| de1eeb214e | |||
| 14ffbe4eb4 | |||
| 5f7dba942c | |||
| 11c5886951 | |||
| 39e2f5a268 | |||
| 5a2560051d | |||
| 336c0d953b |
42
.gitignore
vendored
42
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
24
Makefile
24
Makefile
@@ -1,4 +1,24 @@
|
|||||||
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/...
|
||||||
|
|
||||||
|
lint:
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
go fmt ./...
|
||||||
|
|||||||
173
card.go
173
card.go
@@ -5,8 +5,9 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/logrusorgru/aurora/v4"
|
aurora "github.com/logrusorgru/aurora/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Card struct {
|
type Card struct {
|
||||||
@@ -14,62 +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)
|
||||||
return Card{}, fmt.Errorf("Invalid card string %s", card)
|
if length != 2 {
|
||||||
|
return Card{}, fmt.Errorf("invalid card string %s, must be 2 characters", card)
|
||||||
}
|
}
|
||||||
rank := NewRankFromString(card[0:1])
|
|
||||||
suit := NewSuitFromString(card[1:2])
|
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) {
|
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
|
return Card{Rank: rank, Suit: suit}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,17 +72,17 @@ func NewCardsFromString(cards string) (Cards, error) {
|
|||||||
// supports a string like 9♠,9♣,Q♥,Q♦,K♣
|
// supports a string like 9♠,9♣,Q♥,Q♦,K♣
|
||||||
// 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
|
|
||||||
cardStrings := strings.Split(cards, ",")
|
cardStrings := strings.Split(cards, ",")
|
||||||
for _, cardString := range cardStrings {
|
newCards := make(Cards, len(cardStrings))
|
||||||
|
for i, cardString := range cardStrings {
|
||||||
card, err := NewCardFromString(cardString)
|
card, err := NewCardFromString(cardString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Cards{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newCards = append(newCards, card)
|
newCards[i] = card
|
||||||
}
|
}
|
||||||
if len(newCards) == 0 {
|
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
|
return newCards, nil
|
||||||
}
|
}
|
||||||
@@ -98,42 +93,41 @@ func (c *Card) String() string {
|
|||||||
|
|
||||||
type Cards []Card
|
type Cards []Card
|
||||||
|
|
||||||
func (c Cards) First() Card {
|
func (cards Cards) First() Card {
|
||||||
return c[0]
|
return cards[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cards) Second() Card {
|
func (cards Cards) Second() Card {
|
||||||
return c[1]
|
return cards[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cards) Third() Card {
|
func (cards Cards) Third() Card {
|
||||||
return c[2]
|
return cards[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cards) Fourth() Card {
|
func (cards Cards) Fourth() Card {
|
||||||
return c[3]
|
return cards[3]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cards) Fifth() Card {
|
func (cards Cards) Fifth() Card {
|
||||||
return c[4]
|
return cards[4]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cards) Last() Card {
|
func (cards Cards) Last() Card {
|
||||||
return c[len(c)-1]
|
return cards[len(cards)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cards) SortByRankAscending() Cards {
|
func (cards Cards) SortByRankAscending() Cards {
|
||||||
newCards := make(Cards, len(c))
|
sortedCards := make(Cards, len(cards))
|
||||||
copy(newCards, c)
|
copy(sortedCards, cards)
|
||||||
sort.Slice(newCards, func(i, j int) bool {
|
sort.Slice(sortedCards, func(i, j int) bool {
|
||||||
return newCards[i].Rank.Score() < newCards[j].Rank.Score()
|
return sortedCards[i].Rank.Score() < sortedCards[j].Rank.Score()
|
||||||
})
|
})
|
||||||
|
return sortedCards
|
||||||
return newCards
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cards) PrintToTerminal() {
|
func (cards Cards) PrintToTerminal() {
|
||||||
fmt.Printf("%s", c.FormatForTerminal())
|
fmt.Printf("%s", cards.FormatForTerminal())
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortOrder int
|
type SortOrder int
|
||||||
@@ -143,18 +137,18 @@ const (
|
|||||||
AceHighDescending
|
AceHighDescending
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c Cards) FormatForTerminalSorted(order SortOrder) string {
|
func (cards Cards) FormatForTerminalSorted(order SortOrder) string {
|
||||||
sorted := c.SortByRankAscending() // this is ascending
|
sorted := cards.SortByRankAscending() // this is ascending
|
||||||
if order == AceHighDescending {
|
if order == AceHighDescending {
|
||||||
slices.Reverse(sorted)
|
slices.Reverse(sorted)
|
||||||
}
|
}
|
||||||
return sorted.FormatForTerminal()
|
return sorted.FormatForTerminal()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cards) FormatForTerminal() string {
|
func (cards Cards) FormatForTerminal() string {
|
||||||
var cardstrings []string
|
var cardstrings []string
|
||||||
for i := 0; i < len(c); i++ {
|
for i := 0; i < len(cards); i++ {
|
||||||
cardstrings = append(cardstrings, c[i].FormatForTerminal())
|
cardstrings = append(cardstrings, cards[i].FormatForTerminal())
|
||||||
}
|
}
|
||||||
return strings.Join(cardstrings, ",")
|
return strings.Join(cardstrings, ",")
|
||||||
}
|
}
|
||||||
@@ -179,16 +173,15 @@ func (c Card) FormatForTerminal() string {
|
|||||||
return fmt.Sprintf("%s%s", rank, suit)
|
return fmt.Sprintf("%s%s", rank, suit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cards) HighestRank() Rank {
|
func (cards Cards) HighestRank() Rank {
|
||||||
sorted := c.SortByRankAscending()
|
sorted := cards.SortByRankAscending()
|
||||||
return sorted[len(sorted)-1].Rank
|
return sorted[len(sorted)-1].Rank
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Cards) String() (output string) {
|
func (cards Cards) String() string {
|
||||||
var cardstrings []string
|
var cardstrings []string
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(cards); i++ {
|
||||||
cardstrings = append(cardstrings, s[i].String())
|
cardstrings = append(cardstrings, cards[i].String())
|
||||||
}
|
}
|
||||||
output = strings.Join(cardstrings, ",")
|
return strings.Join(cardstrings, ",")
|
||||||
return output
|
|
||||||
}
|
}
|
||||||
|
|||||||
119
examples/flip/main.go
Normal file
119
examples/flip/main.go
Normal 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
155
examples/montecarlo/main.go
Normal 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
|
||||||
|
}
|
||||||
Binary file not shown.
56
examples/sf/main.go
Normal file
56
examples/sf/main.go
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
170
examples/simgame/main.go
Normal file
170
examples/simgame/main.go
Normal 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")
|
||||||
|
}
|
||||||
@@ -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.
@@ -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)
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -1,17 +1,23 @@
|
|||||||
module git.eeqj.de/sneak/pokercore
|
module sneak.berlin/go/pokercore
|
||||||
|
|
||||||
go 1.22.2
|
go 1.22.2
|
||||||
|
|
||||||
require (
|
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/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
|
sneak.berlin/go/timingbench v0.0.0-20240522212031-a6243a470213
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
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/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // 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
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/term v0.20.0 // indirect
|
||||||
|
gonum.org/v1/gonum v0.15.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
30
go.sum
30
go.sum
@@ -1,25 +1,45 @@
|
|||||||
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/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||||
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-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/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/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=
|
||||||
|
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
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=
|
||||||
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-20220715151400-c0bba94af5f8/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.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=
|
||||||
|
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
|
||||||
|
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
|
||||||
|
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
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=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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=
|
||||||
|
|||||||
125
handhelpers.go
125
handhelpers.go
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
83
perf_test.go
Normal file
83
perf_test.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package pokercore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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.
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package pokercore
|
package pokercore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -13,14 +12,15 @@ type ShuffleTestResults []struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPokerDeck(t *testing.T) {
|
func TestPokerDeck(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
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()
|
||||||
@@ -36,6 +36,7 @@ func TestPokerDeck(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDealing(t *testing.T) {
|
func TestDealing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
d := NewDeckFromCards(Cards{
|
d := NewDeckFromCards(Cards{
|
||||||
Card{Rank: ACE, Suit: HEART},
|
Card{Rank: ACE, Suit: HEART},
|
||||||
Card{Rank: DEUCE, Suit: HEART},
|
Card{Rank: DEUCE, Suit: HEART},
|
||||||
@@ -51,6 +52,7 @@ func TestDealing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSpecialCaseOfFiveHighStraightFlush(t *testing.T) {
|
func TestSpecialCaseOfFiveHighStraightFlush(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// actual bug from first implementation
|
// actual bug from first implementation
|
||||||
d := NewDeckFromCards(Cards{
|
d := NewDeckFromCards(Cards{
|
||||||
Card{Rank: ACE, Suit: HEART},
|
Card{Rank: ACE, Suit: HEART},
|
||||||
@@ -69,6 +71,7 @@ func TestSpecialCaseOfFiveHighStraightFlush(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSpecialCaseOfFiveHighStraight(t *testing.T) {
|
func TestSpecialCaseOfFiveHighStraight(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// actual bug from first implementation
|
// actual bug from first implementation
|
||||||
d := NewDeckFromCards(Cards{
|
d := NewDeckFromCards(Cards{
|
||||||
Card{Rank: ACE, Suit: HEART},
|
Card{Rank: ACE, Suit: HEART},
|
||||||
|
|||||||
184
pokerhand.go
184
pokerhand.go
@@ -1,6 +1,9 @@
|
|||||||
package pokercore
|
package pokercore
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type PokerHandType int
|
type PokerHandType int
|
||||||
|
|
||||||
@@ -28,102 +31,44 @@ 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 {
|
||||||
if ph.Score > other.Score {
|
if ph.Score > other.Score {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -133,75 +78,74 @@ func (ph PokerHand) Compare(other PokerHand) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ph PokerHand) HighestRank() Rank {
|
func (ph *PokerHand) HighestRank() Rank {
|
||||||
return ph.Hand.HighestRank()
|
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())
|
return fmt.Sprintf("<PokerHand: %s (%s)>", ph.Hand.String(), ph.Description())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c PokerHand) Description() string {
|
func (ph *PokerHand) Description() string {
|
||||||
sortedHand := c.Hand.SortByRankAscending()
|
if ph.Type == RoyalFlush {
|
||||||
if c.Type == RoyalFlush {
|
return fmt.Sprintf("a royal flush in %s", ph.Hand[0].Suit)
|
||||||
return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit)
|
|
||||||
}
|
}
|
||||||
if c.Hand.containsStraightFlush() {
|
if ph.Hand.containsStraightFlush() {
|
||||||
if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE {
|
if ph.Hand[3].Rank == FIVE && ph.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", ph.Hand[3].Rank.WithArticle(), ph.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", ph.HighestRank().WithArticle(), ph.Hand[4].Suit)
|
||||||
}
|
}
|
||||||
if c.Hand.containsFourOfAKind() {
|
if ph.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", ph.Hand.fourOfAKindRank().Pluralize(), ph.Hand.fourOfAKindKicker().Rank.WithArticle())
|
||||||
}
|
}
|
||||||
if c.Hand.containsFullHouse() {
|
if ph.Hand.containsFullHouse() {
|
||||||
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", ph.Hand.fullHouseTripsRank().Pluralize(), ph.Hand.fullHousePairRank().Pluralize())
|
||||||
}
|
}
|
||||||
if c.Hand.containsFlush() {
|
if ph.Hand.containsFlush() {
|
||||||
return fmt.Sprintf("%s high flush in %s", c.HighestRank().WithArticle(), sortedHand[4].Suit)
|
return fmt.Sprintf("%s high flush in %s", ph.HighestRank().WithArticle(), ph.Hand[4].Suit)
|
||||||
}
|
}
|
||||||
if c.Hand.containsStraight() {
|
if ph.Hand.containsStraight() {
|
||||||
if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE {
|
if ph.Hand[3].Rank == FIVE && ph.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", 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(
|
return fmt.Sprintf(
|
||||||
"three %s with %s and %s",
|
"three %s with %s and %s",
|
||||||
c.Hand.threeOfAKindTripsRank().Pluralize(),
|
ph.Hand.threeOfAKindTripsRank().Pluralize(),
|
||||||
c.Hand.threeOfAKindFirstKicker().Rank.WithArticle(),
|
ph.Hand.threeOfAKindFirstKicker().Rank.WithArticle(),
|
||||||
c.Hand.threeOfAKindSecondKicker().Rank.WithArticle(),
|
ph.Hand.threeOfAKindSecondKicker().Rank.WithArticle(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if c.Hand.containsTwoPair() {
|
if ph.Hand.containsTwoPair() {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"two pair, %s and %s with %s",
|
"two pair, %s and %s with %s",
|
||||||
c.Hand.twoPairBiggestPair().Pluralize(),
|
ph.Hand.twoPairBiggestPair().Pluralize(),
|
||||||
c.Hand.twoPairSmallestPair().Pluralize(),
|
ph.Hand.twoPairSmallestPair().Pluralize(),
|
||||||
c.Hand.twoPairKicker().Rank.WithArticle(),
|
ph.Hand.twoPairKicker().Rank.WithArticle(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if c.Hand.containsPair() {
|
if ph.Hand.containsPair() {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"a pair of %s with %s, %s, and %s",
|
"a pair of %s with %s, %s, and %s",
|
||||||
c.Hand.pairRank().Pluralize(),
|
ph.Hand.pairRank().Pluralize(),
|
||||||
c.Hand.pairFirstKicker().Rank.WithArticle(),
|
ph.Hand.pairFirstKicker().Rank.WithArticle(),
|
||||||
c.Hand.pairSecondKicker().Rank.WithArticle(),
|
ph.Hand.pairSecondKicker().Rank.WithArticle(),
|
||||||
c.Hand.pairThirdKicker().Rank.WithArticle(),
|
ph.Hand.pairThirdKicker().Rank.WithArticle(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
ph.Hand[4].Rank,
|
||||||
sortedHand[3].Rank.WithArticle(),
|
ph.Hand[3].Rank.WithArticle(),
|
||||||
sortedHand[2].Rank.WithArticle(),
|
ph.Hand[2].Rank.WithArticle(),
|
||||||
sortedHand[1].Rank.WithArticle(),
|
ph.Hand[1].Rank.WithArticle(),
|
||||||
sortedHand[0].Rank.WithArticle(),
|
ph.Hand[0].Rank.WithArticle(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
120
scoring.go
120
scoring.go
@@ -1,6 +1,8 @@
|
|||||||
package pokercore
|
package pokercore
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type HandScore int
|
type HandScore int
|
||||||
|
|
||||||
@@ -22,18 +24,122 @@ 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()
|
||||||
}
|
}
|
||||||
|
|||||||
562
scoring_test.go
562
scoring_test.go
@@ -1,231 +1,423 @@
|
|||||||
package pokercore
|
package pokercore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAceLowStraight(t *testing.T) {
|
func TestHandDescriptionBug(t *testing.T) {
|
||||||
hand := Cards{
|
t.Parallel()
|
||||||
AceOfSpades(),
|
playerCount := 8
|
||||||
DeuceOfHearts(),
|
d := NewDeck()
|
||||||
ThreeOfDiamonds(),
|
d.ShuffleDeterministically(1337)
|
||||||
FourOfClubs(),
|
players := make([]*Cards, playerCount)
|
||||||
FiveOfSpades(),
|
for i := 0; i < playerCount; i++ {
|
||||||
|
c := d.Deal(2)
|
||||||
|
players[i] = &c
|
||||||
|
t.Logf("Player %d dealt: %+v\n", i+1, c)
|
||||||
}
|
}
|
||||||
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
t.Logf("Players: %+v\n", players)
|
||||||
ph, err := hand.PokerHand()
|
|
||||||
assert.Nil(t, err, "Expected no error")
|
community := d.Deal(5)
|
||||||
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero 0")
|
t.Logf("Community: %+v\n", community)
|
||||||
assert.Less(t, ph.Score, 100000000000000000, "Expected score to be less than 100000000000000000")
|
|
||||||
assert.Equal(t, ph.Score, ScoreStraight+100*FIVE.Score())
|
var playerResults []*PokerHand
|
||||||
assert.Equal(t, ph.Description(), "a five high straight")
|
|
||||||
assert.True(t, hand.HighestRank() == ACE, "Expected highest rank to be an ace")
|
for i := 0; i < playerCount; i++ {
|
||||||
assert.True(t, hand.SortByRankAscending().First().Rank == DEUCE, "Expected first card to be a deuce")
|
t.Logf("Player %d hole cards: %+v\n", i+1, *players[i])
|
||||||
assert.True(t, hand.SortByRankAscending().Last().Rank == ACE, "Expected last card in sorted to be a ace")
|
pc := append(*players[i], community...)
|
||||||
assert.True(t, hand.SortByRankAscending().Second().Rank == THREE, "Expected second card to be a three")
|
t.Logf("Player %d cards available: %+v\n", i+1, pc)
|
||||||
assert.True(t, hand.SortByRankAscending().Third().Rank == FOUR, "Expected third card to be a four")
|
hand, err := pc.IdentifyBestFiveCardPokerHand()
|
||||||
assert.True(t, hand.SortByRankAscending().Fourth().Rank == FIVE, "Expected fourth card to be a five")
|
assert.NoError(t, err, "Expected no error")
|
||||||
assert.True(t, hand.SortByRankAscending().Fifth().Rank == ACE, "Expected fifth card to be an ace")
|
ph, err := hand.PokerHand()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, "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, 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, TEN, bp, "Expected biggest pair to be a ten")
|
||||||
|
|
||||||
|
sp := cards.twoPairSmallestPair() // returns Rank, because describing a pair
|
||||||
|
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, 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(),
|
||||||
|
ThreeOfDiamonds(),
|
||||||
|
FourOfClubs(),
|
||||||
|
FiveOfSpades(),
|
||||||
|
}
|
||||||
|
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
||||||
|
ph, err := hand.PokerHand()
|
||||||
|
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, "a five high straight", ph.Description())
|
||||||
|
assert.Equal(t, ACE, hand.HighestRank(), "Expected highest rank to be an ace")
|
||||||
|
s := hand.SortByRankAscending()
|
||||||
|
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) {
|
func TestAceHighStraight(t *testing.T) {
|
||||||
hand := Cards{
|
t.Parallel()
|
||||||
TenOfSpades(),
|
t.Run("Test Ace-High Straight", func(t *testing.T) {
|
||||||
JackOfHearts(),
|
hand := Cards{
|
||||||
KingOfClubs(),
|
TenOfSpades(),
|
||||||
AceOfSpades(),
|
JackOfHearts(),
|
||||||
QueenOfDiamonds(),
|
KingOfClubs(),
|
||||||
}
|
AceOfSpades(),
|
||||||
|
QueenOfDiamonds(),
|
||||||
|
}
|
||||||
|
|
||||||
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
||||||
newDeck := NewDeckFromCards(hand)
|
newDeck := NewDeckFromCards(hand)
|
||||||
newDeck.ShuffleDeterministically(123456789)
|
newDeck.ShuffleDeterministically(123456789)
|
||||||
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.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()
|
sortedHand := shuffledHand.SortByRankAscending()
|
||||||
assert.True(t, sortedHand[0].Rank == TEN, "Expected lowest rank to be a ten")
|
assert.Equal(t, TEN, sortedHand[0].Rank, "Expected lowest rank to be a ten")
|
||||||
assert.True(t, sortedHand[1].Rank == JACK, "Expected second lowest rank to be a jack")
|
assert.Equal(t, JACK, sortedHand[1].Rank, "Expected second lowest rank to be a jack")
|
||||||
assert.True(t, sortedHand[2].Rank == QUEEN, "Expected third lowest rank to be a queen")
|
assert.Equal(t, QUEEN, sortedHand[2].Rank, "Expected third lowest rank to be a queen")
|
||||||
assert.True(t, sortedHand[3].Rank == KING, "Expected fourth lowest rank to be a king")
|
assert.Equal(t, KING, sortedHand[3].Rank, "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, ACE, sortedHand[4].Rank, "Expected highest rank to be an ace")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOtherStraight(t *testing.T) {
|
func TestOtherStraight(t *testing.T) {
|
||||||
hand := Cards{
|
t.Parallel()
|
||||||
DeuceOfSpades(),
|
t.Run("Test Other Straight", func(t *testing.T) {
|
||||||
ThreeOfHearts(),
|
hand := Cards{
|
||||||
FourOfDiamonds(),
|
DeuceOfSpades(),
|
||||||
FiveOfClubs(),
|
ThreeOfHearts(),
|
||||||
SixOfSpades(),
|
FourOfDiamonds(),
|
||||||
}
|
FiveOfClubs(),
|
||||||
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
SixOfSpades(),
|
||||||
|
}
|
||||||
newDeck := NewDeckFromCards(hand)
|
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
||||||
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")
|
|
||||||
|
|
||||||
|
newDeck := NewDeckFromCards(hand)
|
||||||
|
newDeck.ShuffleDeterministically(123456789)
|
||||||
|
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.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) {
|
func TestFlush(t *testing.T) {
|
||||||
hand := Cards{
|
t.Parallel()
|
||||||
AceOfSpades(),
|
t.Run("Test Flush", func(t *testing.T) {
|
||||||
DeuceOfSpades(),
|
hand := Cards{
|
||||||
ThreeOfSpades(),
|
AceOfSpades(),
|
||||||
FourOfSpades(),
|
DeuceOfSpades(),
|
||||||
SixOfSpades(),
|
ThreeOfSpades(),
|
||||||
}
|
FourOfSpades(),
|
||||||
assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
|
SixOfSpades(),
|
||||||
newDeck := NewDeckFromCards(hand)
|
}
|
||||||
newDeck.ShuffleDeterministically(123456789)
|
assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
|
||||||
fmt.Printf("Shuffled deck: %s\n", newDeck.String())
|
newDeck := NewDeckFromCards(hand)
|
||||||
fmt.Printf("new deck has %d cards\n", newDeck.Count())
|
newDeck.ShuffleDeterministically(123456789)
|
||||||
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")
|
||||||
|
|
||||||
x := ScoreFlush
|
// flush value is the sum of the ranks, just like high card
|
||||||
var multiplier HandScore = 100
|
x := ScoreFlush
|
||||||
x += multiplier * DEUCE.Score()
|
x += ACE.Score()
|
||||||
multiplier *= 100
|
x += DEUCE.Score()
|
||||||
x += multiplier * THREE.Score()
|
x += THREE.Score()
|
||||||
multiplier *= 100
|
x += FOUR.Score()
|
||||||
x += multiplier * FOUR.Score()
|
x += SIX.Score()
|
||||||
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.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) {
|
func TestStraightFlush(t *testing.T) {
|
||||||
hand := Cards{
|
t.Parallel()
|
||||||
SixOfSpades(),
|
t.Run("Test Straight Flush", func(t *testing.T) {
|
||||||
DeuceOfSpades(),
|
hand := Cards{
|
||||||
ThreeOfSpades(),
|
SixOfSpades(),
|
||||||
FourOfSpades(),
|
DeuceOfSpades(),
|
||||||
FiveOfSpades(),
|
ThreeOfSpades(),
|
||||||
}
|
FourOfSpades(),
|
||||||
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
FiveOfSpades(),
|
||||||
assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
|
}
|
||||||
assert.True(t, hand.containsStraightFlush(), "Expected hand to be a straight flush")
|
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
||||||
assert.False(t, hand.containsRoyalFlush(), "Expected hand to not be a royal flush")
|
assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
|
||||||
assert.False(t, hand.containsFourOfAKind(), "Expected hand to not be four of a kind")
|
assert.True(t, hand.containsStraightFlush(), "Expected hand to be a straight flush")
|
||||||
assert.False(t, hand.containsFullHouse(), "Expected hand to not be a full house")
|
assert.False(t, hand.containsRoyalFlush(), "Expected hand to not be a royal flush")
|
||||||
assert.False(t, hand.containsThreeOfAKind(), "Expected hand to not be three of a kind")
|
assert.False(t, hand.containsFourOfAKind(), "Expected hand to not be four of a kind")
|
||||||
assert.False(t, hand.containsTwoPair(), "Expected hand to not be two pair")
|
assert.False(t, hand.containsFullHouse(), "Expected hand to not be a full house")
|
||||||
assert.False(t, hand.containsPair(), "Expected hand to not be a pair")
|
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() == 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 := NewDeckFromCards(hand)
|
||||||
nd.ShuffleDeterministically(123456789)
|
nd.ShuffleDeterministically(123456789)
|
||||||
fmt.Printf("Shuffled deck: %s\n", nd.String())
|
shuffledHand := nd.Deal(5)
|
||||||
fmt.Printf("new deck has %d cards\n", nd.Count())
|
assert.True(t, shuffledHand.containsStraightFlush(), "Expected hand to still be a straight flush after shuffle")
|
||||||
shuffledHand := nd.Deal(5)
|
assert.Equal(t, SIX, shuffledHand.HighestRank(), "Expected highest rank to still be a six after shuffle")
|
||||||
assert.True(t, shuffledHand.containsStraightFlush(), "Expected hand to still be a straight flush after shuffle")
|
assert.Equal(t, SIX, shuffledHand.HighestRank(), "Expected highest rank to be a six after shuffle even with aces low")
|
||||||
assert.True(t, shuffledHand.HighestRank() == SIX, "Expected highest rank to still be a six after shuffle")
|
})
|
||||||
assert.True(t, shuffledHand.HighestRank() == SIX, "Expected highest rank to be a six after shuffle even with aces low")
|
}
|
||||||
|
|
||||||
|
func TestFourOfAKind(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("Test Four of a Kind", func(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.Equal(t, SIX, hand.HighestRank(), "Expected highest rank to be a six")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoyalFlush(t *testing.T) {
|
func TestRoyalFlush(t *testing.T) {
|
||||||
hand := Cards{
|
t.Parallel()
|
||||||
TenOfSpades(),
|
t.Run("Test Royal Flush", func(t *testing.T) {
|
||||||
JackOfSpades(),
|
hand := Cards{
|
||||||
QueenOfSpades(),
|
TenOfSpades(),
|
||||||
KingOfSpades(),
|
JackOfSpades(),
|
||||||
AceOfSpades(),
|
QueenOfSpades(),
|
||||||
}
|
KingOfSpades(),
|
||||||
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
AceOfSpades(),
|
||||||
assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
|
}
|
||||||
assert.True(t, hand.containsStraightFlush(), "Expected hand to be a straight flush")
|
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
||||||
assert.True(t, hand.containsRoyalFlush(), "Expected hand to be a royal flush")
|
assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
|
||||||
|
assert.True(t, hand.containsStraightFlush(), "Expected hand to be a straight flush")
|
||||||
|
assert.True(t, hand.containsRoyalFlush(), "Expected hand to be a royal flush")
|
||||||
|
|
||||||
assert.False(t, hand.containsFourOfAKind(), "Expected hand to not be four of a kind")
|
assert.False(t, hand.containsFourOfAKind(), "Expected hand to not be four of a kind")
|
||||||
assert.False(t, hand.containsFullHouse(), "Expected hand to not be a full house")
|
assert.False(t, hand.containsFullHouse(), "Expected hand to not be a full house")
|
||||||
assert.False(t, hand.containsThreeOfAKind(), "Expected hand to not be three of a kind")
|
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.containsTwoPair(), "Expected hand to not be two pair")
|
||||||
assert.False(t, hand.containsPair(), "Expected hand to not be a 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.Equal(t, ACE, hand.HighestRank(), "Expected highest rank to be an ace")
|
||||||
assert.False(t, hand.HighestRank() == TEN, "Expected highest rank to not be an ace")
|
assert.NotEqual(t, TEN, hand.HighestRank(), "Expected highest rank to not be a ten")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmadeHand(t *testing.T) {
|
func TestUnmadeHand(t *testing.T) {
|
||||||
hand := Cards{
|
t.Parallel()
|
||||||
TenOfSpades(),
|
t.Run("Test Unmade Hand", func(t *testing.T) {
|
||||||
JackOfDiamonds(),
|
hand := Cards{
|
||||||
QueenOfSpades(),
|
TenOfSpades(),
|
||||||
KingOfSpades(),
|
JackOfDiamonds(),
|
||||||
DeuceOfSpades(),
|
QueenOfSpades(),
|
||||||
}
|
KingOfSpades(),
|
||||||
assert.False(t, hand.containsStraight(), "Expected hand to not be a straight")
|
DeuceOfSpades(),
|
||||||
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.containsStraight(), "Expected hand to not be a straight")
|
||||||
assert.False(t, hand.containsRoyalFlush(), "Expected hand to not be a royal flush")
|
assert.False(t, hand.containsFlush(), "Expected hand to not be a flush")
|
||||||
assert.False(t, hand.containsFourOfAKind(), "Expected hand to not be four of a kind")
|
assert.False(t, hand.containsStraightFlush(), "Expected hand to not be a straight flush")
|
||||||
assert.False(t, hand.containsFullHouse(), "Expected hand to not be a full house")
|
assert.False(t, hand.containsRoyalFlush(), "Expected hand to not be a royal flush")
|
||||||
assert.False(t, hand.containsThreeOfAKind(), "Expected hand to not be three of a kind")
|
assert.False(t, hand.containsFourOfAKind(), "Expected hand to not be four of a kind")
|
||||||
assert.False(t, hand.containsTwoPair(), "Expected hand to not be two pair")
|
assert.False(t, hand.containsFullHouse(), "Expected hand to not be a full house")
|
||||||
assert.False(t, hand.containsPair(), "Expected hand to not be a pair")
|
assert.False(t, hand.containsThreeOfAKind(), "Expected hand to not be three of a kind")
|
||||||
assert.True(t, hand.HighestRank() == KING, "Expected highest rank to be a king")
|
assert.False(t, hand.containsTwoPair(), "Expected hand to not be two pair")
|
||||||
assert.True(t, hand.isUnmadeHand(), "Expected hand to be unmade")
|
assert.False(t, hand.containsPair(), "Expected hand to not be a pair")
|
||||||
|
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) {
|
func TestTwoPair(t *testing.T) {
|
||||||
hand := Cards{
|
t.Parallel()
|
||||||
KingOfSpades(),
|
t.Run("Test Two Pair", func(t *testing.T) {
|
||||||
JackOfDiamonds(),
|
hand := Cards{
|
||||||
JackOfSpades(),
|
KingOfSpades(),
|
||||||
KingOfDiamonds(),
|
JackOfDiamonds(),
|
||||||
TenOfSpades(),
|
JackOfSpades(),
|
||||||
}
|
KingOfDiamonds(),
|
||||||
assert.False(t, hand.containsStraight(), "Expected hand to not be a straight")
|
TenOfSpades(),
|
||||||
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.containsStraight(), "Expected hand to not be a straight")
|
||||||
assert.False(t, hand.containsRoyalFlush(), "Expected hand to not be a royal flush")
|
assert.False(t, hand.containsFlush(), "Expected hand to not be a flush")
|
||||||
assert.False(t, hand.containsFourOfAKind(), "Expected hand to not be four of a kind")
|
assert.False(t, hand.containsStraightFlush(), "Expected hand to not be a straight flush")
|
||||||
assert.False(t, hand.containsFullHouse(), "Expected hand to not be a full house")
|
assert.False(t, hand.containsRoyalFlush(), "Expected hand to not be a royal flush")
|
||||||
assert.False(t, hand.containsThreeOfAKind(), "Expected hand to not be three of a kind")
|
assert.False(t, hand.containsFourOfAKind(), "Expected hand to not be four of a kind")
|
||||||
assert.True(t, hand.containsTwoPair(), "Expected hand to be two pair")
|
assert.False(t, hand.containsFullHouse(), "Expected hand to not be a full house")
|
||||||
assert.True(t, hand.containsPair(), "Expected hand to also be a pair")
|
assert.False(t, hand.containsThreeOfAKind(), "Expected hand to not be three of a kind")
|
||||||
assert.True(t, hand.HighestRank() == KING, "Expected highest rank to be a king")
|
assert.True(t, hand.containsTwoPair(), "Expected hand to be two pair")
|
||||||
assert.False(t, hand.isUnmadeHand(), "Expected hand to not be unmade")
|
assert.True(t, hand.containsPair(), "Expected hand to also be a pair")
|
||||||
|
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(),
|
||||||
|
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{
|
t.Parallel()
|
||||||
KingOfSpades(),
|
t.Run("Test Hand Score", func(t *testing.T) {
|
||||||
JackOfDiamonds(),
|
hand := Cards{
|
||||||
JackOfSpades(),
|
KingOfSpades(),
|
||||||
KingOfDiamonds(),
|
JackOfDiamonds(),
|
||||||
TenOfSpades(),
|
JackOfSpades(),
|
||||||
}
|
KingOfDiamonds(),
|
||||||
|
TenOfSpades(),
|
||||||
|
}
|
||||||
|
|
||||||
ph, error := hand.PokerHand()
|
ph, err := hand.PokerHand()
|
||||||
assert.Nil(t, error, "Expected no error")
|
assert.NoError(t, err, "Expected no error")
|
||||||
assert.True(t, ph.Score > 0, "Expected score to be nonzero 0")
|
assert.True(t, ph.Score > 0, "Expected score to be nonzero")
|
||||||
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")
|
||||||
|
// write more assertions FIXME
|
||||||
fmt.Printf("PokerHand: %v+\n", ph)
|
})
|
||||||
fmt.Printf("PH score: %d\n", ph.Score)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTwoPairBug(t *testing.T) {
|
func TestTwoPairBug(t *testing.T) {
|
||||||
// this is an actual bug in the first implementation
|
t.Parallel()
|
||||||
c, err := NewCardsFromString("9♠,9♣,Q♥,Q♦,K♣")
|
t.Run("Test Two Pair Bug", func(t *testing.T) {
|
||||||
assert.Nil(t, err, "Expected no error")
|
// this is an actual bug in the first implementation
|
||||||
|
c, err := NewCardsFromString("9♠,9♣,Q♥,Q♦,K♣")
|
||||||
|
assert.NoError(t, err, "Expected no error")
|
||||||
|
|
||||||
ph, err := c.PokerHand()
|
ph, err := c.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 nonzero 0")
|
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero")
|
||||||
desc := ph.Description()
|
desc := ph.Description()
|
||||||
assert.Equal(t, desc, "two pair, queens and nines with a king")
|
assert.Equal(t, "two pair, queens and nines with a king", desc)
|
||||||
|
})
|
||||||
fmt.Printf("PokerHand: %v+\n", ph)
|
}
|
||||||
fmt.Printf("PH score: %d\n", ph.Score)
|
|
||||||
|
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(),
|
||||||
|
DeuceOfDiamonds(),
|
||||||
|
DeuceOfClubs(),
|
||||||
|
AceOfSpades(),
|
||||||
|
}
|
||||||
|
|
||||||
|
handB := Cards{
|
||||||
|
ThreeOfSpades(),
|
||||||
|
ThreeOfHearts(),
|
||||||
|
ThreeOfDiamonds(),
|
||||||
|
ThreeOfClubs(),
|
||||||
|
DeuceOfSpades(),
|
||||||
|
}
|
||||||
|
|
||||||
|
phA, err := handA.PokerHand()
|
||||||
|
assert.NoError(t, err, "Expected no error")
|
||||||
|
phB, err := handB.PokerHand()
|
||||||
|
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(),
|
||||||
|
DeuceOfDiamonds(),
|
||||||
|
AceOfSpades(),
|
||||||
|
AceOfHearts(),
|
||||||
|
}
|
||||||
|
|
||||||
|
phA, err := handA.PokerHand()
|
||||||
|
assert.NoError(t, err, "Expected no error")
|
||||||
|
deucesFullOfAcesScore := phA.Score
|
||||||
|
|
||||||
|
handB := Cards{
|
||||||
|
ThreeOfSpades(),
|
||||||
|
ThreeOfHearts(),
|
||||||
|
ThreeOfDiamonds(),
|
||||||
|
DeuceOfSpades(),
|
||||||
|
DeuceOfHearts(),
|
||||||
|
}
|
||||||
|
phB, err := handB.PokerHand()
|
||||||
|
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")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user