We will create a Rubinstein Bargaining Game with follwing properties:

Planning the game


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

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


We start by defining constants in our 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 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


  • 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

class Subsession(BaseSubsession):
    def creating_session(self):
        if self.round_number in [1,5]:
            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.


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]
            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 for the basic functionality. We will return here later.


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):

class WaitResponse(WaitPage):

class Results(Page):
And we add the page sequence:

page_sequence = [Offer, WaitOffer, Response, WaitResponse, Results] ```