Josh Walters

I'm a software engineer working with big data.

Blog Github Email Linked-In

Monte Carlo Simulation for the Game of Farkle (in Clojure)

January 06, 2015

I recently learned the game of Farkle and I became curious in finding out how to play an optimal game.

The game of Farkle is played differently from group to group, there are a lot of house rules. The scoring system is very different from group to group.

I decided the best way to ‘solve’ the game was to build a system that flexibly enforced the rules and then ran simulation rolls that were then evaluated. You can find my completed source code here.

Building the Rules

The rules are pretty simple, so here is a quick overview. On your turn you roll the six dice. You get points if you roll a three of a kind, four of a kind, six of a kind, a straight, etc. Each group has a different set of hands that score, so it can be flexible. I encoded the rules used by my group here.

Once you score, you can keep rolling the remaining dice. If you roll a hand that doesn’t score, you “Farkle”. You lose all the points you gained so far and pass the dice to the next person. If you roll all of your 6 dice, you can re-roll them again.

The rules are very simple, and they can be combined in a simple function to calculate the score for any given roll.

(defn score-roll
  "Given a roll, return the highest score possible."
  [roll]
  (cond
    (six-of-a-kind? roll)       {:score 3000 :dice-used 6 :name "six of a kind"}
    (two-three-of-a-kind? roll) {:score 2500 :dice-used 6 :name "two three of a kind"}
    (five-of-a-kind? roll)      {:score 2000 :dice-used 5 :name "five of a kind"}
    (three-pairs? roll)         {:score 1500 :dice-used 6 :name "three pairs"}
    (straight? roll)            {:score 1500 :dice-used 6 :name "a straight"}
    (three-ones? roll)          {:score 1000 :dice-used 3 :name "three ones"}
    (four-of-a-kind? roll)      {:score 1000 :dice-used 4 :name "four of a kind"}
    (three-sixes? roll)         {:score 600  :dice-used 3 :name "three sixes"}
    (three-fives? roll)         {:score 500  :dice-used 3 :name "three fives"}
    (three-fours? roll)         {:score 400  :dice-used 3 :name "three fours"}
    (three-threes? roll)        {:score 300  :dice-used 3 :name "three threes"}
    (three-twos? roll)          {:score 200  :dice-used 3 :name "three twos"}
    (one-one? roll)             {:score 100  :dice-used 1 :name "a one"}
    (one-five? roll)            {:score 50   :dice-used 1 :name "a five"}
    :else                       {:score 0    :dice-used 0 :name "a farkle"}))

A better way to generalize this would be to return all scoring hands for a given roll, and then have a function to return the best hand.

Simulation

Now that the scoring rules are implemented, we can start running simulations. The code to do this can be found here.

The player can only act when it is the start of their turn, or when deciding to continue rolling dice, risking greater rewards or possible loss of points.

A number of random rolls are generated and then scored for each die combination. The chance to Farkle can then be counted per number of dice. With this percentage we can calculate the expected return of a given roll as:

(probability of non-Farkle) * (average score for given number of dice) + (probability of Farkle) * (current score)

If the expected return is greater than zero, we proceed with the roll.

I also play with another house rule, hot dice. If a player ends their turn with dice left unrolled (they didn’t Farkle), the next player can continue rolling off their hand and points.

Here I used the same strategy as before. Given the “hot dice” score, and the number of dice remaining to roll, I can calculate the expected return. I can then compare it against the expected return of rolling 6 “fresh” dice. I then can go with whatever strategy gives the best results.

Results

The results of the simulation, combined with the particular house rules I play with, gave me a list of rules to follow to play an optimal game. The following rules were generated with 50,000 random rolls per die count combination:

If 1 die and current score less than 38, then continue to roll.
If 2 die and current score less than 97, then continue to roll.
If 3 die and current score less than 248, then continue to roll.
If 4 die and current score less than 707, then continue to roll.
If 5 die and current score less than 2255, then continue to roll.
If 6 die and current score less than 15282, then continue to roll.
If 1 hot die and hot score greater than 989, then roll off hot dice.
If 2 hot die and hot score greater than 564, then roll off hot dice.
If 3 hot die and hot score greater than 397, then roll off hot dice.
If 4 hot die and hot score greater than 289, then roll off hot dice.
If 5 hot die and hot score greater than 193, then roll off hot dice.
If 6 hot die and hot score greater than 0, then roll off hot dice.

I tested these rules in several games against human players, and the results were favorable. The rules lead to more conservative play than how most people play, likely due to the greedy nature of the game.

Because the scoring function is so simple and flexible, it can be very easily changed to match the rules of a given group. The simulation can be quickly re-run generating rules that can be applied to a different scoring/rules system.

Optimizations

Several things I would have liked to add:

  1. Score function returns all possible scoring hands.
  2. Random evaluations include recursively evaluating resulting hands based on probability of non-Farkle.
  3. Better handeling of errors when performing the simulation on a low number of random simulations.