# 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.