# Understanding "Obstgarten", the game

In [1]:
import random
import numpy as np
import matplotlib.pylab as plt


## "Obstgarten" ("orchard")¶

is a board game (for kids).

### Motivation¶

At some point we were genuinely wondering if the crow wins most of the times. Also, this has served as a greatly motivating example for statistically flavoured python classes I have been teaching.

### Rules of the game¶

• there are four trees, each tree has four fruit. There are red apples, green apples, pears, and plums (I didn't find a unicode symbol for plum, so it is peaches from now on)
• there is a crow that needs to reach a gate. There are five steps necessary to reach the gate
• players aim to harvest everything from all trees and aim to not let the crow reach the gate
• There is a six sided dice that is rolled by the players. The six options of the dice are 🍎, 🍏, 🍐, 🍑, 🧺, 🦅
• if any of the fruit 🍎, 🍏, 🍐, 🍑 is rolled, the player is allowed to harvest a fruit as long as there the corresponding fruit in the tree
• if the basked 🧺 is rolled, the player is to pick a fruit from any fruit tree that has fruit left on it
• if the crow 🦅 is rolled, the corw advances one step towards the gate
• the crow wins, when it reaches the gate
• the players win when all fruit are harvested

These are the options that can be rolled with the dice

In [2]:
dice_options = ['🍎', '🍏', '🍐', '🍑', '🧺', '🦅']
n_options = len(dice_options)
print(f"there are {n_options} options on the dice:")
for item in dice_options:
print(item)

there are 6 options on the dice:
🍎
🍏
🍐
🍑
🧺
🦅


### Rolling Dice in Python¶

generate n_rolls of the dice

In [3]:
n_rolls = 10
np.int_(np.round(np.random.random((n_rolls)) * n_options))

Out[3]:
array([4, 2, 1, 2, 0, 2, 3, 6, 3, 3])
In [4]:
n_rolls = 10
rolls = np.int_(np.round(np.random.random((n_rolls)) * n_options))
rolls

Out[4]:
array([6, 0, 4, 3, 4, 3, 1, 0, 3, 4])

check if the rolls of the dice are uniformly distributed

In [5]:
# roll dice
n_rolls = 10000
randomnumbers = np.int_(np.random.random((n_rolls)) * n_options)
# plot histogram
bins = np.arange(7)
plt.hist(randomnumbers,
bins=bins,
density=True)

Out[5]:
(array([0.1649, 0.1645, 0.1672, 0.1684, 0.1708, 0.1642]),
array([0, 1, 2, 3, 4, 5, 6]),
<a list of 6 Patch objects>)

## Start a Fresh Game¶

First: Setup a counter for the available fruit in each tree and for the number of steps left for the crow on its ladder

In [6]:
counter_available = {'🍎':4,
'🍏':4,
'🍐':4,
'🍑':4,
'🦅':5
}


based on experience, one game should be done after 50 rolls

In [7]:
n_rolls = 50
randomnumbers = np.int_(np.random.random((n_rolls)) * n_options)

In [8]:
randomnumbers

Out[8]:
array([5, 4, 5, 0, 3, 5, 4, 4, 3, 4, 4, 2, 0, 3, 3, 1, 3, 0, 2, 4, 2, 0,
5, 1, 3, 1, 3, 5, 3, 1, 0, 5, 2, 5, 0, 0, 3, 4, 3, 5, 3, 3, 0, 5,
3, 3, 2, 0, 4, 0])
In [9]:
print(type(dice_options))
doa = np.array(dice_options)
print(doa.shape)
print(type(doa))

<class 'list'>
(6,)
<class 'numpy.ndarray'>

In [10]:
doa

Out[10]:
array(['🍎', '🍏', '🍐', '🍑', '🧺', '🦅'], dtype='<U1')
In [11]:
randomnumbers

Out[11]:
array([5, 4, 5, 0, 3, 5, 4, 4, 3, 4, 4, 2, 0, 3, 3, 1, 3, 0, 2, 4, 2, 0,
5, 1, 3, 1, 3, 5, 3, 1, 0, 5, 2, 5, 0, 0, 3, 4, 3, 5, 3, 3, 0, 5,
3, 3, 2, 0, 4, 0])

this would reflect the outcome of n_rolls times rolling the dice in this game

In [12]:
doa[randomnumbers]

Out[12]:
array(['🦅', '🧺', '🦅', '🍎', '🍑', '🦅', '🧺', '🧺', '🍑', '🧺', '🧺', '🍐', '🍎',
'🍑', '🍑', '🍏', '🍑', '🍎', '🍐', '🧺', '🍐', '🍎', '🦅', '🍏', '🍑', '🍏',
'🍑', '🦅', '🍑', '🍏', '🍎', '🦅', '🍐', '🦅', '🍎', '🍎', '🍑', '🧺', '🍑',
'🦅', '🍑', '🍑', '🍎', '🦅', '🍑', '🍑', '🍐', '🍎', '🧺', '🍎'], dtype='<U1')
In [13]:
counter_available['🍑']

Out[13]:
4

this is playing the game once

In [14]:
for cur_i, cur_fruit in enumerate(doa[randomnumbers]):
print(cur_fruit, cur_i)
if cur_fruit == '🧺':
# check which basket is still full
counter_available_without_bird = counter_available.copy()
del counter_available_without_bird['🦅']
v = list(counter_available_without_bird.values())
k = list(counter_available_without_bird.keys())
m = max(v)
# randomly choose one if there are multiple maxima
storage = []
j=0
for i in v:
if i==m:
storage.append(j)
j+=1
print(k[random.choice(storage)])
counter_available[k[random.choice(storage)]] -= 1
elif counter_available['🦅'] == 0:
print('the bird has won!')
raise Exception
else:
print(counter_available)
print(cur_fruit)
counter_available[cur_fruit] -= 1

counter_available_without_bird = counter_available.copy()
del counter_available_without_bird['🦅']
v = list(counter_available_without_bird.values())
if min(v)==0:
print(counter_available_without_bird)
winner = min(counter_available_without_bird, key=counter_available_without_bird.get)
print(winner + ' won!')
break

🦅 0
{'🍎': 4, '🍏': 4, '🍐': 4, '🍑': 4, '🦅': 5}
🦅
🧺 1
🍐
🦅 2
{'🍎': 4, '🍏': 3, '🍐': 4, '🍑': 4, '🦅': 4}
🦅
🍎 3
{'🍎': 4, '🍏': 3, '🍐': 4, '🍑': 4, '🦅': 3}
🍎
🍑 4
{'🍎': 3, '🍏': 3, '🍐': 4, '🍑': 4, '🦅': 3}
🍑
🦅 5
{'🍎': 3, '🍏': 3, '🍐': 4, '🍑': 3, '🦅': 3}
🦅
🧺 6
🍐
🧺 7
🍏
🍑 8
{'🍎': 2, '🍏': 3, '🍐': 3, '🍑': 3, '🦅': 2}
🍑
🧺 9
🍏
🧺 10
🍏
🍐 11
{'🍎': 2, '🍏': 2, '🍐': 2, '🍑': 2, '🦅': 2}
🍐
🍎 12
{'🍎': 2, '🍏': 2, '🍐': 1, '🍑': 2, '🦅': 2}
🍎
🍑 13
{'🍎': 1, '🍏': 2, '🍐': 1, '🍑': 2, '🦅': 2}
🍑
🍑 14
{'🍎': 1, '🍏': 2, '🍐': 1, '🍑': 1, '🦅': 2}
🍑
{'🍎': 1, '🍏': 2, '🍐': 1, '🍑': 0}
🍑 won!


## Analyzing the Game¶

... by playing it n_games times

### putting everything in to a function¶

... to be used for the analysis of the game

In [15]:
def play_orchard(n_games, counter_available_in):
list_winners = []
list_n_throws = []

for cur_game in range(n_games):
#print(cur_game,)
counter_available = counter_available_in.copy()

n_rolls = 50
randomnumbers = np.int_(np.random.random((n_rolls)) * n_options)
doa = np.array(dice_options)

# the game
for cur_i, cur_fruit in enumerate(doa[randomnumbers]):
if cur_fruit == '🧺':
# check which basket is still full
counter_available_without_bird = counter_available.copy()
del counter_available_without_bird['🦅']
v = list(counter_available_without_bird.values())
k = list(counter_available_without_bird.keys())
m = max(v)
# randomly choose one if there are multiple maxima
storage = []
j=0
for i in v:
if i==m:
storage.append(j)
j+=1
counter_available[k[random.choice(storage)]] -= 1
else:
if counter_available[cur_fruit] > 0:
counter_available[cur_fruit] -= 1

if counter_available['🦅'] == 0:
# print('the bird has won!')
winner = '🦅'
list_winners.append(winner)
list_n_throws.append(cur_i)
break
else:
counter_available_without_bird = counter_available.copy()
del counter_available_without_bird['🦅']
v = list(counter_available_without_bird.values())
# Trees win if all trees have no more fruit
if all(x == 0 for x in v):
list_winners.append('trees')
list_n_throws.append(cur_i)
break
perc_win_trees = np.round(list_winners.count('trees') / n_games * 100, 0)
perc_win_crow = np.round(list_winners.count('🦅') / n_games * 100, 0)
print(f"trees win {perc_win_trees}% of all games" )
print(f"crow wins {perc_win_crow}% of all games")
#print(list_winners.count('trees') + list_winners.count('🦅'))

return list_n_throws



## Analyzing the Game¶

In [16]:
n_games = 100000
counter_available = {'🍎':4,
'🍏':4,
'🍐':4,
'🍑':4,
'🦅':5
}
list_n_throws = play_orchard(n_games, counter_available)

trees win 63.0% of all games
crow wins 37.0% of all games

In [17]:
plt.hist(np.array(list_n_throws),
bins=np.arange(40),
density=True)
plt.xlabel('number of dice-rolls until somebody wins')
plt.ylabel('relative frequency')
plt.grid(True)


## Summary¶

In the default setup, the crow wins about in 37% of the games, the fruit harvester in 63% of the games played. Most games require about 20 times rolling the dice until somebody wins

## What if there were six steps necessary for the crow to reach the gate¶

In [18]:
n_games = 100000
counter_available = {'🍎':4,
'🍏':4,
'🍐':4,
'🍑':4,
'🦅':6
}
list_n_throws = play_orchard(n_games, counter_available)

trees win 77.0% of all games
crow wins 23.0% of all games

In [19]:
plt.hist(np.array(list_n_throws),
bins=np.arange(40),
density=True)
plt.xlabel('number of dice-rolls until somebody wins')
plt.ylabel('relative frequency')
plt.grid(True)


In this altered setup, the crow wins about in 23% of the games, the players / fruit harvesters in 77% of the games played. Most games require about 22 times rolling the dice until somebody wins

## What if there were four steps necessary for the crow to reach the gate¶

In [20]:
n_games = 100000
counter_available = {'🍎':4,
'🍏':4,
'🍐':4,
'🍑':4,
'🦅':4
}
list_n_throws = play_orchard(n_games, counter_available)

trees win 46.0% of all games
crow wins 54.0% of all games

In [21]:
plt.hist(np.array(list_n_throws),
bins=np.arange(40),
density=True)
plt.xlabel('number of dice-rolls until somebody wins')
plt.ylabel('relative frequency')
plt.grid(True)


This scenario would even out the chances of winning for the crow and the players (54% for the crow and 46% for the players. Also there would be slightly fewer dice-rolls necessary to achieve victory (on average slightly less than 20).

© Claus Haslauer. Built using Pelican. Theme by Giulio Fidente on github.