added more tests, cleaned up some code, found another bug
This commit is contained in:
parent
5f7dba942c
commit
14ffbe4eb4
|
@ -7,74 +7,148 @@ import (
|
|||
"git.eeqj.de/sneak/pokercore"
|
||||
)
|
||||
|
||||
// just like on tv
|
||||
var delayTime = 2 * time.Second
|
||||
var playerCount = 8
|
||||
|
||||
type Player struct {
|
||||
Hand pokercore.Cards
|
||||
ScoredHand *pokercore.PokerHand
|
||||
Position int
|
||||
CommunityCardsAvailable pokercore.Cards
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
Deck *pokercore.Deck
|
||||
Players []*Player
|
||||
Community pokercore.Cards
|
||||
Street int
|
||||
}
|
||||
|
||||
func NewGame(seed int) *Game {
|
||||
g := &Game{}
|
||||
g.Street = 0
|
||||
g.Deck = pokercore.NewDeck()
|
||||
g.SpreadCards()
|
||||
fmt.Printf("Shuffle up and deal!\n")
|
||||
time.Sleep(delayTime)
|
||||
fmt.Printf("Shuffling...\n")
|
||||
g.Deck.ShuffleDeterministically(3141592653)
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *Game) StreetAsString() string {
|
||||
switch g.Street {
|
||||
case 0:
|
||||
return "pre-flop"
|
||||
case 1:
|
||||
return "flop"
|
||||
case 2:
|
||||
return "turn"
|
||||
case 3:
|
||||
return "river"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func (g *Game) SpreadCards() {
|
||||
fmt.Printf("deck before shuffling: %s\n", g.Deck.FormatForTerminal())
|
||||
}
|
||||
|
||||
func (g *Game) DealPlayersIn() {
|
||||
for i := 0; i < playerCount; i++ {
|
||||
p := Player{Hand: g.Deck.Deal(2), Position: i + 1}
|
||||
g.Players = append(g.Players, &p)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) ShowGameStatus() {
|
||||
fmt.Printf("Street: %s\n", g.StreetAsString())
|
||||
fmt.Printf("Community cards: %s\n", g.Community.FormatForTerminal())
|
||||
for _, p := range g.Players {
|
||||
if p != nil {
|
||||
fmt.Printf("Player %d: %s\n", p.Position, p.Hand.FormatForTerminal())
|
||||
if g.Street > 0 {
|
||||
ac := append(p.Hand, g.Community...)
|
||||
ph, err := ac.PokerHand()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Player %d has %s\n", p.Position, ph.Description())
|
||||
fmt.Printf("Player %d Score: %d\n", p.Position, ph.Score)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) DealFlop() {
|
||||
g.Community = g.Deck.Deal(3)
|
||||
g.Street = 1
|
||||
}
|
||||
|
||||
func (g *Game) DealTurn() {
|
||||
g.Community = append(g.Community, g.Deck.Deal(1)...)
|
||||
g.Street = 2
|
||||
}
|
||||
|
||||
func (g *Game) DealRiver() {
|
||||
g.Community = append(g.Community, g.Deck.Deal(1)...)
|
||||
g.Street = 3
|
||||
}
|
||||
|
||||
func (g *Game) ShowWinner() {
|
||||
var winner *Player
|
||||
var winningHand *pokercore.PokerHand
|
||||
for _, p := range g.Players {
|
||||
if p != nil {
|
||||
ac := append(p.Hand, g.Community...)
|
||||
ph, err := ac.PokerHand()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if winner == nil {
|
||||
winner = p
|
||||
winningHand = ph
|
||||
} else {
|
||||
if ph.Score > winningHand.Score {
|
||||
winner = p
|
||||
winningHand = ph
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("Winner: Player %d with %s.\n", winner.Position, winningHand.Description())
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
sleepTime := 2 * time.Second
|
||||
playerCount := 8
|
||||
d := pokercore.NewDeck()
|
||||
fmt.Printf("deck before shuffling: %s\n", d.FormatForTerminal())
|
||||
// this "randomly chosen" seed somehow deals pocket queens, pocket kings, and pocket aces to three players.
|
||||
// what are the odds? lol
|
||||
d.ShuffleDeterministically(1337)
|
||||
fmt.Printf("deck after shuffling: %s\n", d.FormatForTerminal())
|
||||
var players []pokercore.Cards
|
||||
for i := 0; i < playerCount; i++ {
|
||||
players = append(players, d.Deal(2))
|
||||
}
|
||||
for i, p := range players {
|
||||
fmt.Printf("player %d: %s\n", i+1, p.FormatForTerminal())
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
//g := NewGame(1337))
|
||||
|
||||
fmt.Printf("##############################################\n")
|
||||
// deal the flop
|
||||
var community pokercore.Cards
|
||||
community = d.Deal(3)
|
||||
fmt.Printf("flop: %s\n", community.FormatForTerminal())
|
||||
time.Sleep(sleepTime)
|
||||
// nothing up my sleeve:
|
||||
g := NewGame(3141592653)
|
||||
|
||||
fmt.Printf("##############################################\n")
|
||||
turn := d.Deal(1)
|
||||
fmt.Printf("turn: %s\n", turn.FormatForTerminal())
|
||||
community = append(community, turn...)
|
||||
fmt.Printf("board is now: %s\n", community.FormatForTerminal())
|
||||
g.ShowGameStatus()
|
||||
|
||||
time.Sleep(sleepTime)
|
||||
g.DealPlayersIn()
|
||||
|
||||
fmt.Printf("##############################################\n")
|
||||
river := d.Deal(1)
|
||||
fmt.Printf("river: %s\n", river.FormatForTerminal())
|
||||
community = append(community, river...)
|
||||
fmt.Printf("board is now: %s\n", community.FormatForTerminal())
|
||||
g.ShowGameStatus()
|
||||
|
||||
g.DealFlop()
|
||||
|
||||
g.ShowGameStatus()
|
||||
|
||||
g.DealTurn()
|
||||
|
||||
g.ShowGameStatus()
|
||||
|
||||
g.DealRiver()
|
||||
|
||||
g.ShowGameStatus()
|
||||
|
||||
g.ShowWinner()
|
||||
|
||||
fmt.Printf("##############################################\n")
|
||||
var winningPlayer int
|
||||
var winningHand *pokercore.PokerHand
|
||||
for i, p := range players {
|
||||
fmt.Printf("player %d: %s\n", i+1, p.FormatForTerminal())
|
||||
playerCards, err := p.Append(community).IdentifyBestFiveCardPokerHand()
|
||||
if err != nil {
|
||||
panic("error evaluating hand: " + err.Error())
|
||||
}
|
||||
ph, err := playerCards.PokerHand()
|
||||
if err != nil {
|
||||
panic("error evaluating hand: " + err.Error())
|
||||
}
|
||||
fmt.Printf("player %d has a %s\n", i+1, ph.Description())
|
||||
if winningHand == nil {
|
||||
winningPlayer = i
|
||||
winningHand = ph
|
||||
continue
|
||||
}
|
||||
if ph.Score > winningHand.Score {
|
||||
winningPlayer = i
|
||||
winningHand = ph
|
||||
}
|
||||
}
|
||||
fmt.Printf("##############################################\n")
|
||||
fmt.Printf("player %d wins with %s\n", winningPlayer+1, winningHand.Description())
|
||||
fmt.Printf("##############################################\n")
|
||||
fmt.Printf("What a strange game. The only winning move is to bet really big.\n")
|
||||
fmt.Printf("Insert coin to play again.\n")
|
||||
|
||||
}
|
||||
|
|
|
@ -1,34 +1,5 @@
|
|||
package pokercore
|
||||
|
||||
func (c Cards) scoreStraightFlush() HandScore {
|
||||
if !c.containsStraightFlush() {
|
||||
panic("hand must be a straight flush to score it")
|
||||
}
|
||||
return ScoreStraightFlush + 1000*c.HighestRank().Score()
|
||||
}
|
||||
|
||||
func (c Cards) scoreFlush() HandScore {
|
||||
if !c.containsFlush() {
|
||||
panic("hand must be a flush to score it")
|
||||
}
|
||||
var score HandScore
|
||||
for _, card := range c {
|
||||
score += card.Rank.Score()
|
||||
}
|
||||
return ScoreFlush + score
|
||||
}
|
||||
|
||||
func (c Cards) scoreHighCard() HandScore {
|
||||
if !c.isUnmadeHand() {
|
||||
panic("hand must be a high card to score it")
|
||||
}
|
||||
var score HandScore
|
||||
for _, card := range c {
|
||||
score += card.Rank.Score()
|
||||
}
|
||||
return ScoreHighCard + score
|
||||
}
|
||||
|
||||
func (c Cards) pairRank() Rank {
|
||||
if !c.containsPair() {
|
||||
panic("hand must have a pair to have a pair rank")
|
||||
|
|
98
pokerhand.go
98
pokerhand.go
|
@ -1,6 +1,9 @@
|
|||
package pokercore
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type PokerHandType int
|
||||
|
||||
|
@ -28,93 +31,118 @@ func (c Cards) Append(other Cards) Cards {
|
|||
}
|
||||
|
||||
func (c Cards) PokerHand() (*PokerHand, error) {
|
||||
if len(c) != 5 {
|
||||
return nil, fmt.Errorf("hand must have 5 cards to be scored as a poker hand")
|
||||
}
|
||||
if c.containsDuplicates() {
|
||||
return nil, fmt.Errorf("hand must have no duplicates to be scored as a poker hand")
|
||||
return nil, errors.New("hand must have no duplicates to be scored as a poker hand")
|
||||
}
|
||||
if len(c) < 5 {
|
||||
return nil, errors.New("hand must have at least 5 cards to be scored as a poker hand")
|
||||
}
|
||||
|
||||
phc, err := c.IdentifyBestFiveCardPokerHand()
|
||||
if err != nil {
|
||||
// this should in theory never happen
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ph := new(PokerHand)
|
||||
ph.Hand = c
|
||||
if c.containsRoyalFlush() {
|
||||
ph.Hand = phc.SortByRankAscending()
|
||||
if ph.Hand.containsRoyalFlush() {
|
||||
ph.Type = RoyalFlush
|
||||
ph.Score = ScoreRoyalFlush
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
if c.containsStraightFlush() {
|
||||
if ph.Hand.containsStraightFlush() {
|
||||
ph.Type = StraightFlush
|
||||
ph.Score = ph.Hand.scoreStraightFlush()
|
||||
ph.Score = ScoreStraightFlush
|
||||
ph.Score += 1000 * ph.Hand.HighestRank().Score()
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
if c.containsFourOfAKind() {
|
||||
if ph.Hand.containsFourOfAKind() {
|
||||
ph.Type = FourOfAKind
|
||||
ph.Score = ScoreFourOfAKind + 1000*c.fourOfAKindRank().Score() + c.fourOfAKindKicker().Score()
|
||||
ph.Score = ScoreFourOfAKind
|
||||
ph.Score += 1000 * ph.Hand.fourOfAKindRank().Score()
|
||||
ph.Score += 100 * ph.Hand.fourOfAKindKicker().Score()
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
if c.containsFullHouse() {
|
||||
if ph.Hand.containsFullHouse() {
|
||||
ph.Type = FullHouse
|
||||
ph.Score = ScoreFullHouse + 1000*c.fullHouseTripsRank().Score() + c.fullHousePairRank().Score()
|
||||
ph.Score = ScoreFullHouse
|
||||
// FIXME write a good test for this
|
||||
ph.Score += 1000 * ph.Hand.fullHouseTripsRank().Score()
|
||||
ph.Score += 100 * ph.Hand.fullHousePairRank().Score()
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
if c.containsFlush() {
|
||||
if ph.Hand.containsFlush() {
|
||||
ph.Type = Flush
|
||||
ph.Score = c.scoreFlush()
|
||||
ph.Score = ScoreFlush
|
||||
// flush base score plus sum of card ranks
|
||||
ph.Score += ph.Hand[0].Score()
|
||||
ph.Score += ph.Hand[1].Score()
|
||||
ph.Score += ph.Hand[2].Score()
|
||||
ph.Score += ph.Hand[3].Score()
|
||||
ph.Score += ph.Hand[4].Score()
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
if c.containsStraight() {
|
||||
if ph.Hand.containsStraight() {
|
||||
ph.Type = Straight
|
||||
ph.Score = ScoreStraight
|
||||
|
||||
// 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()
|
||||
sorted := ph.Hand.SortByRankAscending()
|
||||
|
||||
if sorted[3].Rank == FIVE && sorted[4].Rank == ACE {
|
||||
// 5-high straight
|
||||
ph.Score = ScoreStraight + 1000*sorted[3].Score()
|
||||
// 5-high straight, scored by the five's rank
|
||||
ph.Score += sorted[3].Score()
|
||||
} else {
|
||||
// All other straights
|
||||
ph.Score = ScoreStraight + 1000*sorted[4].Score()
|
||||
// All other straights are scored by the highest card in the straight
|
||||
ph.Score += sorted[4].Score()
|
||||
}
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
if c.containsThreeOfAKind() {
|
||||
if ph.Hand.containsThreeOfAKind() {
|
||||
ph.Type = ThreeOfAKind
|
||||
ph.Score = ScoreThreeOfAKind
|
||||
ph.Score += 1000 * c.threeOfAKindTripsRank().Score()
|
||||
ph.Score += 100 * c.threeOfAKindFirstKicker().Score()
|
||||
ph.Score += 10 * c.threeOfAKindSecondKicker().Score()
|
||||
ph.Score += 1000 * ph.Hand.threeOfAKindTripsRank().Score()
|
||||
ph.Score += 100 * ph.Hand.threeOfAKindFirstKicker().Score()
|
||||
ph.Score += 10 * ph.Hand.threeOfAKindSecondKicker().Score()
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
if c.containsTwoPair() {
|
||||
if ph.Hand.containsTwoPair() {
|
||||
ph.Type = TwoPair
|
||||
ph.Score = ScoreTwoPair
|
||||
ph.Score += 1000 * c.twoPairBiggestPair().Score()
|
||||
ph.Score += 100 * c.twoPairSmallestPair().Score()
|
||||
ph.Score += 10 * c.twoPairKicker().Score()
|
||||
ph.Score += 1000 * ph.Hand.twoPairBiggestPair().Score()
|
||||
ph.Score += 100 * ph.Hand.twoPairSmallestPair().Score()
|
||||
ph.Score += 10 * ph.Hand.twoPairKicker().Score()
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
if c.containsPair() {
|
||||
if ph.Hand.containsPair() {
|
||||
ph.Type = Pair
|
||||
ph.Score = ScorePair
|
||||
ph.Score += 1000 * c.pairRank().Score()
|
||||
ph.Score += 100 * c.pairFirstKicker().Score()
|
||||
ph.Score += 10 * c.pairSecondKicker().Score()
|
||||
ph.Score += c.pairThirdKicker().Score()
|
||||
ph.Score += 1000 * ph.Hand.pairRank().Score()
|
||||
ph.Score += 100 * ph.Hand.pairFirstKicker().Score()
|
||||
ph.Score += 10 * ph.Hand.pairSecondKicker().Score()
|
||||
ph.Score += ph.Hand.pairThirdKicker().Score()
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
ph.Type = HighCard
|
||||
ph.Score = c.scoreHighCard()
|
||||
ph.Score = ScoreHighCard // base score
|
||||
// unmade hands are scored like flushes, just add up the values
|
||||
ph.Score += ph.Hand[0].Score()
|
||||
ph.Score += ph.Hand[1].Score()
|
||||
ph.Score += ph.Hand[2].Score()
|
||||
ph.Score += ph.Hand[3].Score()
|
||||
ph.Score += ph.Hand[4].Score()
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package pokercore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
@ -25,18 +24,13 @@ func (c Card) Score() HandScore {
|
|||
}
|
||||
|
||||
func (c Cards) PokerHandScore() (HandScore, error) {
|
||||
if len(c) != 5 {
|
||||
return 0, errors.New("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)
|
||||
}
|
||||
|
|
|
@ -78,9 +78,9 @@ func TestAceLowStraight(t *testing.T) {
|
|||
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.Greater(t, ph.Score, 0, "Expected score to be greater than 0")
|
||||
assert.Less(t, ph.Score, 100000000000000000, "Expected score to be less than 100000000000000000")
|
||||
assert.Equal(t, ph.Score, ScoreStraight+1000*FIVE.Score())
|
||||
assert.Equal(t, ph.Score, ScoreStraight+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")
|
||||
|
@ -296,3 +296,54 @@ func TestTwoPairBug(t *testing.T) {
|
|||
//fmt.Printf("PokerHand: %v+\n", ph)
|
||||
//fmt.Printf("PH score: %d\n", ph.Score)
|
||||
}
|
||||
|
||||
func TestScoringStructureQuads(t *testing.T) {
|
||||
handA := Cards{
|
||||
DeuceOfSpades(),
|
||||
DeuceOfHearts(),
|
||||
DeuceOfDiamonds(),
|
||||
DeuceOfClubs(),
|
||||
AceOfSpades(),
|
||||
}
|
||||
|
||||
handB := Cards{
|
||||
ThreeOfSpades(),
|
||||
ThreeOfHearts(),
|
||||
ThreeOfDiamonds(),
|
||||
ThreeOfClubs(),
|
||||
DeuceOfSpades(),
|
||||
}
|
||||
|
||||
phA, err := handA.PokerHand()
|
||||
assert.Nil(t, err, "Expected no error")
|
||||
phB, err := handB.PokerHand()
|
||||
assert.Nil(t, err, "Expected no error")
|
||||
assert.Greater(t, phB.Score, phA.Score, "Expected hand B to be higher than hand A")
|
||||
}
|
||||
|
||||
func TestScoringStructureFullHouse(t *testing.T) {
|
||||
handA := Cards{
|
||||
DeuceOfSpades(),
|
||||
DeuceOfHearts(),
|
||||
DeuceOfDiamonds(),
|
||||
AceOfSpades(),
|
||||
AceOfHearts(),
|
||||
}
|
||||
|
||||
phA, err := handA.PokerHand()
|
||||
assert.Nil(t, err, "Expected no error")
|
||||
deucesFullOfAcesScore := phA.Score
|
||||
|
||||
handB := Cards{
|
||||
ThreeOfSpades(),
|
||||
ThreeOfHearts(),
|
||||
ThreeOfDiamonds(),
|
||||
DeuceOfSpades(),
|
||||
DeuceOfHearts(),
|
||||
}
|
||||
phB, err := handB.PokerHand()
|
||||
assert.Nil(t, err, "Expected no error")
|
||||
threesFullOfDeucesScore := phB.Score
|
||||
|
||||
assert.Greater(t, threesFullOfDeucesScore, deucesFullOfAcesScore, "Expected Threes full of deuces to beat deuces full of aces")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue