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
|
||||
|
||||
test:
|
||||
cd pokercore && go test -v ./...
|
||||
go test -v ./...
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
func (d *Deck) FormatForTerminal() string {
|
||||
return d.Cards.FormatForTerminal()
|
||||
}
|
||||
|
||||
func (d *Deck) String() string {
|
||||
return fmt.Sprintf("Deck{%s size=%d dealt=%d}", d.Cards, d.Count(), d.Dealt)
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -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())
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -1,24 +1,32 @@
|
|||
package pokercore
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
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
|
||||
// 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) {
|
||||
// and returns the best hand by score. this is recursion and is exponential in time complexity
|
||||
func (hand Cards) IdentifyBestFiveCardPokerHand() (Cards, error) {
|
||||
|
||||
//fmt.Println("hand: ", hand)
|
||||
if hand.containsDuplicates() {
|
||||
return nil, ErrDuplicateCard
|
||||
}
|
||||
|
||||
newHands := make([]Cards, len(hand))
|
||||
for i := 0; i < len(hand); i++ {
|
||||
newHand := hand[:i]
|
||||
newHand = append(newHand, hand[i+1:]...)
|
||||
newHand := make(Cards, len(hand)-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
|
||||
}
|
||||
//fmt.Printf("newHands: %+v\n", newHands)
|
||||
var bestHand Cards
|
||||
var bestScore HandScore
|
||||
|
||||
|
@ -30,7 +38,7 @@ func (hand Cards) identifyBestFiveCardPokerHand() (Cards, error) {
|
|||
bestHand = h
|
||||
}
|
||||
} else {
|
||||
rh, _ := h.identifyBestFiveCardPokerHand()
|
||||
rh, _ := h.IdentifyBestFiveCardPokerHand()
|
||||
score, _ := rh.PokerHandScore()
|
||||
if score > bestScore {
|
||||
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
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ func (c Cards) pairRank() Rank {
|
|||
if !c.containsPair() {
|
||||
panic("hand must have a pair to have a pair rank")
|
||||
}
|
||||
sorted := c.SortByRank()
|
||||
sorted := c.SortByRankAscending()
|
||||
if sorted[0].Rank == sorted[1].Rank {
|
||||
return sorted[0].Rank
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ func (c Cards) pairFirstKicker() Card {
|
|||
if !c.containsPair() {
|
||||
panic("hand must have a pair to have a first kicker")
|
||||
}
|
||||
sorted := c.SortByRank()
|
||||
sorted := c.SortByRankAscending()
|
||||
if sorted[0].Rank == sorted[1].Rank {
|
||||
return sorted[4]
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func (c Cards) pairSecondKicker() Card {
|
|||
if !c.containsPair() {
|
||||
panic("hand must have a pair to have a second kicker")
|
||||
}
|
||||
sorted := c.SortByRank()
|
||||
sorted := c.SortByRankAscending()
|
||||
if sorted[0].Rank == sorted[1].Rank {
|
||||
// first kicker is [4]
|
||||
return sorted[3]
|
||||
|
@ -97,7 +97,7 @@ func (c Cards) pairThirdKicker() Card {
|
|||
if !c.containsPair() {
|
||||
panic("hand must have a pair to have a third kicker")
|
||||
}
|
||||
sorted := c.SortByRank()
|
||||
sorted := c.SortByRankAscending()
|
||||
if sorted[0].Rank == sorted[1].Rank {
|
||||
// first kicker is [4]
|
||||
// second kicker is [3]
|
||||
|
@ -125,7 +125,7 @@ func (c Cards) twoPairBiggestPair() Rank {
|
|||
if !c.containsTwoPair() {
|
||||
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 {
|
||||
return sorted[2].Rank
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ func (c Cards) twoPairSmallestPair() Rank {
|
|||
if !c.containsTwoPair() {
|
||||
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 {
|
||||
return sorted[2].Rank
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ func (c Cards) twoPairKicker() Card {
|
|||
if !c.containsTwoPair() {
|
||||
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 {
|
||||
return sorted[4]
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ func (c Cards) threeOfAKindTripsRank() Rank {
|
|||
if !c.containsThreeOfAKind() {
|
||||
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 {
|
||||
return sorted[0].Rank
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ func (c Cards) threeOfAKindKickers() Cards {
|
|||
if !c.containsThreeOfAKind() {
|
||||
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 {
|
||||
return Cards{sorted[3], sorted[4]}
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ func (c Cards) fourOfAKindRank() Rank {
|
|||
if !c.containsFourOfAKind() {
|
||||
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 {
|
||||
return sorted[0].Rank
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ func (c Cards) fourOfAKindKicker() Card {
|
|||
if !c.containsFourOfAKind() {
|
||||
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 {
|
||||
return sorted[4]
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ func (c Cards) fullHouseTripsRank() Rank {
|
|||
if !c.containsFullHouse() {
|
||||
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 {
|
||||
return sorted[0].Rank
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ func (c Cards) fullHousePairRank() Rank {
|
|||
if !c.containsFullHouse() {
|
||||
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 {
|
||||
return sorted[4].Rank
|
||||
}
|
||||
|
@ -305,7 +305,7 @@ func (hand Cards) containsStraight() bool {
|
|||
if !hand.IsFiveCardPokerHand() {
|
||||
panic("hand must have 5 cards to be scored")
|
||||
}
|
||||
sorted := hand.SortByRank()
|
||||
sorted := hand.SortByRankAscending()
|
||||
|
||||
if sorted[4].Rank == ACE && sorted[3].Rank == FIVE {
|
||||
// special case for A-5 straight
|
||||
|
@ -335,7 +335,13 @@ func (hand Cards) containsRoyalFlush() bool {
|
|||
if !hand.IsFiveCardPokerHand() {
|
||||
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 false
|
||||
|
@ -345,7 +351,7 @@ func (hand Cards) containsFourOfAKind() bool {
|
|||
if !hand.IsFiveCardPokerHand() {
|
||||
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 {
|
||||
// the quads precede the kicker
|
||||
return true
|
||||
|
@ -361,7 +367,7 @@ func (hand Cards) containsFullHouse() bool {
|
|||
if !hand.IsFiveCardPokerHand() {
|
||||
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 {
|
||||
// the trips precede the pair
|
||||
|
@ -378,7 +384,7 @@ func (hand Cards) containsPair() bool {
|
|||
if !hand.IsFiveCardPokerHand() {
|
||||
panic("hand must have 5 cards to be scored")
|
||||
}
|
||||
sorted := hand.SortByRank()
|
||||
sorted := hand.SortByRankAscending()
|
||||
if sorted[0].Rank == sorted[1].Rank {
|
||||
return true
|
||||
}
|
||||
|
@ -398,7 +404,7 @@ func (hand Cards) containsThreeOfAKind() bool {
|
|||
if !hand.IsFiveCardPokerHand() {
|
||||
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 {
|
||||
return true
|
||||
}
|
||||
|
@ -415,7 +421,7 @@ func (hand Cards) containsTwoPair() bool {
|
|||
if !hand.IsFiveCardPokerHand() {
|
||||
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 {
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"git.eeqj.de/sneak/go-poker/pokercore"
|
||||
"git.eeqj.de/sneak/pokercore"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -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()
|
||||
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
|
||||
}
|
||||
|
||||
func (c Cards) Append(other Cards) Cards {
|
||||
return append(c, other...)
|
||||
}
|
||||
|
||||
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")
|
||||
|
@ -65,7 +69,19 @@ func (c Cards) PokerHand() (*PokerHand, error) {
|
|||
|
||||
if c.containsStraight() {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -102,17 +118,40 @@ func (c Cards) PokerHand() (*PokerHand, error) {
|
|||
return ph, nil
|
||||
}
|
||||
|
||||
func (c PokerHand) HighestRank() Rank {
|
||||
return c.Hand.HighestRank()
|
||||
func (ph PokerHand) ToSortedCards() Cards {
|
||||
sorted := ph.Hand.SortByRankAscending()
|
||||
return sorted
|
||||
}
|
||||
|
||||
func (c PokerHand) String() string {
|
||||
sortedHand := c.Hand.SortByRank()
|
||||
func (ph PokerHand) Compare(other PokerHand) int {
|
||||
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 {
|
||||
return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit)
|
||||
}
|
||||
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() {
|
||||
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())
|
||||
}
|
||||
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 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())
|
||||
}
|
||||
if c.Hand.containsThreeOfAKind() {
|
||||
|
@ -151,6 +194,7 @@ func (c PokerHand) String() string {
|
|||
c.Hand.pairThirdKicker().Rank.WithArticle(),
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
// "ace high with an eight, a seven, a six, and a deuce"
|
||||
"%s high with %s, %s, %s, and %s",
|
|
@ -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
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
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) {
|
||||
|
@ -28,13 +41,17 @@ func TestAceHighStraight(t *testing.T) {
|
|||
}
|
||||
|
||||
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.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) {
|
||||
|
@ -197,3 +214,18 @@ func TestHandScore(t *testing.T) {
|
|||
fmt.Printf("PokerHand: %v+\n", ph)
|
||||
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)
|
||||
}
|
|
@ -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