latest, found a bug, wrote a failing test
This commit is contained in:
parent
90a959448a
commit
eb85e1d36e
2
Makefile
2
Makefile
@ -1,4 +1,4 @@
|
|||||||
default: test
|
default: test
|
||||||
|
|
||||||
test:
|
test:
|
||||||
cd pokercore && go test -v ./...
|
go test -v ./...
|
||||||
|
194
card.go
Normal file
194
card.go
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
package pokercore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/logrusorgru/aurora/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Card struct {
|
||||||
|
Rank Rank
|
||||||
|
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) {
|
||||||
|
// FIXME extend this later to common format strings like "9s"
|
||||||
|
if len(card) != 2 {
|
||||||
|
return Card{}, fmt.Errorf("Invalid card string %s", card)
|
||||||
|
}
|
||||||
|
rank := NewRankFromString(card[0:1])
|
||||||
|
suit := NewSuitFromString(card[1:2])
|
||||||
|
if rank == Rank(0) || suit == Suit(0) {
|
||||||
|
return Card{}, fmt.Errorf("Invalid card string %s", card)
|
||||||
|
}
|
||||||
|
return Card{Rank: rank, Suit: suit}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCardsFromString(cards string) (Cards, error) {
|
||||||
|
// supports a string like 9♠,9♣,Q♥,Q♦,K♣
|
||||||
|
// FIXME extend this later to common format strings like "9c Qh Qd Kc"
|
||||||
|
// with or without commas
|
||||||
|
var newCards Cards
|
||||||
|
cardStrings := strings.Split(cards, ",")
|
||||||
|
for _, cardString := range cardStrings {
|
||||||
|
card, err := NewCardFromString(cardString)
|
||||||
|
if err != nil {
|
||||||
|
return Cards{}, err
|
||||||
|
}
|
||||||
|
newCards = append(newCards, card)
|
||||||
|
}
|
||||||
|
if len(newCards) == 0 {
|
||||||
|
return Cards{}, fmt.Errorf("No cards found in string %s", cards)
|
||||||
|
}
|
||||||
|
return newCards, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Card) String() string {
|
||||||
|
return fmt.Sprintf("%s%s", string(c.Rank), string(c.Suit))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cards []Card
|
||||||
|
|
||||||
|
func (c Cards) First() Card {
|
||||||
|
return c[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cards) Second() Card {
|
||||||
|
return c[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cards) Third() Card {
|
||||||
|
return c[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cards) Fourth() Card {
|
||||||
|
return c[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cards) Fifth() Card {
|
||||||
|
return c[4]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cards) Last() Card {
|
||||||
|
return c[len(c)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cards) SortByRankAscending() Cards {
|
||||||
|
newCards := make(Cards, len(c))
|
||||||
|
copy(newCards, c)
|
||||||
|
sort.Slice(newCards, func(i, j int) bool {
|
||||||
|
return newCards[i].Rank.Score() < newCards[j].Rank.Score()
|
||||||
|
})
|
||||||
|
|
||||||
|
return newCards
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cards) PrintToTerminal() {
|
||||||
|
fmt.Printf("%s", c.FormatForTerminal())
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortOrder int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AceHighAscending SortOrder = iota
|
||||||
|
AceHighDescending
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c Cards) FormatForTerminalSorted(order SortOrder) string {
|
||||||
|
sorted := c.SortByRankAscending() // this is ascending
|
||||||
|
if order == AceHighDescending {
|
||||||
|
slices.Reverse(sorted)
|
||||||
|
}
|
||||||
|
return sorted.FormatForTerminal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cards) FormatForTerminal() string {
|
||||||
|
var cardstrings []string
|
||||||
|
for i := 0; i < len(c); i++ {
|
||||||
|
cardstrings = append(cardstrings, c[i].FormatForTerminal())
|
||||||
|
}
|
||||||
|
return strings.Join(cardstrings, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Card) FormatForTerminal() string {
|
||||||
|
var rank string
|
||||||
|
var suit string
|
||||||
|
color := aurora.Red
|
||||||
|
switch c.Suit {
|
||||||
|
case Suit(DIAMOND):
|
||||||
|
color = aurora.Blue
|
||||||
|
case Suit(HEART):
|
||||||
|
color = aurora.Red
|
||||||
|
case Suit(CLUB):
|
||||||
|
color = aurora.Green
|
||||||
|
case Suit(SPADE):
|
||||||
|
color = aurora.Black
|
||||||
|
}
|
||||||
|
|
||||||
|
rank = fmt.Sprintf("%s", aurora.Bold(color(c.Rank.Symbol())))
|
||||||
|
suit = fmt.Sprintf("%s", aurora.Bold(color(c.Suit.Symbol())))
|
||||||
|
return fmt.Sprintf("%s%s", rank, suit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cards) HighestRank() Rank {
|
||||||
|
sorted := c.SortByRankAscending()
|
||||||
|
return sorted[len(sorted)-1].Rank
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Cards) String() (output string) {
|
||||||
|
var cardstrings []string
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
cardstrings = append(cardstrings, s[i].String())
|
||||||
|
}
|
||||||
|
output = strings.Join(cardstrings, ",")
|
||||||
|
return output
|
||||||
|
}
|
@ -85,6 +85,10 @@ func (d *Deck) Deal(n int) (output Cards) {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Deck) FormatForTerminal() string {
|
||||||
|
return d.Cards.FormatForTerminal()
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Deck) String() string {
|
func (d *Deck) String() string {
|
||||||
return fmt.Sprintf("Deck{%s size=%d dealt=%d}", d.Cards, d.Count(), d.Dealt)
|
return fmt.Sprintf("Deck{%s size=%d dealt=%d}", d.Cards, d.Count(), d.Dealt)
|
||||||
}
|
}
|
34
examples/searchbig/main.go
Normal file
34
examples/searchbig/main.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.eeqj.de/sneak/pokercore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
d := pokercore.NewDeck()
|
||||||
|
fmt.Printf("deck before shuffling: %s\n", d.FormatForTerminal())
|
||||||
|
d.ShuffleRandomly()
|
||||||
|
fmt.Printf("deck after shuffling: %s\n", d.FormatForTerminal())
|
||||||
|
offTheTop := d.Deal(11)
|
||||||
|
fmt.Printf("off the top: %s\n", offTheTop.FormatForTerminal())
|
||||||
|
wh, err := offTheTop.IdentifyBestFiveCardPokerHand()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("best hand: %s\n", wh.FormatForTerminalSorted(pokercore.AceHighAscending))
|
||||||
|
score, err := wh.PokerHandScore()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("best hand score: %s\n", score)
|
||||||
|
ph, err := wh.PokerHand()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("best hand string: %s\n", ph.Description())
|
||||||
|
}
|
||||||
|
}
|
BIN
examples/searchbig/searchbig
Executable file
BIN
examples/searchbig/searchbig
Executable file
Binary file not shown.
35
examples/sixhand/main.go
Normal file
35
examples/sixhand/main.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
BIN
examples/sixhand/sixhand
Executable file
BIN
examples/sixhand/sixhand
Executable file
Binary file not shown.
@ -1,24 +1,32 @@
|
|||||||
package pokercore
|
package pokercore
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
var ErrDuplicateCard = errors.New("cannot score a poker hand out of a set of cards with duplicates")
|
var ErrDuplicateCard = errors.New("cannot score a poker hand out of a set of cards with duplicates")
|
||||||
|
|
||||||
// this method makes a n new hands where n is the number of cards in the hand
|
// 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
|
// 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
|
// then it calls the identifyBestFiveCardPokerHand method on each of the new hands
|
||||||
// and returns the best hand by score. this is recursion.
|
// and returns the best hand by score. this is recursion and is exponential in time complexity
|
||||||
func (hand Cards) identifyBestFiveCardPokerHand() (Cards, error) {
|
func (hand Cards) IdentifyBestFiveCardPokerHand() (Cards, error) {
|
||||||
|
|
||||||
|
//fmt.Println("hand: ", hand)
|
||||||
if hand.containsDuplicates() {
|
if hand.containsDuplicates() {
|
||||||
return nil, ErrDuplicateCard
|
return nil, ErrDuplicateCard
|
||||||
}
|
}
|
||||||
|
|
||||||
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 := hand[:i]
|
newHand := make(Cards, len(hand)-1)
|
||||||
newHand = append(newHand, hand[i+1:]...)
|
copy(newHand, hand[:i])
|
||||||
|
copy(newHand[i:], hand[i+1:])
|
||||||
|
//fmt.Printf("generating new subset of hand: %+v\n", newHand)
|
||||||
|
//fmt.Printf("this subset drops the card at index %d: %s\n", i, hand[i].String())
|
||||||
newHands[i] = newHand
|
newHands[i] = newHand
|
||||||
}
|
}
|
||||||
|
//fmt.Printf("newHands: %+v\n", newHands)
|
||||||
var bestHand Cards
|
var bestHand Cards
|
||||||
var bestScore HandScore
|
var bestScore HandScore
|
||||||
|
|
||||||
@ -30,7 +38,7 @@ func (hand Cards) identifyBestFiveCardPokerHand() (Cards, error) {
|
|||||||
bestHand = h
|
bestHand = h
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rh, _ := h.identifyBestFiveCardPokerHand()
|
rh, _ := h.IdentifyBestFiveCardPokerHand()
|
||||||
score, _ := rh.PokerHandScore()
|
score, _ := rh.PokerHandScore()
|
||||||
if score > bestScore {
|
if score > bestScore {
|
||||||
bestScore = score
|
bestScore = score
|
2
go.mod
2
go.mod
@ -1,4 +1,4 @@
|
|||||||
module git.eeqj.de/sneak/go-poker
|
module git.eeqj.de/sneak/pokercore
|
||||||
|
|
||||||
go 1.22.2
|
go 1.22.2
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func (c Cards) pairRank() Rank {
|
|||||||
if !c.containsPair() {
|
if !c.containsPair() {
|
||||||
panic("hand must have a pair to have a pair rank")
|
panic("hand must have a pair to have a pair rank")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
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
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ func (c Cards) pairFirstKicker() Card {
|
|||||||
if !c.containsPair() {
|
if !c.containsPair() {
|
||||||
panic("hand must have a pair to have a first kicker")
|
panic("hand must have a pair to have a first kicker")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
sorted := c.SortByRankAscending()
|
||||||
if sorted[0].Rank == sorted[1].Rank {
|
if sorted[0].Rank == sorted[1].Rank {
|
||||||
return sorted[4]
|
return sorted[4]
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ func (c Cards) pairSecondKicker() Card {
|
|||||||
if !c.containsPair() {
|
if !c.containsPair() {
|
||||||
panic("hand must have a pair to have a second kicker")
|
panic("hand must have a pair to have a second kicker")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
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]
|
||||||
return sorted[3]
|
return sorted[3]
|
||||||
@ -97,7 +97,7 @@ func (c Cards) pairThirdKicker() Card {
|
|||||||
if !c.containsPair() {
|
if !c.containsPair() {
|
||||||
panic("hand must have a pair to have a third kicker")
|
panic("hand must have a pair to have a third kicker")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
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]
|
||||||
// second kicker is [3]
|
// second kicker is [3]
|
||||||
@ -125,7 +125,7 @@ func (c Cards) twoPairBiggestPair() Rank {
|
|||||||
if !c.containsTwoPair() {
|
if !c.containsTwoPair() {
|
||||||
panic("hand must have two pair to have a biggest pair")
|
panic("hand must have two pair to have a biggest pair")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
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
|
||||||
}
|
}
|
||||||
@ -143,7 +143,7 @@ func (c Cards) twoPairSmallestPair() Rank {
|
|||||||
if !c.containsTwoPair() {
|
if !c.containsTwoPair() {
|
||||||
panic("hand must have two pair to have a smallest pair")
|
panic("hand must have two pair to have a smallest pair")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
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
|
||||||
}
|
}
|
||||||
@ -160,7 +160,7 @@ func (c Cards) twoPairKicker() Card {
|
|||||||
if !c.containsTwoPair() {
|
if !c.containsTwoPair() {
|
||||||
panic("hand must have two pair to have a twoPairKicker")
|
panic("hand must have two pair to have a twoPairKicker")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
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]
|
||||||
}
|
}
|
||||||
@ -177,7 +177,7 @@ func (c Cards) threeOfAKindTripsRank() Rank {
|
|||||||
if !c.containsThreeOfAKind() {
|
if !c.containsThreeOfAKind() {
|
||||||
panic("hand must have three of a kind to have a trips rank")
|
panic("hand must have three of a kind to have a trips rank")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
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
|
||||||
}
|
}
|
||||||
@ -194,7 +194,7 @@ func (c Cards) threeOfAKindKickers() Cards {
|
|||||||
if !c.containsThreeOfAKind() {
|
if !c.containsThreeOfAKind() {
|
||||||
panic("hand must have three of a kind to have kickers")
|
panic("hand must have three of a kind to have kickers")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
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]}
|
||||||
}
|
}
|
||||||
@ -221,7 +221,7 @@ func (c Cards) fourOfAKindRank() Rank {
|
|||||||
if !c.containsFourOfAKind() {
|
if !c.containsFourOfAKind() {
|
||||||
panic("hand must have four of a kind to have a four of a kind rank")
|
panic("hand must have four of a kind to have a four of a kind rank")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
sorted := c.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 {
|
||||||
return sorted[0].Rank
|
return sorted[0].Rank
|
||||||
}
|
}
|
||||||
@ -235,7 +235,7 @@ func (c Cards) fourOfAKindKicker() Card {
|
|||||||
if !c.containsFourOfAKind() {
|
if !c.containsFourOfAKind() {
|
||||||
panic("hand must have four of a kind to have a four of a kind kicker")
|
panic("hand must have four of a kind to have a four of a kind kicker")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
sorted := c.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 {
|
||||||
return sorted[4]
|
return sorted[4]
|
||||||
}
|
}
|
||||||
@ -249,7 +249,7 @@ func (c Cards) fullHouseTripsRank() Rank {
|
|||||||
if !c.containsFullHouse() {
|
if !c.containsFullHouse() {
|
||||||
panic("hand must have a full house to have a trips rank")
|
panic("hand must have a full house to have a trips rank")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
sorted := c.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 {
|
||||||
return sorted[0].Rank
|
return sorted[0].Rank
|
||||||
}
|
}
|
||||||
@ -263,7 +263,7 @@ func (c Cards) fullHousePairRank() Rank {
|
|||||||
if !c.containsFullHouse() {
|
if !c.containsFullHouse() {
|
||||||
panic("hand must have a full house to have a pair rank")
|
panic("hand must have a full house to have a pair rank")
|
||||||
}
|
}
|
||||||
sorted := c.SortByRank()
|
sorted := c.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 {
|
||||||
return sorted[4].Rank
|
return sorted[4].Rank
|
||||||
}
|
}
|
||||||
@ -305,7 +305,7 @@ func (hand Cards) containsStraight() bool {
|
|||||||
if !hand.IsFiveCardPokerHand() {
|
if !hand.IsFiveCardPokerHand() {
|
||||||
panic("hand must have 5 cards to be scored")
|
panic("hand must have 5 cards to be scored")
|
||||||
}
|
}
|
||||||
sorted := hand.SortByRank()
|
sorted := hand.SortByRankAscending()
|
||||||
|
|
||||||
if sorted[4].Rank == ACE && sorted[3].Rank == FIVE {
|
if sorted[4].Rank == ACE && sorted[3].Rank == FIVE {
|
||||||
// special case for A-5 straight
|
// special case for A-5 straight
|
||||||
@ -335,7 +335,13 @@ func (hand Cards) containsRoyalFlush() bool {
|
|||||||
if !hand.IsFiveCardPokerHand() {
|
if !hand.IsFiveCardPokerHand() {
|
||||||
panic("hand must have 5 cards to be scored")
|
panic("hand must have 5 cards to be scored")
|
||||||
}
|
}
|
||||||
if hand.containsStraightFlush() && hand.HighestRank() == ACE {
|
// 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.
|
||||||
|
//if hand.containsStraightFlush() && hand.HighestRank() == ACE {
|
||||||
|
// return true
|
||||||
|
//}
|
||||||
|
sorted := hand.SortByRankAscending()
|
||||||
|
if hand.containsStraightFlush() && hand.HighestRank() == ACE && sorted[0].Rank == TEN {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -345,7 +351,7 @@ func (hand Cards) containsFourOfAKind() bool {
|
|||||||
if !hand.IsFiveCardPokerHand() {
|
if !hand.IsFiveCardPokerHand() {
|
||||||
panic("hand must have 5 cards to be scored")
|
panic("hand must have 5 cards to be scored")
|
||||||
}
|
}
|
||||||
sorted := hand.SortByRank()
|
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
|
||||||
return true
|
return true
|
||||||
@ -361,7 +367,7 @@ func (hand Cards) containsFullHouse() bool {
|
|||||||
if !hand.IsFiveCardPokerHand() {
|
if !hand.IsFiveCardPokerHand() {
|
||||||
panic("hand must have 5 cards to be scored")
|
panic("hand must have 5 cards to be scored")
|
||||||
}
|
}
|
||||||
sorted := hand.SortByRank()
|
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 {
|
||||||
// the trips precede the pair
|
// the trips precede the pair
|
||||||
@ -378,7 +384,7 @@ func (hand Cards) containsPair() bool {
|
|||||||
if !hand.IsFiveCardPokerHand() {
|
if !hand.IsFiveCardPokerHand() {
|
||||||
panic("hand must have 5 cards to be scored")
|
panic("hand must have 5 cards to be scored")
|
||||||
}
|
}
|
||||||
sorted := hand.SortByRank()
|
sorted := hand.SortByRankAscending()
|
||||||
if sorted[0].Rank == sorted[1].Rank {
|
if sorted[0].Rank == sorted[1].Rank {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -398,7 +404,7 @@ func (hand Cards) containsThreeOfAKind() bool {
|
|||||||
if !hand.IsFiveCardPokerHand() {
|
if !hand.IsFiveCardPokerHand() {
|
||||||
panic("hand must have 5 cards to be scored")
|
panic("hand must have 5 cards to be scored")
|
||||||
}
|
}
|
||||||
sorted := hand.SortByRank()
|
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
|
||||||
}
|
}
|
||||||
@ -415,7 +421,7 @@ func (hand Cards) containsTwoPair() bool {
|
|||||||
if !hand.IsFiveCardPokerHand() {
|
if !hand.IsFiveCardPokerHand() {
|
||||||
panic("hand must have 5 cards to be scored")
|
panic("hand must have 5 cards to be scored")
|
||||||
}
|
}
|
||||||
sorted := hand.SortByRank()
|
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
|
||||||
}
|
}
|
101
main.go
101
main.go
@ -1,101 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/rpc"
|
|
||||||
"net/rpc/jsonrpc"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JSONRPCServer struct {
|
|
||||||
*rpc.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJSONRPCServer() *JSONRPCServer {
|
|
||||||
return &JSONRPCServer{rpc.NewServer()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
log.Println("rpc server got a request")
|
|
||||||
conn, _, err := w.(http.Hijacker).Hijack()
|
|
||||||
if err != nil {
|
|
||||||
log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
io.WriteString(conn, "HTTP/1.0 200 Connected to Go JSON-RPC\n\n")
|
|
||||||
codec := jsonrpc.NewServerCodec(conn)
|
|
||||||
log.Println("ServeCodec")
|
|
||||||
s.Server.ServeCodec(codec)
|
|
||||||
log.Println("finished serving request")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Args struct {
|
|
||||||
A, B int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Quotient struct {
|
|
||||||
Quo, Rem int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Arith int
|
|
||||||
|
|
||||||
func (t *Arith) Multiply(args *Args, reply *int) error {
|
|
||||||
*reply = args.A * args.B
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Arith) Divide(args *Args, quo *Quotient) error {
|
|
||||||
if args.B == 0 {
|
|
||||||
return errors.New("divide by zero")
|
|
||||||
}
|
|
||||||
quo.Quo = args.A / args.B
|
|
||||||
quo.Rem = args.A % args.B
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
//log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
|
|
||||||
// Output to stdout instead of the default stderr
|
|
||||||
// Can be any io.Writer, see below for File example
|
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
|
|
||||||
// Only log the warning severity or above.
|
|
||||||
//log.SetLevel(log.WarnLevel)
|
|
||||||
|
|
||||||
log.Infof("starting up")
|
|
||||||
|
|
||||||
go runHttpServer()
|
|
||||||
|
|
||||||
running := true
|
|
||||||
|
|
||||||
for running {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHttpServer() {
|
|
||||||
js := NewJSONRPCServer()
|
|
||||||
arith := new(Arith)
|
|
||||||
js.Register(arith)
|
|
||||||
|
|
||||||
port := 8080
|
|
||||||
|
|
||||||
listenaddr := fmt.Sprintf("0.0.0.0:%d", port)
|
|
||||||
|
|
||||||
s := &http.Server{
|
|
||||||
Addr: listenaddr,
|
|
||||||
Handler: js,
|
|
||||||
ReadTimeout: 10 * time.Second,
|
|
||||||
WriteTimeout: 10 * time.Second,
|
|
||||||
MaxHeaderBytes: 1 << 20,
|
|
||||||
}
|
|
||||||
log.Infof("starting up http server %s", listenaddr)
|
|
||||||
log.Fatal(s.ListenAndServe())
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/go-poker/pokercore"
|
"git.eeqj.de/sneak/pokercore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
23
pokercore.go
Normal file
23
pokercore.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package pokercore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
crand "crypto/rand"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestGenerationIteration struct {
|
||||||
|
Deck *Deck
|
||||||
|
Seed int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func cryptoUint64() (v uint64) {
|
||||||
|
err := binary.Read(crand.Reader, binary.BigEndian, &v)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Debugf("crand cryptosource is returning Uint64: %d", v)
|
||||||
|
return v
|
||||||
|
}
|
@ -1,173 +0,0 @@
|
|||||||
package pokercore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
crand "crypto/rand"
|
|
||||||
|
|
||||||
"github.com/logrusorgru/aurora/v4"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Suit rune
|
|
||||||
|
|
||||||
func (s Suit) String() string {
|
|
||||||
return string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
ACE Rank = 'A'
|
|
||||||
DEUCE Rank = '2'
|
|
||||||
THREE Rank = '3'
|
|
||||||
FOUR Rank = '4'
|
|
||||||
FIVE Rank = '5'
|
|
||||||
SIX Rank = '6'
|
|
||||||
SEVEN Rank = '7'
|
|
||||||
EIGHT Rank = '8'
|
|
||||||
NINE Rank = '9'
|
|
||||||
TEN Rank = 'T'
|
|
||||||
JACK Rank = 'J'
|
|
||||||
QUEEN Rank = 'Q'
|
|
||||||
KING Rank = 'K'
|
|
||||||
)
|
|
||||||
|
|
||||||
type Card struct {
|
|
||||||
Rank Rank
|
|
||||||
Suit Suit
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cards []Card
|
|
||||||
|
|
||||||
func (r Rank) Int() int {
|
|
||||||
return int(r.Score())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Rank) HandScore() HandScore {
|
|
||||||
return HandScore(r.Int())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Cards) SortByRank() Cards {
|
|
||||||
newCards := make(Cards, len(c))
|
|
||||||
copy(newCards, c)
|
|
||||||
sort.Slice(newCards, func(i, j int) bool {
|
|
||||||
return newCards[i].Rank.Score() > newCards[j].Rank.Score()
|
|
||||||
})
|
|
||||||
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
|
|
||||||
for i := 0; i < len(c); i++ {
|
|
||||||
cardstrings = append(cardstrings, c[i].FormatForTerminal())
|
|
||||||
}
|
|
||||||
return strings.Join(cardstrings, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Card) FormatForTerminal() string {
|
|
||||||
var rank string
|
|
||||||
var suit string
|
|
||||||
color := aurora.Red
|
|
||||||
switch c.Suit {
|
|
||||||
case Suit(DIAMOND):
|
|
||||||
color = aurora.Blue
|
|
||||||
case Suit(HEART):
|
|
||||||
color = aurora.Red
|
|
||||||
case Suit(CLUB):
|
|
||||||
color = aurora.Green
|
|
||||||
case Suit(SPADE):
|
|
||||||
color = aurora.Black
|
|
||||||
}
|
|
||||||
|
|
||||||
rank = fmt.Sprintf("%s", aurora.Bold(color(c.Rank.String())))
|
|
||||||
suit = fmt.Sprintf("%s", aurora.Bold(color(c.Suit.String())))
|
|
||||||
return fmt.Sprintf("%s%s", rank, suit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cryptoUint64() (v uint64) {
|
|
||||||
err := binary.Read(crand.Reader, binary.BigEndian, &v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Debugf("crand cryptosource is returning Uint64: %d", v)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Card) String() string {
|
|
||||||
return fmt.Sprintf("%s%s", string(c.Rank), string(c.Suit))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Cards) HighestRank() Rank {
|
|
||||||
c = c.SortByRank()
|
|
||||||
return c[0].Rank
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Cards) String() (output string) {
|
|
||||||
var cardstrings []string
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
cardstrings = append(cardstrings, s[i].String())
|
|
||||||
}
|
|
||||||
output = strings.Join(cardstrings, ",")
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
func generate() {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func proto() {
|
|
||||||
myDeck := NewDeck()
|
|
||||||
myDeck.ShuffleDeterministically(42)
|
|
||||||
myHand := myDeck.Deal(2)
|
|
||||||
//spew.Dump(myHand)
|
|
||||||
fmt.Printf("my hand: %s\n", myHand)
|
|
||||||
cmty := myDeck.Deal(5)
|
|
||||||
fmt.Printf("community: %s\n", cmty)
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains(ranks []Rank, rank Rank) bool {
|
|
||||||
for _, r := range ranks {
|
|
||||||
if r == rank {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func unique(intSlice []int) []int {
|
|
||||||
keys := make(map[int]bool)
|
|
||||||
list := []int{}
|
|
||||||
for _, entry := range intSlice {
|
|
||||||
if _, value := keys[entry]; !value {
|
|
||||||
keys[entry] = true
|
|
||||||
list = append(list, entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
package pokercore
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type HandScore int
|
|
||||||
|
|
||||||
const (
|
|
||||||
ScoreHighCard = HandScore(iota * 100_000_000_000)
|
|
||||||
ScorePair
|
|
||||||
ScoreTwoPair
|
|
||||||
ScoreThreeOfAKind
|
|
||||||
ScoreStraight
|
|
||||||
ScoreFlush
|
|
||||||
ScoreFullHouse
|
|
||||||
ScoreFourOfAKind
|
|
||||||
ScoreStraightFlush
|
|
||||||
ScoreRoyalFlush
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c Card) Score() HandScore {
|
|
||||||
return HandScore(c.Rank.Score())
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(ph)
|
|
||||||
return ph.Score, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func scoreToName(x HandScore) string {
|
|
||||||
switch x {
|
|
||||||
case x >= ScoreRoyalFlush:
|
|
||||||
return "Royal Flush"
|
|
||||||
case x >= ScoreStraightFlush:
|
|
||||||
return "Straight Flush"
|
|
||||||
case x >= ScoreFourOfAKind:
|
|
||||||
return "Four of a Kind"
|
|
||||||
case x >= ScoreFullHouse:
|
|
||||||
return "Full House"
|
|
||||||
case x >= ScoreFlush:
|
|
||||||
return "Flush"
|
|
||||||
case x >= ScoreStraight:
|
|
||||||
return "Straight"
|
|
||||||
case x >= ScoreThreeOfAKind:
|
|
||||||
return "Three of a Kind"
|
|
||||||
case x >= ScoreTwoPair:
|
|
||||||
return "Two Pair"
|
|
||||||
case x >= ScorePair:
|
|
||||||
return "Pair"
|
|
||||||
case x >= ScoreHighCard:
|
|
||||||
return "High Card"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (r Rank) Article() string {
|
|
||||||
switch r {
|
|
||||||
case ACE:
|
|
||||||
return "an"
|
|
||||||
case EIGHT:
|
|
||||||
return "an"
|
|
||||||
default:
|
|
||||||
return "a"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Rank) WithArticle() string {
|
|
||||||
return fmt.Sprintf("%s %s", r.Article(), r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Rank) Pluralize() string {
|
|
||||||
switch r {
|
|
||||||
case ACE:
|
|
||||||
return "Aces"
|
|
||||||
case DEUCE:
|
|
||||||
return "Deuces"
|
|
||||||
case THREE:
|
|
||||||
return "Threes"
|
|
||||||
case FOUR:
|
|
||||||
return "Fours"
|
|
||||||
case FIVE:
|
|
||||||
return "Fives"
|
|
||||||
case SIX:
|
|
||||||
return "Sixes"
|
|
||||||
case SEVEN:
|
|
||||||
return "Sevens"
|
|
||||||
case EIGHT:
|
|
||||||
return "Eights"
|
|
||||||
case NINE:
|
|
||||||
return "Nines"
|
|
||||||
case TEN:
|
|
||||||
return "Tens"
|
|
||||||
case JACK:
|
|
||||||
return "Jacks"
|
|
||||||
case QUEEN:
|
|
||||||
return "Queens"
|
|
||||||
case KING:
|
|
||||||
return "Kings"
|
|
||||||
default:
|
|
||||||
panic("nope")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x HandScore) String() string {
|
|
||||||
return fmt.Sprintf("<HandScore %d>", x)
|
|
||||||
//return scoreToName(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Rank) Score() HandScore {
|
|
||||||
switch r {
|
|
||||||
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
|
|
||||||
case ACE:
|
|
||||||
return 14
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
@ -49,3 +49,42 @@ func TestDealing(t *testing.T) {
|
|||||||
x := d.Count()
|
x := d.Count()
|
||||||
assert.Equal(t, 0, x)
|
assert.Equal(t, 0, x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSpecialCaseOfFiveHighStraightFlush(t *testing.T) {
|
||||||
|
// actual bug from first implementation
|
||||||
|
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())
|
||||||
|
ph, err := cards.PokerHand()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
description := ph.Description()
|
||||||
|
assert.Equal(t, "a five high straight flush in ♥", description)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpecialCaseOfFiveHighStraight(t *testing.T) {
|
||||||
|
// actual bug from first implementation
|
||||||
|
d := NewDeckFromCards(Cards{
|
||||||
|
Card{Rank: ACE, Suit: HEART},
|
||||||
|
Card{Rank: DEUCE, Suit: HEART},
|
||||||
|
Card{Rank: THREE, Suit: SPADE},
|
||||||
|
Card{Rank: FOUR, Suit: HEART},
|
||||||
|
Card{Rank: FIVE, Suit: CLUB},
|
||||||
|
})
|
||||||
|
d.ShuffleDeterministically(123456789)
|
||||||
|
cards := d.Deal(5)
|
||||||
|
|
||||||
|
cards = cards.SortByRankAscending()
|
||||||
|
expected := "2♥,3♠,4♥,5♣,A♥"
|
||||||
|
assert.Equal(t, expected, cards.String())
|
||||||
|
ph, err := cards.PokerHand()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
description := ph.Description()
|
||||||
|
assert.Equal(t, "a five high straight", description)
|
||||||
|
}
|
@ -23,6 +23,10 @@ type PokerHand struct {
|
|||||||
Score HandScore
|
Score HandScore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Cards) Append(other Cards) Cards {
|
||||||
|
return append(c, other...)
|
||||||
|
}
|
||||||
|
|
||||||
func (c Cards) PokerHand() (*PokerHand, error) {
|
func (c Cards) PokerHand() (*PokerHand, error) {
|
||||||
if len(c) != 5 {
|
if len(c) != 5 {
|
||||||
return nil, fmt.Errorf("hand must have 5 cards to be scored as a poker hand")
|
return nil, fmt.Errorf("hand must have 5 cards to be scored as a poker hand")
|
||||||
@ -65,7 +69,19 @@ func (c Cards) PokerHand() (*PokerHand, error) {
|
|||||||
|
|
||||||
if c.containsStraight() {
|
if c.containsStraight() {
|
||||||
ph.Type = Straight
|
ph.Type = Straight
|
||||||
ph.Score = ScoreStraight + 1000*c.HighestRank().Score()
|
|
||||||
|
// 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
|
return ph, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,17 +118,40 @@ func (c Cards) PokerHand() (*PokerHand, error) {
|
|||||||
return ph, nil
|
return ph, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c PokerHand) HighestRank() Rank {
|
func (ph PokerHand) ToSortedCards() Cards {
|
||||||
return c.Hand.HighestRank()
|
sorted := ph.Hand.SortByRankAscending()
|
||||||
|
return sorted
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c PokerHand) String() string {
|
func (ph PokerHand) Compare(other PokerHand) int {
|
||||||
sortedHand := c.Hand.SortByRank()
|
if ph.Score > other.Score {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if ph.Score < other.Score {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph PokerHand) HighestRank() Rank {
|
||||||
|
return ph.Hand.HighestRank()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph PokerHand) String() string {
|
||||||
|
return fmt.Sprintf("<PokerHand: %s (%s)>", ph.Hand.String(), ph.Description())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c PokerHand) Description() string {
|
||||||
|
sortedHand := c.Hand.SortByRankAscending()
|
||||||
if c.Type == RoyalFlush {
|
if c.Type == RoyalFlush {
|
||||||
return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit)
|
return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit)
|
||||||
}
|
}
|
||||||
if c.Hand.containsStraightFlush() {
|
if c.Hand.containsStraightFlush() {
|
||||||
return fmt.Sprintf("%s high straight flush in %s", c.HighestRank().WithArticle(), sortedHand[0].Suit)
|
if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE {
|
||||||
|
// special case for steel wheel
|
||||||
|
return fmt.Sprintf("%s high straight flush in %s", sortedHand[3].Rank.WithArticle(), sortedHand[4].Suit)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s high straight flush in %s", c.HighestRank().WithArticle(), sortedHand[4].Suit)
|
||||||
}
|
}
|
||||||
if c.Hand.containsFourOfAKind() {
|
if c.Hand.containsFourOfAKind() {
|
||||||
return fmt.Sprintf("four %s with %s", c.Hand.fourOfAKindRank().Pluralize(), c.Hand.fourOfAKindKicker().Rank.WithArticle())
|
return fmt.Sprintf("four %s with %s", c.Hand.fourOfAKindRank().Pluralize(), c.Hand.fourOfAKindKicker().Rank.WithArticle())
|
||||||
@ -121,9 +160,13 @@ func (c PokerHand) String() string {
|
|||||||
return fmt.Sprintf("a full house, %s full of %s", c.Hand.fullHouseTripsRank().Pluralize(), c.Hand.fullHousePairRank().Pluralize())
|
return fmt.Sprintf("a full house, %s full of %s", c.Hand.fullHouseTripsRank().Pluralize(), c.Hand.fullHousePairRank().Pluralize())
|
||||||
}
|
}
|
||||||
if c.Hand.containsFlush() {
|
if c.Hand.containsFlush() {
|
||||||
return fmt.Sprintf("%s high flush in %s", c.HighestRank().WithArticle(), sortedHand[0].Suit)
|
return fmt.Sprintf("%s high flush in %s", c.HighestRank().WithArticle(), sortedHand[4].Suit)
|
||||||
}
|
}
|
||||||
if c.Hand.containsStraight() {
|
if c.Hand.containsStraight() {
|
||||||
|
if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE {
|
||||||
|
// special case for wheel straight
|
||||||
|
return fmt.Sprintf("%s high straight", sortedHand[3].Rank.WithArticle())
|
||||||
|
}
|
||||||
return fmt.Sprintf("%s high straight", c.HighestRank().WithArticle())
|
return fmt.Sprintf("%s high straight", c.HighestRank().WithArticle())
|
||||||
}
|
}
|
||||||
if c.Hand.containsThreeOfAKind() {
|
if c.Hand.containsThreeOfAKind() {
|
||||||
@ -151,6 +194,7 @@ func (c PokerHand) String() string {
|
|||||||
c.Hand.pairThirdKicker().Rank.WithArticle(),
|
c.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",
|
145
rank.go
Normal file
145
rank.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package pokercore
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Rank rune
|
||||||
|
|
||||||
|
const (
|
||||||
|
ACE Rank = 'A'
|
||||||
|
DEUCE Rank = '2'
|
||||||
|
THREE Rank = '3'
|
||||||
|
FOUR Rank = '4'
|
||||||
|
FIVE Rank = '5'
|
||||||
|
SIX Rank = '6'
|
||||||
|
SEVEN Rank = '7'
|
||||||
|
EIGHT Rank = '8'
|
||||||
|
NINE Rank = '9'
|
||||||
|
TEN Rank = 'T'
|
||||||
|
JACK Rank = 'J'
|
||||||
|
QUEEN Rank = 'Q'
|
||||||
|
KING Rank = 'K'
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r Rank) String() string {
|
||||||
|
switch r {
|
||||||
|
case ACE:
|
||||||
|
return "ace"
|
||||||
|
case DEUCE:
|
||||||
|
return "deuce"
|
||||||
|
case THREE:
|
||||||
|
return "three"
|
||||||
|
case FOUR:
|
||||||
|
return "four"
|
||||||
|
case FIVE:
|
||||||
|
return "five"
|
||||||
|
case SIX:
|
||||||
|
return "six"
|
||||||
|
case SEVEN:
|
||||||
|
return "seven"
|
||||||
|
case EIGHT:
|
||||||
|
return "eight"
|
||||||
|
case NINE:
|
||||||
|
return "nine"
|
||||||
|
case TEN:
|
||||||
|
return "ten"
|
||||||
|
case JACK:
|
||||||
|
return "jack"
|
||||||
|
case QUEEN:
|
||||||
|
return "queen"
|
||||||
|
case KING:
|
||||||
|
return "king"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rank) Symbol() string {
|
||||||
|
return string(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rank) Int() int {
|
||||||
|
return int(r.Score())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rank) HandScore() HandScore {
|
||||||
|
return HandScore(r.Int())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rank) Article() string {
|
||||||
|
switch r {
|
||||||
|
case ACE:
|
||||||
|
return "an"
|
||||||
|
case EIGHT:
|
||||||
|
return "an"
|
||||||
|
default:
|
||||||
|
return "a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rank) WithArticle() string {
|
||||||
|
return fmt.Sprintf("%s %s", r.Article(), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rank) Pluralize() string {
|
||||||
|
switch r {
|
||||||
|
case ACE:
|
||||||
|
return "aces"
|
||||||
|
case DEUCE:
|
||||||
|
return "deuces"
|
||||||
|
case THREE:
|
||||||
|
return "threes"
|
||||||
|
case FOUR:
|
||||||
|
return "fours"
|
||||||
|
case FIVE:
|
||||||
|
return "fives"
|
||||||
|
case SIX:
|
||||||
|
return "sixes"
|
||||||
|
case SEVEN:
|
||||||
|
return "sevens"
|
||||||
|
case EIGHT:
|
||||||
|
return "eights"
|
||||||
|
case NINE:
|
||||||
|
return "nines"
|
||||||
|
case TEN:
|
||||||
|
return "tens"
|
||||||
|
case JACK:
|
||||||
|
return "jacks"
|
||||||
|
case QUEEN:
|
||||||
|
return "queens"
|
||||||
|
case KING:
|
||||||
|
return "kings"
|
||||||
|
default:
|
||||||
|
panic("nope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rank) Score() HandScore {
|
||||||
|
switch r {
|
||||||
|
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
|
||||||
|
case ACE:
|
||||||
|
return 14
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
39
scoring.go
Normal file
39
scoring.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package pokercore
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type HandScore int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScoreHighCard = HandScore(iota * 100_000_000_000)
|
||||||
|
ScorePair
|
||||||
|
ScoreTwoPair
|
||||||
|
ScoreThreeOfAKind
|
||||||
|
ScoreStraight
|
||||||
|
ScoreFlush
|
||||||
|
ScoreFullHouse
|
||||||
|
ScoreFourOfAKind
|
||||||
|
ScoreStraightFlush
|
||||||
|
ScoreRoyalFlush
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c Card) Score() HandScore {
|
||||||
|
return HandScore(c.Rank.Score())
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println(ph)
|
||||||
|
return ph.Score, nil
|
||||||
|
}
|
||||||
|
func (x HandScore) String() string {
|
||||||
|
return fmt.Sprintf("<HandScore %d>", x)
|
||||||
|
//return scoreToName(x)
|
||||||
|
}
|
@ -16,6 +16,19 @@ func TestAceLowStraight(t *testing.T) {
|
|||||||
FiveOfSpades(),
|
FiveOfSpades(),
|
||||||
}
|
}
|
||||||
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
||||||
|
ph, err := hand.PokerHand()
|
||||||
|
assert.Nil(t, err, "Expected no error")
|
||||||
|
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero 0")
|
||||||
|
assert.Less(t, ph.Score, 100000000000000000, "Expected score to be less than 100000000000000000")
|
||||||
|
assert.Equal(t, ph.Score, ScoreStraight+100*FIVE.Score())
|
||||||
|
assert.Equal(t, ph.Description(), "a five high straight")
|
||||||
|
assert.True(t, hand.HighestRank() == ACE, "Expected highest rank to be an ace")
|
||||||
|
assert.True(t, hand.SortByRankAscending().First().Rank == DEUCE, "Expected first card to be a deuce")
|
||||||
|
assert.True(t, hand.SortByRankAscending().Last().Rank == ACE, "Expected last card in sorted to be a ace")
|
||||||
|
assert.True(t, hand.SortByRankAscending().Second().Rank == THREE, "Expected second card to be a three")
|
||||||
|
assert.True(t, hand.SortByRankAscending().Third().Rank == FOUR, "Expected third card to be a four")
|
||||||
|
assert.True(t, hand.SortByRankAscending().Fourth().Rank == FIVE, "Expected fourth card to be a five")
|
||||||
|
assert.True(t, hand.SortByRankAscending().Fifth().Rank == ACE, "Expected fifth card to be an ace")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAceHighStraight(t *testing.T) {
|
func TestAceHighStraight(t *testing.T) {
|
||||||
@ -28,13 +41,17 @@ func TestAceHighStraight(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
assert.True(t, hand.containsStraight(), "Expected hand to be a straight")
|
||||||
|
|
||||||
newDeck := NewDeckFromCards(hand)
|
newDeck := NewDeckFromCards(hand)
|
||||||
newDeck.ShuffleDeterministically(123456789)
|
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)
|
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")
|
||||||
|
sortedHand := shuffledHand.SortByRankAscending()
|
||||||
|
assert.True(t, sortedHand[0].Rank == TEN, "Expected lowest rank to be a ten")
|
||||||
|
assert.True(t, sortedHand[1].Rank == JACK, "Expected second lowest rank to be a jack")
|
||||||
|
assert.True(t, sortedHand[2].Rank == QUEEN, "Expected third lowest rank to be a queen")
|
||||||
|
assert.True(t, sortedHand[3].Rank == KING, "Expected fourth lowest rank to be a king")
|
||||||
|
assert.True(t, sortedHand[4].Rank == ACE, "Expected highest rank to be an ace")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOtherStraight(t *testing.T) {
|
func TestOtherStraight(t *testing.T) {
|
||||||
@ -197,3 +214,18 @@ func TestHandScore(t *testing.T) {
|
|||||||
fmt.Printf("PokerHand: %v+\n", ph)
|
fmt.Printf("PokerHand: %v+\n", ph)
|
||||||
fmt.Printf("PH score: %d\n", ph.Score)
|
fmt.Printf("PH score: %d\n", ph.Score)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTwoPairBug(t *testing.T) {
|
||||||
|
// this is an actual bug in the first implementation
|
||||||
|
c, err := NewCardsFromString("9♠,9♣,Q♥,Q♦,K♣")
|
||||||
|
assert.Nil(t, err, "Expected no error")
|
||||||
|
|
||||||
|
ph, err := c.PokerHand()
|
||||||
|
assert.Nil(t, err, "Expected no error")
|
||||||
|
assert.Greater(t, ph.Score, 0, "Expected score to be nonzero 0")
|
||||||
|
desc := ph.Description()
|
||||||
|
assert.Equal(t, desc, "two pair, queens and nines with a king")
|
||||||
|
|
||||||
|
fmt.Printf("PokerHand: %v+\n", ph)
|
||||||
|
fmt.Printf("PH score: %d\n", ph.Score)
|
||||||
|
}
|
19
suit.go
Normal file
19
suit.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package pokercore
|
||||||
|
|
||||||
|
type Suit rune
|
||||||
|
|
||||||
|
func (s Suit) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Suit) Symbol() string {
|
||||||
|
// this is just to match Rank's Symbol() method
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
CLUB Suit = '\u2663' // ♣
|
||||||
|
SPADE Suit = '\u2660' // ♠
|
||||||
|
DIAMOND Suit = '\u2666' // ♦
|
||||||
|
HEART Suit = '\u2665' // ♥
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user