Merge branch 'master' of github.com:sneak/gopoker

This commit is contained in:
Jeffrey Paul 2019-03-23 21:13:06 -07:00 committed by sneak
commit 3dc63db70c
13 changed files with 773 additions and 172 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
.DS_Store

View File

@ -1,4 +1,4 @@
default: test default: test
test: test:
cd pokercore && make test cd pokercore && go test -v ./...

17
go.mod Normal file
View File

@ -0,0 +1,17 @@
module git.eeqj.de/sneak/go-poker
go 1.22.2
require (
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/logrusorgru/aurora/v4 v4.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

25
go.sum Normal file
View File

@ -0,0 +1,25 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -3,14 +3,14 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
log "github.com/sirupsen/logrus"
"github.com/sneak/gopoker/pokercore"
"io" "io"
"net/http" "net/http"
"net/rpc" "net/rpc"
"net/rpc/jsonrpc" "net/rpc/jsonrpc"
"os" "os"
"time" "time"
log "github.com/sirupsen/logrus"
) )
type JSONRPCServer struct { type JSONRPCServer struct {

View File

@ -1,10 +1,18 @@
package main package main
import "fmt" import (
import "github.com/sneak/gopoker/pokercore" "fmt"
"git.eeqj.de/sneak/go-poker/pokercore"
)
func main() { func main() {
myDeck := pokercore.NewDeck() myDeck := pokercore.NewDeck()
myDeck.ShuffleRandomly() myDeck.ShuffleRandomly()
fmt.Println("%s", myDeck.Cards) a := myDeck.Deal(5)
b := myDeck.Deal(7)
a.PrintToTerminal()
fmt.Printf("\n%#v\n", a)
b.PrintToTerminal()
fmt.Printf("\n%#v\n", b)
} }

View File

@ -1,10 +0,0 @@
default: test
.PHONY: pkgs test
fetch:
go get -t
test: *.go
go test -v

94
pokercore/deck.go Normal file
View File

@ -0,0 +1,94 @@
package pokercore
import (
"fmt"
rand "math/rand"
"sync"
)
type Deck struct {
mu sync.Mutex
Cards Cards
ShuffleSeedVal int64
Dealt int
}
func NewDeckFromCards(cards Cards) *Deck {
d := new(Deck)
d.Cards = make([]Card, len(cards))
copy(d.Cards, cards)
return d
}
func NewEmptyDeck() *Deck {
d := new(Deck)
d.Cards = make([]Card, 0)
return d
}
// NewDeck returns a new deck of 52 sorted cards.
func NewDeck() *Deck {
d := NewEmptyDeck()
ranks := []Rank{
ACE, DEUCE, THREE, FOUR, FIVE,
SIX, SEVEN, EIGHT, NINE, TEN, JACK,
QUEEN, KING}
// This is the suit order used by dealers at
// The Golden Nugget in Las Vegas.
suits := []Suit{SPADE, HEART, DIAMOND, CLUB}
for _, s := range suits {
for _, r := range ranks {
d.AddCard(Card{Rank: r, Suit: s})
}
}
return d
}
func (d *Deck) AddCard(c Card) {
d.mu.Lock()
defer d.mu.Unlock()
d.Cards = append(d.Cards, c)
}
// ShuffleRandomly shuffles the deck using cryptographically random numbers.
func (d *Deck) ShuffleRandomly() {
d.mu.Lock()
defer d.mu.Unlock()
rnd := rand.New(rand.NewSource(int64(cryptoUint64())))
//FIXME(sneak) not sure if this is constant time or not
rnd.Shuffle(len(d.Cards), func(i, j int) { d.Cards[i], d.Cards[j] = d.Cards[j], d.Cards[i] })
}
// ShuffleDeterministically shuffles the deck using a deterministic seed for testing.
func (d *Deck) ShuffleDeterministically(seed int64) {
d.mu.Lock()
defer d.mu.Unlock()
r := rand.New(rand.NewSource(seed))
//FIXME(sneak) not sure if this is constant time or not
r.Shuffle(len(d.Cards), func(i, j int) { d.Cards[i], d.Cards[j] = d.Cards[j], d.Cards[i] })
}
// Deal removes n cards from the top of the deck and returns them. The Deck is
// modified in place.to remove the dealt cards, just like in real life.
func (d *Deck) Deal(n int) (output Cards) {
d.mu.Lock()
defer d.mu.Unlock()
for i := 0; i < n; i++ {
output = append(output, d.Cards[i])
}
d.Cards = d.Cards[n:]
d.Dealt += n
return output
}
func (d *Deck) String() string {
return fmt.Sprintf("Deck{%s size=%d dealt=%d}", d.Cards, d.Count(), d.Dealt)
}
func (d *Deck) Count() int {
return len(d.Cards)
}

View File

@ -1,31 +1,37 @@
package pokercore package pokercore
import "encoding/binary" import (
import "fmt" "encoding/binary"
import crand "crypto/rand" "fmt"
import . "github.com/logrusorgru/aurora" "sort"
import log "github.com/sirupsen/logrus"
import rand "math/rand"
import "strings" crand "crypto/rand"
type Suit rune "github.com/logrusorgru/aurora/v4"
type Rank rune
const ( log "github.com/sirupsen/logrus"
CLUB Suit = '\u2663'
SPADE Suit = '\u2660' "strings"
DIAMOND Suit = '\u2666'
HEART Suit = '\u2665'
) )
/* type Suit rune
// emoji are cooler anyway
const CLUB = "C" func (s Suit) String() string {
const SPADE = "S" return string(s)
const DIAMOND = "D" }
const HEART = "H"
*/ type Rank rune
func (r Rank) String() string {
return string(r)
}
const (
CLUB Suit = '\u2663' // ♣
SPADE Suit = '\u2660' // ♠
DIAMOND Suit = '\u2666' // ♦
HEART Suit = '\u2665' // ♥
)
const ( const (
ACE Rank = 'A' ACE Rank = 'A'
@ -43,53 +49,67 @@ const (
KING Rank = 'K' KING Rank = 'K'
) )
type TestGenerationIteration struct {
Deck *Deck
Seed int64
}
type Card struct { type Card struct {
Rank Rank Rank Rank
Suit Suit Suit Suit
} }
type Cards []*Card type Cards []Card
type Deck struct { func (r Rank) Int(x AcesHighOrLow) int {
Cards Cards return int(rankToScore(r, x))
DealIndex int
ShuffleSeedVal int64
} }
func formatCardsForTerminal(c Cards) (output string) { func (r Rank) HandScore(x AcesHighOrLow) HandScore {
return HandScore(r.Int(x))
}
func (c Cards) SortByRank(x AcesHighOrLow) Cards {
newCards := make(Cards, len(c))
copy(newCards, c)
sort.Slice(newCards, func(i, j int) bool {
return rankToScore(newCards[i].Rank, x) > rankToScore(newCards[j].Rank, x)
})
return newCards
}
type TestGenerationIteration struct {
Deck *Deck
Seed int64
}
func (c Cards) PrintToTerminal() {
fmt.Printf("%s", c.FormatCardsForTerminal())
}
func (c Cards) FormatCardsForTerminal() string {
var cardstrings []string var cardstrings []string
for i := 0; i < len(c); i++ { for i := 0; i < len(c); i++ {
cardstrings = append(cardstrings, formatCardForTerminal(*c[i])) cardstrings = append(cardstrings, c[i].FormatForTerminal())
} }
output = strings.Join(cardstrings, ",") return strings.Join(cardstrings, ",")
return output
} }
func formatCardForTerminal(c Card) (output string) { func (c Card) FormatForTerminal() string {
var rank string var rank string
var suit string var suit string
color := Red color := aurora.Red
switch c.Suit { switch c.Suit {
case Suit(DIAMOND): case Suit(DIAMOND):
color = Blue color = aurora.Blue
case Suit(HEART): case Suit(HEART):
color = Red color = aurora.Red
case Suit(CLUB): case Suit(CLUB):
color = Green color = aurora.Green
case Suit(SPADE): case Suit(SPADE):
color = Black color = aurora.Black
} }
rank = fmt.Sprintf("%s", BgGray(Bold(color(c.Rank)))) rank = fmt.Sprintf("%s", aurora.Bold(color(c.Rank.String())))
suit = fmt.Sprintf("%s", BgGray(Bold(color(c.Suit)))) suit = fmt.Sprintf("%s", aurora.Bold(color(c.Suit.String())))
output = fmt.Sprintf("%s%s", rank, suit) return fmt.Sprintf("%s%s", rank, suit)
return output
} }
func cryptoUint64() (v uint64) { func cryptoUint64() (v uint64) {
@ -101,70 +121,13 @@ func cryptoUint64() (v uint64) {
return v return v
} }
func (self *Card) String() string { func (c *Card) String() string {
return fmt.Sprintf("%s%s", string(self.Rank), string(self.Suit)) return fmt.Sprintf("%s%s", string(c.Rank), string(c.Suit))
} }
func NewDeck() *Deck { func (c Cards) HighestRank(x AcesHighOrLow) Rank {
c = c.SortByRank(x)
self := new(Deck) return c[0].Rank
ranks := []Rank{
ACE, DEUCE, THREE, FOUR, FIVE,
SIX, SEVEN, EIGHT, NINE, TEN, JACK,
QUEEN, KING}
suits := []Suit{HEART, DIAMOND, CLUB, SPADE}
self.Cards = make([]*Card, 52)
tot := 0
for i := 0; i < len(ranks); i++ {
for n := 0; n < len(suits); n++ {
self.Cards[tot] = &Card{
Rank: ranks[i],
Suit: suits[n],
}
tot++
}
}
self.DealIndex = 0
return self
}
func (self *Deck) ShuffleRandomly() {
rnd := rand.New(rand.NewSource(int64(cryptoUint64())))
//FIXME(sneak) not sure if this is constant time or not
rnd.Shuffle(len(self.Cards), func(i, j int) { self.Cards[i], self.Cards[j] = self.Cards[j], self.Cards[i] })
self.DealIndex = 0
}
func (self *Deck) ShuffleDeterministically(seed int64) {
r := rand.New(rand.NewSource(seed))
//FIXME(sneak) not sure if this is constant time or not
r.Shuffle(len(self.Cards), func(i, j int) { self.Cards[i], self.Cards[j] = self.Cards[j], self.Cards[i] })
self.DealIndex = 0
}
func (self *Deck) Deal(n int) (output Cards) {
if (self.DealIndex + n) > len(self.Cards) {
return output
}
for i := 0; i < n; i++ {
output = append(output, self.Cards[self.DealIndex+1])
self.DealIndex++
}
return output
}
func (self *Deck) Dealt() int {
return self.DealIndex
}
func (self *Deck) Remaining() int {
return (len(self.Cards) - self.DealIndex)
} }
func (s Cards) String() (output string) { func (s Cards) String() (output string) {
@ -190,43 +153,23 @@ func proto() {
fmt.Printf("community: %s\n", cmty) fmt.Printf("community: %s\n", cmty)
} }
func scorePokerHand(input Cards) (score int) { func contains(ranks []Rank, rank Rank) bool {
/* for _, r := range ranks {
if r == rank {
scoring system: return true
high card: }
high card * 14^4 }
+ first kicker * 14^3 return false
+ second kicker * 14^2 }
+ third kicker * 14
+ fourth kicker func unique(intSlice []int) []int {
max(AKQJ9): 576,011 keys := make(map[int]bool)
single pair: list := []int{}
pair value * 1,000,000 for _, entry := range intSlice {
+ first kicker * 14^2 if _, value := keys[entry]; !value {
+ second kicker * 14 keys[entry] = true
+ third kicker list = append(list, entry)
max(AAKQJ): 14,002,727 }
two pair: }
higher of the two pair value * 100,000,000 return list
+ lower of the two pair value * 14
+ kicker value
max(AAKKQ): 1,300,000,179
trips:
trips value * 1,000,000,000 (min 2,000,000,000)
+ first kicker * 14
+ second kicker
max (AAAKQ): 14,000,000,194
straight:
highest card * 10,000,000,000
straight to the ace: 140,000,000,000
flush:
highest card * 100,000,000,000
min(23457): 700,000,000,000
max(AXXXX): 1,400,000,000,000
boat:
*/
return 1
} }

View File

@ -1,7 +1,11 @@
package pokercore package pokercore
import "github.com/stretchr/testify/assert" import (
import "testing" "fmt"
"testing"
"github.com/stretchr/testify/assert"
)
type ShuffleTestResults []struct { type ShuffleTestResults []struct {
SeedVal int64 SeedVal int64
@ -10,20 +14,38 @@ type ShuffleTestResults []struct {
func TestPokerDeck(t *testing.T) { func TestPokerDeck(t *testing.T) {
d := NewDeck() d := NewDeck()
fmt.Printf("newdeck: %+v\n", d)
d.ShuffleDeterministically(437) d.ShuffleDeterministically(437)
fmt.Printf("deterministically shuffled deck: %+v\n", d)
cards := d.Deal(7) cards := d.Deal(7)
//expected := "7C,5S,QS,2D,6D,QC,3H" expected := "6♥,A♦,7♥,9♣,6♠,9♥,8♣"
expected := "7♣,5♠,Q♠,2♦,6♦,Q♣,3♥" fmt.Printf("deterministically shuffled deck after dealing: %+v\n", d)
assert.Equal(t, cards.String(), expected) fmt.Printf("cards: %+v\n", cards)
assert.Equal(t, expected, cards.String())
x := d.Remaining() x := d.Count()
assert.Equal(t, 45, x) assert.Equal(t, 45, x)
d.ShuffleDeterministically(123456789) d.ShuffleDeterministically(123456789)
cards = d.Deal(10) cards = d.Deal(10)
expected = "2♣,T♠,4♥,Q♣,9♦,7♥,7♠,6♥,5♥,5♠" expected = "A♣,7♠,8♠,4♠,7♦,K♠,2♣,J♦,A♠,2♦"
assert.Equal(t, expected, cards.String()) assert.Equal(t, expected, cards.String())
x = d.Remaining() x = d.Count()
assert.Equal(t, 42, x) assert.Equal(t, 35, x)
} }
func TestDealing(t *testing.T) {
d := NewDeckFromCards(Cards{
Card{Rank: ACE, Suit: HEART},
Card{Rank: DEUCE, Suit: HEART},
Card{Rank: THREE, Suit: HEART},
Card{Rank: FOUR, Suit: HEART},
Card{Rank: FIVE, Suit: HEART},
})
cards := d.Deal(5)
expected := "A♥,2♥,3♥,4♥,5♥"
assert.Equal(t, expected, cards.String())
x := d.Count()
assert.Equal(t, 0, x)
}

263
pokercore/scoring.go Normal file
View File

@ -0,0 +1,263 @@
package pokercore
import "fmt"
type HandScore int
const (
ScoreNoPair = HandScore(iota * 100_000_000_000)
ScorePair
ScoreTwoPair
ScoreThreeOfAKind
ScoreStraight
ScoreFlush
ScoreFullHouse
ScoreFourOfAKind
ScoreStraightFlush
)
type AcesHighOrLow int
const (
AcesHigh AcesHighOrLow = iota
AcesLow
)
func rankToScore(rank Rank, AcesHighOrLow AcesHighOrLow) HandScore {
switch rank {
case ACE:
if AcesHighOrLow == AcesHigh {
return 14 // Aces are high, so we give them the highest value
} else {
return 1
}
case DEUCE:
return 2
case THREE:
return 3
case FOUR:
return 4
case FIVE:
return 5
case SIX:
return 6
case SEVEN:
return 7
case EIGHT:
return 8
case NINE:
return 9
case TEN:
return 10
case JACK:
return 11
case QUEEN:
return 12
case KING:
return 13
default:
panic("nope")
}
}
func (c Cards) ScoreHand() (HandScore, error) {
if !c.IsFiveCardPokerHand() {
return 0, fmt.Errorf("hand must have 5 cards with no duplicates to be scored")
}
if c.containsRoyalFlush() {
return ScoreStraightFlush + 1000*ACE.HandScore(AcesHigh), nil
}
if c.containsStraightFlush() {
return ScoreStraightFlush + 1000*c.HighestRank(AcesHigh).HandScore(AcesHigh), nil
}
panic("not implemented")
// FIXME finish this
return 0, nil
}
func (hand Cards) containsDuplicates() bool {
seen := make(map[Card]bool)
for _, card := range hand {
if _, ok := seen[card]; ok {
return true
}
seen[card] = true
}
return false
}
func (hand Cards) IsFiveCardPokerHand() bool {
return len(hand) == 5 && !hand.containsDuplicates()
}
func (hand Cards) containsFlush() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
suit := hand[0].Suit
for i := 1; i < len(hand); i++ {
if hand[i].Suit != suit {
return false
}
}
return true
}
func (hand Cards) containsStraight() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRank(AcesHigh)
if sorted[0].Rank == ACE && sorted[1].Rank == FIVE {
// special case for A-5 straight
if sorted[1].Rank == FIVE && sorted[2].Rank == FOUR && sorted[3].Rank == THREE && sorted[4].Rank == DEUCE {
return true
}
}
return sorted[0].Rank.Int(AcesHigh) == sorted[1].Rank.Int(AcesHigh)+1 && sorted[1].Rank.Int(AcesHigh) == sorted[2].Rank.Int(AcesHigh)+1 && sorted[2].Rank.Int(AcesHigh) == sorted[3].Rank.Int(AcesHigh)+1 && sorted[3].Rank.Int(AcesHigh) == sorted[4].Rank.Int(AcesHigh)+1
}
func (hand Cards) containsStraightFlush() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
if hand.containsStraight() && hand.containsFlush() {
return true
}
return false
}
func (hand Cards) containsRoyalFlush() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRank(AcesHigh)
if hand.containsStraightFlush() && sorted[0].Rank == ACE {
return true
}
return false
}
func (hand Cards) containsFourOfAKind() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRank(AcesHigh)
if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank {
return true
}
if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank {
return true
}
return false
}
func (hand Cards) containsFullHouse() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRank(AcesHigh)
if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank {
return true
}
if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank {
return true
}
return false
}
func (hand Cards) containsPair() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRank(AcesHigh)
if sorted[0].Rank == sorted[1].Rank {
return true
}
if sorted[1].Rank == sorted[2].Rank {
return true
}
if sorted[2].Rank == sorted[3].Rank {
return true
}
if sorted[3].Rank == sorted[4].Rank {
return true
}
return false
}
func (hand Cards) containsThreeOfAKind() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRank(AcesHigh)
if sorted[0].Rank == sorted[1].Rank && sorted[1].Rank == sorted[2].Rank {
return true
}
if sorted[1].Rank == sorted[2].Rank && sorted[2].Rank == sorted[3].Rank {
return true
}
if sorted[2].Rank == sorted[3].Rank && sorted[3].Rank == sorted[4].Rank {
return true
}
return false
}
func (hand Cards) containsTwoPair() bool {
if !hand.IsFiveCardPokerHand() {
panic("hand must have 5 cards to be scored")
}
sorted := hand.SortByRank(AcesHigh)
if sorted[0].Rank == sorted[1].Rank && sorted[2].Rank == sorted[3].Rank {
return true
}
if sorted[0].Rank == sorted[1].Rank && sorted[3].Rank == sorted[4].Rank {
return true
}
if sorted[1].Rank == sorted[2].Rank && sorted[3].Rank == sorted[4].Rank {
return true
}
return false
}
func (hand Cards) isUnmadeHand() bool {
if !hand.IsFiveCardPokerHand() {
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()
}
// this method makes a n new hands where n is the number of cards in the hand
// each of the new hands has one card removed from the original hand
// then it calls the identifyBestFiveCardPokerHand method on each of the new hands
// and returns the best hand by score. this is recursion.
func (hand Cards) identifyBestFiveCardPokerHand() (Cards, error) {
newHands := make([]Cards, len(hand))
for i := 0; i < len(hand); i++ {
newHand := hand[:i]
newHand = append(newHand, hand[i+1:]...)
newHands[i] = newHand
}
var bestHand Cards
var bestScore HandScore
for _, h := range newHands {
if h.IsFiveCardPokerHand() {
score, _ := h.ScoreHand()
if score > bestScore {
bestScore = score
bestHand = h
}
} else {
rh, _ := h.identifyBestFiveCardPokerHand()
score, _ := rh.ScoreHand()
if score > bestScore {
bestScore = score
bestHand = rh
}
}
}
return bestHand, nil
}

181
pokercore/scoring_test.go Normal file
View File

@ -0,0 +1,181 @@
package pokercore
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAceLowStraight(t *testing.T) {
hand := Cards{
AceOfSpades(),
DeuceOfHearts(),
ThreeOfDiamonds(),
FourOfClubs(),
FiveOfSpades(),
}
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
}
func TestAceHighStraight(t *testing.T) {
hand := Cards{
TenOfSpades(),
JackOfHearts(),
KingOfClubs(),
AceOfSpades(),
QueenOfDiamonds(),
}
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
newDeck := NewDeckFromCards(hand)
newDeck.ShuffleDeterministically(123456789)
fmt.Printf("Shuffled deck: %s\n", newDeck.String())
fmt.Printf("new deck has %d cards\n", newDeck.Count())
shuffledHand := newDeck.Deal(5)
assert.True(t, shuffledHand.containsStraight(), "Expected hand to still be a straight after shuffle")
}
func TestOtherStraight(t *testing.T) {
hand := Cards{
DeuceOfSpades(),
ThreeOfHearts(),
FourOfDiamonds(),
FiveOfClubs(),
SixOfSpades(),
}
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
newDeck := NewDeckFromCards(hand)
newDeck.ShuffleDeterministically(123456789)
fmt.Printf("Shuffled deck: %s\n", newDeck.String())
fmt.Printf("new deck has %d cards\n", newDeck.Count())
shuffledHand := newDeck.Deal(5)
assert.True(t, shuffledHand.containsStraight(), "Expected hand to still be a straight after shuffle")
assert.False(t, shuffledHand.containsTwoPair(), "Expected hand to not be two pair")
}
func TestFlush(t *testing.T) {
hand := Cards{
AceOfSpades(),
DeuceOfSpades(),
ThreeOfSpades(),
FourOfSpades(),
SixOfSpades(),
}
assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
newDeck := NewDeckFromCards(hand)
newDeck.ShuffleDeterministically(123456789)
fmt.Printf("Shuffled deck: %s\n", newDeck.String())
fmt.Printf("new deck has %d cards\n", newDeck.Count())
shuffledHand := newDeck.Deal(5)
assert.True(t, shuffledHand.containsFlush(), "Expected hand to still be a flush after shuffle")
x := ScoreFlush
var multiplier HandScore = 100
x += multiplier * DEUCE.HandScore(AcesHigh)
multiplier *= 100
x += multiplier * THREE.HandScore(AcesHigh)
multiplier *= 100
x += multiplier * FOUR.HandScore(AcesHigh)
multiplier *= 100
x += multiplier * SIX.HandScore(AcesHigh)
fmt.Printf("a-2-3-4-6 flush score should be: %d\n", x)
}
func TestStraightFlush(t *testing.T) {
hand := Cards{
SixOfSpades(),
DeuceOfSpades(),
ThreeOfSpades(),
FourOfSpades(),
FiveOfSpades(),
}
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
assert.True(t, hand.containsFlush(), "Expected hand to be a flush")
assert.True(t, hand.containsStraightFlush(), "Expected hand to be a straight flush")
assert.False(t, hand.containsRoyalFlush(), "Expected hand to not be a royal flush")
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.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(AcesHigh) == SIX, "Expected highest rank to be a six")
nd := NewDeckFromCards(hand)
nd.ShuffleDeterministically(123456789)
fmt.Printf("Shuffled deck: %s\n", nd.String())
fmt.Printf("new deck has %d cards\n", nd.Count())
shuffledHand := nd.Deal(5)
assert.True(t, shuffledHand.containsStraightFlush(), "Expected hand to still be a straight flush after shuffle")
assert.True(t, shuffledHand.HighestRank(AcesHigh) == SIX, "Expected highest rank to still be a six after shuffle")
assert.True(t, shuffledHand.HighestRank(AcesLow) == SIX, "Expected highest rank to be a six after shuffle even with aces low")
}
func TestRoyalFlush(t *testing.T) {
hand := Cards{
TenOfSpades(),
JackOfSpades(),
QueenOfSpades(),
KingOfSpades(),
AceOfSpades(),
}
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
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.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.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(AcesHigh) == ACE, "Expected highest rank to be an ace")
assert.False(t, hand.HighestRank(AcesHigh) == TEN, "Expected highest rank to not be an ace")
}
func TestUnmadeHand(t *testing.T) {
hand := Cards{
TenOfSpades(),
JackOfDiamonds(),
QueenOfSpades(),
KingOfSpades(),
DeuceOfSpades(),
}
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.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.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(AcesHigh) == KING, "Expected highest rank to be a king")
assert.True(t, hand.isUnmadeHand(), "Expected hand to be unmade")
}
func TestTwoPair(t *testing.T) {
hand := Cards{
KingOfSpades(),
JackOfDiamonds(),
JackOfSpades(),
KingOfDiamonds(),
TenOfSpades(),
}
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.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.containsThreeOfAKind(), "Expected hand to not be three of a kind")
assert.True(t, hand.containsTwoPair(), "Expected hand to be two pair")
assert.True(t, hand.containsPair(), "Expected hand to also be a pair")
assert.True(t, hand.HighestRank(AcesHigh) == KING, "Expected highest rank to be a king")
assert.False(t, hand.isUnmadeHand(), "Expected hand to not be unmade")
}

