Python framework for multiplayer decision games, behavioral experiments, and surveys
Python HTML CSS
Latest commit e68ab93 Feb 14, 2017 oTree-org rename Base.html to Page.html
Permalink
Failed to load latest commit information.
_rooms add example rooms and participant label file Aug 28, 2016
_static/global first commit Aug 23, 2016
_templates/global rename Base.html to Page.html Feb 13, 2017
bargaining rename Base.html to Page.html Feb 13, 2017
battle_of_the_sexes rename Base.html to Page.html Feb 13, 2017
bertrand rename Base.html to Page.html Feb 13, 2017
common_value_auction rename Base.html to Page.html Feb 13, 2017
cournot rename Base.html to Page.html Feb 13, 2017
dictator rename Base.html to Page.html Feb 13, 2017
guess_two_thirds rename Base.html to Page.html Feb 13, 2017
lemon_market rename Base.html to Page.html Feb 13, 2017
matching_pennies rename Base.html to Page.html Feb 13, 2017
payment_info rename Base.html to Page.html Feb 13, 2017
principal_agent rename Base.html to Page.html Feb 13, 2017
prisoner rename Base.html to Page.html Feb 13, 2017
public_goods rename Base.html to Page.html Feb 13, 2017
public_goods_simple rename Base.html to Page.html Feb 13, 2017
quiz rename Base.html to Page.html Feb 13, 2017
real_effort rename Base.html to Page.html Feb 13, 2017
stackelberg rename Base.html to Page.html Feb 13, 2017
survey rename Base.html to Page.html Feb 13, 2017
traveler_dilemma rename Base.html to Page.html Feb 13, 2017
trust rename Base.html to Page.html Feb 13, 2017
trust_simple rename Base.html to Page.html Feb 13, 2017
ultimatum rename Base.html to Page.html Feb 13, 2017
vickrey_auction rename Base.html to Page.html Feb 13, 2017
volunteer_dilemma rename Base.html to Page.html Feb 13, 2017
.gitignore add quiz app Sep 20, 2016
.travis.yml travis should use py3.5 Oct 13, 2016
Procfile first commit Aug 23, 2016
README.md added AdminReport to public goods and lemon market Dec 26, 2016
manage.py first commit Aug 23, 2016
requirements.txt added comments Jan 31, 2017
requirements_base.txt remove CountryField, it's not used much and an unnecessary dependency Jan 12, 2017
requirements_server.txt added comments Jan 31, 2017
runtime.txt . Oct 5, 2016
settings.py added comments Jan 31, 2017
utils.py first commit Aug 23, 2016

README.md

oTree 1.0

oTree is a framework based on Python and Django that lets you build:

  • Multiplayer strategy games, like the prisoner's dilemma, public goods game, and auctions
  • Controlled behavioral experiments in economics, psychology, and related fields
  • Surveys and quizzes

Live demo

http://demo.otree.org/

Homepage

http://www.otree.org/

Docs

http://otree.readthedocs.org

Quick start

Rather than cloning this repo directly, run these commands:

pip3 install -U otree-core
otree startproject oTree
otree resetdb
otree runserver

Example game: guess 2/3 of the average

Below is a full implementation of the Guess 2/3 of the average game, where everyone guesses a number, and the winner is the person closest to 2/3 of the average. The game is repeated for 3 rounds. You can play the below game here.

models.py

from otree.api import (
    models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer,
    Currency
)

class Constants(BaseConstants):
    players_per_group = 3
    num_rounds = 3
    name_in_url = 'guess_two_thirds'

    jackpot = Currency(100)
    guess_max = 100


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    two_thirds_avg = models.FloatField()
    best_guess = models.PositiveIntegerField()
    num_winners = models.PositiveIntegerField()

    def set_payoffs(self):
        players = self.get_players()
        guesses = [p.guess for p in players]
        two_thirds_avg = (2 / 3) * sum(guesses) / len(players)
        self.two_thirds_avg = round(two_thirds_avg, 2)

        self.best_guess = min(guesses,
            key=lambda guess: abs(guess - self.two_thirds_avg))

        winners = [p for p in players if p.guess == self.best_guess]
        self.num_winners = len(winners)

        for p in winners:
            p.is_winner = True
            p.payoff = Constants.jackpot / self.num_winners

    def two_thirds_avg_history(self):
        return [g.two_thirds_avg for g in self.in_previous_rounds()]


class Player(BasePlayer):
    guess = models.PositiveIntegerField(max=Constants.guess_max)
    is_winner = models.BooleanField(initial=False)

views.py

from . import models
from otree.api import Page, WaitPage


class Introduction(Page):
    def is_displayed(self):
        return self.round_number == 1


class Guess(Page):
    form_model = models.Player
    form_fields = ['guess']


class ResultsWaitPage(WaitPage):
    def after_all_players_arrive(self):
        self.group.set_payoffs()


class Results(Page):
    def vars_for_template(self):
        sorted_guesses = sorted(p.guess for p in self.group.get_players())

        return {'sorted_guesses': sorted_guesses}


page_sequence = [Introduction,
                 Guess,
                 ResultsWaitPage,
                 Results]

HTML templates

Instructions.html Introduction.html Guess.html Results.html

tests.py (optional)

Test bots for multiplayer games run in parallel, and can run either from the command line, or in the browser, which you can try here.

from otree.api import Bot, SubmissionMustFail
from . import views
from .models import Constants

class PlayerBot(Bot):
    cases = ['p1_wins', 'p1_and_p2_win']

    def play_round(self):
        if self.subsession.round_number == 1:
            yield (views.Introduction)

        if self.case == 'p1_wins':
            if self.player.id_in_group == 1:
                for invalid_guess in [-1, 101]:
                    yield SubmissionMustFail(views.Guess, {"guess": invalid_guess})
                yield (views.Guess, {"guess": 9})
                assert self.player.payoff == Constants.jackpot
                assert 'you win' in self.html
            else:
                yield (views.Guess, {"guess": 10})
                assert self.player.payoff == 0
                assert 'you did not win' in self.html
        else:
            if self.player.id_in_group in [1, 2]:
                yield (views.Guess, {"guess": 9})
                assert self.player.payoff == Constants.jackpot / 2
                assert 'you are one of the 2 winners' in self.html
            else:
                yield (views.Guess, {"guess": 10})
                assert self.player.payoff == 0
                assert 'you did not win' in self.html

        yield (views.Results)

See docs on bots.

Features

Contact & support

Help & discussion mailing list

Contact chris@otree.org with bug reports.

Contributors

Related repositories

The oTree core libraries are here.