Rubinstein
We will create a Rubinstein Bargaining Game with follwing properties:
The pie to bargain on: 100 ECU
Two players: One proposes, one accepts/rejects
If the responder accept the proposal, they recieve the relevant amount. If not in the next round:
- They swith roles
- The pie dimnishes by 25 ECU
If there is no agreement, the game stops after the 4th round. (When the total pie is over)
In our game we will have two separate games. The players play the game twice with different partners.
Planning the game
Pages
Page | Description |
---|---|
MatchInfo | Display informaiton about matching |
Offer | Proposer offers an amout between 0 and 100 |
WaitOffer | Responder waits |
Response | Responder accepts or rejects the offer |
WaitResponse | Proposer waits |
Results | They will both see whether it is accepted or not |
FinalResults | They will see the final payoffs |
Variables in models.py
Variable | Description | Scope | Field/Type |
---|---|---|---|
players_per_group | (built-in) Number of players in group | Constants | |
num_rounds | (built-in) Number of rounds | Constants | |
initial pie | Initial amount to divide | Constants | Currency c() |
dimnisihing | The amount of pie dimnishes each round | Constants | Currency c() |
role | "proposer" or "responder" |
Player | Method returning string |
offer | The amount offered by proposer | Group | CurrencyField |
sent_back_amount | The response of responder "accept" or "reject" |
Group | CurrencyField |
current_pie | Current size (undimnished part) of the pie | Group | CurrencyField |
Building the game
Constants
We start by defining constants in our models.py
. Nothing tricky there:
name_in_url = 'rubinstein'
players_per_group = 2
num_rounds = 8
initial_pie = c(100)
dimnishing = c(25)
Group variables
- We define the variables in
models.py
as we planned:
class Group(BaseGroup):
offer = models.CurrencyField(min=0, label = "How much would you like to offer?")
response = models.StringField(choices = ["accept", "reject"], label = "Please tell your response",)
current_pie = models.CurrencyField()
Note that we didn’t define the maximum offer because as the pie decreases each round, we should define it dynamically. For this purpose we should create a method called offer_max
. oTree recognizes the format FIELDNAME_max
so, it runs the method when the page is loaded and submitted and checks whether the input is valid. (See Forms - Determining form fields dynamically for details.)
We define it by this method in Group
class:
def offer_max(self):
self.current_pie = Constants.initial_pie - ((self.round_number -1) % 4 )* Constants.dimnishing
return self.current_pie
Matching
- We have a particular matching structure:
- Players will be grouped randomly in round 1
- They will play with the same partner until the pie dimnishes (4 rounds)
- They will be matched with another partner in round 5
This can be done with the group_randomly()
and group_like_round()
methods in Subsession
class in models.py
.
class Subsession(BaseSubsession):
def creating_session(self):
if self.round_number in [1,5]:
self.group_randomly()
else:
self.group_like_round(self.round_number -1)
print(self.get_group_matrix()) # Here we add the print statement so we can actually see it.
Roles
We want roles to be switched in each round, ie, the player who was the proposer should be the responder in the second round (if exists) and proposer again in the third round and so on. We can rely on id_in_group
attribute in Player
class. So:
class Player(BasePlayer):
def role(self):
if self.round_number % 2 == 1:
return {1: 'proposer', 2: 'responder'}[self.id_in_group]
else:
return {2: 'proposer', 1: 'responder'}[self.id_in_group]
Here we use the modulus operator (%
) which lets us to check if the round number is even or odd.
So far we are done with the models.py
for the basic functionality. We will return here later.
Pages
First we create the pages with form input:
Offer page
class Offer(Page):
form_model = 'group'
form_fields = ['offer']
def is_displayed(self):
return self.player.role() == 'proposer'
class Response(Page):
form_model = 'group'
form_fields = ['response']
def is_displayed(self):
return self.player.role() == 'responder'
These pages has nothing particular except the display condition is_diplayed
for each role. And we add the rest of the pages with even less input.
class WaitOffer(WaitPage):
pass
class WaitResponse(WaitPage):
pass
class Results(Page):
pass
```
And we add the page sequence:
page_sequence = [Offer, WaitOffer, Response, WaitResponse, Results] ```