57
pokercore/shortcuts.go Normal file
View File

@ -0,0 +1,57 @@
package pokercore
func AceOfSpades() Card { return Card{ACE, SPADE} }
func DeuceOfSpades() Card { return Card{DEUCE, SPADE} }
func ThreeOfSpades() Card { return Card{THREE, SPADE} }
func FourOfSpades() Card { return Card{FOUR, SPADE} }
func FiveOfSpades() Card { return Card{FIVE, SPADE} }
func SixOfSpades() Card { return Card{SIX, SPADE} }
func SevenOfSpades() Card { return Card{SEVEN, SPADE} }
func EightOfSpades() Card { return Card{EIGHT, SPADE} }
func NineOfSpades() Card { return Card{NINE, SPADE} }
func TenOfSpades() Card { return Card{TEN, SPADE} }
func JackOfSpades() Card { return Card{JACK, SPADE} }
func QueenOfSpades() Card { return Card{QUEEN, SPADE} }
func KingOfSpades() Card { return Card{KING, SPADE} }
func AceOfHearts() Card { return Card{ACE, HEART} }
func DeuceOfHearts() Card { return Card{DEUCE, HEART} }
func ThreeOfHearts() Card { return Card{THREE, HEART} }
func FourOfHearts() Card { return Card{FOUR, HEART} }
func FiveOfHearts() Card { return Card{FIVE, HEART} }
func SixOfHearts() Card { return Card{SIX, HEART} }
func SevenOfHearts() Card { return Card{SEVEN, HEART} }
func EightOfHearts() Card { return Card{EIGHT, HEART} }
func NineOfHearts() Card { return Card{NINE, HEART} }
func TenOfHearts() Card { return Card{TEN, HEART} }
func JackOfHearts() Card { return Card{JACK, HEART} }
func QueenOfHearts() Card { return Card{QUEEN, HEART} }
func KingOfHearts() Card { return Card{KING, HEART} }
func AceOfDiamonds() Card { return Card{ACE, DIAMOND} }
func DeuceOfDiamonds() Card { return Card{DEUCE, DIAMOND} }
func ThreeOfDiamonds() Card { return Card{THREE, DIAMOND} }
func FourOfDiamonds() Card { return Card{FOUR, DIAMOND} }
func FiveOfDiamonds() Card { return Card{FIVE, DIAMOND} }
func SixOfDiamonds() Card { return Card{SIX, DIAMOND} }
func SevenOfDiamonds() Card { return Card{SEVEN, DIAMOND} }
func EightOfDiamonds() Card { return Card{EIGHT, DIAMOND} }
func NineOfDiamonds() Card { return Card{NINE, DIAMOND} }
func TenOfDiamonds() Card { return Card{TEN, DIAMOND} }
func JackOfDiamonds() Card { return Card{JACK, DIAMOND} }
func QueenOfDiamonds() Card { return Card{QUEEN, DIAMOND} }
func KingOfDiamonds() Card { return Card{KING, DIAMOND} }
func AceOfClubs() Card { return Card{ACE, CLUB} }
func DeuceOfClubs() Card { return Card{DEUCE, CLUB} }
func ThreeOfClubs() Card { return Card{THREE, CLUB} }
func FourOfClubs() Card { return Card{FOUR, CLUB} }
func FiveOfClubs() Card { return Card{FIVE, CLUB} }
func SixOfClubs() Card { return Card{SIX, CLUB} }
func SevenOfClubs() Card { return Card{SEVEN, CLUB} }
func EightOfClubs() Card { return Card{EIGHT, CLUB} }
func NineOfClubs() Card { return Card{NINE, CLUB} }
func TenOfClubs() Card { return Card{TEN, CLUB} }
func JackOfClubs() Card { return Card{JACK, CLUB} }
func QueenOfClubs() Card { return Card{QUEEN, CLUB} }
func KingOfClubs() Card { return Card{KING, CLUB} }