Guessing Game
__init__.py
# ...
class Group(BaseGroup):
= models.FloatField()
target_number = models.FloatField()
average_guess # ...
Guessing game (also called as the Beauty Contest Game) is a game theory experiment demonstrate how people don’t do backward induction fully and but over time converge to the theoretical predictions.(Nagel 1995).
Game Rules
Each player chooses a number between 0 and 100.
The person who chooses the number closest to 2/3 of the average of all numbers chosen wins.
It is often played with several rounds, and between rounds the winning number and person are announced.
Pages
- We should have two pages:
A page where players can enter their guess. Let’s call it
Guess
A page where players can see the results of the round. Let’s call it
Results
(in fact, oTree default app template already has a page calledResults
so we will can use that page instead of creating a new one)
To summarize:
Page | Description |
---|---|
Guess | A page where players can enter their guess |
Results | A page where players can see the results of the round |
Data Structure (Models and Fields)
Each should player has a
guess
field to store the number they choose. This is the similar to our Survey, as it is an individual field.Then each group should have a
target_number
. We can calculate it directly and record it but for convenience, let’s also store the group average in a field calledaverage_guess
.We need to determine everybody’s difference from the winning number. We can call it
guess_difference
. This, apparently is an individual field too as it will be different for each player.We will calculate the person who is closest to the winning number. Then we can store the
is_winner
field for that person.And finally, we need to determine a fixed reward for the winner. We can call it
reward
. We can store it in a constant, as it will be the same for every group/player.
To summarize:
Model | Field | Type | Description |
---|---|---|---|
Constant | reward | Currency | The reward for the winner |
Player | guess | Float | The number the player chooses |
Player | guess_difference | Float | The difference between the player’s guess and the winning number |
Player | is_winner | Boolean | Whether the player is the winner |
Group | target_number | Float | The winning number |
Group | average_guess | Float | The average of all guesses |
Code
- Start a new app and add it to
settings.py
:
otree startapp guessing
settings.py
# ...
= [
SESSION_CONFIGS dict(
='guessing',
name=['guessing'],
app_sequence=3,
num_demo_participants
),# ... other apps
]# ...
We can start by what we already know from the survey we built: Collecting the
guess
field from the participants.oTree generates a default page called MyPage. We can change the name of this page by:
- Renaming
guessing/MyPage.html
toGuess.html
. - Changing
class MyPage(Page):
toclass Guess(Page):
in__init__.py
. - And finally renaming
MyPage
toGuess
inpage_sequence
list in__init__.py
.
- Renaming
So, the last part of our __init__.py
should look like this:
__init__.py
# ...
# PAGES
class Guessing(Page):
pass
class ResultsWaitPage(WaitPage):
pass
class Results(Page):
pass
= [Guessing, ResultsWaitPage, Results] page_sequence
- Let’s prepare our
Guessing.html
by modifying the text in it.
Guessing.html
{{ block title }}
Please enter your guess
{{ endblock }}
{{ block content }}<p> Guess a number between 0 and 100.</p>
<p> If your answer is closest to the 2/3 of the mean answer,
</p>
you will earn 10 points.
{{ formfields }}
{{ next_button }}
{{ endblock }}
Then let’s add the guess
field to our Player
model.
__init__.py
# ...
class Player(BasePlayer):
= models.IntegerField(min=0, max=100, label='Please enter your guess.')
guess # ...
and finally, let’s add the guess
field to our Guessing
page.
__init__.py
# ...
class Guessing(Page):
= 'player'
form_model = ['guess']
form_fields # ...
Constants
Now let’s add our parameters. You can see that in C
class, we already have several parameters:
NAME_IN_URL
: This is the name of the app in the URL. It is rarely modified. But participants see this name on their browser’s adress bar when they are playing the game. So if it’s too revealing, you might want to change it.PLAYERS_PER_GROUP
: This is the number of players in each group. For demonstration we have 3 people in each group, so let’s set it to 3.NUM_ROUNDS
: This is the number of rounds in the game. We will set it to 4.
As we discussed above, we would like to define the reward for the winner as a constant.
So our C
class should look like this:
__init__.py
class C(BaseConstants):
= 'guessing'
NAME_IN_URL = 3
PLAYERS_PER_GROUP = 4
NUM_ROUNDS
= cu(10) reward
Now let’s look at the class C
. We’d like to change the number of players in the game. Let’s modify the PLAYERS_PER_GROUP
to 3.
Then we need to define the reward for the winner. Let’s do these changes.
__init__.py
# ...
class C(BaseConstants):
= 'guessing'
NAME_IN_URL = None
PLAYERS_PER_GROUP = 1
NUM_ROUNDS
= cu(10)
reward
# ...
In the reward, instead of setting reward as an integer (reward = 10
), we are using using cu
function to wrap it up. This convert the number to a currency field. You can define it as a number directly but it is useful to define it as a currency field so that you can use it in the templates directly (We will see this next.) It also is more robust in roundings and so on but we will not get into that here. So the rule of thumb is to use cu
function whenever you are dealing with money and experimental points.
Regarding the reward
, let’s go back to the Guessing.html
now and see the line we talk about it.
<p> If your answer is closest to the 2/3 of the mean answer,
</p> you will earn 10 points.
Now that we have defined the reward as a constant, we can use it in the template. Let’s change the line to:
Guessing.html
<p> If your answer is closest to the 2/3 of the mean answer,
</p> you will earn {{ C.reward }}.
Notice that {{ C.reward }}
will not just be replaced by 10, but also it will get the name of the currency (points as the default). So if you change the reward or the currency, it will be reflected in the template automatically. If you’d like to know about how to use different names for the points or real currencies, see Using alternative currencies
Let’s move on and add other columns to our Player
and Group
models.
Player
__init__.py
# ...
class Player(BasePlayer):
= models.IntegerField(min=0, max=100, label='Please enter your guess.')
guess = models.FloatField()
guess_difference = models.BooleanField(initial=False)
is_winner
Group
__init__.py
# ...
class Group(BaseGroup):
= models.FloatField()
target_number = models.FloatField()
average_guess # ...
So we have defined all the fields we need and they should be available in the database1.
1 Except for the reward
field which is a constant and not stored in the database. But you can store it to another field Player
or Subsession
if you’d like. See the recipe Storing Constants).
Results page
Recall that we already had a Results
page created by oTree. Let’s prepare it for our game.
Our Result
class in __init__.py
looks like this:
__init__.py
# ...
class Results(Page):
pass
# ...
This class should be sufficent for us, as we don’t get any data input from participants in this page. And lets modify Results.html
as follows:
Results.html
{{ block title }}
Results
{{ endblock }}
{{ block content }}<p> You guessed {{ player.guess }}. </p>
<p> The average guess was . </p>
<p> 2/3 of the average was . </p>
{{ next_button }} {{ endblock }}
The HTML templates can reach group fields as they can reach player fields: {{ group.average_guess }}
. However we don’t have these values yet. Before we add them in our template, we need to write function to calculate these values.
Calculating the results
To calculate the results we need two things:
function: to write a function that takes the guess of each player and calculates the average guess of the group.
trigger: we need to run this function at the right place2.
2 oTree is quite flexible and you can run functions in different places and different times. For some more information about different information and practices, see the recipe on triggers for more information.
We can define functions anywhere in the __init__.py
file. Usually you put them between your pages and your models3
3 If you have many functions or your functions are long, it might make sense to put them in some other files. See the recipe on splitting the __init__.py
file for more information.
The function we need takes the group object as input, and should set calculate and set the values on the fields of our group.
__init__.py
def set_payoffs(group):
= group.get_players()
players
= [p.guess for p in players]
guesses
= sum(guesses) / len(guesses)
group.average_guess
= group.average_guess * 2 / 3
group.target_number
for p in players:
= abs(p.guess - group.target_number)
p.guess_difference
= [p.guess_difference for p in players]
guess_differences
= min(guess_differences)
minimum_difference
for p in players:
if p.guess_difference == minimum_difference:
= True
p.is_winner
if p.is_winner:
= C.reward
p.payoff else:
= 0
p.payoff
Now we have to tell oTree to run this function at a precise time after all participants made a guess. (otherwise our calculations will be wrong.)
oTree WaitPages has a built-in variable: after_all_players_arrive
. We already had a WaitPage in between our pages called ResultsWaitPage
. Let’s go ahead tell oTree that we want to trigger that function.
__init__.py
# ...
class ResultsWaitPage(WaitPage):
= set_payoffs
after_all_players_arrive # ...
And since we have our group variables ready, we can further update our Results.html
template.
Results.html
{{ block title }}
Results
{{ endblock }}
{{ block content }}
You are in Round {{ player.round_number }}.
<p> Your guess was {{ player.guess }}. </p>
<p> The average guess was {{ group.average_guess }}. </p>
<p> 2/3 of the average was {{ group.target_number }}. </p>
{{ next_button }}
{{ endblock }}
Results.html
{{ block title }}
Results
{{ endblock }}
{{ block content }}
You are in Round {{ player.round_number }}.
<p> Your guess was {{ player.guess }}. </p>
<p> The average guess was {{ group.average_guess |to2}}. </p>
<p> 2/3 of the average was {{ group.target_number|to2 }}. </p>
{% if player.is_winner %}<p> <strong> You were the winner! </strong></p>
{% else %}<p> <strong> You were not the winner. </strong> </p>
{% endif %}
{{ next_button }}
{{ endblock }}