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.

What I learned: Part 1 Linux commands

Note: the "What I learned" series of posts is intended as a reference.

Problem 1: starting a long running process process on a GNU/Linux (Ubuntu) machine without having to keep your session open

If you find yourself working regularly on a the same GNU/Linux server but your session doesn't stay alive then there is one command that is really helpful:
tmux
The short flow for using tmux is:
  1. ssh into the remote machine
  2. start tmux by typing tmux into the shell
  3. start the process you want inside the started tmux session
  4. leave/detach the tmux session by typing Ctrl-B and then D or close your ssh session
Whenever you want to continue working in your tmux session you can just do this with
tmux attach
Some useful tmux commands:
Ctrl+b d         detach
Ctrl+b %         split window vertically
Ctrl+b "         split window horizontally
Ctrl+b x         kill pane
Ctrl+b arrow     navigate between panes
For more information on tmux:
http://robots.thoughtbot.com/post/2641409235/a-tmux-crash-course
http://askubuntu.com/a/220880

tmux

Problem 2: Viewing the lists of processes and their memory usage

top
top

Problem 3: Displaying the user rights of a directory

ls -la "directory_name"
ls

Problem 4: copy a file from Windows to a GNU/Linux box with ssh

pscp /path/to/your/file.txt username@example.com:/path/on/server
pscp can be downloaded at the same place as the ssh tool putty: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html.
pscp

Conclusion

A lot can be done with the built-in linux tools or some downloads but you have to know they exist or where to look for them. A very good introduction to GNU/Linux and its wonderful world of commands is A Practical Guide to Linux Commands, Editors, and Shell Programming.

Creating a node.js GeometryService : part 3

Its been a while since I posted my first and second post on a GeometryService based on node.js and PostGIS.

Today I want to announce that I open sourced my work in progress. It is in a pre-alpha state and it can be found here. The only thing it can do at the moment is convert ESRI Json geometries from one coordinate system to another. This is done by converting the geometry to WKT, creating a sql statement for the re-projection and then converting the GeoJSON result back to ESRI Json.

A sample input url when you started the geometryservice is : http://127.0.0.1:3000/rest/services/Geometry/GeometryServer/project?inSR=4326&outSR=3819&geometries={%22geometryType%22:%22esriGeometryPoint%22,%22geometries%22:[{%22x%22:-117,%22y%22:34},%20{%22x%22:-115,%22y%22:25}]}. This returns

{"geometries":[{"x":-115.00495449693217,"y":24.99533030379053},{"x":-117.00524224953409,"y":33.99507554185914}]}
as a result. The intermediate sql statement looks like this :
SELECT st_asgeojson(st_transform(st_geomfromtext(geometries.geometry, '4326'), '3819')) g FROM  (SELECT ST_ASTEXT('POINT(-117 34)') geometry UNION SELECT ST_ASTEXT('POINT(-115 25)')) geometries
.

Most of the logic of the code happens in geometryService.js. The conversion from and to ESRI Json happens in datatransformer.js. As stated in my previous post I also created some tests for the functions in the datatransformer.js. There is still a lot to do, especially everything related to error handling and logging and off course the implementation of the other geometry operations.

The main dependency is a runnning instance of PostgreSQL with PostGIS. As the connection string is not configurable yet you'll have to change it in the executeSQL function in geometryService.js.

The code can be found on following url https://bitbucket.org/gissolved/node_geometryservice and it is licensed under the MIT license.

If you like this then consider buying me a book or a license for Sublime Text 2.

Timing functions in Python

Recently I saw a blogpost on tracking time in Python. This is inspired me to share how I track time in my python scripts. First I put the following python decorator function in my code or preferably import it from a common module.

import time                                                

def timeit(method):

    def timed(*args, **kw):
        ts = time.time()
        result = method(*args, **kw)
        te = time.time()

        print('%r (%r, %r) %2.2f sec' % (method.__name__, args, kw, te-ts))
        return result

    return timed

You can use the timeit function like this:

@timeit
def longRunningOperation(dummyArg, optionalDummyArg=None):
    time.sleep(3)
    
longRunningOperation("test", "optional test")

The output of the timeit decorator function looks like this:

'longRunningOperation' (('test', 'optional test'), {}) 3.00 sec

As you can see, the timit function reports the function name, the passed arguments and the elapsed time

If you want to learn more about decorator functions I would suggest to read the python.org wiki article on decorators. On the same site there is also an article with useful decorator functions for things like caching, properties, pre- and post conditions, memoization and many more.

Roundup January 2012

This post is a roundup post for the month january.

Blogging

Just before the end of the year I created a tumblr blog where I shared some inspiring or interesting quotes.

On this blog I wrote 2 posts. The first post of january was my second post on developing a GeometryService in node.js. Some things I talked about where nodemonw for monitoring your files for changes and restarting node.js, node-inspector for debugging and vowsjs for writing tests. The second post of this month elaborated on the Geometry Visualizer that I created this month. The Geometry Visualizer uses the ArcGIS JavaScript API and openlayers to provide a .

GIS/Geography

Programming

Teaching/learning

Other

Entertainment

New blogs to follow