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
	
	Block a user