Monte Carlo Experiment of the Orchard Game: What are the odds of winning ?

Update november 2021: Simon Naarmann noticed a bug in the worst scenario, I've updated this post to include the fix.

Recently my little son received the  Orchard Game (Amazon). After playing it a few time I started wondering what the odds of winning the game are. To find this out I wrote a short Monte Carlo simulation of the game.

The rules

The orchard game is a very simple collaborative game with the following rules:

  1. On the board 4 types of fruits with 10 pieces each.
  2. Every player throws a six sided dice with 4 colors (for each fruit a color), a fruit basket and a raven.
  3. If you throw a color you may harvest one piece of fruit with the same color.
  4. If you throw the fruit basket you can take two pieces of the fruit of your choice.
  5. If you throw the raven you have to put one of the 9 pieces of the raven on the board.
  6. If all fruits are collected by the players before the raven is complete then the players have won the game.
Note that to play perfectly you have to pick up the most abundant fruits when you've thrown the fruit basket.

The program

I wrote the following simulation in Python. First thing I wrote was a simulation of the game. The actual strategy used to decide which fruit is harvested when the fruit basket is thrown is passed in as a function.

import random

def orchard(fruitbox_strategy):
    raven = 0
    fruits = 4 * [10]
    while sum(fruits) > 0 and raven < 9:
        n = random.randint(0,5)
        if n == 5:
            raven = raven+1
        elif n == 4:
            fruitbox_strategy(fruits)
            fruitbox_strategy(fruits)
        elif fruits[n] > 0:
            fruits[n] = fruits[n]-1
    return raven < 9

Next I implemented 3 different fruit basket strategies. The perfect playing strategy is decrease_max. It will always harvest the most abundant fruit(s).


def decrease_max(fruits):
    mi, mv = 0,0
    for i, v in enumerate(fruits):
        if v > mv:
            mi,mv = i,v
    fruits[mi] = mv-1

def decrease_min(fruits):
    mi, mv = 0, 11
    for i, v in enumerate(fruits):
        if v < mv and v > 0:
            mi,mv = i,v
    if mv < 11:
        fruits[mi] = mv-1

def decrease_random(fruits):
    mi = random.randint(0,3)
    v = fruits[mi]
    if v > 0:
        fruits[mi] = v-1
    elif sum(fruits) > 0:
        decrease_random(fruits)

After that I implemented the Monte Carlo simulation. It will repeatedly play the game with the given strategy and return the percentage of games it won. The number of times the game is simulated can be picked up front. To get an idea of the robustness of the result I repeated the simulation 10 times to be able to inspect the variability in the outcome. Increasing the number of games played within a simulation makes the results more stable.

def monte_carlo_simulation(game, count):
    won = 0
    for _ in range(count):
        if game():
            won = won + 1
    return (won*100) /count

def simulate_orchard_best(count):
    return monte_carlo_simulation(lambda:orchard(decrease_max), count)

def simulate_orchard_worst(count):
    return monte_carlo_simulation(lambda:orchard(decrease_min), count)

def simulate_orchard_random(count):
    return monte_carlo_simulation(lambda:orchard(decrease_random), count)


print('Winning rates of 10 runs of the best strategy with 50 games: \n%s' %
      ([str(simulate_orchard_best(50)) + '%' for _ in range(10)]))
    
print('Winning rates of 10 runs of the best strategy with 1000 games: %s' %
      ([str(simulate_orchard_best(1000)) + '%' for _ in range(10)])) 

print('Winning rates of 10 runs of the worst strategy with 50 games: \n%s' %
      ([str(simulate_orchard_worst(50)) + '%' for _ in range(10)]))

print('Winning rates of 10 runs of the worst strategy with 1000 games: \n%s' %
      [str(simulate_orchard_worst(1000)) + '%' for _ in range(10)])

print('Winning rates of 10 runs of the random strategy with 50 games: \n%s' % 
      ([str(simulate_orchard_random(50)) + '%' for _ in range(10)]))

print('Winning rates of 10 runs of the random strategy with 1000 games: \n%s' %
      [str(simulate_orchard_random(1000)) + '%' for _ in range(10)])

A typical result looks like this:

Winning rates of 10 runs of the best strategy with 50 games: 
['58%', '80%', '70%', '62%', '70%', '66%', '62%', '78%', '64%', '70%']
Winning rates of 10 runs of the best strategy with 1000 games:
['67%', '68%', '68%', '68%', '68%', '67%', '69%', '67%', '71%', '69%']
Winning rates of 10 runs of the worst strategy with 50 games: 
['60%', '44%', '62%', '42%', '50%', '50%', '62%', '54%', '64%', '58%']
Winning rates of 10 runs of the worst strategy with 1000 games: 
['53%', '51%', '54%', '54%', '53%', '52%', '54%', '57%', '50%', '54%']
Winning rates of 10 runs of the random strategy with 50 games: 
['72%', '58%', '60%', '66%', '60%', '58%', '60%', '70%', '60%', '72%']
Winning rates of 10 runs of the random strategy with 1000 games: 
['62%', '62%', '63%', '63%', '62%', '60%', '60%', '62%', '61%', '65%']

The full source code is in the github repository of this blog.

No comments: