Five Card Draw for Gophers
Tags: go
Published: 2019-06-24 05:46:42

---
title: Five Card Draw for Gophers
published: true
description: Lets make a stupid poker game
tags: Go
---

So, I'm on this airplane.

![Airplane](https://thepracticaldev.s3.amazonaws.com/i/fkg3bmkof7ooag2te07p.jpg)

It's an 8 hour flight from LAX to Lima with an entertainment system to keep me ... entertained. Before starting a movie, I check out the games section and come across this poker game.

![Game](https://thepracticaldev.s3.amazonaws.com/i/rc56r44djcp1r4rz6yrg.jpg)

It's five card draw. One player. In Spanish. Buy in is $200 fake dollars and my balance after 30 minutes is $1,585. Ya, this is not a hard game.

In fact, this game is too easy. Annoyingly easy. Like it's letting me win. Is it possible that this game is actually random and I'm just some genius poker god? Or is the game feeding me cards to let me win? 

This question bothers me. Deeply.

![Facepalm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1f6mwkhlp3ce37e408fc.gif)

Hey, wait a minute. I'm a software engineer. I can just clone the game and fiddle with the internals to determine why this implementation seems so unbalanced. 

![Deadpool Reference](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xiy92b85fuwc8b2bdky6.jpg)

Let's do it!

---

Here's the game: https://github.com/KevBurnsJr/stupid-poker

[![](https://i.imgur.com/ecybRk3.png)](http://stupid-poker.kevburnsjr.com)

---

It starts with a deck

```go
package poker

type deck [52]string

var suits = []string{"S", "H", "D", "C"}
var cards = []string{"A", "2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K"}

func newDeck() *deck {
	d := &deck{}
	for i, s := range suits {
		for j, c := range cards {
			d[i*13+j] = c + s
		}
	}
	return d
}
```

An ordered deck isn't very useful, so let's make it shuffle.

```go
// shuffles the deck in place
func (d *deck) shuffle() {
	for i := len(d) - 1; i > 0; i-- {
		j := rand.Intn(i + 1)
		d[i], d[j] = d[j], d[i]
	}
}
```

Now that we have a deck, let's create the game

```go
const (
	stateReady = iota
	stateDealt
)

type Game interface {
	// Deal initializes the game, returning the player's initial hand and new balance
	Deal() (Hand, int, error)

	// Exchange accepts a slice of integers corresponding to indicies for cards in the given hand
	// It discards those cards from the hand and replaces them with new cards from the deck
	// It returns the final hand and the new balance
	Exchange([]int) (Hand, int)
}
```

The game only has 2 states. Either you are dealing a hand, or you are drawing cards and the hand is over.

```go
type game struct {
	deck    *deck
	state   int
	anty    int
	balance int
	hand    Hand
	mutex   *sync.Mutex
}

func NewGame(anty int, balance int) Game {
	return &game{
		deck:    newDeck(),
		state:   stateReady,
		anty:    anty,
		balance: balance,
		mutex:   &sync.Mutex{},
	}
}

func (g *game) Deal() (Hand, int, error) {
	g.mutex.Lock()
	defer g.mutex.Unlock()
	if g.balance < g.anty {
		return Hand{}, g.balance, ErrNoBalance
	}
	g.deck.shuffle()
	g.balance -= g.anty
	g.hand = newHand(g.deck[0:5])
	g.state = stateDealt
	return g.hand, g.balance, nil
}

func (g *game) Exchange(cards []int) (Hand, int) {
	g.mutex.Lock()
	defer g.mutex.Unlock()
	if g.state != stateDealt {
		return g.hand, g.balance
	}
	hand := g.hand
	for i, n := range cards {
		if n >= len(hand) {
			continue
		}
		hand[n] = g.deck[i+5]
	}
	res := hand.Score()
	g.balance += payout[res]
	if payout[res] > 0 {
		g.balance += g.anty
	}
	g.state = stateReady
	return hand, g.balance
}

```
The hand is also an array of strings.
```go
const (
	Hand_RoyalFlush    = "Royal Flush"
	Hand_StraightFlush = "Straight Flush"
	Hand_Quads         = "Four of a Kind"
	Hand_FullHouse     = "Full House"
	Hand_Flush         = "Flush"
	Hand_Straight      = "Straight"
	Hand_Trips         = "Three of a Kind"
	Hand_TwoPair       = "Two Pair"
	Hand_Jacks         = "Jacks or Higher"
	Hand_Nothing       = "Nothing"
)

type Hand [5]string

// Score scores the hand
func (h Hand) Score() string {
	if len(h[0]) < 1 {
		return Hand_Nothing
	}
	if h.isFlush() {
		if h.isRoyal() {
			return Hand_RoyalFlush
		}
		if h.isStraight() {
			return Hand_StraightFlush
		}
		return Hand_Flush
	}
	if h.isStraight() {
		return Hand_Straight
	}
	pairs, trips, quads, highPair := h.detectDupes()
	if quads > 0 {
		return Hand_Quads
	}
	if trips > 0 {
		if pairs > 0 {
			return Hand_FullHouse
		}
		return Hand_Trips
	}
	if pairs > 1 {
		return Hand_TwoPair
	}
	if pairs == 1 && highPair {
		return Hand_Jacks
	}
	return Hand_Nothing
}
```

We'll need some tests to ensure the hand scoring method continues to work as expected.

```go
// Table based test to ensure correct scoring of hands
func TestScoreHand(t *testing.T) {
	var tests = map[string]string{
		"AS KS QS JS TS": Hand_RoyalFlush,
		"JD TD AD QD KD": Hand_RoyalFlush,
		"KS QS JS TS 9S": Hand_StraightFlush,
		"KS KH KD KC 9S": Hand_Quads,
		"KS 9S KH KD KC": Hand_Quads,
		"KS 9S KH KD 9C": Hand_FullHouse,
		"TS 8S QS JS 4S": Hand_Flush,
		"AS KD QS JS TS": Hand_Straight,
		"AS AH AD JS TS": Hand_Trips,
		"AS AH JS AD 4S": Hand_Trips,
		"AS AH JS JD 4S": Hand_TwoPair,
		"AS JD 4S AH JS": Hand_TwoPair,
		"AS JD AH JS 4S": Hand_TwoPair,
		"AS AD 8H JS 4S": Hand_Jacks,
		"JS JD 8H 9S 4S": Hand_Jacks,
		"TS TD 8H 9S 4S": Hand_Nothing,
	}
	for c, exp := range tests {
		h := newHand(strings.Split(c, " "))
		res := h.Score()
		if exp != res {
			t.Log("Unexpected Result", h, res, "!=", exp)
			t.Fail()
		}
	}
}
```

After that we just slap an interface on it and call it a game :)
http://stupid-poker.kevburnsjr.com

You can find the full code on Github
https://github.com/KevBurnsJr/stupid-poker/tree/master/internal/poker

---

So... The end result?

It's still pretty easy to win. That probably means the balance issue has nothing to do with the random number generator and has more to do with the payouts being too high in relation to the the odds. 

We had fun and we built a game. It's not likely to earn us the Turing Award.