Example code

This page contains examples of how various oTree functions should be used. For example, search this page for before_next_page or after_all_players_arrive.

quiz_with_explanation/MyPage.html

From otree-snippets


{{ block title }}
    Quiz
{{ endblock }}
{{ block content }}

    {{ include Constants.form_template }}
    {{ next_button }}

{{ endblock }}

quiz_with_explanation/form.html

From otree-snippets


{{ for d in fields }}
    {{ formfield d.name }}
    {{ if show_solutions }}
        {{ if d.is_correct }}
        <p class="solution-correct">
            Correct.
        </p>
        {{ else }}
        <p class="solution-incorrect">
            {{ d.explanation }}
        </p>
        {{ endif }}
    {{ endif }}
{{ endfor }}

questions_from_csv/__init__.py

From otree-snippets


from otree.api import *

doc = """
Read quiz questions from a CSV.
(Also randomizes order)
It would be much simpler to implement this using rounds (1 question per round),
as is done in the image rating app; however, this approach with live pages
has faster gameplay since it's all done in 1 page, and leads to a more compact
data export. Consider using this version if you have many questions or if speed is a high priority. 
"""


class Constants(BaseConstants):
    name_in_url = 'questions_from_csv'
    players_per_group = None
    num_rounds = 1


def read_csv():
    import csv
    import random

    f = open(__name__ + '/stimuli.csv', encoding='utf-8-sig')
    rows = list(csv.DictReader(f))

    random.shuffle(rows)
    return rows


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    for p in subsession.get_players():
        stimuli = read_csv()
        p.num_trials = len(stimuli)
        for stim in stimuli:
            Trial.create(player=p, **stim)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    num_correct = models.IntegerField(initial=0)
    raw_responses = models.LongStringField()


class Trial(ExtraModel):
    player = models.Link(Player)
    question = models.StringField()
    optionA = models.StringField()
    optionB = models.StringField()
    optionC = models.StringField()
    solution = models.StringField()
    choice = models.StringField()
    is_correct = models.BooleanField()


def to_dict(trial: Trial):
    return dict(
        question=trial.question,
        optionA=trial.optionA,
        optionB=trial.optionB,
        optionC=trial.optionC,
        id=trial.id,
    )


# PAGES
class Stimuli(Page):
    form_model = 'player'
    form_fields = ['raw_responses']

    @staticmethod
    def js_vars(player: Player):
        stimuli = [to_dict(trial) for trial in Trial.filter(player=player)]
        return dict(trials=stimuli)

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        import json

        responses = json.loads(player.raw_responses)
        for trial in Trial.filter(player=player):
            # have to use str() because Javascript implicitly converts keys to strings
            trial.choice = responses[str(trial.id)]
            trial.is_correct = trial.choice == trial.solution
            player.num_correct += int(trial.is_correct)
        # don't need it anymore
        player.raw_responses = ''


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(trials=Trial.filter(player=player))


page_sequence = [Stimuli, Results]


def custom_export(players):
    yield ['participant', 'question', 'choice', 'is_correct']

    for player in players:
        participant = player.participant

        trials = Trial.filter(player=player)

        for t in trials:
            yield [participant.code, t.question, t.choice, t.is_correct]

questions_from_csv/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <p>
    You gave {{ player.num_correct }} correct answers.
    </p>

    <table class="table">
        <tr>
            <th>question</th>
            <th>optionA</th>
            <th>optionB</th>
            <th>optionC</th>
            <th>Your choice</th>
            <th>solution</th>
            <th>correct?</th>
        </tr>
        {{ for trial in trials }}
        <tr>
            <td>{{ trial.question }}</td>
            <td>{{ trial.optionA }}</td>
            <td>{{ trial.optionB }}</td>
            <td>{{ trial.optionC }}</td>
            <td>{{ trial.choice }}</td>
            <td>{{ trial.solution }}</td>
            <td>{{ trial.is_correct }}</td>
        </tr>
        {{ endfor }}
    </table>

{{ endblock }}

questions_from_csv/Stimuli.html

From otree-snippets


{{ block title }}
{{ endblock }}
{{ block content }}

<p id="question"></p>

<div>
    <button type="button" onclick="recordResponse(this)" value="A" id="optionA">
    </button>
    <button type="button" onclick="recordResponse(this)" value="B" id="optionB">
    </button>
    <button type="button" onclick="recordResponse(this)" value="C" id="optionC">
    </button>
</div>

<input type="hidden" name="raw_responses" id="raw_responses">

<script>

    let responses = {}

    let trialIndex = 0;
    let trials = js_vars.trials;

    function updateUI() {
        for (let item of ['question', 'optionA', 'optionB', 'optionC']) {
            document.getElementById(item).innerText = trials[trialIndex][item];
        }
    }

    function recordResponse(btn) {
        let trialId = trials[trialIndex].id;
        responses[trialId] = btn.value;
        trialIndex++;
        if (trialIndex === trials.length) {
            document.getElementById('raw_responses').value = JSON.stringify(responses)
            document.getElementById('form').submit();
        } else {
            updateUI();
        }
    }

    updateUI();
</script>

{{ endblock }}

gbat_keep_same_groups_part0/__init__.py

From otree-snippets


from otree.api import *


doc = """
Your app description
"""


class Constants(BaseConstants):
    name_in_url = 'gbat_keep_same_groups_part0'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# PAGES
class MyPage(Page):
    pass


page_sequence = [MyPage]

gbat_keep_same_groups_part0/MyPage.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}
{{ block content }}

    <p><i>
        This would just be the first app that appears first in the app sequence,
        to filter out some users before doing
        group_by_arrival_time.
        Maybe it asks for their consent, or has them do some real-effort task,
        to filter out people who are likely to drop out.
    </i></p>

    {{ next_button }}

{{ endblock }}

comprehension_test/Failed.html

From otree-snippets


{{ block content }}
    Sorry, you gave too many wrong answers to the comprehension test.
{{ endblock }}

back_button/Instructions.html

From otree-snippets


{{ block title }}
Instructions
{{ endblock }}
{{ block content }}

<style>
    .tab {
        display: none;
    }
</style>

<div class="tab">
    Instructions first page content goes here...
    <p>
        <button type="button" class="btn btn-primary btn-tab" data-offset="1">Next</button>
    </p>
</div>
<div class="tab">
    <!-- Duplicate this div if you have more than 3 pages of instructions ... -->
    Instructions middle page content goes here.
    <p>
        <button type="button" class="btn btn-primary btn-tab" data-offset="-1">Previous</button>
        <button type="button" class="btn btn-primary btn-tab" data-offset="1">Next</button>
    </p>
</div>
<div class="tab">
    Instructions last page content goes here...
    <p>
        <button type="button" class="btn btn-primary btn-tab" data-offset="-1">Previous</button>
        <button class="btn btn-primary">Next</button>
    </p>
</div>

<script>

    let activeTab = 0;

    function showCurrentTabOnly() {
        let tabIndex = 0;
        for (let tab of document.getElementsByClassName('tab')) {
            tab.style.display = tabIndex === activeTab ? 'block' : 'none';
            tabIndex++;
        }
    }

    showCurrentTabOnly();
    for (let btn of document.getElementsByClassName('btn-tab')) {
        btn.onclick = function () {
            activeTab += parseInt(btn.dataset.offset);
            showCurrentTabOnly();
        }
    }
</script>

{{ endblock }}

back_button/Task.html

From otree-snippets


{{ block title }}
    Task
{{ endblock }}

{{ block content }}

    <p><i>Experiment goes here...</i></p>
    {{ next_button }}
{{ endblock }}

comprehension_test/__init__.py

From otree-snippets


from otree.api import *

doc = """
Comprehension test. If the user fails too many times, they exit.
"""


class Constants(BaseConstants):
    name_in_url = 'comprehension_test'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    num_failed_attempts = models.IntegerField(initial=0)
    failed_too_many = models.BooleanField(initial=False)
    quiz1 = models.IntegerField(label='What is 2 + 2?')
    quiz2 = models.StringField(
        label='What is the capital of Canada?',
        choices=['Ottawa', 'Toronto', 'Vancouver'],
    )
    quiz3 = models.IntegerField(label="What year did COVID-19 start?")
    quiz4 = models.BooleanField(label="Is 9 a prime number")


class MyPage(Page):
    form_model = 'player'
    form_fields = ['quiz1', 'quiz2', 'quiz3', 'quiz4']

    @staticmethod
    def error_message(player: Player, values):
        # alternatively, you could make quiz1_error_message, quiz2_error_message, etc.
        # but if you have many similar fields, this is more efficient.
        solutions = dict(quiz1=4, quiz2='Ottawa', quiz3=2019, quiz4=False)

        errors = {f: 'Wrong' for f in solutions if values[f] != solutions[f]}
        if errors:
            player.num_failed_attempts += 1
            if player.num_failed_attempts >= 3:
                player.failed_too_many = True
            else:
                return errors


class Failed(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.failed_too_many


class Results(Page):
    pass


page_sequence = [MyPage, Failed, Results]

min_time_on_page/Page1.html

From otree-snippets


{{ block title }}
    Page 1
{{ endblock }}
{{ block content }}

    <p><i>Click next...</i></p>

    {{ next_button }}

{{ endblock }}

pay_random_app3/Results.html

From otree-snippets


{{ block title }}
    Final Results
{{ endblock }}
{{ block content }}
    <p>
        The app that was randomly chosen for payment is {{ player.app_to_pay }}.
        You payoff from that app (and therefore your total payoff)
        is {{ participant.payoff }}.
    </p>
{{ endblock }}

pay_random_app3/__init__.py

From otree-snippets


from otree.api import *


doc = """
App where we choose the app to be paid
"""


class Constants(BaseConstants):
    name_in_url = 'pay_random_app3'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    app_to_pay = models.StringField()


class PayRandomApp(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        import random

        participant = player.participant
        apps = [
            'pay_random_app1',
            'pay_random_app2',
        ]
        app_to_pay = random.choice(apps)
        participant.payoff = participant.app_payoffs[app_to_pay]
        player.app_to_pay = app_to_pay


class Results(Page):
    pass


page_sequence = [PayRandomApp, Results]

pay_random_app3/PayRandomApp.html

From otree-snippets


{{ block content }}

    <p>A random app will now be chosen for payment.</p>
    {{ next_button }}

{{ endblock }}

appcopy1/MyPage.html

From otree-snippets


{{ block title }}
    App A
{{ endblock }}
{{ block content }}

    <p><i>This app gets repeated, with another app in between.</i></p>

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

appcopy1/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'appcopy1'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    bbb = models.IntegerField(widget=widgets.RadioSelectHorizontal)


def bbb_choices(player: Player):
    return [1, 2, 3]


class MyPage(Page):
    # every page needs an explicit template_name
    template_name = 'appcopy1/MyPage.html'

    form_model = 'player'
    form_fields = ['bbb']


page_sequence = [MyPage]

pay_random_app1/MyPage.html

From otree-snippets


{{ block title }}
    App 1
{{ endblock }}
{{ block content }}

    <p><i>Your game would normally go here. In this case, your payoff will be determined randomly.</i></p>
    {{ next_button }}

{{ endblock }}

pay_random_app1/Results.html

From otree-snippets


{{ block title }}
App 1 Results
{{ endblock }}

{{ block content }}

<p>
    Your payoff in this app is {{ player.potential_payoff }}.
</p>

{{ next_button }}
{{ endblock }}

comprehension_test/Results.html

From otree-snippets


{{ block title }}
    Thank you
{{ endblock }}

{{ block content }}
    <p>You answered all questions correctly</p>
{{ endblock }}

comprehension_test/MyPage.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}
{{ block content }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

gbat_new_partners/MyPage.html

From otree-snippets


{{ block title }}
    Round {{ subsession.round_number }}
{{ endblock }}
{{ block content }}

    <p><i>
        This game uses group_by_arrival_time for multiple rounds.
        In each round, we ensure that you are matched with a different player.
    </i></p>

    <p>
    Your partner is player {{ partner.id_in_subsession }}.
    </p>

    <p>Here are the pairs that have already played together: {{ session.past_groups }}</p>

    {{ next_button }}

{{ endblock }}

gbat_new_partners/__init__.py

From otree-snippets


from otree.api import *


doc = """
group by arrival time, but in each round assign to a new partner.
"""


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


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    session = subsession.session
    session.past_groups = []


def group_by_arrival_time_method(subsession: Subsession, waiting_players):
    session = subsession.session

    import itertools

    for possible_group in itertools.combinations(waiting_players, 2):
        # use a set, so that we can easily compare even if order is different
        # e.g. {1, 2} == {2, 1}
        pair_ids = set(p.id_in_subsession for p in possible_group)
        if pair_ids not in session.past_groups:
            # mark this group as used, so we don't repeat it in the next round.
            session.past_groups.append(pair_ids)
            return possible_group


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class ResultsWaitPage(WaitPage):
    group_by_arrival_time = True
    body_text = "Waiting to pair you with someone you haven't already played with"


class MyPage(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(partner=player.get_others_in_group()[0])


page_sequence = [ResultsWaitPage, MyPage]

css/MyPage.html

From otree-snippets


{{ block title }}
Demo of custom styles
{{ endblock }}
{{ block content }}
<style>
    .otree-timer {
        position: sticky;
        top: 0px;
    }

    .chat-widget {
        position: fixed;
        bottom: 0px;
        right: 0px;
        max-width: 50em;
        z-index: 100;
        background-color: #eee;
        padding: 1em;
    }

</style>

<p>
    Sticky timer and chat box in bottom-right corner.
</p>
<p>
    Sample text sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
</p>
<p>
    Sample text sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
</p>
<p>
    Sample text sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
</p>
<p>
    Sample text sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
</p>
<p>
    Sample text sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
</p>
<p>
    Sample text sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
    sample text sample text sample text sample text sample text sample text sample text sample text
</p>

<div class="chat-widget">
    <b>Chat with your group</b>
    {{ chat }}
</div>


{{ endblock }}

css/__init__.py

From otree-snippets


from otree.api import *


doc = """
Using CSS to style timer and chat box.
"""


class Constants(BaseConstants):
    name_in_url = 'css'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# PAGES
class MyPage(Page):
    timeout_seconds = 30 * 60


page_sequence = [MyPage]

live_volunteer/MyPage.html

From otree-snippets


{{ block content }}

<p>
    This is a volunteer dilemma with {{ Constants.players_per_group }} players per group.
    If someone in the group volunteers,
    each player will get a reward of {{ Constants.reward }}.
    But the volunteer will pay a penalty of {{ Constants.volunteer_cost }}.
</p>

<button type="button" class="btn btn-primary" onclick="sendVolunteer()">I volunteer</button>
<br><br>

<p>Here you can chat with your group.</p>
{{ chat }}

<script>
    function sendVolunteer() {
        liveSend({'volunteer': true});
    }

    function liveRecv(data) {
        if (data.finished) {
            document.getElementById('form').submit();
        }
    }

    document.addEventListener('DOMContentLoaded', (event) => {
        liveSend({});
    });
</script>

{{ endblock }}

live_volunteer/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    {{ if player.is_volunteer }}
        <p>You volunteered.</p>
    {{ else }}
        <p>Someone else volunteered.</p>
    {{ endif }}
    <p>Your payoff is therefore {{ player.payoff }}.</p>

    {{ next_button }}
{{ endblock }}

live_volunteer/__init__.py

From otree-snippets


from otree.api import *


doc = """
Live volunteer's dilemma (first player to click moves everyone forward).
"""


class Constants(BaseConstants):
    name_in_url = 'live_volunteer'
    players_per_group = 3
    num_rounds = 1
    reward = cu(1000)
    volunteer_cost = cu(500)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    has_volunteer = models.BooleanField(initial=False)


class Player(BasePlayer):
    is_volunteer = models.BooleanField()
    volunteer_id = models.IntegerField()


# PAGES
class MyPage(Page):
    @staticmethod
    def is_displayed(player: Player):
        group = player.group
        return not group.has_volunteer

    @staticmethod
    def live_method(player: Player, data):
        group = player.group

        if group.has_volunteer:
            return
        if data.get('volunteer'):
            group.has_volunteer = True
            for p in player.get_others_in_group():
                p.payoff = Constants.reward
                p.is_volunteer = False
            player.is_volunteer = True
            player.payoff = Constants.reward - Constants.volunteer_cost
            return {0: dict(finished=True)}

    @staticmethod
    def error_message(player: Player, values):
        group = player.group
        if not group.has_volunteer:
            return "Can't move forward"


class Results(Page):
    pass


page_sequence = [MyPage, Results]

multi_page_timeout/Intro.html

From otree-snippets


{{ block title }}
    Introduction
{{ endblock }}

{{ block content }}

    <p>Press next to start the timer...</p>
    {{ next_button }}
{{ endblock }}

multi_page_timeout/__init__.py

From otree-snippets


from otree.api import *

doc = """
Timeout spanning multiple pages
"""


class Constants(BaseConstants):
    name_in_url = 'multi_page_timeout'
    players_per_group = None
    num_rounds = 1
    timer_text = "Time to complete this section:"


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


def get_timeout_seconds1(player: Player):
    participant = player.participant
    import time

    return participant.expiry - time.time()


def is_displayed1(player: Player):
    return get_timeout_seconds1(player) > 0


class Intro(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        participant = player.participant
        import time

        participant.expiry = time.time() + 60


class Page1(Page):
    is_displayed = is_displayed1
    get_timeout_seconds = get_timeout_seconds1
    timer_text = Constants.timer_text


class Page2(Page):
    is_displayed = is_displayed1
    get_timeout_seconds = get_timeout_seconds1
    timer_text = Constants.timer_text


class Page3(Page):
    is_displayed = is_displayed1
    timer_text = Constants.timer_text
    get_timeout_seconds = get_timeout_seconds1


page_sequence = [Intro, Page1, Page2, Page3]

multi_page_timeout/Page3.html

From otree-snippets


{{ block title }}
    Page 3
{{ endblock }}
{{ block content }}

    <p>Page content goes here...</p>

    {{ next_button }}

{{ endblock }}

multi_page_timeout/Page2.html

From otree-snippets


{{ block title }}
    Page 2
{{ endblock }}
{{ block content }}

    <p>Page content goes here...</p>

    {{ next_button }}

{{ endblock }}

balance_treatments_for_dropouts/Task.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}

{{ block content }}
    <p>
        You were assigned to the <b>{{ player.color }}</b> treatment, because this treatment group currently
        was completed by the fewest number of players.
    </p>

    {{ next_button }}
{{ endblock }}

multi_page_timeout/Page1.html

From otree-snippets


{{ block title }}
    Page 1
{{ endblock }}
{{ block content }}

    <p>Page content goes here...</p>

    {{ next_button }}

{{ endblock }}

getattr_setattr/Results.html

From otree-snippets


{{ block content }}
    <p>
        You chose number {{ player.chosen_number }}.
        The random number in that field was {{ chosen_value }}.
        Therefore, your payoff is {{ player.payoff }}.
        By the way, the sum of all numbers from num1 to num{{ player.chosen_number }}
        was {{ sum_to_n }}.
    </p>
{{ endblock }}

gbat_treatments/MyPage.html

From otree-snippets


{{ block content }}

    Your group is in the {{ if group.treatment }} treatment {{ else }} control {{ endif }} cohort.

{{ endblock }}

sequential/__init__.py

From otree-snippets


from otree.api import *


doc = """
Sequential game (asymmetric)
"""


class Constants(BaseConstants):
    name_in_url = 'sequential'
    players_per_group = 3
    num_rounds = 1
    main_template = __name__ + '/Decide.html'


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    mixer = models.StringField(
        choices=['Pineapple juice', 'Orange juice', 'Cola', 'Milk'],
        label="Choose a mixer",
        widget=widgets.RadioSelect,
    )
    liqueur = models.StringField(
        choices=['Blue curacao', 'Triple sec', 'Amaretto', 'Kahlua'],
        label="Choose a liqueur",
        widget=widgets.RadioSelect,
    )
    spirit = models.StringField(
        choices=['Vodka', 'Rum', 'Gin', 'Tequila'],
        label="Choose a spirit",
        widget=widgets.RadioSelect,
    )


class Player(BasePlayer):
    pass


# PAGES
class P1(Page):
    form_model = 'group'
    form_fields = ['mixer']
    template_name = Constants.main_template

    @staticmethod
    def is_displayed(player: Player):
        return player.id_in_group == 1


class WaitPage1(WaitPage):
    pass


class P2(Page):
    form_model = 'group'
    form_fields = ['liqueur']
    template_name = Constants.main_template

    @staticmethod
    def is_displayed(player: Player):
        return player.id_in_group == 2


class WaitPage2(WaitPage):
    pass


class P3(Page):
    form_model = 'group'
    form_fields = ['spirit']
    template_name = Constants.main_template

    @staticmethod
    def is_displayed(player: Player):
        return player.id_in_group == 3


class WaitPage3(WaitPage):
    pass


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        group = player.group
        return dict(players_with_contributions=group.get_players())


page_sequence = [P1, WaitPage1, P2, WaitPage2, P3, WaitPage3, Results]

sequential/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}
    <p>The cocktail consists of {{ group.mixer }}, {{ group.liqueur }}, and {{ group.spirit }}</p>
{{ endblock }}

sequential/Decide.html

From otree-snippets


{{ block title }}
    Make a cocktail!
{{ endblock }}
{{ block content }}

    <ul>
        <li>This is a sequential game with {{ Constants.players_per_group }} players.</li>
        <li>You are player {{ player.id_in_group }}.</li>
        <li>The objective is to make a cocktail with 3 ingredients. Each player chooses one ingredient.</li>
    </ul>

    {{ if player.id_in_group >= 2}}
        <p>Player 1 chose {{ group.mixer }}.</p>
    {{ endif }}

    {{ if player.id_in_group >= 3}}
        <p>Player 2 chose {{ group.liqueur }}.</p>
    {{ endif }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

pass_data_between_apps_part2/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'pass_data_between_apps2'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# PAGES
class MyPage(Page):
    pass


page_sequence = [MyPage]

pass_data_between_apps_part2/MyPage.html

From otree-snippets


{{ block title }}
    App 2
{{ endblock }}
{{ block content }}

    <p>
        In the previous app, you said your main language is
        <b>{{ participant.language}}</b>.
    </p>

{{ endblock }}

rank_players/__init__.py

From otree-snippets


from otree.api import *


doc = """
Rank players
"""


class Constants(BaseConstants):
    name_in_url = 'rank_players'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    age = models.IntegerField(label="Enter your age")
    rank = models.IntegerField()


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['age']


class ResultsWaitPage(WaitPage):
    @staticmethod
    def after_all_players_arrive(group: Group):
        players = group.get_players()
        # to do descending, use -p.age
        players.sort(key=lambda p: p.age)
        for i in range(len(players)):
            # this code checks if there is a tie and then assigns the same rank
            # if you don't need to deal with ties, then you can delete this.
            if i > 0 and players[i].age == players[i - 1].age:
                rank = players[i - 1].rank
            else:
                rank = i + 1
            players[i].rank = rank


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        group = player.group
        return dict(num_players=len(group.get_players()))


page_sequence = [MyPage, ResultsWaitPage, Results]

rank_players/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}
    <p>
        Your age is {{ player.age }}.
        In your group of {{ num_players }} players,
        your rank is {{ player.rank }}
        (youngest to oldest).
    </p>
{{ endblock }}

rank_players/MyPage.html

From otree-snippets


{{ block content }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

pay_random_app2/__init__.py

From otree-snippets


from otree.api import *


doc = """
"""


class Constants(BaseConstants):
    name_in_url = 'pay_random_app2'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    potential_payoff = models.CurrencyField()


# PAGES
class MyPage(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        """in a single-player game you typically set payoff in before_next_page,
        so that's what we demonstrate here.
        """
        participant = player.participant
        import random

        potential_payoff = random.randint(100, 200)
        player.potential_payoff = potential_payoff
        # this is designed for apps that have a single round.
        # if your app has multiple rounds, see the pay_random_round app.
        participant.app_payoffs[__name__] = potential_payoff


class Results(Page):
    pass


page_sequence = [MyPage, Results]

pay_random_app2/Results.html

From otree-snippets


{{ block title }}
App 2 Results
{{ endblock }}

{{ block content }}

<p>
    Your payoff in this app is {{ player.potential_payoff }}.
</p>

{{ next_button }}
{{ endblock }}

pay_random_app2/MyPage.html

From otree-snippets


{{ block title }}
    App 2
{{ endblock }}
{{ block content }}

    <p><i>Your game would normally go here. In this case, your payoff will be determined randomly.</i></p>
    {{ next_button }}

{{ endblock }}

placeholder/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'placeholder'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# PAGES
class MyPage(Page):
    pass


page_sequence = [MyPage]

placeholder/MyPage.html

From otree-snippets


{{ block title }}
    Placeholder
{{ endblock }}
{{ block content }}

    <p><i>This app is just a placeholder.</i></p>

{{ endblock }}

sequential_symmetric/__init__.py

From otree-snippets


from otree.api import *


doc = """
Sequential / cascade game (symmetric)
"""


class Constants(BaseConstants):
    name_in_url = 'sequential_symmetric'
    players_per_group = 3
    num_rounds = 1
    main_template = __name__ + '/Decide.html'
    table_template = __name__ + '/table.html'
    form_fields = ['decision']


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    decision = models.IntegerField(
        label="How many countries are there in Africa? (Make your best guess)"
    )


def vars_for_template1(player: Player):
    return dict(
        players=[
            p
            for p in player.get_others_in_group()
            if p.id_in_group < player.id_in_group
        ]
    )


# PAGES
class P1(Page):
    form_model = 'player'
    form_fields = Constants.form_fields
    template_name = Constants.main_template

    @staticmethod
    def is_displayed(player: Player):
        return player.id_in_group == 1

    vars_for_template = vars_for_template1


class WaitPage1(WaitPage):
    pass


class P2(Page):
    form_model = 'player'
    form_fields = Constants.form_fields
    template_name = Constants.main_template

    @staticmethod
    def is_displayed(player: Player):
        return player.id_in_group == 2

    vars_for_template = vars_for_template1


class WaitPage2(WaitPage):
    pass


class P3(Page):
    form_model = 'player'
    form_fields = Constants.form_fields
    template_name = Constants.main_template

    @staticmethod
    def is_displayed(player: Player):
        return player.id_in_group == 3

    vars_for_template = vars_for_template1


class WaitPage3(WaitPage):
    pass


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        group = player.group
        return dict(players=group.get_players())


page_sequence = [P1, WaitPage1, P2, WaitPage2, P3, WaitPage3, Results]

sequential_symmetric/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}
    <p>Here are the results. (You are player {{ player.id_in_group }}.)</p>

    {{ include Constants.table_template }}
{{ endblock }}

gbat_treatments/__init__.py

From otree-snippets


from otree.api import *


doc = """
Conventionally, group-level treatments are assigned in creating_session:

for g in subsession.get_groups():
    g.treatment = random.choice([True, False])

However, this doesn't work when using group_by_arrival_time,
because groups are not determined until players arrive at the wait page.
(All players are in the same group initially.)
Instead, you need to assign treatments in after_all_players_arrive.
"""


class Constants(BaseConstants):
    name_in_url = 'gbat_treatments'
    players_per_group = 2
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    treatment = models.BooleanField()


class Player(BasePlayer):
    pass


class GBATWaitPage(WaitPage):
    group_by_arrival_time = True

    @staticmethod
    def after_all_players_arrive(group: Group):
        import random

        group.treatment = random.choice([True, False])


class MyPage(Page):
    pass


page_sequence = [GBATWaitPage, MyPage]

sequential_symmetric/Decide.html

From otree-snippets


{{ block content }}
    <ul>
        <li>This is a sequential game with {{ Constants.players_per_group }} players.</li>
        <li>You are player {{ player.id_in_group }}.</li>
        <li>Each player will see the previous player's choices</li>
    </ul>

    {{ include Constants.table_template }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

random_num_rounds_multiplayer/__init__.py

From otree-snippets


from otree.api import *


doc = """
Random number of rounds for multiplayer (random stopping rule)
"""


class Constants(BaseConstants):
    name_in_url = 'random_num_rounds_multiplayer'
    players_per_group = None
    # choose num_rounds high enough that the chance of
    # maxing out is negligible
    num_rounds = 50
    stopping_probability = 0.2


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    for p in subsession.get_players():
        p.participant.finished_rounds = False


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# PAGES
class MyPage(Page):
    pass


class ResultsWaitPage(WaitPage):
    @staticmethod
    def after_all_players_arrive(group: Group):
        import random

        if random.random() < Constants.stopping_probability:
            print('ending game')
            for p in group.get_players():
                p.participant.finished_rounds = True

        # your usual after_all_players_arrive goes here...


class Results(Page):
    @staticmethod
    def app_after_this_page(player: Player, upcoming_apps):
        participant = player.participant
        if participant.finished_rounds:
            return upcoming_apps[0]


page_sequence = [MyPage, ResultsWaitPage, Results]

slider_live_label/__init__.py

From otree-snippets


from otree.api import *


doc = """
Slider with live updating label
"""


class Constants(BaseConstants):
    name_in_url = 'slider_live_label'
    players_per_group = None
    num_rounds = 1
    endowment = 100


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    give = models.IntegerField(
        min=0, max=Constants.endowment, label="How much do you want to give?"
    )


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['give']

    @staticmethod
    def js_vars(player: Player):
        return dict(endowment=Constants.endowment)


page_sequence = [MyPage]

slider_live_label/MyPage.html

From otree-snippets


{{ block title }}

{{ endblock }}
{{ block content }}

<p>Move the slider to decide how much to give.</p>

<input type="range" name="give" min="0" max="{{ Constants.endowment }}" oninput="updateDescription(this)">
<p id="description"></p>
<!-- by leaving the description blank initially, we prompt the user to move the slider,
reducing the anchoring/default effect. -->

<script>
    let description = document.getElementById('description');
    function updateDescription(input) {
        let give = parseInt(input.value);
        let keep = js_vars.endowment - give;
        description.innerText = `Give ${give} points and keep ${keep} for yourself.`
    }
</script>

{{ next_button }}

{{ endblock }}

random_num_rounds_multiplayer/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}
{{ block content }}

    <p><i>Your results page goes here...</i></p>

    {{ if participant.finished_rounds }}
        <p>The game was randomly determined to stop at this round.</p>
    {{ else }}
        <p>The game will continue to the next round.</p>
    {{ endif }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

random_num_rounds_multiplayer/MyPage.html

From otree-snippets


{{ block title }}
Game
{{ endblock }}
{{ block content }}

<p>
    This game uses a random stopping rule. After each round,
    the game has a probability of {{ Constants.stopping_probability }} of being stopped.
</p>

<p><i>Your game goes here...</i></p>

{{ formfields }}
{{ next_button }}

{{ endblock }}

pay_random_app1/__init__.py

From otree-snippets


from otree.api import *


doc = """
Your app description
"""


class Constants(BaseConstants):
    name_in_url = 'pay_random_app1'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    for p in subsession.get_players():
        p.participant.app_payoffs = {}


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    potential_payoff = models.CurrencyField()


# PAGES
class MyPage(Page):
    pass


class ResultsWaitPage(WaitPage):
    @staticmethod
    def after_all_players_arrive(group: Group):
        """
        In multiplayer games, payoffs are typically set in after_all_players_arrive,
        so that's what we demonstrate here.
        """
        import random

        for p in group.get_players():
            participant = p.participant
            potential_payoff = random.randint(100, 200)
            p.potential_payoff = potential_payoff
            # __name__ is the name of the current app
            participant.app_payoffs[__name__] = potential_payoff


class Results(Page):
    pass


page_sequence = [MyPage, ResultsWaitPage, Results]

waiting_too_long_part2/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'waiting_too_long_solo'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class SoloTask(Page):
    pass


page_sequence = [SoloTask]

waiting_too_long_part2/SoloTask.html

From otree-snippets


{{ block title }}
    Single-player task
{{ endblock }}
{{ block content }}

    <p><i>Here you can put a single-player task....</i></p>

{{ endblock }}

radio/MyPage.html

From otree-snippets


{{ block content }}

<p><i>Radio buttons without labels (visual/analog scale, similar to a slider)</i></p>
<p>
    Least &nbsp;
    {{ for choice in form.f1 }}
    {{ choice }}
    {{ endfor }}
    &nbsp; Most
</p>
{{ formfield_errors 'f1' }}
<br>

<p><i>Labels under radio buttons</i></p>
<div style="display: flex">
        {{ for choice in form.f2 }}
        <div style="flex: 1; text-align: center">
            {{ choice }}
            <br>
            <span style="text-align: center">{{ choice.label }}</span>
        </div>
        {{ endfor }}
</div>
{{ formfield_errors 'f2' }}
<br>

{# todo: radio buttons laid out individually, no loop (by index) #}

{{ next_button }}

{{ endblock }}

radio/__init__.py

From otree-snippets


from otree.api import *


doc = """
Radio buttons in various layouts, looping over radio choices
"""


class Constants(BaseConstants):
    name_in_url = 'radio'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    f1 = models.IntegerField(
        widget=widgets.RadioSelect, choices=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    )
    f2 = models.IntegerField(
        widget=widgets.RadioSelect, choices=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    )


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['f1', 'f2']



page_sequence = [MyPage]

constant_sum/__init__.py

From otree-snippets


from otree.api import *


doc = """
Your app description
"""


class Constants(BaseConstants):
    name_in_url = 'constant_sum'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    a = models.CurrencyField()
    b = models.CurrencyField()
    c = models.CurrencyField()


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['a', 'b', 'c']

    @staticmethod
    def error_message(player: Player, values):
        # since 'values' is a dict, you could also do sum(values.values())
        if values['a'] + values['b'] + values['c'] != 100:
            return 'Numbers must add up to 100'


page_sequence = [MyPage]

constant_sum/MyPage.html

From otree-snippets


{{ block content }}

<p>Please split your 100 points between A, B, and C.</p>

{{ formfields }}
<p>
    <b>Total: <span id="total"></span></b>
</p>

<script>
    let inputs = document.getElementsByTagName('input');
    let totalDisplay = document.getElementById('total');

    function updateSum() {
        let total = 0;
        for (let input of inputs) {
            total += parseInt(input.value || 0);
        }
        totalDisplay.innerText = total;
    }

    for (let input of inputs) {
        input.oninput = updateSum;
    }
</script>
{{ next_button }}

{{ endblock }}

history_table/__init__.py

From otree-snippets


from otree.api import *

doc = """History table"""

class Constants(BaseConstants):
    name_in_url = 'history_table'
    players_per_group = None
    num_rounds = 10


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    number = models.IntegerField(label="Enter a number")


class MyPage(Page):
    form_model = 'player'
    form_fields = ['number']


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(me_in_all_rounds=player.in_all_rounds())


page_sequence = [MyPage, Results]

history_table/Results.html

From otree-snippets


{{ block title }}
History
{{ endblock }}
{{ block content }}

<table class="table">
    <tr>
        <th>Round</th>
        <th>Number</th>
    </tr>
    {{ for p in me_in_all_rounds }}
    <tr>
        <td>{{ p.round_number }}</td>
        <td>{{ p.number }}</td>
    </tr>
    {{ endfor }}
</table>

{{ next_button }}

{{ endblock }}

waiting_too_long_part1/__init__.py

From otree-snippets


from otree.api import *

doc = """group_by_arrival_time timeout (continue with solo task)"""


class Constants(BaseConstants):
    name_in_url = 'waiting_too_long'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


def group_by_arrival_time_method(subsession, waiting_players):
    if len(waiting_players) >= 2:
        return waiting_players[:2]
    for player in waiting_players:
        if waiting_too_long(player):
            # make a single-player group.
            return [player]


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    favorite_color = models.StringField()


def waiting_too_long(player: Player):
    participant = player.participant

    import time

    # assumes you set wait_page_arrival in PARTICIPANT_FIELDS.
    return time.time() - participant.wait_page_arrival > 60


class GBAT(WaitPage):
    group_by_arrival_time = True

    @staticmethod
    def app_after_this_page(player: Player, upcoming_apps):
        if len(player.get_others_in_group()) == 0:
            return upcoming_apps[0]


class GroupTask(Page):
    form_model = 'player'
    form_fields = ['favorite_color']


class MyWait(WaitPage):
    pass


class Results(Page):
    pass


page_sequence = [GBAT, GroupTask, MyWait, Results]

waiting_too_long_part1/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <p>The colors chosen in your group were:</p>

    <ul>
        {{ for p in group.get_players() }}
            <li>{{ p.favorite_color }}</li>
        {{ endfor }}
    </ul>

    {{ next_button }}
{{ endblock }}

waiting_too_long_part1/GroupTask.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}
{{ block content }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

groups_csv/__init__.py

From otree-snippets


from otree.api import *
from pprint import pprint


doc = """
Reads groups from a CSV file.
Inside this app, you will find a groups6.csv,
which defines the groups in the case where there are 6 players.
You can edit the file in Excel, or in plain text.
In the below example, there are 5 rows, defining 5 rounds. 
In each row, empty cells are used to separate groups. 
So, in round 1, there are 3 groups: players 1&4, 2&5, 3&6:
1,4,,2,5,,3,6
1,2,,3,4,,6,5
1,3,,6,2,,5,4
1,6,,5,3,,4,2
1,5,,4,6,,2,3
If you want to create a session with a different number of players, 
such as 12, you would need to create a file called groups12.csv.
"""


def make_group(comma_delim_string):
    return [int(x) for x in comma_delim_string.split(',')]


class Constants(BaseConstants):
    name_in_url = 'groups_csv'
    players_per_group = None
    num_rounds = 5


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# FUNCTIONS
def creating_session(subsession: Subsession):
    session = subsession.session

    if subsession.round_number == 1:
        num_participants = session.num_participants
        fn = 'groups_csv/groups{}.csv'.format(num_participants)
        with open(fn) as f:
            matrices = []
            for line in f:
                line = line.strip()
                group_specs = line.split(',,')
                matrix = [make_group(spec) for spec in group_specs]
                matrices.append(matrix)
            session.matrices = matrices
    this_round_matrix = session.matrices[subsession.round_number - 1]
    subsession.set_group_matrix(this_round_matrix)
    pprint(this_round_matrix)


# PAGES
class MyPage(Page):
    pass


page_sequence = [
    MyPage,
]

groups_csv/MyPage.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}

{{ block content }}

    <p>
        No content here; this app is just to demonstrate group shuffling
        (look at the groups in the admin interface).
    </p>

    {{ next_button }}

{{ endblock }}

radio_switching_point/__init__.py

From otree-snippets


from otree.api import *

doc = """
Table where each row has a left/right choice,
like the strategy method.
This app enforces a single switching point
"""


class Constants(BaseConstants):
    name_in_url = 'radio_switching_point'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    left_side_amount = models.IntegerField(initial=10)
    switching_point = models.IntegerField()


# PAGES
class Decide(Page):
    form_model = 'player'
    form_fields = ['switching_point']

    @staticmethod
    def vars_for_template(player):
        return dict(right_side_amounts=range(10, 21, 1))


class Results(Page):
    pass


page_sequence = [Decide, Results]

radio_switching_point/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <p>Your switching point was {{ player.switching_point }}</p>

{{ endblock }}

radio_switching_point/Decide.html

From otree-snippets


{{ block title }}
Choose a value for each row
{{ endblock }}

{{ block content }}

<input type="hidden" name="switching_point" id="id_switching_point">
{{ formfield_errors 'switching_point' }}

<table class="table table-striped">
    <colgroup>
        <col width="45%">
        <col width="10%">
        <col width="45%">
    </colgroup>
    <tr>
        <td align="right"><b>Option A</b></td>
        <td></td>
        <td align="left"><b>Option B</b></td>
    </tr>
    {{ for amount in right_side_amounts }}
    <tr>
        <td align="right">
            <b>{{ player.left_side_amount }}</b> now
        <td align="middle">
            <input type="radio"
                   value="left"
                   name="{{ amount }}"
                   required>&nbsp;&nbsp;
            <input type="radio"
                   name="{{ amount }}"
                   value="right" data-amount="{{ amount }}"
                   required>
        </td>
        <td align="left">
            <b>{{ amount }} </b> next month
    </tr>
    {{ endfor }}
</table>


<button type="button" class="btn btn-primary" onclick="submitForm()">Next</button>


{{ endblock }}


{{ block scripts }}
<script>
    let allRadios = document.querySelectorAll('input[type=radio]')
    function submitForm() {
        let form = document.getElementById('form');
        if (form.reportValidity()) {
            let switchingPoint = document.getElementById('id_switching_point');

            let allChoicesAreOnLeft = true;
            for (let radio of allRadios) {
                if (radio.value === 'right' && radio.checked) {
                    switchingPoint.value = radio.dataset.amount;
                    allChoicesAreOnLeft = false;
                    break;
                }
            }
            if (allChoicesAreOnLeft) {
                // '9999' represents the valueInput if the user didn't click the right side for any choice
                // it means their switching point is off the scale. you can change 9999 to some other valueInput
                // that is larger than any right-hand-side choice.
                switchingPoint.value = '9999';
            }
            form.submit();
        }
    }

    function onRadioClick(evt) {
        let clickedRadio = evt.target;
        let afterClickedRadio = false;
        let clickedRightRadio = clickedRadio.value === 'right';

        for (let aRadio of allRadios) {
            if (aRadio === clickedRadio) {
                afterClickedRadio = true;
                continue;
            }
            if (clickedRightRadio && afterClickedRadio && aRadio.value === 'right') {
                aRadio.checked = true;
            }
            if (!clickedRightRadio && !afterClickedRadio && aRadio.value === 'left') {
                aRadio.checked = true;
            }
        }
    }

    document.addEventListener("DOMContentLoaded", function (event) {
        for (let radio of document.querySelectorAll('input[type=radio]')) {
            radio.onchange = onRadioClick;
        }
    });

</script>
{{ endblock }}

waiting_too_long_part0/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'waiting_too_long_part0'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class MyPage(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        participant = player.participant

        import time
        participant.wait_page_arrival = time.time()


page_sequence = [MyPage]

waiting_too_long_part0/MyPage.html

From otree-snippets


{{ block title }}
    Welcome
{{ endblock }}
{{ block content }}

    <p>
        Welcome! Please press next.
        You will be grouped with another participant.
        If we cannot group you with another participant within
        a minute, you will proceed to a single-player task.
    </p>

    {{ next_button }}

{{ endblock }}

rank_topN/__init__.py

From otree-snippets


from otree.api import *


doc = """
Ranking your top N choices from a list of options.
"""


class Constants(BaseConstants):
    name_in_url = 'rank_topN'
    players_per_group = None
    num_rounds = 1
    choices = ['Martini', 'Margarita', 'White Russian', 'Pina Colada', 'Gin & Tonic']


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


def make_rank_field(label):
    return models.StringField(choices=Constants.choices, label=label)


class Player(BasePlayer):
    rank1 = make_rank_field("Top choice")
    rank2 = make_rank_field("Second choice")
    rank3 = make_rank_field("Third choice")


class MyPage(Page):
    form_model = 'player'
    form_fields = ['rank1', 'rank2', 'rank3']

    @staticmethod
    def error_message(player: Player, values):
        choices = [values['rank1'], values['rank2'], values['rank3']]
        if len(set(choices)) != len(choices):
            return "You cannot choose the same item twice"


class Results(Page):
    pass


page_sequence = [MyPage, Results]

rank_topN/Results.html

From otree-snippets


{{ block content }}

    <p>Your top choices are {{ player.rank1}}, {{ player.rank2}}, and {{ player.rank3 }}.</p>

{{ endblock }}

rank_topN/MyPage.html

From otree-snippets


{{ block title }}
Rank your favorite drinks
{{ endblock }}
{{ block content }}

{{ formfields }}

{{ next_button }}

{{ endblock }}

gbat_treatments_complex/__init__.py

From otree-snippets


from otree.api import *


doc = """
Similar to the basic gbat_treatments app, except:
-   Treatments are balanced rather than independently randomized.
-   The game persists for multiple rounds
"""


class Constants(BaseConstants):
    name_in_url = 'gbat_treatments_complex'
    players_per_group = 2
    num_rounds = 3
    # boolean works when there are 2 treatments
    # if you have >2 treatments, change this to numbers or strings like
    # [1, 2, 3] or ['A', 'B', 'C'], etc.
    treatments = [True, False]


class Subsession(BaseSubsession):
    num_groups_created = models.IntegerField(initial=0)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class GBATWaitPage(WaitPage):
    group_by_arrival_time = True

    @staticmethod
    def is_displayed(player: Player):
        """only do GBAT in the first round. this way, players stay in the same group
        for all rounds."""
        return player.round_number == 1

    @staticmethod
    def after_all_players_arrive(group: Group):
        subsession = group.subsession

        # % is the modulus operator.
        # so when num_groups_created exceeds the max list index,
        # we go back to 0, thus creating a cycle.
        idx = subsession.num_groups_created % len(Constants.treatments)

        treatment = Constants.treatments[idx]
        for p in group.get_players():
            # since we want the treatment to persist for all rounds, we need to assign it
            # in a participant field (which persists across rounds)
            # rather than a group field, which is specific to the round.
            p.participant.time_pressure = treatment

        subsession.num_groups_created += 1


class MyPage(Page):
    pass


page_sequence = [GBATWaitPage, MyPage]

gbat_treatments_complex/MyPage.html

From otree-snippets


{{ block title }}
    Round {{ subsession.round_number }}
{{ endblock }}
{{ block content }}

    <p>
        Your group is assigned to the
        {{ if participant.time_pressure }}
            "time-pressure"
        {{ else }}
            "non-time-pressure"
        {{ endif }}
        treatment.
    </p>

    {{ next_button }}
{{ endblock }}

random_num_rounds_multiplayer_end/__init__.py

From otree-snippets


from otree.api import *


doc = """
Your app description
"""


class Constants(BaseConstants):
    name_in_url = 'random_num_rounds_multiplayer_end'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# PAGES
class MyPage(Page):
    pass


page_sequence = [MyPage]

random_num_rounds_multiplayer_end/Results.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}

{{ block content }}

    {{ next_button }}
{{ endblock }}

random_num_rounds_multiplayer_end/MyPage.html

From otree-snippets


{{ block content }}
    Thank you.
{{ endblock }}

slider_graphic/__init__.py

From otree-snippets


from otree.api import *


doc = """
An image that changes when you move a slider.
If your image is a some kind of chart, it's better to use Highcharts than static images.
See the SVO example.
"""


class Constants(BaseConstants):
    name_in_url = 'slider_graphic'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    feeling = models.IntegerField(min=0, max=3)


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['feeling']

    @staticmethod
    def vars_for_template(player: Player):
        img_paths = ['slider_graphic/{}.svg'.format(i) for i in range(4)]
        return dict(img_paths=img_paths)


page_sequence = [MyPage]

slider_graphic/MyPage.html

From otree-snippets


{{ block content }}

    <style>
        .slider-graphic {
            display: none;
            width: 6em;
        }
    </style>


    <p>Drag the slider to indicate how you feel right now.</p>
    <input type="range" name="feeling" value="1" min="0" max="3" oninput="changeGraphic(this)">
    {{ for img_path in img_paths }}
        <img src="{{ static img_path }}" class="slider-graphic">
    {{ endfor }}

    <script>
        let graphics = document.getElementsByClassName('slider-graphic');

        function changeGraphic(input) {
            for (let img of graphics) {
                img.style.display = 'none';
            }
            graphics[parseInt(input.value)].style.display = 'block';
        }
    </script>

    {{ next_button }}

{{ endblock }}

input_calculation/__init__.py

From otree-snippets


from otree.api import *


doc = """
Your app description
"""


class Constants(BaseConstants):
    name_in_url = 'input_calculation'
    players_per_group = None
    num_rounds = 1
    APR = 0.07


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    amount = models.CurrencyField(min=0, max=100000)
    num_years = models.IntegerField(min=1, max=50)


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['amount', 'num_years']

    @staticmethod
    def js_vars(player: Player):
        return dict(APR=Constants.APR)


page_sequence = [MyPage]

input_calculation/Results.html

From otree-snippets


{{ block content }}
    <p>Thank you...</p>
{{ endblock }}

input_calculation/MyPage.html

From otree-snippets


{{ block content }}

<p>Choose what investment to make at an APR of {{ Constants.APR }}</p>

{{ formfields }}
<br>
<p>Your investment will be worth:</p>
<h2><span id="projection"></span> <small>points</small></h2>

{{ next_button }}

<script>
    let amountInput = document.getElementsByName('amount')[0];
    let numYearsInput = document.getElementsByName('num_years')[0];
    let projectionEle = document.getElementById('projection');

    function recalc() {
        let amount = parseFloat(amountInput.value);
        let numYears = parseInt(numYearsInput.value);

        if (isNaN(amount) || isNaN(numYears)) {
            projectionEle.innerText = '';
        } else {
            let projection = amount * Math.pow((1 + js_vars.APR), numYears);
            projectionEle.innerText = Math.round(projection);
        }
    }

    amountInput.oninput = recalc;
    numYearsInput.oninput = recalc;
</script>

{{ endblock }}

history_table/MyPage.html

From otree-snippets


{{ block title }}
Enter a number
{{ endblock }}
{{ block content }}

{{ formfields }}

{{ next_button }}

{{ endblock }}

sequential_symmetric/table.html

From otree-snippets


{{ if players }}
<table class="table" style="width: auto">
    <tr>
        <th>Player</th>
        <th>Guess</th>
    </tr>
    {{ for p in players }}
    <tr>
        <td>Player {{ p.id_in_group }}</td>
        <td>{{ p.decision }}</td>
    </tr>
    {{ endfor }}
</table>
{{ endif }}

chat_with_experimenter/papercups.html

From otree-snippets


<script>
window.Papercups = {
  config: {
    accountId: "5ee2437e-b9e9-4348-8e1c-483959b1d826",
    title: "Welcome to our experiment",
    subtitle: "Ask us anything in the chat window below",
    primaryColor: "#1890ff",
    greeting: "",
    awayMessage: "",
    newMessagePlaceholder: "Start typing...",
    showAgentAvailability: false,
    agentAvailableText: "We're online right now!",
    agentUnavailableText: "We're away at the moment.",
    requireEmailUpfront: false,
    iconVariant: "outlined",
    // note: you need to set up your own Papercups chat server (quite easy).
    baseUrl: "https://otree-papercups.herokuapp.com",
    customer: {
      name: '{{participant.code}}',
      external_id: '{{participant.code}}',
    }
  },
};
</script>
<script
type="text/javascript"
async
defer
src="https://otree-papercups.herokuapp.com/widget.js"
></script>

redirect_to_other_website/Redirect.html

From otree-snippets


{{ block title }}
    Redirecting...
{{ endblock }}

{{ block content }}

    <script>
        window.location.href = js_vars.redirect_url;
    </script>

{{ endblock }}

chat_with_experimenter/MyPage.html

From otree-snippets


{{ block title }}
Chat with experimenter
{{ endblock }}
{{ block content }}

<p>
    In the bottom right corner of the screen,
    there is a button to start a chat with the experimenter.
</p>

<p><i>
    In this demo, these messages are currently being sent to oTree.org's Papercups server.
    To set up your own server,
    See <a href="https://otree.readthedocs.io/en/latest/admin.html#experimenter-chat">here</a>.
</i></p>

{# you should put this 'include' on every page that needs a chat widget #}
{{ include Constants.papercups_template }}

{{ endblock }}

chat_with_experimenter/__init__.py

From otree-snippets


from otree.api import *


doc = """
Chat with experimenter, using Papercups
"""


class Constants(BaseConstants):
    name_in_url = 'chat_with_experimenter'
    players_per_group = None
    num_rounds = 1
    papercups_template = __name__ + '/papercups.html'


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# PAGES
class MyPage(Page):
    pass


page_sequence = [MyPage]

experimenter_input/Intro.html

From otree-snippets


{{ block content }}

    <p><i>Put the first part of your game here...</i></p>

    {{ next_button }}

{{ endblock }}

experimenter_input/MyPage.html

From otree-snippets


{{ block content }}

    <p>The number drawn by the experimenter was {{ group.exp_input }}.</p>

    <p><i>Your game can continue here....</i></p>

{{ endblock }}

experimenter_input/__init__.py

From otree-snippets


from otree.api import *


doc = """
Experimenter input during the experiment,
e.g. entering the result of a random draw.

If you want the experimenter to be able to make an input at any time,
you can use the REST API (especially the session_vars endpoint).
"""


class Constants(BaseConstants):
    name_in_url = 'experimenter_input'
    players_per_group = None
    num_rounds = 1
    password = 'mypass'


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    exp_input = models.IntegerField()
    has_exp_input = models.BooleanField(initial=False)


class Player(BasePlayer):
    pass


# PAGES
class Intro(Page):
    pass


class ExpInput(Page):
    """
    It should be a live page so that you can notify all other players to advance
    also, prevents everyone from moving forward prematurely
    """

    @staticmethod
    def live_method(player: Player, data):
        group = player.group

        if ('exp_input' in data) and ('password' in data):
            if data['password'] != Constants.password:
                return {player.id_in_group: dict(error="Incorrect password")}
            group.exp_input = data['exp_input']
            group.has_exp_input = True
        return {0: dict(finished=group.has_exp_input)}

    @staticmethod
    def error_message(player: Player, values):
        group = player.group
        if not group.has_exp_input:
            return "Experimenter has not input data yet"


class MyPage(Page):
    pass


page_sequence = [Intro, ExpInput, MyPage]

experimenter_input/ExpInput.html

From otree-snippets


{{ block title }}
Random draw
{{ endblock }}
{{ block content }}

<p>Please wait. The experimenter will draw a random number.</p>

<details>
    <summary>If you are the experimenter, click here.</summary>
    <label class="col-form-label">
        Number drawn
        <input type="number" class="form-control" id="exp_input">
    </label>
    <br>
    <label class="col-form-label">
        Password
        {# you can add type="password" for a proper password input #}
        <input class="form-control" id="password">
    </label>
    <br>
    <button type="button" onclick="sendData()">Submit</button>
    <p>
    <small>
        Hint for demo purposes: password is "{{ Constants.password }}".
        You can get to this page by opening the participant's start URL.
    </small>
    </p>

</details>

<script>
    let expInput = document.getElementById('exp_input');
    let passwordInput = document.getElementById('password');

    function sendData() {
        liveSend({'exp_input': parseInt(expInput.value), password: passwordInput.value});
    }

    function liveRecv(data) {
        if (data.finished) {
            document.getElementById('form').submit();
        }
        if (data.error) {
            alert(data.error);
        }
    }

    document.addEventListener("DOMContentLoaded", function (event) {
        // need this so that you proceed even if you arrive late or got disconnected
        liveSend({});
    });

</script>


{{ endblock }}

count_button_clicks/MyPage.html

From otree-snippets


{{ block title }}
    Click the button
{{ endblock }}
{{ block content }}

    <p>
        This app records to the database the number of times you click the button.
    </p>

    <p>
        <button type="button" onclick="buttonClicked()">Click me</button>
    </p>

    <p>Number of clicks: <span id="num_clicks_feedback">0</span></p>

    {{ next_button }}

    <input type="hidden" name="num_clicks" id="num_clicks" />

    <script>
        let numClicks = 0;

        function buttonClicked() {
            numClicks++;
            // this stores it in the database through the formfield
            document.getElementById('num_clicks').value = numClicks;
            // this just displays it back to the user
            document.getElementById('num_clicks_feedback').innerText = numClicks;
        }
    </script>

{{ endblock }}

redirect_to_other_website/__init__.py

From otree-snippets


from otree.api import *




class Constants(BaseConstants):
    name_in_url = 'redirect_to_other_website'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    citizenship = models.StringField()



class MyPage(Page):
    form_model = 'player'
    form_fields = ['citizenship']


class Redirect(Page):
    @staticmethod
    def js_vars(player: Player):
        # google is just an example. you should change this to qualtrics or whatever survey provider
        # you are using.
        return dict(redirect_url='https://www.google.com/search?q=' + player.citizenship)


page_sequence = [MyPage, Redirect]

timer_custom/__init__.py

From otree-snippets


from otree.api import *


doc = """
Timer: replacing the default timer with your own
"""


class Constants(BaseConstants):
    name_in_url = 'timer_custom'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# PAGES
class MyPage(Page):
    timeout_seconds = 60


page_sequence = [MyPage]

count_button_clicks/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}
    <p>You clicked the button {{ player.num_clicks }} times.</p>

{{ endblock }}

count_button_clicks/__init__.py

From otree-snippets


from otree.api import *

doc = """Count button clicks (hidden input)"""

class Constants(BaseConstants):
    name_in_url = 'count_button_clicks'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    num_clicks = models.IntegerField()


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['num_clicks']


class Results(Page):
    pass


page_sequence = [MyPage, Results]

multi_language/Game.html

From otree-snippets


{{ block title }}
    {{ Lexicon.your_decision }}
{{ endblock }}

{{ block content }}

    {{ formfields }}

    {{ next_button }}

{{ endblock }}

multi_language/Results.html

From otree-snippets


{{ block title }}
    {{ Lexicon.results }}
{{ endblock }}

{{ block content }}

    {{ if de }}
    Sie haben sich entschieden {{ boxes_collected }} von {{ boxes_total }}  Boxen zu
    sammeln.
    {{ else }}
    You chose to collect {{ boxes_collected }} out of {{ boxes_total }} boxes.
    {{ endif }}

    <p>
    {{ if de }}
    Die Bombe war hinter der Box in Reihe {{ bomb_row }}, Spalte {{ bomb_col }} versteckt.
    {{ else }}
    The bomb was hidden behind the box in row {{ bomb_row }}, column {{ bomb_col }}.
    {{ endif }}
    </p>
    <p>
    {{ if bomb }}
        {{ if de }}
        Die Bombe befand sich unter den von Ihnen gesammelten {{ boxes_collected }}
        Boxen.<br/> Entsprechend wurden alle Ihre gesammelten Erträge zerstört und
        Ihre Auszahlung in dieser Aufgabe beträgt {{ player.payoff }}.
        {{ else }}
        The bomb was among your {{ boxes_collected }} collected boxes.<br/>
        Accordingly, all your earnings in this task were destroyed and your payoff amounts to {{ player.payoff }}.
        {{ endif }}
    {{ else }}
        {{ if de }}
        Die Bombe war nicht unter den von Ihnen eingesammelten Boxen.<br/>
        Dementsprechend erhalten Sie {{ box_value }} für jede der {{ boxes_collected }}
        Boxen, sodass sich Ihre Auszahlung in dieser Aufgabe auf <b>{{ player.payoff }}</b>
        beläuft.
        {{ else }}
        Your collected boxes did not contain the bomb.<br/>
        Thus, you receive {{ box_value }} for each of the {{ boxes_collected }} boxes
        you collected such that your payoff from this task amounts to <b>{{ player.payoff }}</b>.
        {{ endif }}
    {{ endif }}
    </p>

    {{ next_button }}

{{ endblock }}

multi_language/__init__.py

From otree-snippets


import random

from otree.api import *

from settings import LANGUAGE_CODE

doc = """
How to translate an app to multiple languages (e.g. English and German).

There are 2 ways to define localizable strings:
(1) Put it in a "lexicon" file (see lexicon_en.py, lexicon_de.py).
    This is the easiest technique, and it allows you to easily reuse the same string multiple times.
(2) If the string contains variables, then it should to be defined in the template.
    Use an if-statement, like {{ if de }} Nein {{ else }} No {{ endif }}

When you change the LANGUAGE_CODE in settings.py, the language will automatically be changed.

Note: this technique does not require .po files, which are a more complex technique.    
"""


if LANGUAGE_CODE == 'de':
    from .lexicon_de import Lexicon
else:
    from .lexicon_en import Lexicon


# this is the dict you should pass to each page in vars_for_template,
# enabling you to do if-statements like {{ if de }} Nein {{ else }} No {{ endif }}
which_language = {'en': False, 'de': False, 'zh': False}  # noqa
which_language[LANGUAGE_CODE[:2]] = True


class Constants(BaseConstants):
    name_in_url = 'bret'
    num_rounds = 1
    players_per_group = None


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    boxes_collected = models.IntegerField(label=Lexicon.boxes_collected)


class Game(Page):
    form_model = 'player'
    form_fields = ['boxes_collected']

    @staticmethod
    def vars_for_template(player: Player):
        return dict(Lexicon=Lexicon, **which_language)


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        # this is just fake data
        return dict(
            boxes_total=64,
            bomb_row=2,
            bomb_col=1,
            bomb=True,
            payoff=player.payoff,
            box_value=cu(5),
            boxes_collected=player.boxes_collected,
            Lexicon=Lexicon,
            **which_language
        )


page_sequence = [Game, Results]

timer_custom/MyPage.html

From otree-snippets


{{ block title }}
    Custom timer element
{{ endblock }}
{{ block content }}

    <p>You have <span id="time-left"></span> seconds left.</p>

    {{ formfields }}
    {{ next_button }}

    <script>
        let customTimerEle = document.getElementById('time-left');

        document.addEventListener("DOMContentLoaded", function (event) {
            $('.otree-timer__time-left').on('update.countdown', function (event) {
                customTimerEle.innerText = event.offset.totalSeconds;
            });
        });

    </script>
{{ endblock }}

balance_treatments_for_dropouts/Intro.html

From otree-snippets


{{ block title }}

{{ endblock }}
{{ block content }}

    Welcome
    {{ next_button }}

{{ endblock }}

balance_treatments_for_dropouts/__init__.py

From otree-snippets


from otree.api import *


doc = """
Your app description
"""

TREATMENTS = ['red', 'blue', 'green']


class Constants(BaseConstants):
    name_in_url = 'balance_treatments_for_dropouts'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    session = subsession.session
    session.completions_by_treatment = {color: 0 for color in TREATMENTS}


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    color = models.StringField()


# PAGES
class Intro(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        session = player.session

        player.color = min(
            TREATMENTS, key=lambda color: session.completions_by_treatment[color],
        )


class Task(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        session = player.session
        session.completions_by_treatment[player.color] += 1


page_sequence = [Intro, Task]

dropout_detection/ByeDropout.html

From otree-snippets


{{ block title }}
    End
{{ endblock }}
{{ block content }}

    Sorry, you did not submit the page in time.
    The experiment is now finished.

{{ endblock }}

dropout_detection/__init__.py

From otree-snippets


from otree.api import *


doc = """
Dropout detection (if user does not submit page in time)
"""


class Constants(BaseConstants):
    name_in_url = 'detect_dropout'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    is_dropout = models.BooleanField(initial=False)


class Page1(Page):
    timeout_seconds = 10

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        # note: bugfix
        if timeout_happened:
            player.is_dropout = True


class ByeDropout(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.is_dropout

    @staticmethod
    def error_message(player: Player, values):
        return "Cannot proceed past this page"


class Page2(Page):
    pass


page_sequence = [Page1, ByeDropout, Page2]

dropout_detection/Page2.html

From otree-snippets


{{ block content }}

    <p><i>Experiment continues here...</i></p>

    {{ next_button }}
{{ endblock }}

redirect_to_other_website/MyPage.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}
{{ block content }}


    {{ formfields }}

    <p><i>
        After the user clicks 'next', they will be directed to another website.
        We append the user's data to the URL, for example:
        <code>google.com/search?q=Canada</code>
    </i></p>
    {{ next_button }}

{{ endblock }}

dropout_detection/Page1.html

From otree-snippets


{{ block content }}
    <p>
        You need to submit this page before the timeout occurs.
        Otherwise you will be considered a dropout.
    </p>

    <p><i>You can put formfields on this page etc.</i></p>

    {{ next_button }}

{{ endblock }}

other_player_previous_rounds/MyPage.html

From otree-snippets


{{ block title }}
Round {{ subsession.round_number }}
{{ endblock }}
{{ block content }}

<p><i>
    This app shows how you can chain methods like <code>.in_all_rounds()</code>
    with <code>group.get_players()</code>, <code>player.get_others_in_group()</code>,
    etc, to get the history of other players in different ways.
</i></p>

<h3>My current partner's history</h3>
<p>My current partner: player {{ partner.id_in_subsession }}</p>
<table class="table">
    <tr>
        <th>Round</th>
        <th>contribution</th>
    </tr>
    {{ for p in my_partner_previous }}
    <tr>
        <td>{{ p.round_number }}</td>
        <td>{{ p.contribution }}</td>
    </tr>
    {{ endfor }}
</table>

<h3>History of my partners</h3>
<table class="table">
    <tr>
        <th>Round</th>
        <th>Player</th>
        <th>contribution</th>
    </tr>
    {{ for p in my_previous_partners }}
    <tr>
        <td>{{ p.round_number }}</td>
        <td>{{ p.id_in_subsession }}</td>
        <td>{{ p.contribution }}</td>
    </tr>
    {{ endfor }}
</table>

{{ next_button }}

{{ endblock }}

other_player_previous_rounds/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'other_player_previous_rounds'
    players_per_group = 2
    num_rounds = 5


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    import random

    subsession.group_randomly()

    # for demo purposes we just generate random data.
    # of course in a real game, there would be a formfield where a user
    # enters their contribution
    for player in subsession.get_players():
        player.contribution = random.randint(0, 99)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    contribution = models.IntegerField()


def get_partner(player: Player):
    return player.get_others_in_group()[0]


# PAGES
class MyPage(Page):
    @staticmethod
    def vars_for_template(player: Player):
        partner = get_partner(player)
        my_partner_previous = partner.in_all_rounds()
        my_previous_partners = [
            get_partner(me_prev) for me_prev in player.in_all_rounds()
        ]

        return dict(
            partner=partner,
            my_partner_previous=my_partner_previous,
            my_previous_partners=my_previous_partners,
        )


page_sequence = [MyPage]

back_button/__init__.py

From otree-snippets


from otree.api import *

doc = """
Back button for multiple instructions pages
"""

class Constants(BaseConstants):
    name_in_url = 'back_button'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# PAGES
class Instructions(Page):
    pass


class Task(Page):
    pass


page_sequence = [Instructions, Task]

getattr_setattr/__init__.py

From otree-snippets


from otree.api import *


doc = """
Using getattr() and setattr() to access numbered fields, e.g. 
player.num1, player.num2, ..., player.num10,
without writing repetitive if-statements.

NOTE: having numbered fields is often not the best or easiest design.
For example, let's say you have fields like this:

    num1 = models.IntegerField()
    num2 = models.IntegerField()
    ...
    num10 = models.IntegerField()

If you don't need to put them in a form, then you can replace this simply with 
a list in a participant field, since they can be more easily accessed by number, 
e.g. participant.my_numbers[5]

If you have many numbered fields, like more than 20, you should consider using 
ExtraModel.

Participant fields and ExtraModel also have the advantage that you don't need to know in
advance exactly how many you will have.
"""


class Constants(BaseConstants):
    name_in_url = 'getattr_setattr'
    players_per_group = None
    num_rounds = 1
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    num1 = models.IntegerField()
    num2 = models.IntegerField()
    num3 = models.IntegerField()
    num4 = models.IntegerField()
    num5 = models.IntegerField()
    num6 = models.IntegerField()
    num7 = models.IntegerField()
    num8 = models.IntegerField()
    num9 = models.IntegerField()
    num10 = models.IntegerField()

    chosen_number = models.IntegerField(
        choices=Constants.numbers, label="Choose a random number from 1 to 10"
    )


class Page1(Page):
    form_model = 'player'
    form_fields = ['num{}'.format(n) for n in Constants.numbers]


class Page2(Page):
    form_model = 'player'
    form_fields = ['chosen_number']


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        # if chosen_number was 7, this will give you player.num7
        field_name = 'num{}'.format(player.chosen_number)
        chosen_value = getattr(player, field_name)
        player.payoff = chosen_value

        # if chosen number was 7, this gives you
        # player.num1 + player.num2 + ... + player.num7
        sum_to_n = sum(
            getattr(player, 'num{}'.format(n))
            for n in range(1, player.chosen_number + 1)
        )
        return dict(chosen_value=chosen_value, sum_to_n=sum_to_n)


page_sequence = [Page1, Page2, Results]

getattr_setattr/Page2.html

From otree-snippets


{{ block content }}

    {{ formfields }}

    {{ next_button }}

{{ endblock }}

getattr_setattr/Page1.html

From otree-snippets


{{ block content }}
    <p>
        Enter 10 random numbers from 1 to 100.
    </p>

    {{ formfields }}

    {{ next_button }}

{{ endblock }}

show_other_players_payoffs/Results.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}

{{ block content }}

    <p>Your payoff is {{ player.payoff }}.</p>

    <p>Here are the other players' payoffs:</p>

    <table>
        {{ for other in others }}
        <tr>
            <td>Player {{ other.id_in_group }}</td>
            <td>{{ other.payoff }}</td>
        </tr>
        {{ endfor }}
    </table>

{{ endblock }}

show_other_players_payoffs/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'show_other_players_payoffs'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(others=player.get_others_in_group())


page_sequence = [Results]

supergames/Play.html

From otree-snippets


{{ block title }}
    Supergame {{ subsession.sg }}, period {{ subsession.period }}
{{ endblock }}
{{ block content }}

    <p><i>Your game goes here...</i></p>
    {{ next_button }}

{{ endblock }}

supergames/__init__.py

From otree-snippets


from otree.api import *


doc = """
Supergames consisting of multiple rounds each
"""


def cumsum(lst):
    total = 0
    new = []
    for ele in lst:
        total += ele
        new.append(total)
    return new


class Constants(BaseConstants):
    name_in_url = 'supergames'
    players_per_group = None

    # first supergame lasts 2 rounds, second supergame lasts 3 rounds, etc...
    rounds_per_sg = [2, 3, 4, 5]
    sg_ends = cumsum(rounds_per_sg)
    num_rounds = sum(rounds_per_sg)


class Subsession(BaseSubsession):
    sg = models.IntegerField()
    period = models.IntegerField()
    is_last_period = models.BooleanField()


def creating_session(subsession: Subsession):
    if subsession.round_number == 1:
        sg = 1
        period = 1
        for ss in subsession.in_rounds(1, Constants.num_rounds):
            ss.sg = sg
            ss.period = period
            is_last_period = ss.round_number in Constants.sg_ends
            ss.is_last_period = is_last_period
            if is_last_period:
                sg += 1
                period = 1
            else:
                period += 1


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class NewSupergame(Page):
    @staticmethod
    def is_displayed(player: Player):
        subsession = player.subsession
        return subsession.period == 1


class Play(Page):
    pass


page_sequence = [NewSupergame, Play]

supergames/NewSupergame.html

From otree-snippets


{{ block title }}
    Supergame {{ subsession.sg }}
{{ endblock }}
{{ block content }}

    <p>This page is only shown at the beginning of a supergame...</p>
    {{ next_button }}

{{ endblock }}

practice_rounds/Play.html

From otree-snippets


{{ block title }}
    {{ if subsession.is_practice_round }}
        Practice round {{ subsession.round_number }} of {{ Constants.num_practice_rounds }}
    {{ else }}
        Round {{ subsession.real_round_number }} of {{ Constants.num_real_rounds }}
    {{ endif}}
{{ endblock }}
{{ block content }}

    <p>Math question: what is {{ player.round_number }} squared?</p>

    {{ formfields }}

    {{ next_button }}

{{ endblock }}

practice_rounds/PracticeFeedback.html

From otree-snippets


{{ block title }}
    Practice feedback
{{ endblock }}

{{ block content }}

    {{ if player.is_correct }}
        <p>You got the practice question correct!</p>
    {{ else }}
        <p>You answered {{ player.response }} but the correct answer was {{ player.solution }}.</p>
    {{ endif }}

    <p>Once the real rounds start, you won't see this feedback page anymore.</p>

    {{ next_button }}

{{ endblock }}

practice_rounds/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <p>Your got {{ score }} answers correct.</p>

{{ endblock }}

practice_rounds/__init__.py

From otree-snippets


from otree.api import *


doc = """Practice rounds"""


class Constants(BaseConstants):
    name_in_url = 'practice_rounds'
    players_per_group = None
    num_practice_rounds = 2
    num_real_rounds = 10
    num_rounds = num_practice_rounds + num_real_rounds


class Subsession(BaseSubsession):
    is_practice_round = models.BooleanField()
    real_round_number = models.IntegerField()


def creating_session(subsession: Subsession):
    if subsession.round_number == 1:
        for ss in subsession.in_rounds(1, Constants.num_rounds):
            ss.is_practice_round = ss.round_number <= Constants.num_practice_rounds
            if not ss.is_practice_round:
                ss.real_round_number = ss.round_number - Constants.num_practice_rounds


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    response = models.IntegerField()
    solution = models.IntegerField()
    is_correct = models.BooleanField()


def real_round_number(player: Player):
    return player


class Play(Page):
    form_model = 'player'
    form_fields = ['response']

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        player.solution = player.round_number ** 2
        player.is_correct = player.response == player.solution


class PracticeFeedback(Page):
    @staticmethod
    def is_displayed(player: Player):
        subsession = player.subsession
        return subsession.is_practice_round


class Results(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == Constants.num_rounds

    @staticmethod
    def vars_for_template(player: Player):
        score = sum(
            p.is_correct
            for p in player.in_rounds(
                Constants.num_practice_rounds + 1, Constants.num_rounds
            )
        )
        return dict(score=score)


page_sequence = [Play, PracticeFeedback, Results]

wait_for_specific_people/Intro.html

From otree-snippets


{{ block title }}
    Intro
{{ endblock }}
{{ block content }}

    <p>
        This app demonstrates how to have a waiting page that just waits for certain people to arrive before proceeding.
        You can make it any subset of participants: for example, just the participants you marked as being online currently,
        or just those who gave a specific answer to a question.
    </p>

    <p>
        In this demo, the players were randomly selected as: {{ session.wait_for_ids }}.
    </p>

    <p>
        You are player {{ player.id_in_subsession }}, so you
        {{ if player.selected_for_waitpage }}
            must wait on
        {{ else}}
            can skip
        {{ endif }}
        the following wait page.
    </p>

    <p>Click next.</p>
    {{ next_button }}

{{ endblock }}

wait_for_specific_people/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <p><i>Next page content would go here...</i></p>

    {{ next_button }}
{{ endblock }}

wait_for_specific_people/__init__.py

From otree-snippets


from otree.api import *


doc = """
Wait only for specific people
"""


class Constants(BaseConstants):
    name_in_url = 'wait_for_specific_people'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    session = subsession.session
    import random

    session.wait_for_ids = set()
    session.arrived_ids = set()

    for p in subsession.get_players():
        # we just determine it randomly here.
        # in your game, you should replace it with your desired logic.
        selected = random.choice([False, True])
        p.selected_for_waitpage = selected
        if selected:
            session.wait_for_ids.add(p.id_in_subsession)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    selected_for_waitpage = models.BooleanField()


class Intro(Page):
    pass


class WaitForSelected(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.selected_for_waitpage

    @staticmethod
    def live_method(player: Player, data):
        session = player.session
        session.arrived_ids.add(player.id_in_subsession)
        not_arrived_yet = session.wait_for_ids - session.arrived_ids
        if not_arrived_yet:
            return {0: dict(not_arrived_yet=list(not_arrived_yet))}
        return {0: dict(finished=True)}

    @staticmethod
    def error_message(player: Player, values):
        session = player.session
        if session.arrived_ids != session.wait_for_ids:
            return "Page somehow proceeded before all players are ready"


class Results(Page):
    pass


page_sequence = [Intro, WaitForSelected, Results]

wait_for_specific_people/WaitForSelected.html

From otree-snippets


{{ block title }}
Waiting
{{ endblock }}
{{ block content }}

<progress></progress>
<p>Waiting for players: <span id="wait_for_ids"></span></p>

<script>
    function liveRecv(data) {
        console.log('data', data)
        if (data.finished) {
            document.getElementById("form").submit();
        } else {
            document.getElementById('wait_for_ids').innerText = data.not_arrived_yet;
        }
    }

    document.addEventListener("DOMContentLoaded", (event) => {
        liveSend({});
    });
</script>

{{ endblock }}

bmi_calculator/MyPage.html

From otree-snippets


{{ block title }}
    BMI (Body Mass Index) calculator
{{ endblock }}
{{ block content }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

bmi_calculator/Results.html

From otree-snippets


{{ block content }}
    <p>Your BMI is {{ player.bmi }}.</p>
{{ endblock }}

bmi_calculator/__init__.py

From otree-snippets


from otree.api import *


doc = """
Basic single-player game (BMI calculator)
"""


class Constants(BaseConstants):
    name_in_url = 'bmi_calculator'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    weight_kg = models.IntegerField(label="Weight (in kg)")
    height_cm = models.IntegerField(label="Height (in cm)")
    bmi = models.FloatField()


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['weight_kg', 'height_cm']

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        bmi = player.weight_kg / ((player.height_cm / 100) ** 2)
        player.bmi = round(bmi, 1)


class Results(Page):
    pass


page_sequence = [MyPage, Results]

random_question_order/MyPage.html

From otree-snippets


{{ block title }}
    Answer these questions
{{ endblock }}
{{ block content }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

random_question_order/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'random_question_order'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    aaa = models.BooleanField()
    bbb = models.BooleanField()
    ccc = models.StringField()
    ddd = models.IntegerField()


# PAGES
class MyPage(Page):
    form_model = 'player'

    @staticmethod
    def get_form_fields(player: Player):
        import random

        form_fields = ['aaa', 'bbb', 'ccc', 'ddd']
        random.shuffle(form_fields)
        return form_fields


page_sequence = [MyPage]

factorial_treatments/MyPage.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}
{{ block content }}

    <p><i>Check the admin 'Data' tab to see the results of the randomization</i></p>

{{ endblock }}

factorial_treatments/__init__.py

From otree-snippets


from otree.api import *

doc = """Randomize multiple factors in a balanced way"""


class Constants(BaseConstants):
    name_in_url = 'randomize_cross_product'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


def creating_session(subsession):
    import itertools

    treatments = itertools.cycle(
        itertools.product([True, False], [True, False], [100, 200, 300])
    )
    for p in subsession.get_players():
        treatment = next(treatments)
        p.time_pressure = treatment[0]
        p.high_tax = treatment[1]
        p.endowment = treatment[2]


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    time_pressure = models.BooleanField()
    high_tax = models.BooleanField()
    endowment = models.CurrencyField()


class MyPage(Page):
    pass


page_sequence = [MyPage]

multi_select_complex/MyPage.html

From otree-snippets


{{ block content }}

<p>What languages do you speak? Select all that apply.</p>
{{ for field in Constants.languages }}
<label>
    <input type="checkbox" name="{{ field.name }}" value="1">
    {{ field.label }}
</label><br>
{{ endfor }}

<p>
{{ next_button }}
</p>
{{ endblock }}

multi_select_complex/__init__.py

From otree-snippets


from otree.api import *


doc = """
Question that lets you select multiple options
(multi-select, multiple choice / multiple answer)
"""


class Constants(BaseConstants):
    name_in_url = 'multi_select_complex'
    players_per_group = None
    num_rounds = 1
    languages = [
        dict(name='english', label="I speak English"),
        dict(name='french', label="Je parle français"),
        dict(name='spanish', label="Hablo español"),
        dict(name='finnish', label="Puhun suomea"),
    ]


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    english = models.BooleanField(blank=True)
    french = models.BooleanField(blank=True)
    spanish = models.BooleanField(blank=True)
    finnish = models.BooleanField(blank=True)


# PAGES
class MyPage(Page):
    form_model = 'player'

    @staticmethod
    def get_form_fields(player: Player):
        return [lang['name'] for lang in Constants.languages]

    @staticmethod
    def error_message(player: Player, values):
        num_selected = 0
        for lang in Constants.languages:
            if values[lang['name']]:
                num_selected += 1
        if num_selected < 1:
            return "You must select at least 1 language."


page_sequence = [MyPage]

appcopy2/__init__.py

From otree-snippets


from appcopy1 import *


class Constants(Constants):
    name_in_url = 'appcopy2'


# need to copy/paste Subsession/Group/Player classes from appcopy1
class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    aaa = models.IntegerField()


class Player(BasePlayer):
    bbb = models.IntegerField()

gbat_keep_same_groups_part2/MyPage.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}
{{ block content }}

    <p><i>This is the next app. Again you have been paired with the same partner.</i></p>
    {{ next_button }}

{{ endblock }}

multi_select/__init__.py

From otree-snippets


from otree.api import *


doc = """
Question that lets you select multiple options
(multi-select, multiple choice / multiple answer)
"""


class Constants(BaseConstants):
    name_in_url = 'select_multiple'
    players_per_group = None
    num_rounds = 1
    languages = ['english', 'german', 'french', 'spanish', 'italian', 'chinese']


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    english = models.BooleanField(blank=True)
    german = models.BooleanField(blank=True)
    french = models.BooleanField(blank=True)
    spanish = models.BooleanField(blank=True)
    italian = models.BooleanField(blank=True)
    chinese = models.BooleanField(blank=True)


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = Constants.languages


page_sequence = [MyPage]

multi_select/MyPage.html

From otree-snippets


{{ block content }}

<p>What languages do you speak? Select all that apply.</p>
{{ for field in Constants.languages }}
<label>
    <input type="checkbox" name="{{ field }}" value="1">
    {{ field }}
</label><br>
{{ endfor }}

<p>
{{ next_button }}
</p>
{{ endblock }}

complex_form_layout/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'complex_form_layout'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    a1 = models.IntegerField()
    a2 = models.IntegerField()
    a3 = models.IntegerField()
    a4 = models.IntegerField()

    b1 = models.StringField()
    b2 = models.StringField()
    b3 = models.StringField()


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['a1', 'a2', 'a3', 'a4', 'b1', 'b2', 'b3']

    @staticmethod
    def vars_for_template(player: Player):
        import random

        a_fields = ['a1', 'a2', 'a3', 'a4']
        b_fields = ['b1', 'b2', 'b3']

        random.shuffle(a_fields)
        random.shuffle(b_fields)

        return dict(a_fields=a_fields, b_fields=b_fields)


page_sequence = [MyPage]

complex_form_layout/MyPage.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}
{{ block content }}

    <p><i>fields are in 2 sections, and randomized within a section.</i></p>

    <p>Please answer the following questions about topic A</p>

    {{ for field in a_fields }}
        {{ formfield field }}
    {{ endfor }}

    <p>Please answer the following questions about topic B</p>

    {{ for field in b_fields }}
        {{ formfield field }}
    {{ endfor }}

    {{ next_button }}

{{ endblock }}

rank_widget/__init__.py

From otree-snippets


from otree.api import *


doc = """
"Widget to rank/reorder items". See http://sortablejs.github.io/Sortable/
for more examples.
"""


class Constants(BaseConstants):
    name_in_url = 'rank_widget'
    players_per_group = None
    num_rounds = 1
    choices = ['Martini', 'Margarita', 'White Russian', 'Pina Colada', 'Gin & Tonic']


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    ranking = models.StringField()


class MyPage(Page):
    form_model = 'player'
    form_fields = ['ranking']


class Results(Page):
    pass


page_sequence = [MyPage, Results]

rank_widget/Results.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}

{{ block content }}

    <p>Your ranking is: {{ player.ranking }}</p>

    {{ next_button }}
{{ endblock }}

rank_widget/MyPage.html

From otree-snippets


{{ block title }}
Rank your favorite drinks
{{ endblock }}
{{ block content }}


<ul id="items" class="list-group list-group-numbered" style="cursor: move">
    {{ for choice in Constants.choices }}
    <li data-id="{{ choice }}" class="list-group-item">{{ choice }}</li>
    {{ endfor }}
</ul>

<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script>
    let el = document.getElementById('items');
    let sortable = Sortable.create(el, {
        onChange: function (evt) {
            document.getElementById('ranking').value = sortable.toArray().join(',');
        }
    });
</script>

<input type="hidden" name="ranking" id="ranking">

{{ formfield_errors 'ranking' }}

<p>
    {{ next_button }}

</p>

{{ endblock }}

question_with_other_option/__init__.py

From otree-snippets


from otree.api import *

doc = """
Menu with an 'other' option that lets you type in a valueInput manually
"""


class Constants(BaseConstants):
    name_in_url = 'question_with_other_option'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    native_language = models.StringField(
        choices=['German', 'English', 'Chinese', 'Turkish', 'Other']
    )
    native_language_other = models.StringField(
        label="You selected 'other'. What is your native language?"
    )


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['native_language']


class MyPage2(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.native_language == 'Other'

    form_model = 'player'
    form_fields = ['native_language_other']


page_sequence = [MyPage, MyPage2]

question_with_other_option/MyPage2.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}

{{ block content }}

    {{ formfields }}

    {{ next_button }}
{{ endblock }}

question_with_other_option/MyPage.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}
{{ block content }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

wait_page_timeout/Task.html

From otree-snippets


{{ block title }}
    Task
{{ endblock }}
{{ block content }}

    <p>Continue the experiment...</p>

    {{ next_button }}

{{ endblock }}

gbat_keep_same_groups_part2/__init__.py

From otree-snippets


from otree.api import *


doc = """
Preserve same groups as a previous app that used group_by_arrival time.
"""


class Constants(BaseConstants):
    name_in_url = 'gbat_keep_same_groups_part2'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


def group_by_arrival_time_method(subsession: Subsession, waiting_players):
    d = {}
    for p in waiting_players:
        group_id = p.participant.past_group_id
        if group_id not in d:
            d[group_id] = []
        players_in_my_group = d[group_id]
        players_in_my_group.append(p)
        if len(players_in_my_group) == 2:
            return players_in_my_group


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class GBATWait(WaitPage):
    group_by_arrival_time = True


class MyPage(Page):
    pass


page_sequence = [GBATWait, MyPage]

gbat_keep_same_groups_part1/MyPage.html

From otree-snippets


{{ block title }}
    Page title
{{ endblock }}
{{ block content }}

    <p><i>
        group_by_arrival_time has paired you with a partner.
    </i></p>
    {{ next_button }}

{{ endblock }}

gbat_keep_same_groups_part1/__init__.py

From otree-snippets


from otree.api import *


doc = """
Your app description
"""


class Constants(BaseConstants):
    name_in_url = 'gbat_keep_same_groups'
    players_per_group = 2
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class GBATWait(WaitPage):
    group_by_arrival_time = True

    @staticmethod
    def after_all_players_arrive(group: Group):
        for p in group.get_players():
            participant = p.participant
            participant.past_group_id = group.id


class MyPage(Page):
    pass


page_sequence = [GBATWait, MyPage]

pass_data_between_apps_part1/MyPage.html

From otree-snippets


{{ block title }}
    App 1
{{ endblock }}
{{ block content }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

pass_data_between_apps_part1/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'pass_data_between_apps1'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    language = models.StringField(label='What is your main language?')


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['language']

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        participant = player.participant

        # in settings.py need to add 'language' to PARTICIPANT_FIELDS.
        participant.language = player.language


page_sequence = [MyPage]

treatments_from_spreadsheet/MyPage.html

From otree-snippets


{{ block content }}

<p><i>Look in the admin "data" tab to see the treatments that were assigned.</i></p>

{{ endblock }}

treatments_from_spreadsheet/__init__.py

From otree-snippets


from otree.api import *


doc = """
Reading treatment parameters from a CSV spreadsheet
"""


class Constants(BaseConstants):
    name_in_url = 'treatments_from_spreadsheet'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    import csv

    f = open(__name__ + '/treatments.csv', encoding='utf-8-sig')

    rows = list(csv.DictReader(f))
    players = subsession.get_players()
    for i in range(len(players)):
        row = rows[i]
        player = players[i]
        player.time_pressure = bool(int(row['time_pressure']))
        player.endowment = cu(row['endowment'])
        player.high_tax = bool(int(row['high_tax']))
        player.color = row['color']


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    time_pressure = models.BooleanField()
    endowment = models.CurrencyField()
    high_tax = models.BooleanField()
    color = models.StringField()


class MyPage(Page):
    pass


page_sequence = [MyPage]

wait_page_timeout/__init__.py

From otree-snippets


from otree.api import *

doc = """Timeout on a WaitPage (exit the experiment)"""

class Constants(BaseConstants):
    name_in_url = 'wait_page_timeout'
    players_per_group = None
    num_rounds = 1
    timeout = 15


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    import random

    for p in subsession.get_players():
        p.completion_code = random.randint(10 ** 6, 10 ** 7)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    timeout = models.FloatField()
    completion_code = models.IntegerField()


# PAGES
class MyPage(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        import time

        # 15 seconds on wait page max
        player.timeout = time.time() + Constants.timeout


class ResultsWaitPage(WaitPage):
    template_name = 'wait_page_timeout/ResultsWaitPage.html'

    @staticmethod
    def js_vars(player: Player):
        return dict(timeout=Constants.timeout)

    @staticmethod
    def vars_for_template(player: Player):
        import time

        timeout_happened = time.time() > player.timeout
        return dict(timeout_happened=timeout_happened)


class Task(Page):
    pass


page_sequence = [MyPage, ResultsWaitPage, Task]

random_task_order/TaskB2.html

From otree-snippets


{{ block title }}
    Task B, Page 2
{{ endblock }}
{{ block content }}

    {{ next_button }}

{{ endblock }}

random_task_order/TaskB1.html

From otree-snippets


{{ block title }}
    Task B, Page 1
{{ endblock }}

{{ block content }}

    {{ next_button }}

{{ endblock }}

random_task_order/__init__.py

From otree-snippets


import random
from otree.api import *

doc = """
For each participant, randomize the order of tasks A, B, and C.
Task B has 2 pages, which are always shown in the same order.
The page_sequence contains all tasks;
in each round we show a randomly determined subset of pages.
"""


class Constants(BaseConstants):
    name_in_url = 'random_task_order'
    players_per_group = None
    tasks = ['A', 'B', 'C']
    num_rounds = len(tasks)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


# FUNCTIONS
def creating_session(subsession: Subsession):
    if subsession.round_number == 1:
        for p in subsession.get_players():
            round_numbers = list(range(1, Constants.num_rounds + 1))
            random.shuffle(round_numbers)
            p.participant.task_rounds = dict(zip(Constants.tasks, round_numbers))


# PAGES
class TaskA(Page):
    @staticmethod
    def is_displayed(player: Player):
        participant = player.participant

        return player.round_number == participant.task_rounds['A']


class TaskB1(Page):
    @staticmethod
    def is_displayed(player: Player):
        participant = player.participant

        return player.round_number == participant.task_rounds['B']


class TaskB2(Page):
    @staticmethod
    def is_displayed(player: Player):
        participant = player.participant

        return player.round_number == participant.task_rounds['B']


class TaskC(Page):
    @staticmethod
    def is_displayed(player: Player):
        participant = player.participant

        return player.round_number == participant.task_rounds['C']


page_sequence = [
    TaskA,
    TaskB1,
    TaskB2,
    TaskC,
]

random_task_order/TaskC.html

From otree-snippets


{{ block title }}
    Task C
{{ endblock }}
{{ block content }}

    {{ next_button }}

{{ endblock }}

random_task_order/TaskA.html

From otree-snippets


{{ block title }}
    Task A
{{ endblock }}

{{ block content }}


    {{ next_button }}

{{ endblock }}

progress_bar/progress.html

From otree-snippets


<!--
Simplest way to calculate the "max" is to run through the experiment
once and then see what participant.progress is at the very end,
then plug that in here.

if you want a prettier progress bar, you can use Bootstrap's.
-->
<p>
    <label>Step {{ participant.progress }} of 10
        <progress value="{{ participant.progress }}" max="10">
        </progress>
    </label>
</p>

progress_bar/__init__.py

From otree-snippets


from otree.api import *


doc = """
All you need is a participant field called 'progress' then keep adding 1 to it.
"""


class Constants(BaseConstants):
    name_in_url = 'progress_bar'
    players_per_group = None
    num_rounds = 5
    progress_template = __name__ + '/progress.html'


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    for player in subsession.get_players():
        participant = player.participant
        participant.progress = 1


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class Page1(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        participant = player.participant
        # remember to add 'progress' to PARTICIPANT_FIELDS.
        participant.progress += 1


class Page2(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        participant = player.participant
        # progress can be defined in different ways, not only by page number
        # (especially if pages get skipped)
        # so feel free to do things like:
        # - incrementing by more than 1:
        # participant.progress += 2
        # - setting to a specific valueInput:
        # participant.progress = 8
        participant.progress += 1


page_sequence = [Page1, Page2]

wait_page_timeout/MyPage.html

From otree-snippets


{{ block title }}
    Welcome
{{ endblock }}
{{ block content }}

    <p>Press next to continue...</p>
    {{ next_button }}

{{ endblock }}

wait_page_timeout/ResultsWaitPage.html

From otree-snippets


{{ extends 'otree/WaitPage.html' }}

{{ block title }}
Please wait
{{ endblock }}
{{ block content }}

{{ if timeout_happened }}
    <p>
        No other players showed up in time.
        Please submit this HIT with completion code <b>{{ player.completion_code }}</b>
    </p>
{{ else }}
    <p>
        If you are left waiting for longer than {{ Constants.timeout }} seconds,
        the game will end.
    </p>

    <script>
        setInterval(function () {
            window.location.reload();
        }, js_vars.timeout * 1000);

    </script>
{{ endif }}

{{ endblock }}

detect_mobile/Task.html

From otree-snippets


{{ block title }}
    Task.
{{ endblock }}

{{ block content }}

    <p>You are not using a mobile browser, so you can continue.</p>

    {{ next_button }}
{{ endblock }}

detect_mobile/__init__.py

From otree-snippets


from otree.api import *

doc = """Detect and block mobile browsers"""


class Constants(BaseConstants):
    name_in_url = 'detect_mobile'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    is_mobile = models.BooleanField()


# PAGES
class MobileCheck(Page):
    form_model = 'player'
    form_fields = ['is_mobile']

    def error_message(player: Player, values):
        if values['is_mobile']:
            return "Sorry, this experiment does not allow mobile browsers."


class Task(Page):
    pass


page_sequence = [MobileCheck, Task]

detect_mobile/MobileCheck.html

From otree-snippets


{{ block title }}
Start
{{ endblock }}
{{ block content }}

<input type="hidden" name="is_mobile" id="is_mobile">

<p>Please click next.</p>

{{ next_button }}

<script>
    function isMobile() {
        const toMatch = [
            /Android/i,
            /iPhone/i,
            /iPad/i,
        ];

        return toMatch.some((toMatchItem) => {
            return navigator.userAgent.match(toMatchItem);
        });
    }

    // here is an alternative technique that checks screen resolution
    // function isMobile() {
    //     return ((window.innerWidth <= 800) && (window.innerHeight <= 600));
    // }

    document.getElementById('is_mobile').value = isMobile() ? 1 : 0;

</script>

{{ endblock }}

chat_from_scratch/__init__.py

From otree-snippets


from otree.api import *


doc = """
Of course oTree has a readymade chat widget described here: 
https://otree.readthedocs.io/en/latest/multiplayer/chat.html

But you can use this if you want a chat box that is more easily customizable,
or if you want programmatic access to the chat messages. 

This app can also help you learn about live pages in general.
"""


class Constants(BaseConstants):
    name_in_url = 'chat_from_scratch'
    players_per_group = None
    num_rounds = 1
    chat_template = __name__ + '/chat.html'


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class Message(ExtraModel):
    group = models.Link(Group)
    sender = models.Link(Player)
    text = models.StringField()


def to_dict(msg: Message):
    return dict(sender=msg.sender.id_in_group, text=msg.text)


# PAGES
class MyPage(Page):
    @staticmethod
    def js_vars(player: Player):
        return dict(my_id=player.id_in_group)

    @staticmethod
    def live_method(player: Player, data):
        my_id = player.id_in_group
        group = player.group

        if 'text' in data:
            text = data['text']
            msg = Message.create(group=group, sender=player, text=text)
            return {0: [to_dict(msg)]}
        return {my_id: [to_dict(msg) for msg in Message.filter(group=group)]}


page_sequence = [MyPage]

chat_from_scratch/chat.html

From otree-snippets


<div id="chat_messages">
</div>

<div>
    <input type="text" id="chat_input">
    <button type="button" onclick="sendMsg()">Send</button>
</div>
<script>

    let chat_input = document.getElementById('chat_input');

    chat_input.addEventListener("keydown", function (event) {
        if (event.key === "Enter") {
            sendMsg();
        }
    });

    function sendMsg() {
        let text = chat_input.value.trim();
        if (text) {
            liveSend({'text': text});
        }
        chat_input.value = '';
    }

    let chat_messages = document.getElementById('chat_messages');

    function liveRecv(messages) {
        for (let msg of messages) {
            let msgSpan = document.createElement('span');
            msgSpan.textContent = msg.text;
            let sender = msg.sender === js_vars.my_id ? 'Me' : `Player ${msg.sender}`;
            let row = `<div><b>${sender}</b>: ${msgSpan.innerHTML}</div>`;
            chat_messages.insertAdjacentHTML('beforeend', row);
        }
    }

    document.addEventListener("DOMContentLoaded", function (event) {
        liveSend({});
    });

</script>

chat_from_scratch/MyPage.html

From otree-snippets


{{ block content }}

{{ include Constants.chat_template }}

{{ endblock }}

are_you_sure/__init__.py

From otree-snippets


from otree.api import *


doc = """
'Are you sure?' popup based on the user's input
"""


class Constants(BaseConstants):
    name_in_url = 'are_you_sure'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    contribution = models.CurrencyField(
        min=0, max=100, label="How much of your 100 points do you want to contribute?"
    )
    reason = models.LongStringField(
        blank=True,
        label="Please write a message to your teammates explaining your decision",
    )


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['contribution', 'reason']


page_sequence = [MyPage]

are_you_sure/MyPage.html

From otree-snippets


{{ block content }}

    <p><i>This page warns if the user contributes 0 or their explanation is too short.</i></p>

    {{ formfields }}

    <button type="button" class="btn btn-primary" onclick="checkSubmit()">Next</button>

    <script>
        function checkSubmit() {
            let form = document.getElementById('form');
            let isValid = form.reportValidity();
            if (!isValid) return;

            let warnings = [];

            let contribution = document.getElementsByName('contribution')[0].value;
            if (contribution === '0') {
                warnings.push("Are you sure you don't want to contribute anything?");
            }

            let reason = document.getElementsByName('reason')[0].value;
            if (reason.length < 10) {
                warnings.push("Are you sure you don't want to give a longer explanation?")
            }

            if (warnings.length > 0) {
                warnings.push("Press OK to proceed anyway.")
                let confirmed = window.confirm(warnings.join('\r\n'));
                if (!confirmed) return;
            }

            form.submit();
        }
    </script>


{{ endblock }}

longitudinal/Bridge.html

From otree-snippets


{{ block content }}
    <p>Thank you for participating in part 1.</p>
    <p>
        Please come back after <b>{{ player.part2_start_time_readable }}</b>
        to take part in the next phase.
    </p>
{{ endblock }}

longitudinal/Part1.html

From otree-snippets


{{ block title }}
    Survey
{{ endblock }}
{{ block content }}

    <p><i>The first phase of your experiment goes here...</i></p>

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

longitudinal/__init__.py

From otree-snippets


from otree.api import *


doc = """
Longitudinal study (2-part study taking place across days/weeks)

Another way to do longitudinal studies is just to give participants a Room URL.
Since that URL is persistent, you can create a new session when the next phase has begun.

But the technique here has the advantage of storing both phases together in a single session.
For example, you can easily compare the user's answer to their answer in the previous phase.
"""


class Constants(BaseConstants):
    name_in_url = 'longitudinal'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    question = models.StringField()
    part2_start_time = models.FloatField()
    part2_start_time_readable = models.StringField()


# PAGES
class Part1(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        from datetime import datetime, timedelta

        start = datetime.now() + timedelta(weeks=1)
        # or can make it for a specific date:
        # start = datetime.strptime('2022-07-15', '%Y-%m-%d')

        player.part2_start_time = start.timestamp()
        player.part2_start_time_readable = start.strftime('%A, %B %d')


def still_waiting_for_part_2(player: Player):
    import time

    return time.time() < player.part2_start_time


class Bridge(Page):
    @staticmethod
    def is_displayed(player: Player):
        return still_waiting_for_part_2(player)

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        raise Exception(
            "Player somehow tried to proceed past a page with no next button"
        )


class Part2(Page):
    pass


page_sequence = [Part1, Bridge, Part2]

longitudinal/Part2.html

From otree-snippets


{{ block title }}
    Survey
{{ endblock }}
{{ block content }}

    <p><i>The second phase of your experiment goes here...</i></p>

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

comprehension_test_simple/__init__.py

From otree-snippets


from otree.api import *

doc = """
Simple version of comprehension test
"""


class Constants(BaseConstants):
    name_in_url = 'comprehension_test_simple'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    quiz1 = models.IntegerField(label='What is 2 + 2?')
    quiz2 = models.IntegerField(label="What year did COVID-19 start?")
    quiz3 = models.BooleanField(label="Is 9 a prime number?")


class MyPage(Page):
    form_model = 'player'
    form_fields = ['quiz1', 'quiz2', 'quiz3']

    @staticmethod
    def error_message(player: Player, values):
        solutions = dict(quiz1=4, quiz2=2019, quiz3=False)

        if values != solutions:
            return "One or more answers were incorrect."


class Results(Page):
    pass


page_sequence = [MyPage, Results]

comprehension_test_simple/Results.html

From otree-snippets


{{ block title }}
    Thank you
{{ endblock }}

{{ block content }}
    <p>You answered all questions correctly</p>
{{ endblock }}

comprehension_test_simple/MyPage.html

From otree-snippets


{{ block title }}
    Comprehension test
{{ endblock }}
{{ block content }}

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

random_num_rounds/End.html

From otree-snippets


{{ block title }}
    End
{{ endblock }}
{{ block content }}

    The session is finished.

{{ endblock }}

random_num_rounds/__init__.py

From otree-snippets


from otree.api import *


class Constants(BaseConstants):
    name_in_url = 'random_num_rounds'
    players_per_group = None
    num_rounds = 20


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    import random

    for p in subsession.get_players():
        p.participant.num_rounds = random.randint(1, 20)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    num_rounds = models.IntegerField()


# PAGES
class MyPage(Page):
    @staticmethod
    def is_displayed(player: Player):
        participant = player.participant

        return player.round_number < participant.num_rounds


class End(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == Constants.num_rounds


page_sequence = [MyPage, End]

random_num_rounds/MyPage.html

From otree-snippets


{{ block title }}
    Round {{ subsession.round_number }}
{{ endblock }}
{{ block content }}

    <p>
        This player will continue for {{ participant.num_rounds }} rounds.
    </p>

    {{ next_button }}

{{ endblock }}

persist_raw/__init__.py

From otree-snippets


from otree.api import *


doc = """
Sliders and checkboxes that don't get wiped out on form reload.
Also works for text/number inputs, etc. 
"""


class Constants(BaseConstants):
    name_in_url = 'persist_raw'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    f_int = models.IntegerField(min=10)
    f_bool1 = models.BooleanField(blank=True)
    f_bool2 = models.BooleanField(blank=True)
    f_bool3 = models.BooleanField(blank=True)
    f_bool4 = models.BooleanField(blank=True)
    f_bool5 = models.BooleanField(blank=True)


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = [
        'f_int',
        'f_bool1',
        'f_bool2',
        'f_bool3',
        'f_bool4',
        'f_bool5',
    ]


page_sequence = [MyPage]

persist_raw/MyPage.html

From otree-snippets


{{ block content }}

<p>
    If you've used raw HTML widgets (slider/checkbox),
    you may have noticed that they are wiped out on when the form is re-rendered
    to show errors.
    This page contains simple code to overcome that limitation.
</p>

<p>
    To test, modify the form fields, then submit the page.
    (The form will fail validation until you set the slider to the correct value.)
</p>

<label class="col-form-label">
    Here is a slider:
</label>

<div style="display: flex">
    0
    &nbsp;
    <input type="range" name="f_int" min="0" max="10" style="flex: 1" class="persist">
    &nbsp;
    10
</div>
{{ formfield_errors 'f_int' }}
<br>

<p>Here are some checkboxes:</p>
<input type="checkbox" name="f_bool1" value="1" class="persist">
<input type="checkbox" name="f_bool2" value="1" class="persist">
<input type="checkbox" name="f_bool3" value="1" class="persist">
<input type="checkbox" name="f_bool4" value="1" class="persist">
<input type="checkbox" name="f_bool5" value="1" class="persist">

<br><br>
{{ next_button }}

{#
INSTRUCTIONS
(1) make sure your _static/ folder contains persist-raw.js
(2) copy the below 'script' tag into your template
(2) add class="persist" to your raw HTML inputs
#}

<script src="{{ static 'persist-raw.js' }}"></script>

{{ endblock }}

progress_bar/Page2.html

From otree-snippets


{{ block content }}
    {{ include Constants.progress_template }}

    {{ next_button }}

{{ endblock }}

progress_bar/Page1.html

From otree-snippets


{{ block content }}
    {{ include Constants.progress_template }}

    {{ next_button }}

{{ endblock }}

min_time_on_page/__init__.py

From otree-snippets


from otree.api import *


doc = """
Minimum time on a page
"""


class Constants(BaseConstants):
    name_in_url = 'min_time_on_page'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):

    pass


class Player(BasePlayer):
    page_pass_time = models.FloatField()


class Page1(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        import time

        player.page_pass_time = time.time() + 10


# PAGES
class Page2(Page):
    @staticmethod
    def error_message(player: Player, values):
        import time

        if time.time() < player.page_pass_time:
            return "You cannot pass this page yet."


class Page3(Page):
    pass


page_sequence = [Page1, Page2, Page3]

min_time_on_page/Page3.html

From otree-snippets


{{ block title }}
    Page 3
{{ endblock }}
{{ block content }}

{{ endblock }}

min_time_on_page/Page2.html

From otree-snippets


{{ block title }}
    Page 2
{{ endblock }}
{{ block content }}

    <p>You must stay on this page for at least 10 seconds.</p>

    {{ next_button }}

{{ endblock }}

dropout_end_game/DropoutHappened.html

From otree-snippets


{{ block content }}
<p>
    A player in your group dropped out.
    Therefore, you will be forwarded to the next app.
</p>
{{ next_button }}

{{ endblock }}

dropout_end_game/DropoutTest.html

From otree-snippets


{{ block title }}
    Dropout check
{{ endblock }}
{{ block content }}
    <p>
        Important: click "next" before the timeout occurs.
        Otherwise you will be considered a dropout.
    </p>

    {{ next_button }}

{{ endblock }}

dropout_end_game/__init__.py

From otree-snippets


from otree.api import *


doc = """
Dropout detection for multiplayer game (end the game)
"""


class Constants(BaseConstants):
    name_in_url = 'dropout_end_game'
    players_per_group = None
    num_rounds = 5


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    has_dropout = models.BooleanField(initial=False)


class Player(BasePlayer):
    is_dropout = models.BooleanField()


class Game(Page):
    timeout_seconds = 10


class DropoutTest(Page):
    timeout_seconds = 10

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        group = player.group
        if timeout_happened:
            group.has_dropout = True
            player.is_dropout = True


class WaitForOthers(WaitPage):
    pass


class DropoutHappened(Page):
    @staticmethod
    def is_displayed(player: Player):
        group = player.group
        return group.has_dropout

    @staticmethod
    def app_after_this_page(player: Player, upcoming_apps):
        return upcoming_apps[0]


page_sequence = [Game, DropoutTest, WaitForOthers, DropoutHappened]

dropout_end_game/Game.html

From otree-snippets


{{ block title }}
    Game, round {{ subsession.round_number }}
{{ endblock }}
{{ block content }}

    <p><i>Your game goes here...</i></p>

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

configurable_players_per_group/__init__.py

From otree-snippets


from otree.api import *


doc = """
Configurable players per group.
See here: https://otree.readthedocs.io/en/latest/treatments.html#configure-sessions
"""


class Constants(BaseConstants):
    name_in_url = 'configurable_players_per_group'
    # Since Constants does not have access to the session config,
    # (it is loaded when the server starts, rather than for each session)
    # we set the groups manually inside creating_session.
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    session = subsession.session
    ppg = session.config['players_per_group']
    players = subsession.get_players()
    matrix = []
    for i in range(0, len(players), ppg):
        matrix.append(players[i : i + ppg])
    subsession.set_group_matrix(matrix)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    pass


class MyPage(Page):
    pass


page_sequence = [MyPage]

configurable_players_per_group/MyPage.html

From otree-snippets


{{ block content }}

    <p><i>
    Check the number of players per group in the "Data" tab.
    It will match whatever you selected in "Configure session".
    </i></p>

{{ endblock }}

image_choices/__init__.py

From otree-snippets


from otree.api import *


doc = """
Images in radio button choices
"""


def make_image_data(image_names):
    return [dict(name=name, path='shapes/{}'.format(name)) for name in image_names]


class Constants(BaseConstants):
    name_in_url = 'image_choices'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    img_choice = models.StringField()


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['img_choice']

    @staticmethod
    def vars_for_template(player: Player):
        image_names = [
            'circle-blue.svg',
            'plus-green.svg',
            'star-red.svg',
            'triangle-yellow.svg',
        ]
        return dict(image_data=make_image_data(image_names))


page_sequence = [MyPage]

image_choices/MyPage.html

From otree-snippets


{{ block content }}

    <p>Choose your favorite image.</p>
    {{ for image in image_data }}
        <label style="text-align: center">
            <img src="{{ static image.path }}" width="200px">
            <br>
            <input type="radio" name="img_choice" value="{{ image.name }}" class="persist">
        </label>
    {{ endfor }}
    {{ formfield_errors 'img_choice' }}

    <br>
    {{ next_button }}

    <script src="{{ static 'persist-raw.js' }}"></script>

{{ endblock }}

pay_random_round/__init__.py

From otree-snippets


from otree.api import *


doc = """
Select a random round for payment
"""


class Constants(BaseConstants):
    name_in_url = 'pay_random_round'
    players_per_group = None
    num_rounds = 4
    endowment = cu(100)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    give_amount = models.CurrencyField(
        min=0, max=100, label="How much do you want to give?"
    )


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['give_amount']

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        import random

        participant = player.participant

        if player.round_number == Constants.num_rounds:
            random_round = random.randint(1, Constants.num_rounds)
            participant.selected_round = random_round
            player_in_selected_round = player.in_round(random_round)
            player.payoff = Constants.endowment - player_in_selected_round.give_amount


class Results(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == Constants.num_rounds


page_sequence = [MyPage, Results]

pay_random_round/Results.html

From otree-snippets


{{ block content }}
    {{ if subsession.round_number == Constants.num_rounds }}
    <p>
        Round {{ participant.selected_round }} was randomly selected for payment.
        Your final payoff is therefore {{ player.payoff }}.
    </p>
    {{ endif }}

{{ endblock }}

pay_random_round/MyPage.html

From otree-snippets


{{ block title }}
    Round {{ subsession.round_number }}
{{ endblock }}
{{ block content }}

    <p>You have {{ Constants.endowment }} to split between you and another player.</p>

    {{ formfields }}
    {{ next_button }}

{{ endblock }}

quiz_with_explanation/__init__.py

From otree-snippets


from otree.api import *


doc = """
Quiz with explanation. Re-display the previous page's form as read-only, with answers/explanation.
"""


class Constants(BaseConstants):
    name_in_url = 'quiz_with_explanation'
    players_per_group = None
    num_rounds = 1
    form_template = __name__ + '/form.html'


def get_quiz_data():
    return [
        dict(
            name='a',
            solution=True,
            explanation="2 is prime. It has no factorization other than 1 and itself.",
        ),
        dict(
            name='b',
            solution=False,
            explanation="39 is not prime because it can be factored to 3 * 13.",
        ),
    ]


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    a = models.BooleanField(label="Is 2 a prime number?")
    b = models.BooleanField(label="Is 39 a prime number?")


class MyPage(Page):
    form_model = 'player'
    form_fields = ['a', 'b']

    @staticmethod
    def vars_for_template(player: Player):
        fields = get_quiz_data()
        return dict(fields=fields, show_solutions=False)


class Results(Page):
    form_model = 'player'
    form_fields = ['a', 'b']

    @staticmethod
    def vars_for_template(player: Player):
        fields = get_quiz_data()
        # we add an extra key 'is_correct' to each field
        for d in fields:
            d['is_correct'] = getattr(player, d['name']) == d['solution']
        return dict(fields=fields, show_solutions=True)

    @staticmethod
    def error_message(player: Player, values):
        for field in values:
            if getattr(player, field) != values[field]:
                return "A field was somehow changed but this page is read-only."


page_sequence = [MyPage, Results]

quiz_with_explanation/Results.html

From otree-snippets


{{ block title }}
    Results
{{ endblock }}
{{ block content }}

    <style>
        /* we use this instead of setting 'disabled' because disabled inputs don't get
        submitted by the form, and therefore the server would complain that the form is missing.
         */
        input {
            pointer-events: none;
        }

        .solution-incorrect {
            color: red;
        }

        .solution-correct {
            color: green;
        }
    </style>

    <p>Here are your answers along with the solutions.</p>

    {{ include Constants.form_template }}

    {{ next_button }}

{{ endblock }}

dollar_auction/Results.html

From otree-demo


{{ block title }}
    Page title
{{ endblock }}

{{ block content }}

{{ if player.is_top_bidder }}
    You were the top bidder. Your bid was {{ group.top_bid }}.
{{ elif player.is_second_bidder }}
    You were the second bidder. Your bid was {{ group.second_bid }}.
{{ else }}
    You were not the top bidder or the second bidder.
{{ endif }}
    Therefore, your final payoff is {{ player.payoff }}.
{{ endblock }}

quiz/__init__.py

From otree-demo


from otree.api import *

doc = """
Your app description
"""


class Constants(BaseConstants):
    name_in_url = 'quiz'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    for p in subsession.get_players():
        stimuli = read_csv()
        p.num_trials = len(stimuli)
        for stim in stimuli:
            Trial.create(player=p, **stim)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    num_completed = models.IntegerField(initial=0)
    num_correct = models.IntegerField(initial=0)
    num_trials = models.IntegerField()


def get_current_trial(player: Player):
    return Trial.filter(player=player, choice=None)[0]


def is_finished(player: Player):
    return player.num_completed == player.num_trials


class Trial(ExtraModel):
    player = models.Link(Player)
    question = models.StringField()
    optionA = models.StringField()
    optionB = models.StringField()
    optionC = models.StringField()
    solution = models.StringField()
    choice = models.StringField()
    is_correct = models.BooleanField()


def to_dict(trial: Trial):
    return dict(
        question=trial.question,
        optionA=trial.optionA,
        optionB=trial.optionB,
        optionC=trial.optionC,
        id=trial.id,
    )


def read_csv():
    import csv
    import random

    f = open('quiz/stimuli.csv', encoding='utf8')
    rows = list(csv.DictReader(f))

    random.shuffle(rows)
    return rows


# PAGES
class Stimuli(Page):
    @staticmethod
    def live_method(player: Player, data):
        my_id = player.id_in_group

        if 'choice' in data:
            if is_finished(player):
                return
            trial = get_current_trial(player)
            if data['trialId'] != trial.id:
                return
            trial.choice = data['choice']
            trial.is_correct = trial.choice == trial.solution
            player.num_correct += int(trial.is_correct)
            player.num_completed += 1

        if is_finished(player):
            return {my_id: dict(status='finished')}
        return {my_id: dict(status='next_stimulus', stimulus=to_dict(get_current_trial(player)))}


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(trials=Trial.filter(player=player))


page_sequence = [Stimuli, Results]

quiz/Results.html

From otree-demo


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <p>
    You gave {{ player.num_correct }} correct answers.
    </p>

    <table class="table">
        <tr>
            <th>question</th>
            <th>optionA</th>
            <th>optionB</th>
            <th>optionC</th>
            <th>Your choice</th>
            <th>solution</th>
            <th>correct?</th>
        </tr>
        {{ for trial in trials }}
        <tr>
            <td>{{ trial.question }}</td>
            <td>{{ trial.optionA }}</td>
            <td>{{ trial.optionB }}</td>
            <td>{{ trial.optionC }}</td>
            <td>{{ trial.choice }}</td>
            <td>{{ trial.solution }}</td>
            <td>{{ trial.is_correct }}</td>
        </tr>
        {{ endfor }}
    </table>

    <p>
        You can export this raw data using <code>custom_export</code>,
        or do some calculation based on the responses in <code>before_next_page</code>.
    </p>


{{ endblock }}

quiz/Stimuli.html

From otree-demo


{{ block title }}
{{ endblock }}

{{ block content }}

<p id="question"></p>

<div>
    <button type="button" onclick="sendClick(this)" value="A" id="optionA">
    </button>
    <button type="button" onclick="sendClick(this)" value="B" id="optionB">
    </button>
    <button type="button" onclick="sendClick(this)" value="C" id="optionC">
    </button>
</div>

<script>

    let trialId = null;

    function liveRecv(data) {
        if (data.status === 'finished') {
            document.getElementById('form').submit();
        } else {
            let stimulus = data.stimulus;
            trialId = stimulus.id;

            for (let item of ['question', 'optionA', 'optionB', 'optionC']) {
                document.getElementById(item).innerText = stimulus[item];
            }
        }
    }

    function sendClick(btn) {
        liveSend({'choice': btn.value, 'trialId': trialId});
    }

    document.addEventListener("DOMContentLoaded", function (event) {
        // send empty message to load initial question, or in case page is refreshed.
        liveSend({});
    });
</script>

{{ endblock }}

monty_hall/Decide2.html

From otree-demo


{{ block title }}
    Final decision
{{ endblock }}

{{ block content }}

<p>
    We now reveal that door {{ player.door_opened }} does not contain the prize.
    Do you want to stay with door {{ player.door_first_chosen }}
    or switch to door {{ player.door_not_opened }}?
</p>

    <button name="door_finally_chosen" value="{{ player.door_first_chosen }}">
        Stay with door {{ player.door_first_chosen }}
    </button>
    <button name="door_finally_chosen" value="{{ player.door_not_opened }}">
        Switch to door {{ player.door_not_opened }}
    </button>

{{ endblock }}

monty_hall/__init__.py

From otree-demo


from otree.api import *

doc = """
Monty Hall problem
"""


class Constants(BaseConstants):
    name_in_url = 'monty_hall'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    door_first_chosen = models.IntegerField(choices=[1, 2, 3])
    door_opened = models.IntegerField()
    door_not_opened = models.IntegerField()
    door_finally_chosen = models.IntegerField()
    door_with_prize = models.IntegerField()
    is_winner = models.BooleanField()


def door_finally_chosen_choices(player: Player):
    return [player.door_first_chosen, player.door_not_opened]


class Decide1(Page):
    form_model = 'player'
    form_fields = ['door_first_chosen']

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        import random

        player.door_with_prize = random.choice([1, 2, 3])
        remaining_doors = [1, 2, 3]
        remaining_doors.remove(player.door_first_chosen)
        if player.door_with_prize == player.door_first_chosen:
            random.shuffle(remaining_doors)
            [player.door_opened, player.door_not_opened] = remaining_doors
        else:
            player.door_not_opened = player.door_with_prize
            remaining_doors.remove(player.door_not_opened)
            [player.door_opened] = remaining_doors


class Decide2(Page):
    form_model = 'player'
    form_fields = ['door_finally_chosen']

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        player.is_winner = player.door_finally_chosen == player.door_with_prize


class Results(Page):
    pass


page_sequence = [Decide1, Decide2, Results]

monty_hall/Results.html

From otree-demo


{{ block title }}
Results
{{ endblock }}

{{ block content }}

<p>
    The door with the prize was {{ player.door_with_prize }}.
    {{ if player.is_winner }}
    You won!
    {{ else }}
    Better luck next time.
    {{ endif }}
</p>


{{ endblock }}

monty_hall/Decide1.html

From otree-demo


{{ block title }}
Choose a door
{{ endblock }}

{{ block content }}

<p>Behind one of these doors there is a prize. Which door will you pick?</p>
<p>
    After you make your choice, we will reveal one of the doors that does not contain the prize,
    and you will have a chance to change your choice.
</p>
<button name="door_first_chosen" value="1">Door 1</button>
<button name="door_first_chosen" value="2">Door 2</button>
<button name="door_first_chosen" value="3">Door 3</button>

{{ endblock }}

matching_pennies/ResultsSummary.html

From otree-demo


{{ block title }}Final results{{ endblock }}
{{ block content }}

    <table class="table">
        <tr>
            <th>Round</th>
            <th>Player and outcome</th>
        </tr>
        {{ for p in player_in_all_rounds }}
            <tr>
                <td>{{ p.round_number }}</td>
                <td>
                  You were the {{ p.role }}
                  and {{ if p.is_winner }} won {{ else }} lost {{ endif }}
                </td>
            </tr>
        {{ endfor }}
    </table>

    <p>
        The paying round was {{ paying_round }}.
        Your total payoff is therefore {{ total_payoff }}.
    </p>

{{ endblock }}

matching_pennies/Choice.html

From otree-demo


{{ block title }}Round {{ subsession.round_number }} of {{ Constants.num_rounds }}{{ endblock }}
{{ block content }}

    <h4>Instructions</h4>
    <p>
        This is a matching pennies game.
        Player 1 is the 'Mismatcher' and wins if the choices mismatch;
        Player 2 is the 'Matcher' and wins if they match.

    </p>

    <p>
        At the end, a random round will be chosen for payment.
    </p>

    <p>

    <h4>Round history</h4>
    <table class="table">
        <tr>
            <th>Round</th>
            <th>Player and outcome</th>
        </tr>
        {{ for p in player_in_previous_rounds }}
            <tr>
                <td>{{ p.round_number }}</td>
                <td>
                  You were the {{ p.role }}
                  and {{ if p.is_winner }} won {{ else }} lost {{ endif }}
                </td>
            </tr>
        {{ endfor }}
    </table>

    <p>
        In this round, you are the {{ player.role }}.
    </p>

    {{ formfields }}

    {{ next_button }}

{{ endblock }}

matching_pennies/__init__.py

From otree-demo


from otree.api import *

doc = """
A demo of how rounds work in oTree, in the context of 'matching pennies'
"""


class Constants(BaseConstants):
    name_in_url = 'matching_pennies'
    players_per_group = 2
    num_rounds = 4
    stakes = cu(100)

    matcher_role = 'Matcher'
    mismatcher_role = 'Mismatcher'


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    penny_side = models.StringField(
        choices=[['Heads', 'Heads'], ['Tails', 'Tails']],
        widget=widgets.RadioSelect,
        label="I choose:",
    )
    is_winner = models.BooleanField()


# FUNCTIONS
def creating_session(subsession: Subsession):
    session = subsession.session
    import random

    if subsession.round_number == 1:
        paying_round = random.randint(1, Constants.num_rounds)
        session.vars['paying_round'] = paying_round
    if subsession.round_number == 3:
        # reverse the roles
        matrix = subsession.get_group_matrix()
        for row in matrix:
            row.reverse()
        subsession.set_group_matrix(matrix)
    if subsession.round_number > 3:
        subsession.group_like_round(3)


def set_payoffs(group: Group):
    subsession = group.subsession
    session = group.session

    p1 = group.get_player_by_id(1)
    p2 = group.get_player_by_id(2)
    for p in [p1, p2]:
        is_matcher = p.role == Constants.matcher_role
        p.is_winner = (p1.penny_side == p2.penny_side) == is_matcher
        if subsession.round_number == session.vars['paying_round'] and p.is_winner:
            p.payoff = Constants.stakes
        else:
            p.payoff = cu(0)


# PAGES
class Choice(Page):
    form_model = 'player'
    form_fields = ['penny_side']

    @staticmethod
    def vars_for_template(player: Player):
        return dict(player_in_previous_rounds=player.in_previous_rounds())


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class ResultsSummary(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == Constants.num_rounds

    @staticmethod
    def vars_for_template(player: Player):
        session = player.session

        player_in_all_rounds = player.in_all_rounds()
        return dict(
            total_payoff=sum([p.payoff for p in player_in_all_rounds]),
            paying_round=session.vars['paying_round'],
            player_in_all_rounds=player_in_all_rounds,
        )


page_sequence = [Choice, ResultsWaitPage, ResultsSummary]

double_auction/chart.html

From otree-demo


<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/series-label.js"></script>

<div id="highchart">

</div>

<script>
    function redrawChart(series) {
        Highcharts.chart('highchart', {

            title: {
                text: 'Trade history'
            },

            yAxis: {
                title: {
                    text: 'Price'
                }
            },

            xAxis: {
                title: {
                    text: 'Time (seconds)'
                },
                min: 0
            },

            plotOptions: {
                series: {
                    label: {
                        enabled: false
                    },
                }
            },

            series: [{
                data: series,
                type: 'scatter'

            }],

            credits: {
                enabled: false
            }

        });
    }
</script>

double_auction/__init__.py

From otree-demo


from otree.api import *
from shared_out import set_players_per_group
import time
import random


class Constants(BaseConstants):
    name_in_url = 'double_auction'
    players_per_group = None
    num_rounds = 1
    items_per_seller = 3
    valuation_min = cu(50)
    valuation_max = cu(110)
    production_costs_min = cu(10)
    production_costs_max = cu(80)


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    set_players_per_group(subsession)
    players = subsession.get_players()
    for p in players:
        # for more buyers, change the 2 to 3
        p.is_buyer = p.id_in_group % 2 > 0
        if p.is_buyer:
            p.num_items = 0
            p.break_even_point = random.randint(Constants.valuation_min, Constants.valuation_max)
            p.current_offer = 0
        else:
            p.num_items = Constants.items_per_seller
            p.break_even_point = random.randint(
                Constants.production_costs_min, Constants.production_costs_max
            )
            p.current_offer = Constants.valuation_max + 1


class Group(BaseGroup):
    start_timestamp = models.IntegerField()


class Player(BasePlayer):
    is_buyer = models.BooleanField()
    current_offer = models.CurrencyField()
    break_even_point = models.CurrencyField()
    num_items = models.IntegerField()


class Transaction(ExtraModel):
    group = models.Link(Group)
    buyer = models.Link(Player)
    seller = models.Link(Player)
    price = models.CurrencyField()
    seconds = models.IntegerField(doc="Timestamp (seconds since beginneng of trading)")


def find_match(buyers, sellers):
    for buyer in buyers:
        for seller in sellers:
            if seller.num_items > 0 and seller.current_offer <= buyer.current_offer:
                return [buyer, seller]


def live_method(player: Player, data):
    group = player.group
    players = group.get_players()
    buyers = [p for p in players if p.is_buyer]
    sellers = [p for p in players if not p.is_buyer]
    news = None
    if data:
        try:
            offer = int(data['offer'])
        except Exception:
            print('invalid message received:', data)
            return
        player.current_offer = offer
        if player.is_buyer:
            match = find_match(buyers=[player], sellers=sellers)
        else:
            match = find_match(buyers=buyers, sellers=[player])
        if match:
            [buyer, seller] = match
            price = buyer.current_offer
            Transaction.create(
                group=group,
                buyer=buyer,
                seller=seller,
                price=price,
                seconds=int(time.time() - group.start_timestamp),
            )
            buyer.num_items += 1
            seller.num_items -= 1
            buyer.payoff += buyer.break_even_point - price
            seller.payoff += price - seller.break_even_point
            buyer.current_offer = 0
            seller.current_offer = Constants.valuation_max + 1
            news = dict(buyer=buyer.id_in_group, seller=seller.id_in_group, price=price)

    bids = sorted([p.current_offer for p in buyers if p.current_offer > 0], reverse=True)
    asks = sorted([p.current_offer for p in sellers if p.current_offer <= Constants.valuation_max])
    highcharts_series = [[tx.seconds, tx.price] for tx in Transaction.filter(group=group)]

    return {
        p.id_in_group: dict(
            bids=bids,
            asks=asks,
            highcharts_series=highcharts_series,
            num_items=p.num_items,
            current_offer=p.current_offer,
            news=news,
            payoff=p.payoff,
        )
        for p in players
    }


# PAGES
class WaitToStart(WaitPage):
    @staticmethod
    def after_all_players_arrive(group: Group):
        group.start_timestamp = int(time.time())


class Trading(Page):
    live_method = live_method

    @staticmethod
    def js_vars(player: Player):
        return dict(id_in_group=player.id_in_group, is_buyer=player.is_buyer)

    @staticmethod
    def get_timeout_seconds(player: Player):
        import time

        group = player.group
        return 2 * 60 + group.start_timestamp - time.time()


class ResultsWaitPage(WaitPage):
    pass


class Results(Page):
    pass


page_sequence = [WaitToStart, Trading, ResultsWaitPage, Results]

double_auction/Results.html

From otree-demo


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <table class="table">
        <tr>
            <th>Items in your possession</th>
            <td>{{ player.num_items }}</td>
        </tr>
        <tr>
            <th>Payoff</th>
            <td>{{ player.payoff }}</td>
        </tr>
    </table>

{{ endblock }}

double_auction/Trading.html

From otree-demo


{{ block title }}
Trade
{{ endblock }}
{{ block content }}

<p id="news" style="color: green"></p>

<table class="table">
    <tr>
        <td>Your role</td>
        <th>
            {{ if player.is_buyer }}buyer{{ else }}seller{{ endif }}
        </th>
    </tr>
    <tr>
        <td>
            Your break-even point
            {{ if player.is_buyer }}
                (you should buy for less than)
            {{ else }}
                (you should sell for more than)
            {{ endif }}
        </td>
        <th>
            {{ player.break_even_point }}
        </th>
    </tr>
    <tr>
        <td>Items in your possession</td>
        <th id="num_items"></th>
    </tr>
    <tr>
        <td>Your current offer</td>
        <th id="current_offer"></th>
    </tr>
    <tr>
        <td>Profits</td>
        <th id="payoff"></th>
    </tr>
</table>


<input type="number" id="my_offer">
<button type="button" onclick="sendOffer()" id="btn-offer">Make offer</button>
<br><br>
<div class="container">
    <div class="row">
        <div class="col-sm">
            <h4>Bids</h4>
            <table id="bids_table"></table>
        </div>
        <div class="col-sm">
            <h4>Asks</h4>
            <table id="asks_table"></table>
        </div>
    </div>
</div>

<br><br>
{{ include 'double_auction/chart.html' }}


<script>

    let bids_table = document.getElementById('bids_table');
    let asks_table = document.getElementById('asks_table');
    let my_id = js_vars.id_in_group;
    let news_div = document.getElementById('news');
    let is_buyer = js_vars.is_buyer;
    let btnOffer = document.getElementById('btn-offer');

    function showNews(msg) {
        news_div.innerText = msg;
        setTimeout(function () {
            news_div.innerText = ''
        }, 10000)
    }

    function cu(amount) {
        return `${amount} points`;
    }

    function liveRecv(data) {
        console.log(data)
        // javascript destructuring assignment
        let {bids, asks, highcharts_series, num_items, current_offer, payoff, news} = data;
        if (news) {
            let {buyer, seller, price} = news;
            if (buyer === my_id) {
                showNews(`You bought from player ${seller} for ${cu(price)}`);
            } else if (seller === my_id) {
                showNews(`You sold to player ${buyer} for ${cu(price)}`);
            }
        }
        document.getElementById('num_items').innerText = num_items;
        document.getElementById('current_offer').innerText = cu(current_offer);
        document.getElementById('payoff').innerText = cu(payoff);
        if (!is_buyer && num_items === 0) {
            btnOffer.disabled = true;
        }
        bids_table.innerHTML = bids.map(e => `<tr><td>${cu(e)}</td></tr>`).join('');
        asks_table.innerHTML = asks.map(e => `<tr><td>${cu(e)}</td></tr>`).join('');
        redrawChart(highcharts_series);
    }

    function sendOffer() {
        liveSend({'offer': document.getElementById('my_offer').value})
    }

    window.addEventListener('DOMContentLoaded', (event) => {
        liveSend({});
    });
</script>

{{ endblock }}

dictator/__init__.py

From otree-demo


from otree.api import *

doc = """
One player decides how to divide a certain amount between himself and the other
player.
See: Kahneman, Daniel, Jack L. Knetsch, and Richard H. Thaler. "Fairness
and the assumptions of economics." Journal of business (1986):
S285-S300.
"""


class Constants(BaseConstants):
    name_in_url = 'dictator'
    players_per_group = 2
    num_rounds = 1
    instructions_template = 'dictator/instructions.html'
    # Initial amount allocated to the dictator
    endowment = cu(100)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    kept = models.CurrencyField(
        doc="""Amount dictator decided to keep for himself""",
        min=0,
        max=Constants.endowment,
        label="I will keep",
    )


class Player(BasePlayer):
    pass


# FUNCTIONS
def set_payoffs(group: Group):
    p1 = group.get_player_by_id(1)
    p2 = group.get_player_by_id(2)
    p1.payoff = group.kept
    p2.payoff = Constants.endowment - group.kept


# PAGES
class Introduction(Page):
    pass


class Offer(Page):
    form_model = 'group'
    form_fields = ['kept']

    @staticmethod
    def is_displayed(player: Player):
        return player.id_in_group == 1


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        group = player.group

        return dict(offer=Constants.endowment - group.kept)


page_sequence = [Introduction, Offer, ResultsWaitPage, Results]

dictator/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}
    <p>

        {{ if player.id_in_group == 1 }}
            You decided to keep <strong>{{ group.kept }}</strong> for yourself.
        {{ else }}
            Participant 1 decided to keep <strong>{{ group.kept }}</strong>, so
            you got <strong>{{ offer }}</strong>.
        {{ endif }}


    </p>
    {{ include Constants.instructions_template }}
{{ endblock }}

dictator/instructions.html

From otree-demo


<div class="card bg-light m-3">
<div class="card-body">
    <h3>
        Instructions
    </h3>

    <p>
        You will be paired randomly and anonymously with another participant.
        In this study, one of you will be Participant 1 and the other
        Participant 2. Prior to making a decision, you will learn your role,
        which will be randomly assigned.
    </p>
    <p>
        There is {{ Constants.endowment }} to split. Participant 1 will
        decide how much she or he will retain. Then the rest will go to
        Participant 2.
    </p>
</div>
</div>

dictator/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}
    {{ include Constants.instructions_template }}
    {{ next_button }}
{{ endblock }}

dictator/Offer.html

From otree-demo


{{ block title }}Your Decision{{ endblock }}
{{ block content }}
    <p>
        You are <b>Participant 1</b>.
        Please decide how much of the {{ Constants.endowment }}
        you will keep for yourself.
    </p>

    {{ formfields }}

    {{ next_button }}

    {{ include Constants.instructions_template }}

{{ endblock }}

trust/SendBack.html

From otree-demo


{{ block title }}Your Choice{{ endblock }}
{{ block content }}
    <p>
        You are Participant B.
        Participant A sent you {{ group.sent_amount }} and you received {{ tripled_amount }}.
        Now you have {{ tripled_amount }}.
        How much will you send to participant A?
    </p>

    {{ formfields }}
    <p>{{ next_button }}</p>
    {{ include Constants.instructions_template }}

{{ endblock }}

trust/Send.html

From otree-demo


{{ block title }}Your Choice{{ endblock }}
{{ block content }}

    <p>
    You are Participant A. Now you have {{Constants.endowment}}. How much will you send to participant B?
    </p>

    {{ formfields }}
    <p>
    {{ next_button }}
    </p>

    {{ include Constants.instructions_template }}

{{ endblock }}

trust/__init__.py

From otree-demo


from otree.api import *

doc = """
This is a standard 2-player trust game where the amount sent by player 1 gets
tripled. The trust game was first proposed by
<a href="http://econweb.ucsd.edu/~jandreon/Econ264/papers/Berg%20et%20al%20GEB%201995.pdf" target="_blank">
    Berg, Dickhaut, and McCabe (1995)
</a>.
"""


class Constants(BaseConstants):
    name_in_url = 'trust'
    players_per_group = 2
    num_rounds = 1
    instructions_template = 'trust/instructions.html'
    # Initial amount allocated to each player
    endowment = cu(100)
    multiplier = 3


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    sent_amount = models.CurrencyField(
        min=0,
        max=Constants.endowment,
        doc="""Amount sent by P1""",
        label="Please enter an amount from 0 to 100:",
    )
    sent_back_amount = models.CurrencyField(doc="""Amount sent back by P2""", min=cu(0))


class Player(BasePlayer):
    pass


# FUNCTIONS
def sent_back_amount_max(group: Group):
    return group.sent_amount * Constants.multiplier


def set_payoffs(group: Group):
    p1 = group.get_player_by_id(1)
    p2 = group.get_player_by_id(2)
    p1.payoff = Constants.endowment - group.sent_amount + group.sent_back_amount
    p2.payoff = group.sent_amount * Constants.multiplier - group.sent_back_amount


# PAGES
class Introduction(Page):
    pass


class Send(Page):
    """This page is only for P1
    P1 sends amount (all, some, or none) to P2
    This amount is tripled by experimenter,
    i.e if sent amount by P1 is 5, amount received by P2 is 15"""

    form_model = 'group'
    form_fields = ['sent_amount']

    @staticmethod
    def is_displayed(player: Player):
        return player.id_in_group == 1


class SendBackWaitPage(WaitPage):
    pass


class SendBack(Page):
    """This page is only for P2
    P2 sends back some amount (of the tripled amount received) to P1"""

    form_model = 'group'
    form_fields = ['sent_back_amount']

    @staticmethod
    def is_displayed(player: Player):
        return player.id_in_group == 2

    @staticmethod
    def vars_for_template(player: Player):
        group = player.group

        tripled_amount = group.sent_amount * Constants.multiplier
        return dict(tripled_amount=tripled_amount)


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class Results(Page):
    """This page displays the earnings of each player"""

    @staticmethod
    def vars_for_template(player: Player):
        group = player.group

        return dict(tripled_amount=group.sent_amount * Constants.multiplier)


page_sequence = [
    Introduction,
    Send,
    SendBackWaitPage,
    SendBack,
    ResultsWaitPage,
    Results,
]

trust/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}

    {{ if player.id_in_group == 1 }}
        <p>
            You chose to send participant B {{ group.sent_amount }}.
            Participant B returned {{ group.sent_back_amount }}.
        </p>
        <p>
            You were initially endowed with {{ Constants.endowment }},
            chose to send {{ group.sent_amount }},
            received {{ group.sent_back_amount }}
            thus you now have:
            {{ Constants.endowment }}-{{ group.sent_amount }}+{{ group.sent_back_amount }}=<b>{{ player.payoff }}</b>
        </p>
    {{ else }}
        <p>
            Participant A sent you {{ group.sent_amount }}.
            They were tripled so you received {{ tripled_amount }}.
            You chose to return {{ group.sent_back_amount }}.
        </p>
        <p>
            You received {{ tripled_amount }},
            chose to return {{ group.sent_back_amount }}
            thus you now have:
            ({{ tripled_amount }})-({{ group.sent_back_amount }})=<b>{{ player.payoff }}</b>
        </p>
        .
    {{ endif }}

    {{ include Constants.instructions_template }}

{{ endblock }}

trust/instructions.html

From otree-demo


<div class="card bg-light m-3">
    <div class="card-body">

    <h3>
        Instructions
    </h3>
    <p>
        You have been randomly and anonymously paired with another participant.
        One of you will be selected at random to be participant A;
        the other will be participant B.
        You will learn whether you are participant A or B prior to making any
        decision.
    </p>
    <p>
        To start, participant A receives {{ Constants.endowment }};
        participant B receives
        nothing.
        Participant A can send some or all of
        his {{ Constants.endowment }} to participant B.
        Before B receives this amount, it will be multiplied
        by {{ Constants.multiplier }}. Once B receives
        the tripled amount he can decide to send some or all of it to
        A.
    </p>
</div>
</div>

trust/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}

    {{ include Constants.instructions_template }}

    {{ next_button }}

{{ endblock }}

bertrand/__init__.py

From otree-demo


from otree.api import *

doc = """
2 firms complete in a market by setting prices for homogenous goods.
See "Kruse, J. B., Rassenti, S., Reynolds, S. S., & Smith, V. L. (1994).
Bertrand-Edgeworth competition in experimental markets.
Econometrica: Journal of the Econometric Society, 343-371."
"""


class Constants(BaseConstants):
    players_per_group = 2
    name_in_url = 'bertrand'
    num_rounds = 1
    instructions_template = 'bertrand/instructions.html'
    maximum_price = cu(100)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    winning_price = models.CurrencyField()


class Player(BasePlayer):
    price = models.CurrencyField(
        min=0,
        max=Constants.maximum_price,
        doc="""Price player offers to sell product for""",
        label="Please enter an amount from 0 to 100 as your price",
    )
    is_winner = models.BooleanField()


# FUNCTIONS
def set_payoffs(group: Group):
    import random

    players = group.get_players()
    group.winning_price = min([p.price for p in players])
    winners = [p for p in players if p.price == group.winning_price]
    winner = random.choice(winners)
    for p in players:
        if p == winner:
            p.is_winner = True
            p.payoff = p.price
        else:
            p.is_winner = False
            p.payoff = cu(0)



# PAGES
class Introduction(Page):
    pass


class Decide(Page):
    form_model = 'player'
    form_fields = ['price']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class Results(Page):
    pass


page_sequence = [Introduction, Decide, ResultsWaitPage, Results]

bertrand/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}
    <table class="table">
        <tr>
            <th>Your price</th>
            <td>{{ player.price }}</td>
        </tr>
        <tr>
            <th>Lowest price</th>
            <td>{{ group.winning_price }}</td>
        </tr>
        <tr>
            <th>Was your product sold?</th>
            <td>{{ if player.is_winner }} Yes {{ else }} No {{ endif }}</td>
        </tr>
        <tr>
            <th>Your payoff</th>
            <td>{{ player.payoff }}</td>
        </tr>
    </table>



    {{ include Constants.instructions_template }}

{{ endblock }}

bertrand/Decide.html

From otree-demo


{{ block title }}Set Your Price{{ endblock }}
{{ block content }}

    {{ formfields }}


    <p>{{ next_button }}</p>

    {{ include Constants.instructions_template }}

{{ endblock }}

bertrand/instructions.html

From otree-demo


<div class="instructions well well-lg" style="">

    <h3>
        Instructions
    </h3>
    <p>
        You have been randomly and anonymously paired with another participant.
        Each of you will represent a firm. Each firm manufactures one unit of
        the same product at no cost.
    </p>
    <p>
        Each of you privately sets your price, anything from 0 to {{ Constants.maximum_price }}.
        The buyer in the market will always buy one unit of the product at the
        lower price. In case of a tie, the buyer will buy from one of you at
        random. Your profit is your price if your product is sold and zero
        otherwise.
    </p>
</div>

bertrand/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}
    {{ include Constants.instructions_template }}
    {{ next_button }}
{{ endblock }}

volunteer_dilemma/Decision.html

From otree-demo


{{ block title }}Your Choice{{ endblock }}
{{ block content }}

    {{ formfields }}
    <p>{{ next_button }}</p>

    {{ include Constants.instructions_template }}

{{ endblock }}

volunteer_dilemma/__init__.py

From otree-demo


from otree.api import *
from shared_out import set_players_per_group


doc = """
Each player decides if to free ride or to volunteer from which all will
benefit.
See: Diekmann, A. (1985). Volunteer's dilemma. Journal of Conflict
Resolution, 605-610.
"""


class Constants(BaseConstants):
    name_in_url = 'volunteer_dilemma'
    players_per_group = None
    num_rounds = 1
    instructions_template = 'volunteer_dilemma/instructions.html'
    # """Payoff for each player if at least one volunteers"""
    general_benefit = cu(100)
    # """Cost incurred by volunteering player"""
    volunteer_cost = cu(40)


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    set_players_per_group(subsession)


class Group(BaseGroup):
    num_volunteers = models.IntegerField()


class Player(BasePlayer):
    volunteer = models.BooleanField(
        label='Do you wish to volunteer?', doc="""Whether player volunteers"""
    )


# FUNCTIONS
def set_payoffs(group: Group):
    players = group.get_players()
    group.num_volunteers = sum([p.volunteer for p in players])
    if group.num_volunteers > 0:
        baseline_amount = Constants.general_benefit
    else:
        baseline_amount = cu(0)
    for p in players:
        p.payoff = baseline_amount
        if p.volunteer:
            p.payoff -= Constants.volunteer_cost


# PAGES
class Introduction(Page):
    pass


class Decision(Page):
    form_model = 'player'
    form_fields = ['volunteer']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class Results(Page):
    pass


page_sequence = [Introduction, Decision, ResultsWaitPage, Results]

volunteer_dilemma/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}
    <p>
        {{ if player.volunteer }}
            You volunteered. As a result, your payoff is
            <strong>{{ player.payoff }}</strong>.
        {{ elif group.num_volunteers > 0 }}
            You did not volunteer but some did. As a result, your payoff is
            <strong>{{ player.payoff }}</strong>.
        {{ else }}
            You did not volunteer and no one did. As a result, your payoff is
            <strong>{{ player.payoff }}</strong>.
        {{ endif }}
    </p>

    <p></p>

    {{ include Constants.instructions_template }}

{{ endblock }}

volunteer_dilemma/instructions.html

From otree-demo


<div class="card bg-light m-3">
    <div class="card-body">

    <h3>
        Instructions
    </h3>

    <p>
        You will be grouped randomly and anonymously in a group of
        {{ session.config.players_per_group }}
        participants.
    </p>
    <p>
        Each of you decides independently and simultaneously whether you will
        volunteer or not. If at least one of you volunteers, everyone will get
        {{ Constants.general_benefit }}. However, the volunteer(s) will
        pay {{ Constants.volunteer_cost }}. If no one
        volunteers, everyone receives nothing.
    </p>
    </div>
</div>

volunteer_dilemma/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}
    {{ include Constants.instructions_template }}
    {{ next_button }}
{{ endblock }}

guess_two_thirds/Guess.html

From otree-demo


{{ block title }}Your Guess{{ endblock }}
{{ block content }}

    {{ if player.round_number > 1 }}
        <p>
            Here were the two-thirds-average values in previous rounds:
            {{ two_thirds_avg_history }}
        </p>
    {{ endif }}

    {{ formfields }}
    {{ next_button }}

    {{ include Constants.instructions_template }}

{{ endblock }}

guess_two_thirds/__init__.py

From otree-demo


from otree.api import *
from shared_out import set_players_per_group


doc = """
a.k.a. Keynesian beauty contest.
Players all guess a number; whoever guesses closest to
2/3 of the average wins.
See https://en.wikipedia.org/wiki/Guess_2/3_of_the_average
"""


class Constants(BaseConstants):
    players_per_group = 3
    num_rounds = 3
    name_in_url = 'guess_two_thirds'
    jackpot = Currency(100)
    guess_max = 100
    instructions_template = 'guess_two_thirds/instructions.html'


class Subsession(BaseSubsession):
    pass

def creating_session(subsession: Subsession):
    set_players_per_group(subsession)


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


class Player(BasePlayer):
    guess = models.IntegerField(
        min=0, max=Constants.guess_max, label="Please pick a number from 0 to 100:"
    )
    is_winner = models.BooleanField(initial=False)


# FUNCTIONS
def set_payoffs(group: Group):
    players = group.get_players()
    guesses = [p.guess for p in players]
    two_thirds_avg = (2 / 3) * sum(guesses) / len(players)
    group.two_thirds_avg = round(two_thirds_avg, 2)
    group.best_guess = min(guesses, key=lambda guess: abs(guess - group.two_thirds_avg))
    winners = [p for p in players if p.guess == group.best_guess]
    group.num_winners = len(winners)
    for p in winners:
        p.is_winner = True
        p.payoff = Constants.jackpot / group.num_winners


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


class Introduction(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == 1


class Guess(Page):
    form_model = 'player'
    form_fields = ['guess']

    @staticmethod
    def vars_for_template(player: Player):
        group = player.group

        return dict(two_thirds_avg_history=two_thirds_avg_history(group))


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        group = player.group

        sorted_guesses = sorted(p.guess for p in group.get_players())
        return dict(sorted_guesses=sorted_guesses)


page_sequence = [Introduction, Guess, ResultsWaitPage, Results]

guess_two_thirds/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}

    <p>Here were the numbers guessed:</p>

    <p>
        {{ sorted_guesses }}
    </p>

    <p>
        Two-thirds of the average of these numbers is {{ group.two_thirds_avg }};
        the closest guess was {{ group.best_guess }}.
    </p>

    <p>Your guess was {{ player.guess }}.</p>

    <p>
        {{ if player.is_winner }}
            {{ if group.num_winners > 1 }}
                Therefore, you are one of the {{ group.num_winners }} winners
                who tied for the best guess.
            {{ else }}
                Therefore, you win!
            {{ endif }}
        {{ else }}
            Therefore, you did not win.
        {{ endif }}
    Your payoff is {{ player.payoff }}.
    </p>



    {{ include Constants.instructions_template }}

{{ endblock }}

guess_two_thirds/instructions.html

From otree-demo


<div class="card bg-light m-3">
    <div class="card-body">

    <h3>
        Instructions
    </h3>

    <p>
        You are in a group of {{ session.config.players_per_group }} people.
        Each of you will be asked to choose a
        number between 0 and {{ Constants.guess_max }}.
        The winner will be the participant whose
        number is closest to 2/3 of the
        average of all chosen numbers.
    </p>

    <p>
        The winner will receive {{ Constants.jackpot }}.
        In case of a tie, the {{ Constants.jackpot }}
        will be equally divided among winners.
    </p>

    <p>This game will be played for {{ Constants.num_rounds }} rounds.</p>

</div>
</div>

guess_two_thirds/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}

    {{ include Constants.instructions_template }}

    {{ next_button }}

{{ endblock }}

bargaining/Request.html

From otree-demo


{{ block title }}Request{{ endblock }}
{{ block content }}

    <p>How much will you demand for yourself?</p>

    {{ formfields }}

    <p>{{ next_button }}</p>

    {{ include Constants.instructions_template }}
    
{{ endblock }}

bargaining/__init__.py

From otree-demo


from otree.api import *

doc = """
This bargaining game involves 2 players. Each demands for a portion of some
available amount. If the sum of demands is no larger than the available
amount, both players get demanded portions. Otherwise, both get nothing.
"""


class Constants(BaseConstants):
    name_in_url = 'bargaining'
    players_per_group = 2
    num_rounds = 1
    instructions_template = 'bargaining/instructions.html'
    amount_shared = cu(100)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    total_requests = models.CurrencyField()


class Player(BasePlayer):
    request = models.CurrencyField(
        doc="""
        Amount requested by this player.
        """,
        min=0,
        max=Constants.amount_shared,
        label="Please enter an amount from 0 to 100",
    )


# FUNCTIONS
def set_payoffs(group: Group):
    players = group.get_players()
    group.total_requests = sum([p.request for p in players])
    if group.total_requests <= Constants.amount_shared:
        for p in players:
            p.payoff = p.request
    else:
        for p in players:
            p.payoff = cu(0)


def other_player(player: Player):
    return player.get_others_in_group()[0]


# PAGES
class Introduction(Page):
    pass


class Request(Page):
    form_model = 'player'
    form_fields = ['request']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(other_player_request=other_player(player).request)


page_sequence = [Introduction, Request, ResultsWaitPage, Results]

bargaining/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}
    <table class=table style="width: auto">
        <tr>
            <th>You demanded</th>
            <td>{{ player.request }}</td>
        </tr>
        <tr>
            <th>The other participant demanded</th>
            <td>{{ other_player_request }}</td>
        </tr>
        <tr>
            <th>Sum of your demands</th>
            <td>{{ group.total_requests }}</td>
        </tr>
        <tr>
            <th>Thus you earn</th>
            <td>{{ player.payoff }}</td>
        </tr>
    </table>

    {{ include Constants.instructions_template }}

{{ endblock }}

bargaining/instructions.html

From otree-demo


<div class="card bg-light m-3">

    <div class="card-body">
        <h3>
            Instructions
        </h3>

        <p>
            You have been randomly and anonymously paired with another participant.
            There is {{ Constants.amount_shared }} for you to divide.
            Both of you have to simultaneously and independently demand a portion
            of the {{ Constants.amount_shared }} for yourselves. If the sum of your
            demands is smaller or equal to {{ Constants.amount_shared }}, both of
            you get what you demanded. If the sum of your demands is larger
            than {{ Constants.amount_shared }},
            both of you get nothing.
        </p>
    </div>
</div>

bargaining/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}
    {{ include Constants.instructions_template }}
    {{ next_button }}
{{ endblock }}

prisoner/Decision.html

From otree-demo


{{ block title }}Your Choice{{ endblock }}
{{ block content }}

    <div class="form-group required">
        <table class="table table-bordered text-center" style="width: auto; margin: auto">
            <tr>
                <th colspan="2" rowspan="2"></th>
                <th colspan="2">The Other Participant</th>
            </tr>
            <tr>
                <th>Cooperate</th>
                <th>Defect</th>
            </tr>
            <tr>
                <th rowspan="2"><span>You</span></th>
                <td><button name="decision" value="Cooperate" class="btn btn-primary btn-large">I will cooperate</button></td>
                <td>{{Constants.both_cooperate_payoff}}, {{Constants.both_cooperate_payoff}}</td>
                <td>{{ Constants.betrayed_payoff }}, {{Constants.betray_payoff}}</td>
            </tr>
            <tr>
                <td><button name="decision" value="Defect" class="btn btn-primary btn-large">I will defect</button></td>
                <td>{{Constants.betray_payoff}}, {{ Constants.betrayed_payoff }}</td>
                <td>{{Constants.both_defect_payoff}}, {{Constants.both_defect_payoff}}</td>
            </tr>
        </table>
    </div>

    <p>Here you can chat with the other participant.</p>

    {{ chat }}


    {{ include Constants.instructions_template }}

{{ endblock }}

prisoner/__init__.py

From otree-demo


from otree.api import *

doc = """
This is a one-shot "Prisoner's Dilemma". Two players are asked separately
whether they want to cooperate or defect. Their choices directly determine the
payoffs.
"""


class Constants(BaseConstants):
    name_in_url = 'prisoner'
    players_per_group = 2
    num_rounds = 1
    instructions_template = 'prisoner/instructions.html'
    # payoff if 1 player defects and the other cooperates""",
    betray_payoff = cu(300)
    betrayed_payoff = cu(0)
    # payoff if both players cooperate or both defect
    both_cooperate_payoff = cu(200)
    both_defect_payoff = cu(100)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    decision = models.StringField(
        choices=[['Cooperate', 'Cooperate'], ['Defect', 'Defect']],
        doc="""This player's decision""",
        widget=widgets.RadioSelect,
    )


# FUNCTIONS
def set_payoffs(group: Group):
    for p in group.get_players():
        set_payoff(p)


def other_player(player: Player):
    return player.get_others_in_group()[0]


def set_payoff(player: Player):
    payoff_matrix = dict(
        Cooperate=dict(
            Cooperate=Constants.both_cooperate_payoff, Defect=Constants.betrayed_payoff
        ),
        Defect=dict(
            Cooperate=Constants.betray_payoff, Defect=Constants.both_defect_payoff
        ),
    )
    player.payoff = payoff_matrix[player.decision][other_player(player).decision]


# PAGES
class Introduction(Page):
    timeout_seconds = 100


class Decision(Page):
    form_model = 'player'
    form_fields = ['decision']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        me = player
        opponent = other_player(me)
        return dict(
            my_decision=me.decision,
            opponent_decision=opponent.decision,
            same_choice=me.decision == opponent.decision,
        )


page_sequence = [Introduction, Decision, ResultsWaitPage, Results]

prisoner/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}

    <p>
        {{ if same_choice }}
            Both of you chose to {{ my_decision }}.
        {{ else }}
            You chose to {{ my_decision }} and the other participant chose to {{ opponent_decision }}.
        {{ endif }}
    </p>

    <p>
        As a result, you earned {{ player.payoff }}.
    </p>



    {{ include Constants.instructions_template }}

{{ endblock }}

prisoner/instructions.html

From otree-demo


<div class="card bg-light m-3">
    <div class="card-body">

    <h3>
        Instructions
    </h3>

    <link rel="stylesheet" type="text/css"
          href="{{ static 'global/matrix.css' }}"/>

    <p>
        In this study, you will be randomly and anonymously paired with another
        participant.
        Each of you simultaneously and privately chooses whether you want to
        cooperate or defect.
        Your payoffs will be determined by the choices of both as below:
    </p>
    <p><i>In each cell, the amount to the left is the payoff for
        you and to the right for the other participant.</i></p>

    <table class='table table-bordered text-center'
           style='width: auto; margin: auto'>
        <tr>
            <th colspan=2 rowspan=2></th>
            <th colspan=2>The Other Participant</th>
        </tr>
        <tr>
            <th>Cooperate</th>
            <th>Defect</th>
        </tr>
        <tr>
            <th rowspan=2><span style="transform: rotate(-90deg);">You</span></th>
            <th>Cooperate</th>
            <td>{{ Constants.both_cooperate_payoff }}, {{ Constants.both_cooperate_payoff }}</td>
            <td>0, {{ Constants.betray_payoff }}</td>
        </tr>
        <tr>
            <th>Defect</th>
            <td>{{ Constants.betray_payoff }}, 0</td>
            <td>{{ Constants.both_defect_payoff }}, {{ Constants.both_defect_payoff }}</td>
        </tr>
    </table>

</div>
</div>

prisoner/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}

    {{ include Constants.instructions_template }}

    {{ next_button }}

{{ endblock }}

nim/__init__.py

From otree-demo


from otree.api import *

doc = """
Game of Nim. Players take turns adding a number. First to 15 wins.
"""


class Constants(BaseConstants):
    name_in_url = 'nim'
    players_per_group = None
    num_rounds = 1
    target = 15


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    current_number = models.IntegerField(initial=1)
    whose_turn = models.IntegerField(initial=1)
    winner_id = models.IntegerField()
    game_over = models.BooleanField(initial=False)


class Player(BasePlayer):
    is_winner = models.BooleanField(initial=False)


# PAGES
class Game(Page):
    @staticmethod
    def js_vars(player: Player):
        return dict(my_id=player.id_in_group)

    @staticmethod
    def live_method(player: Player, number):
        group = player.group
        my_id = player.id_in_group
        other_id = 3 - my_id
        if (
            number
            and number in [1, 2, 3]
            and group.whose_turn == my_id
            # if you're at 14, you can't choose 3.
            and group.current_number + number <= Constants.target
        ):
            group.current_number += number
            news = dict(id_in_group=my_id, number=number)
            if group.current_number == Constants.target:
                group.winner_id = player.id_in_group
                group.game_over = True
            else:
                group.whose_turn = other_id
        else:
            news = None

        return {
            0: dict(
                game_over=group.game_over,
                current_number=group.current_number,
                whose_turn=group.whose_turn,
                news=news,
            )
        }


class ResultsWaitPage(WaitPage):
    @staticmethod
    def after_all_players_arrive(group: Group):
        winner = group.get_player_by_id(group.winner_id)
        winner.is_winner = True


class Results(Page):
    pass


page_sequence = [Game, ResultsWaitPage, Results]

nim/Results.html

From otree-demo


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    {{ if player.is_winner }}
        You won!
    {{ else }}
        you lost :(
    {{ endif }}

{{ endblock }}

nim/Game.html

From otree-demo


{{ block title }}
    Nim
{{ endblock }}
{{ block content }}

    <p id="news"></p>
    <p>Current number:</p>
    <div id="current_number"></div>
    <p>How much to add:</p>
    <button type="button" class="btn-step" onclick="liveSend(1)">1</button>
    <button type="button" class="btn-step" onclick="liveSend(2)">2</button>
    <button type="button" class="btn-step" onclick="liveSend(3)">3</button>

    <script>

        let newsEle = document.getElementById('news');
        let current_number = document.getElementById('current_number');

        function liveRecv(data) {
            if (data.game_over) {
                document.getElementById('form').submit();
            }
            let lastActor;
            let btnDisabledStatus;
            if (data.whose_turn === js_vars.my_id) {
                lastActor = 'The other player';
                btnDisabledStatus = ''
            } else {
                lastActor = 'You';
                btnDisabledStatus = 'disabled'
            }
            for (let btn of document.getElementsByClassName('btn-step')) {
                btn.disabled = btnDisabledStatus;
            }
            current_number.innerText = data.current_number;
            let news = data.news;
            if (news) {
                newsEle.innerText = `${lastActor} added ${news.number}`;
            }
        }

        window.addEventListener('DOMContentLoaded', (event) => {
            liveSend(null);
        });
    </script>

    <p>
        This is a game of nim. You and the other take turns adding to a number.
        The game starts at 1, and on each turn you can add 1, 2, or 3.
        The player who reaches {{ Constants.target }} wins the game.
    </p>

{{ endblock }}

bigfive/Survey.html

From otree-demo


{{ block title }}
Personality test
{{ endblock }}

{{ block content }}

<p>
Please evaluate the following statements, to complete the sentence:
    <b>"I see myself as someone who...</b>".
</p>


<table class="table table-striped">
    <tr>
        <th></th>
        <th>Disagree strongly</th>
        <th>Disagree a little</th>
        <th>Neither agree nor disagree</th>
        <th>Agree a little</th>
        <th>Agree strongly</th>
    </tr>
    {{ for field in form }}
    <tr>
        <th>{{ field.label }}</th>
        {{ for option in field }}
        <td>{{ option }}</td>
        {{ endfor }}
    </tr>
    {{ endfor }}
</table>

{{ next_button }}

{{ endblock }}

bigfive/__init__.py

From otree-demo


from otree.api import *

doc = """Big 5 personality test"""


class Constants(BaseConstants):
    name_in_url = 'bigfive'
    players_per_group = None
    num_rounds = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


def make_q(label):
    return models.IntegerField(label=label, choices=[1, 2, 3, 4, 5], widget=widgets.RadioSelect)


class Player(BasePlayer):
    q1 = make_q('is reserved')
    q2 = make_q('is generally trusting')
    q3 = make_q('tends to be lazy')
    q4 = make_q('is relaxed, handles stress well')
    q5 = make_q('has few artistic interests')
    q6 = make_q('is outgoing, sociable')
    q7 = make_q('tends to find fault with others')
    q8 = make_q('does a thorough job')
    q9 = make_q('gets nervous easily')
    q10 = make_q('has an active imagination')

    extraversion = models.FloatField()
    agreeableness = models.FloatField()
    conscientiousness = models.FloatField()
    neuroticism = models.FloatField()
    openness = models.FloatField()


def combine_score(positive, negative):
    return 3 + (positive - negative) / 2


class Survey(Page):
    form_model = 'player'
    form_fields = ['q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10']

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        player.extraversion = combine_score(player.q6, player.q1)
        player.agreeableness = combine_score(player.q2, player.q7)
        player.conscientiousness = combine_score(player.q8, player.q3)
        player.neuroticism = combine_score(player.q9, player.q4)
        player.openness = combine_score(player.q10, player.q5)


class Results(Page):
    pass


page_sequence = [Survey, Results]

bigfive/Results.html

From otree-demo


{{ block title }}
Results
{{ endblock }}

{{ block content }}

<table class="table">
    <tr>
        <td>Extraversion</td>
        <td>{{ player.extraversion }} / 5</td>
    </tr>
    <tr>
        <td>Agreeableness</td>
        <td>{{ player.agreeableness }} / 5</td>
    </tr>
    <tr>
        <td>Conscientiousness</td>
        <td>{{ player.conscientiousness }} / 5</td>
    </tr>
    <tr>
        <td>Neuroticism</td>
        <td>{{ player.neuroticism }} / 5</td>
    </tr>
    <tr>
        <td>Openness</td>
        <td>{{ player.openness }} / 5</td>
    </tr>
</table>

{{ endblock }}

public_goods/__init__.py

From otree-demo


from otree.api import *

from shared_out import set_players_per_group, get_or_none

doc = """
This is a one-period public goods game with 3 players.
"""


class Constants(BaseConstants):
    name_in_url = 'public_goods'
    players_per_group = None
    num_rounds = 1
    instructions_template = 'public_goods/instructions.html'
    # """Amount allocated to each player"""


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    set_players_per_group(subsession)


def vars_for_admin_report(subsession: Subsession):
    contributions = [
        p.contribution
        for p in subsession.get_players()
        if get_or_none(p, 'contribution') != None
    ]
    if contributions:
        return dict(
            avg_contribution=sum(contributions) / len(contributions),
            min_contribution=min(contributions),
            max_contribution=max(contributions),
        )
    else:
        return dict(
            avg_contribution='(no data)',
            min_contribution='(no data)',
            max_contribution='(no data)',
        )


class Group(BaseGroup):
    total_contribution = models.CurrencyField()
    individual_share = models.CurrencyField()


class Player(BasePlayer):
    contribution = models.CurrencyField(
        min=0,
        doc="""The amount contributed by the player""",
        label="How much will you contribute to the project (from 0 to 100)?",
    )


def contribution_max(player: Player):
    session = player.session
    config = session.config

    return config['endowment']


def set_payoffs(group: Group):
    session = group.session
    config = session.config

    group.total_contribution = sum([p.contribution for p in group.get_players()])
    group.individual_share = (
        group.total_contribution * config['multiplier'] / config['players_per_group']
    )
    for p in group.get_players():
        p.payoff = (config['endowment'] - p.contribution) + group.individual_share


class Introduction(Page):
    pass


class Contribute(Page):

    form_model = 'player'
    form_fields = ['contribution']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs
    body_text = "Waiting for other participants to contribute."


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        group = player.group
        session = group.session

        return dict(
            total_earnings=group.total_contribution * session.config['multiplier']
        )


page_sequence = [Introduction, Contribute, ResultsWaitPage, Results]

public_goods/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}
  

    <table class="table-condensed" style="width:500px; margin-top:20px;">
        <tr><td>You contributed:</td><td>{{ player.contribution }}</td></tr>
        <tr><td>Other participants contributed:</td><td></td></tr>
        {{ for p in player.get_others_in_group }}
            <tr><td></td><td>{{ p.contribution }}</td></tr>
        {{ endfor }}

        <tr><td>Total contribution:</td><td>{{ group.total_contribution }}</td></tr>

        <tr><td colspan="2"><hr/></td></tr>

        <tr><td>Total earnings from the project:</td><td>{{ total_earnings }}</td></tr>
        <tr><td>Your earnings from the project:</td><td>{{ group.individual_share }}</td></tr>

        <tr><td colspan="2"><hr/></td></tr>

        <tr><td>Thus in total you earned:</td><td>{{ player.payoff }}</td></tr>

    </table>
    <p></p>


    {{ include Constants.instructions_template }}


{{ endblock }}

public_goods/admin_report.html

From otree-demo


<table class="table">
    <tr>
        <th>Average contribution</th>
        <td>{{ avg_contribution }}</td>
    </tr>
    <tr>
        <th>Min contribution</th>
        <td>{{ min_contribution }}</td>
    </tr>
    <tr>
        <th>Max contribution</th>
        <td>{{ max_contribution }}</td>
    </tr>
</table>

public_goods/Contribute.html

From otree-demo


{{ block title }}Contribute{{ endblock }}
{{ block content }}

    {{ formfields }}

    {{ next_button }}

    {{ include Constants.instructions_template }}

{{ endblock }}

public_goods/instructions.html

From otree-demo


<div class="card bg-light m-3">
    <div class="card-body">
    <h3>
        Instructions
    </h3>

    <p>
        In this study, you will be in a randomly formed group
        of {{ session.config.players_per_group }} participants. Each participant in
        the group is given {{ session.config.endowment }}. The group
        has the opportunity to undertake a joint project. Each participant in
        the group decides how much she or he is going to contribute to
        the project. Contribution could be any integer from 0
        to {{ session.config.endowment }}.
    </p>
    <p>
        The earnings from the project are calculated as follows: The
        contributions of all {{ session.config.players_per_group }} participants are
        added up, the total contribution
        is multiplied by a factor of {{ session.config.multiplier }}, and the
        resulting amount is the total earnings from the project, which is
        evenly split among all {{ session.config.players_per_group }}
        participants. Your payoff equals your earnings from the project, plus
        the amount you did not contribute.
    </p>
    </div>
</div>

public_goods/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}

    {{ include Constants.instructions_template }}

    {{ next_button }}

{{ endblock }}

go_no_go/Task.html

From otree-demo


{{ block title }}
{{ endblock }}

{{ block content }}

  {{ for path in image_paths }}
      <img class="img-stimulus" src="{{ static path }}" style="display: none"></img>
  {{ endfor }}

  <div id="feedback" style="font-size: 100px"></div>
  <div id="loading">Get ready...</div>

  <script>
      let images = document.getElementsByClassName('img-stimulus');
      let feedback = document.getElementById('feedback');
      let displayed_timestamp;
      let loading = document.getElementById('loading');
      let image_id_global = null;

      // time before we unhideDiv the first image (give time to get hands ready on keyboard)
      const INITIAL_DELAY = 2000;

      // time in between showing showing ✓ or ✗, and showing the next image
      const IN_BETWEEN_DELAY = 1000;

      function liveRecv(data) {
          for (let image of images) {
              image.style.display = 'none';
          }
          if (data.feedback) {
            feedback.innerHTML = data.feedback;
            feedback.style.display = 'block';
          }
          if (data.is_finished) {
              document.getElementById('form').submit();
          } else {
            image_id_global = data.image_id;
            setTimeout(() => loadImage(data.image_id), IN_BETWEEN_DELAY);
          }
      }

      function loadImage(image_id) {
          feedback.style.display = 'none';
          images[image_id].style.display = 'block';
          displayed_timestamp = performance.now();
          setTimeout(() => {
            liveSend({'image_id': image_id, 'pressed': false})
          }, 3000);
      }

      document.addEventListener("keypress", function (event) {
        if (event.key === '1') {
          liveSend({
              'image_id': image_id_global,
              'pressed': true,
              'displayed_timestamp': displayed_timestamp,
              'answered_timestamp': performance.now()
          })
        }
      });

      document.addEventListener('DOMContentLoaded', function (event) {
          setTimeout(function () {
              loading.style.display = 'none';
              liveSend({});
          }, INITIAL_DELAY);
      });
  </script>


{{ endblock }}

go_no_go/__init__.py

From otree-demo


from otree.api import *

doc = """
"""


class Constants(BaseConstants):
    name_in_url = 'go_no_go'
    players_per_group = None
    num_rounds = 1
    red_images = [0, 4, 8, 17]
    num_images = 10  # actually there are 20 images but we just show 10 for brevity


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    for p in subsession.get_players():
        participant = p.participant
        image_ids = generate_ordering()
        for stim in image_ids:
            is_red = stim in Constants.red_images
            Trial.create(player=p, image_id=stim, is_red=is_red)

        participant.reaction_times = []


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    num_completed = models.IntegerField(initial=0)
    num_errors = models.IntegerField(initial=0)
    avg_reaction_ms = models.FloatField()


def get_current_trial(player: Player):
    return Trial.filter(player=player, is_error=None)[0]


def is_finished(player: Player):
    return player.num_completed == Constants.num_images


class Trial(ExtraModel):
    player = models.Link(Player)
    reaction_ms = models.IntegerField()
    image_id = models.IntegerField()
    is_red = models.BooleanField()
    is_error = models.BooleanField()
    pressed = models.BooleanField()


def generate_ordering():
    import random

    numbers = list(range(Constants.num_images))
    random.shuffle(numbers)
    return numbers


# PAGES
class Introduction(Page):
    pass


class Task(Page):
    @staticmethod
    def live_method(player: Player, data):
        participant = player.participant
        if 'pressed' in data:
            trial = get_current_trial(player)
            # this is necessary because the timeout will cause duplicates to be sent
            if data['image_id'] != trial.image_id:
                return
            trial.is_error = trial.is_red == data['pressed']
            if trial.is_error:
                feedback = '✗'
                player.num_errors += 1
            else:
                feedback = '✓'
                if not trial.is_red:
                    trial.reaction_ms = data['answered_timestamp'] - data['displayed_timestamp']
                    participant.reaction_times.append(trial.reaction_ms)
            player.num_completed += 1
        else:
            feedback = ''

        if is_finished(player):
            return {player.id_in_group: dict(is_finished=True)}

        trial = get_current_trial(player)
        return {
            player.id_in_group: dict(image_id=trial.image_id, feedback=feedback, trialId=trial.id)
        }

    @staticmethod
    def vars_for_template(player: Player):
        image_paths = [
            'go_no_go/{}.png'.format(image_id) for image_id in range(Constants.num_images)
        ]

        return dict(image_paths=image_paths)

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        participant = player.participant
        import statistics

        # if the participant never pressed, this list will be empty
        if participant.reaction_times:
            avg_reaction = statistics.mean(participant.reaction_times)
            player.avg_reaction_ms = int(avg_reaction)


def get_or_none(obj, fieldname):
    """oTree will raise an error for null fields"""
    try:
        return getattr(obj, fieldname)
    except TypeError:
        return None


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(avg_reaction_ms=get_or_none(player, 'avg_reaction_ms'))


page_sequence = [Introduction, Task, Results]

go_no_go/Results.html

From otree-demo


{{ block title }}
  Results
{{ endblock }}

{{ block content }}

You made {{ player.num_errors }} errors.
{{ if avg_reaction_ms }}
  Your average reaction time was {{ avg_reaction_ms }} ms.
{{ endif }}
{{ endblock }}

go_no_go/Introduction.html

From otree-demo


{{ block title }}
  Instructions
{{ endblock }}

{{ block content }}

  <p>
    You will be shown a series of images in quick succession.
    If the image is green, press the '1' key as quickly as possible.
    If the image is red, don't press anything. Instead, wait for the next image.
  </p>

  <p>When you are ready to start, press the button below.</p>
  <button class="btn btn-primary">Start</button>
{{ endblock }}

traveler_dilemma/__init__.py

From otree-demo


from otree.api import *

doc = """
Kaushik Basu's famous traveler's dilemma (
<a href="http://www.jstor.org/stable/2117865" target="_blank">
    AER 1994
</a>).
It is a 2-player game. The game is framed as a traveler's dilemma and intended
for classroom/teaching use.
"""


class Constants(BaseConstants):
    name_in_url = 'traveler_dilemma'
    players_per_group = 2
    num_rounds = 1
    instructions_template = 'traveler_dilemma/instructions.html'
    # Player's reward for the lowest claim"""
    adjustment_abs = cu(2)
    # Player's deduction for the higher claim
    # The maximum claim to be requested
    max_amount = cu(100)
    # The minimum claim to be requested
    min_amount = cu(2)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    lower_claim = models.CurrencyField()


class Player(BasePlayer):
    claim = models.CurrencyField(
        min=Constants.min_amount,
        max=Constants.max_amount,
        label='How much will you claim for your antique?',
        doc="""
        Each player's claim
        """,
    )
    adjustment = models.CurrencyField()


# FUNCTIONS
def set_payoffs(group: Group):
    p1, p2 = group.get_players()
    if p1.claim == p2.claim:
        group.lower_claim = p1.claim
        for p in [p1, p2]:
            p.payoff = group.lower_claim
            p.adjustment = cu(0)
    else:
        if p1.claim < p2.claim:
            winner = p1
            loser = p2
        else:
            winner = p2
            loser = p1
        group.lower_claim = winner.claim
        winner.adjustment = Constants.adjustment_abs
        loser.adjustment = -Constants.adjustment_abs
        winner.payoff = group.lower_claim + winner.adjustment
        loser.payoff = group.lower_claim + loser.adjustment


def other_player(player: Player):
    return player.get_others_in_group()[0]


# PAGES
class Introduction(Page):
    pass


class Claim(Page):
    form_model = 'player'
    form_fields = ['claim']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(other_player_claim=other_player(player).claim)


page_sequence = [Introduction, Claim, ResultsWaitPage, Results]

traveler_dilemma/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}
    <table class=table style='width: auto'>
        <tr>
            <td>You claimed</td>
            <td>{{ player.claim }}</td>
        </tr>
        <tr>
            <td>The other traveler claimed</td>
            <td>{{ other_player_claim }}</td>
        </tr>
        <tr>
            <td>Winning claim (i.e. lower claim)</td>
            <td>{{ group.lower_claim }}</td>
        </tr>
        <tr>
            <td>Your adjustment</td>
            <td>{{ player.adjustment }}</td>
        </tr>
        <tr>
            <td>Thus you receive</td>
            <td>{{ player.payoff }}</td>
        </tr>

    </table>

    <p></p>
    {{ include Constants.instructions_template }}
{{ endblock }}

traveler_dilemma/instructions.html

From otree-demo


<div class="card bg-light m-3">
    <div class="card-body">

    <h3>
        Instructions
    </h3>

    <p>
        You have been randomly and anonymously paired with another participant.
        Now please image the following scenario.
    </p>
    <p>
        You and another traveler (the other participant) just returned from a
        remote island where both of you bought the same antiques.
        Unfortunately, you discovered that your airline managed to smash the
        antiques, as they always do. The airline manager assures you of
        adequate compensation. Without knowing the true value of your antiques,
        he offers you the following scheme. Both of you simultaneously and
        independently make a claim for the value of your own antique (ranging
        from {{ Constants.min_amount }} to {{ Constants.max_amount }}):
    </p>
    <ul>
        <li>
            If both claim the same amount, then this amount will be paid to
            both.
        </li>
        <li>
            If you claim different amounts, then the lower amount will be paid
            to both. Additionally, the one with lower claim will receive a
            reward of {{ Constants.adjustment_abs }}; the one with higher claim will
            receive a penalty of {{ Constants.adjustment_abs }}.
        </li>
    </ul>
</div>
</div>

traveler_dilemma/Claim.html

From otree-demo


{{ block title }}Claim{{ endblock }}
{{ block content }}

    {{ formfields }}
    {{ next_button }}
    
    {{ include Constants.instructions_template }}

{{ endblock }}

traveler_dilemma/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}
    {{ include Constants.instructions_template }}
    {{ next_button }}
{{ endblock }}

stroop/Task.html

From otree-demo


{{ block title }}
{{ endblock }}

{{ block content }}

  {{ for path in image_paths }}
    <img class="stroopimage" src="{{ static path }}" style="display: none">
  {{ endfor }}

  <div id="lastresult" style="font-size: 100px"></div>
  <div id="loading">Get ready...</div>

  <script>
      let image_id;
      let images = document.getElementsByClassName('stroopimage');
      let lastresult = document.getElementById('lastresult');
      let displayed_timestamp;
      let loading = document.getElementById('loading');

      // time before we unhideDiv the first image (give time to get hands ready on keyboard)
      const INITIAL_DELAY = 1000;

      // time in between showing showing ✓ or ✗, and showing the next image
      const IN_BETWEEN_DELAY = 1000;

      function liveRecv(data) {
          console.log('liveRecv', data)
          for (let image of images) {
              image.style.display = 'none';
          }
          if (data.feedback)
            lastresult.innerHTML = data.feedback;
          lastresult.style.display = 'block';
          if (data.is_finished) {
              document.getElementById('form').submit();
          } else {
              image_id = data.image_id;
              setTimeout(loadImage, IN_BETWEEN_DELAY);
          }
      }

      function loadImage() {
          lastresult.style.display = 'none';
          images[image_id].style.display = 'block';
          displayed_timestamp = performance.now();
      }

      document.addEventListener("keypress", function (event) {
          let color = js_vars.color_keys[event.key];
          if (color) {
              liveSend({
                  submission: color,
                  image_id: image_id,
                  displayed_timestamp: displayed_timestamp,
                  answered_timestamp: performance.now()
              })
          }
      });

      document.addEventListener('DOMContentLoaded', function (event) {
        setTimeout(function () {
              loading.style.display = 'none';
              liveSend({});
          }, INITIAL_DELAY);
      });
  </script>


{{ endblock }}

stroop/__init__.py

From otree-demo


from otree.api import *

doc = """Stroop test."""


def get_permutations():
    colors = Constants.colors

    idx = 0
    items = []
    for decoy_text in colors:
        for color in colors:
            items.append(
                dict(
                    decoy_text=decoy_text,
                    color=color,
                    image_id=idx,
                    is_congruent=decoy_text == color,
                )
            )
            idx += 1
    return items


def randomize_order():
    import random

    permutations = get_permutations()
    random.shuffle(permutations)
    return permutations


class Constants(BaseConstants):
    name_in_url = 'stroop'
    instructions_template = 'stroop/instructions.html'
    players_per_group = None
    num_rounds = 1
    colors = ['red', 'yellow', 'blue', 'green']
    color_keys = [('r', 'red'), ('y', 'yellow'), ('b', 'blue'), ('g', 'green')]
    num_trials = len(colors) * len(colors)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    num_completed = models.IntegerField(initial=0)
    num_correct = models.IntegerField(initial=0)
    avg_congruent = models.FloatField()
    avg_incongruent = models.FloatField()
    incongruent_minus_congruent = models.FloatField()


class Trial(ExtraModel):
    player = models.Link(Player)
    image_id = models.IntegerField()
    decoy_text = models.StringField()
    color = models.StringField()
    is_correct = models.BooleanField()
    is_congruent = models.BooleanField()
    reaction_ms = models.IntegerField()


def get_current_trial(player: Player):
    return Trial.filter(player=player, is_correct=None)[0]


def is_finished(player: Player):
    return player.num_completed == Constants.num_trials


# FUNCTIONS
def creating_session(subsession: Subsession):
    for p in subsession.get_players():
        for permutation in randomize_order():
            Trial.create(player=p, **permutation)


def live_method(player: Player, data):

    if data:
        if is_finished(player):
            return
        trial = get_current_trial(player)

        # guard against double-clicks
        if data['image_id'] != trial.image_id:
            return

        displayed_timestamp = data['displayed_timestamp']
        answered_timestamp = data['answered_timestamp']
        trial.submission = data['submission']

        trial.is_correct = trial.submission == trial.color
        trial.reaction_ms = answered_timestamp - displayed_timestamp

        if trial.is_correct:
            player.num_correct += 1
            feedback = '✓'
        else:
            feedback = '✗'
        player.num_completed += 1

    else:
        feedback = ''

    if is_finished(player):
        return {player.id_in_group: dict(is_finished=True)}

    payload = dict(feedback=feedback, image_id=get_current_trial(player).image_id)
    return {player.id_in_group: payload}


# PAGES
class Introduction(Page):
    pass


class Task(Page):
    live_method = live_method

    @staticmethod
    def vars_for_template(player: Player):
        image_paths = ['stroop/{}.png'.format(i) for i in range(Constants.num_trials)]
        return dict(image_paths=image_paths)

    @staticmethod
    def js_vars(player: Player):
        return dict(color_keys=dict(Constants.color_keys))

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        from statistics import mean

        congruent_times = []
        incongruent_times = []
        for trial in Trial.filter(player=player):
            if trial.is_congruent:
                lst = congruent_times
            else:
                lst = incongruent_times
            lst.append(trial.reaction_ms)

        player.avg_congruent = int(mean(congruent_times))
        player.avg_incongruent = int(mean(incongruent_times))
        player.incongruent_minus_congruent = player.avg_incongruent - player.avg_congruent


class Results(Page):
    pass


page_sequence = [Introduction, Task, Results]

stroop/Results.html

From otree-demo


{{ block title }}
  Results
{{ endblock }}

{{ block content }}

  <table class="table">
    <tr>
      <th>Number of correct answers</th>
      <td>{{ player.num_correct }} / {{ Constants.num_trials }}</td>
    </tr>
    <tr>
      <th>Average for congruent</th>
      <td>{{ player.avg_congruent }} ms</td>
    </tr>
    <tr>
      <th>Average for incongruent</th>
      <td>{{ player.avg_incongruent }} ms</td>
    </tr>
    <tr>
      <th>Difference</th>
      <td>{{ player.incongruent_minus_congruent }} ms</td>
    </tr>
  </table>

{{ endblock }}

stroop/Introduction.html

From otree-demo


{{ block title }}
Instructions
{{ endblock }}

{{ block content }}

This is a stroop test.

<p>When you see a word, quickly enter the color of the word (not the word itself)</p>
<p>With your keyboard, press the key representing the first letter of the color.</p>

<table class="table">
    <tr>
        <th>Color</th>
        <th>Letter</th>
    </tr>
    {{ for entry in Constants.color_keys }}
    <tr>
        <td>{{ entry.1 }}</td>
        <td>{{ entry.0 }}</td>
    </tr>
    {{ endfor }}
</table>

<p>When you are ready to start, press the button below.</p>
<button class="btn btn-primary">Start</button>
{{ endblock }}

common_value_auction/__init__.py

From otree-demo


from otree.api import *
from shared_out import set_players_per_group



doc = """
In a common value auction game, players simultaneously bid on the item being
auctioned.<br/>
Prior to bidding, they are given an estimate of the actual value of the item.
This actual value is revealed after the bidding.<br/>
Bids are private. The player with the highest bid wins the auction, but
payoff depends on the bid amount and the actual value.<br/>
"""


class Constants(BaseConstants):
    name_in_url = 'common_value_auction'
    players_per_group = None
    num_rounds = 1
    instructions_template = 'common_value_auction/instructions.html'
    min_allowable_bid = cu(0)
    max_allowable_bid = cu(10)
    # Error margin for the value estimates shown to the players
    estimate_error_margin = cu(1)


class Subsession(BaseSubsession):
    pass

def creating_session(subsession: Subsession):
    set_players_per_group(subsession)


class Group(BaseGroup):
    item_value = models.CurrencyField(
        doc="""Common value of the item to be auctioned, random for treatment"""
    )
    highest_bid = models.CurrencyField()


class Player(BasePlayer):
    item_value_estimate = models.CurrencyField(
        doc="""Estimate of the common value, may be different for each player"""
    )
    bid_amount = models.CurrencyField(
        min=Constants.min_allowable_bid,
        max=Constants.max_allowable_bid,
        doc="""Amount bidded by the player""",
        label="Bid amount",
    )
    is_winner = models.BooleanField(
        initial=False, doc="""Indicates whether the player is the winner"""
    )


# FUNCTIONS
def creating_session(subsession: Subsession):
    for g in subsession.get_groups():
        import random

        item_value = random.uniform(
            Constants.min_allowable_bid, Constants.max_allowable_bid
        )
        g.item_value = round(item_value, 1)


def set_winner(group: Group):
    import random

    players = group.get_players()
    group.highest_bid = max([p.bid_amount for p in players])
    players_with_highest_bid = [p for p in players if p.bid_amount == group.highest_bid]
    winner = random.choice(
        players_with_highest_bid
    )  # if tie, winner is chosen at random
    winner.is_winner = True
    for p in players:
        set_payoff(p)


def generate_value_estimate(group: Group):
    import random

    minimum = group.item_value - Constants.estimate_error_margin
    maximum = group.item_value + Constants.estimate_error_margin
    estimate = random.uniform(minimum, maximum)
    estimate = round(estimate, 1)
    if estimate < Constants.min_allowable_bid:
        estimate = Constants.min_allowable_bid
    if estimate > Constants.max_allowable_bid:
        estimate = Constants.max_allowable_bid
    return estimate


def set_payoff(player: Player):
    group = player.group

    if player.is_winner:
        player.payoff = group.item_value - player.bid_amount
        if player.payoff < 0:
            player.payoff = 0
    else:
        player.payoff = 0



# PAGES
class Introduction(Page):
    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        group = player.group

        player.item_value_estimate = generate_value_estimate(group)


class Bid(Page):
    form_model = 'player'
    form_fields = ['bid_amount']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_winner


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        group = player.group

        return dict(is_greedy=group.item_value - player.bid_amount < 0)


page_sequence = [Introduction, Bid, ResultsWaitPage, Results]

common_value_auction/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}

    <p>
        {{ if player.is_winner }}
            You won the auction!
            {{ if is_greedy }}
                However, your bid amount was higher than the actual value of the item. Your payoff is therefore zero.
            {{ elif player.payoff == 0 }}
                Your payoff, however, is zero.
            {{ endif }}
        {{ else }}
            You did not win the auction.
        {{ endif }}
    </p>

    <table class="table" style="width:400px">

        <tr>
            <th>Your bid</th>
            <th>Winning bid</th>
            <th>Actual value</th>
            <th>Your payoff</th>
        </tr>

    <tr>
        <td>{{ player.bid_amount }}</td>
        <td>{{ group.highest_bid }}</td>
        <td>{{ group.item_value }}</td>
        <td>{{ player.payoff }}</td>
    </tr>

    </table>



{{ endblock }}

common_value_auction/Bid.html

From otree-demo


{{ block title }}Bid{{ endblock }}
{{ block content }}

    <p>
        The value of the item is estimated to be {{ player.item_value_estimate }}.
        This estimate may deviate from the actual value by at most {{ Constants.estimate_error_margin }}.
    </p>

    <p>
        Please make your bid now. The amount can be between {{ Constants.min_allowable_bid }} and {{ Constants.max_allowable_bid }}, inclusive.
    </p>

    {{ formfields }}

    {{ next_button }}

    {{ include Constants.instructions_template }}

{{ endblock }}

common_value_auction/instructions.html

From otree-demo


<div class="card bg-light m-3">
<div class="card-body">
    <h3>
        Instructions
    </h3>
    <p>
        You have been randomly and anonymously grouped with other players.
        All players see these same instructions.
    </p>

    <p>
        Your task will be to bid for an item that is being auctioned.
        Prior to bidding, each player will be given an estimate of the actual
        value of the item. The estimates may be different between players.
        The actual value of the item, which is common to all players, will be
        revealed after the bidding has taken place.
    </p>

    <p>
        Based on the value estimate, each player will submit a single bid
        within a given range.
        All bids are private and submitted at the same time.
    </p>

    <p>
        The highest bidder will receive the actual value of the item as payoff
        minus their own bid amount.
        If the winner's bid amount is higher than the actual value of the item,
        the payoff will be zero.
        In the event of a tie between two or more players, the winner will be
        chosen at random. Other players will receive nothing.
    </p>
</div>
</div>

common_value_auction/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}

    {{ include Constants.instructions_template }}

    {{ next_button }}

{{ endblock }}

dollar_auction/__init__.py

From otree-demo


from otree.api import *
from shared_out import set_players_per_group


doc = """
Your app description
"""


class Constants(BaseConstants):
    name_in_url = 'dollar_auction'
    instructions_template = 'dollar_auction/instructions.html'
    players_per_group = None
    num_rounds = 1
    jackpot = 100


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    set_players_per_group(subsession)


class Group(BaseGroup):
    top_bid = models.CurrencyField(initial=0)
    second_bid = models.CurrencyField(initial=0)

    top_bidder = models.IntegerField(initial=-1)
    second_bidder = models.IntegerField(initial=-1)

    auction_timeout = models.FloatField()


def get_state(group: Group):
    return dict(
        top_bid=group.top_bid,
        top_bidder=group.top_bidder,
        second_bid=group.second_bid,
        second_bidder=group.second_bidder,
    )


class Player(BasePlayer):
    is_top_bidder = models.BooleanField(initial=False)
    is_second_bidder = models.BooleanField(initial=False)


class Intro(Page):
    pass


class WaitToStart(WaitPage):
    @staticmethod
    def after_all_players_arrive(group: Group):
        import time

        group.auction_timeout = time.time() + 60


# PAGES
class Bid(Page):
    @staticmethod
    def get_timeout_seconds(player: Player):
        import time

        group = player.group
        return group.auction_timeout - time.time()

    @staticmethod
    def js_vars(player: Player):
        return dict(my_id=player.id_in_group)

    @staticmethod
    def live_method(player: Player, bid):
        group = player.group
        my_id = player.id_in_group
        if bid:
            if bid > group.top_bid:
                group.second_bid = group.top_bid
                group.second_bidder = group.top_bidder
                group.top_bid = bid
                group.top_bidder = my_id
                return {0: dict(get_state(group), new_top_bid=True)}
        else:
            return {my_id: get_state(group)}


class ResultsWaitPage(WaitPage):
    @staticmethod
    def after_all_players_arrive(group: Group):
        if group.top_bidder > 0:
            top_bidder = group.get_player_by_id(group.top_bidder)
            top_bidder.payoff = Constants.jackpot - group.top_bid
            top_bidder.is_top_bidder = True

        if group.second_bidder > 0:
            second_bidder = group.get_player_by_id(group.second_bidder)
            second_bidder.payoff = -group.second_bid
            second_bidder.is_second_bidder = True


class Results(Page):
    pass


page_sequence = [Intro, WaitToStart, Bid, ResultsWaitPage, Results]

dollar_auction/Intro.html

From otree-demo


{{ block title }}
Intro
{{ endblock }}
{{ block content }}
{{ include Constants.instructions_template}}
{{ next_button }}
{{ endblock }}

dollar_auction/Bid.html

From otree-demo


{{ block title }}
Auction
{{ endblock }}
{{ block content }}

<p id="msg-my-status"></p>
<p id="msg-my-bid"></p>

<button type="button" id="btn-bid" onclick="sendBid(this)"></button>

<br><br>
{{ include Constants.instructions_template }}

<script>
    let bidBtn = document.getElementById('btn-bid');
    let msgMyStatus = document.getElementById('msg-my-status');
    let msgMyBid = document.getElementById('msg-my-bid');

    function sendBid(btn) {
        liveSend(parseInt(btn.value));
    }

    function liveRecv(data) {
        console.log('liveRecv', data)
        let am_top_bidder = data.top_bidder === js_vars.my_id;
        let am_second_bidder = data.second_bidder === js_vars.my_id;

        if (data.top_bid === 0) {
            msgMyStatus.innerText = 'Nobody has made a bid yet';
        } else if (am_top_bidder) {
            msgMyStatus.innerText = 'You are the top bidder';
            bidBtn.disabled = 'disabled';
            msgMyBid.innerText = `Your bid is ${data.top_bid} points.`;
        } else {
            bidBtn.disabled = '';
            if (am_second_bidder) {
                msgMyBid.innerText = `Your bid is ${data.second_bid} points. The top bid is ${data.top_bid} points (player ${data.top_bidder})`;
                msgMyStatus.innerText = 'You are the second bidder';
            } else {
                msgMyBid.innerText = '';
                msgMyStatus.innerText = 'You are not the top or second bidder.'
            }

        }

        let nextBid = data.top_bid + 10;
        bidBtn.value = nextBid;
        bidBtn.innerText = `Bid ${nextBid} points`;
    }

    document.addEventListener("DOMContentLoaded", function (event) {
        liveSend({});
    });
</script>


{{ endblock }}

dollar_auction/instructions.html

From otree-demo


<h3>Rules</h3>

<p>
    In this auction, the jackpot of {{ Constants.jackpot }}
    points goes to the highest bidder.

    The second highest bidder still has to pay the
    amount of their bid.
</p>

cournot/__init__.py

From otree-demo


from otree.api import *

doc = """
In Cournot competition, firms simultaneously decide the units of products to
manufacture. The unit selling price depends on the total units produced. In
this implementation, there are 2 firms competing for 1 period.
"""


class Constants(BaseConstants):
    name_in_url = 'cournot'
    players_per_group = 2
    num_rounds = 1
    instructions_template = 'cournot/instructions.html'
    # Total production capacity of all players
    total_capacity = 60
    max_units_per_player = int(total_capacity / players_per_group)


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    unit_price = models.CurrencyField()
    total_units = models.IntegerField(doc="""Total units produced by all players""")


class Player(BasePlayer):
    units = models.IntegerField(
        min=0,
        max=Constants.max_units_per_player,
        doc="""Quantity of units to produce""",
        label="How many units will you produce (from 0 to 30)?",
    )


# FUNCTIONS
def set_payoffs(group: Group):
    players = group.get_players()
    group.total_units = sum([p.units for p in players])
    group.unit_price = Constants.total_capacity - group.total_units
    for p in players:
        p.payoff = group.unit_price * p.units


def other_player(player: Player):
    return player.get_others_in_group()[0]


# PAGES
class Introduction(Page):
    pass


class Decide(Page):
    form_model = 'player'
    form_fields = ['units']


class ResultsWaitPage(WaitPage):
    body_text = "Waiting for the other participant to decide."
    after_all_players_arrive = set_payoffs


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(other_player_units=other_player(player).units)


page_sequence = [Introduction, Decide, ResultsWaitPage, Results]

cournot/Results.html

From otree-demo


{{ block title }}Results{{ endblock }}
{{ block content }}

    <p>The results are shown in the following table.</p>

    <table class="table">

        <tr>
            <td>Your firm produced:</td>
            <td>{{ player.units }} units</td>
        </tr>

        <tr>
            <td>The other firm produced:</td>
            <td>{{ other_player_units }} units</td>
        </tr>

        <tr>
            <td>Total production:</td>
            <td>{{ group.total_units }} units</td>
        </tr>

        <tr>
            <td style="border-top-width:4px">Unit selling price:</td>
            <td style="border-top-width:4px">{{ Constants.total_capacity }} – {{ group.total_units }} = {{ group.unit_price }}</td>
        </tr>

        <tr>
            <td>Your profit:</td>
            <td>{{ player.payoff }}</td>
        </tr>

    </table>



    {{ include Constants.instructions_template }}

{{ endblock }}

cournot/Decide.html

From otree-demo


{{ block title }}Production{{ endblock }}
{{ block content }}

    {{ formfields }}

    {{ next_button }}

    {{ include Constants.instructions_template }}

{{ endblock }}

cournot/instructions.html

From otree-demo


<div class="card bg-light m-3">
    <div class="card-body">

    <h3>
        Instructions
    </h3>

    <p>
        You have been randomly and anonymously paired with another participant.
        Each of you will represent a firm. Both firms manufacture the same product.
    </p>

    <p>
        The two of you decide simultaneously and independently how many units to manufacture.
        Your choices can be any number from 0 to {{ Constants.max_units_per_player }}.
        All produced units will be sold, but the more is produced, the lower the unit selling price will be.
    </p>


    <p>
        The unit selling price is:
    </p>

    <ul>
        <b>Unit selling price = {{ Constants.total_capacity }} – Units produced by your firm – Units produced by the other firm</b>
    </ul>

    <p>
        Your profit is therefore:
    </p>

    <ul>
        <b>Your profit = Unit selling price × Units produced by your firm</b>
    </ul>

    <p>
        For your convenience, these instructions will remain available to you on all subsequent screens of this study.
    </p>

</div>
</div>

cournot/Introduction.html

From otree-demo


{{ block title }}Introduction{{ endblock }}
{{ block content }}

    {{ include Constants.instructions_template }}

    {{ next_button }}

{{ endblock }}

stroop/Task.html

From otree-more-demos


{{ block title }}
{{ endblock }}

{{ block content }}

  {{ for path in image_paths }}
    <img class="stroopimage" src="{{ static path }}" style="display: none">
  {{ endfor }}

  <div id="lastresult" style="font-size: 100px"></div>
  <div id="loading">Get ready...</div>

  <script>
      let image_id;
      let images = document.getElementsByClassName('stroopimage');
      let lastresult = document.getElementById('lastresult');
      let displayed_timestamp;
      let loading = document.getElementById('loading');

      // time before we unhideDiv the first image (give time to get hands ready on keyboard)
      const INITIAL_DELAY = 1000;

      // time in between showing showing ✓ or ✗, and showing the next image
      const IN_BETWEEN_DELAY = 1000;

      function liveRecv(data) {
          for (let image of images) {
              image.style.display = 'none';
          }
          if (data.feedback)
            lastresult.innerHTML = data.feedback;
          lastresult.style.display = 'block';
          if (data.is_finished) {
              document.getElementById('form').submit();
          } else {
              image_id = data.image_id;
              setTimeout(loadImage, IN_BETWEEN_DELAY);
          }
      }

      function loadImage() {
          lastresult.style.display = 'none';
          images[image_id].style.display = 'block';
          isRefractoryPeriod = false;
          displayed_timestamp = performance.now();
      }

      let isRefractoryPeriod = false;

      document.addEventListener("keypress", function (event) {
          let color = js_vars.color_keys[event.key];
          if (isRefractoryPeriod) return;
          isRefractoryPeriod = true;
          if (color) {
              liveSend({
                  submission: color,
                  image_id: image_id,
                  displayed_timestamp: displayed_timestamp,
                  answered_timestamp: performance.now()
              })
          }
      });

      document.addEventListener('DOMContentLoaded', function (event) {
        setTimeout(function () {
              loading.style.display = 'none';
              liveSend({});
          }, INITIAL_DELAY);
      });
  </script>


{{ endblock }}

wisconsin/instructions.html

From otree-more-demos


<div class="card">
  <div class="card-body">
    <h5 class="card-title">Instructions</h5>
    <p class="card-text">
        You need to guess which of the above 4 decks each card matches with.
        The matching is based on either the color, shape, or number of items in the card.
        After each guess, you will be told whether your guess was right.
        However, the matching rule will periodically change throughout the game.
    </p>
  </div>
</div>

wisconsin/Play.html

From otree-more-demos


{{ block title}}
Progress: <span id="task-progress"></span> / {{ Constants.NUM_TRIALS }}
{{ endblock }}

{{ block content }}

<style>
    .wisc-card {
        width: 10rem;
        height: 14rem;
        align-items: center;
        justify-content: center;
    }
    
    .wisc-img {
        display: block;
    }

    #feedback {
        font-size: x-large;
    }
</style>

<div class="container">

    <p id="feedback"></p>

    <div class="row">
        {{ for deck_number in deck_numbers }}
        <div class="col">
            <button type="button" onclick="selectDeck(this)" value="{{ deck_number }}" class="btn-card">
                <!-- it's just a coincidence that we use the bootstrap 'card' element to represent a card :) -->
                <div class="card wisc-card">
                    <div class="card-body" id="deck-{{deck_number}}">
                    </div>
                </div>
            </button>
        </div>
        {{ endfor }}

    </div>

    <br>
    <p>Match the below card to one of the above cards.</p>
    <div class="card wisc-card">
        <div class="card-body" id="test-card">
        </div>
    </div>

    <br><br>
    {{ include 'wisconsin/instructions.html' }}
</div>

<script>

    let buttons = document.getElementsByClassName('btn-card');
    let msgProgress = document.getElementById('task-progress');
    let msgFeedback = document.getElementById('feedback');

    function selectDeck(btn) {
        liveSend({'deck_number': parseInt(btn.value)});
        for (let btn of buttons) {
            btn.disabled = 'disabled';
        }
    }

    function makeCardContent({number, shape, color}) {
        let images = [];
        for (let i = 0; i < number; i++) {
            let image = `<img src="/static/wisconsin/${shape}-${color}.svg" width="50em" class="wisc-img">`;
            images.push(image);
        }
        return images.join('');
    }

    function liveRecv(data) {
        if ('finished' in data) {
            document.getElementById('form').submit();
            return;
        }
        if ('feedback' in data) {
            msgFeedback.innerHTML = data.feedback;
        }
        if ('decks' in data) {
            for (let i = 0; i < 4; i++) {
                document.getElementById(`deck-${i}`).innerHTML = makeCardContent(data.decks[i])
            }
        }
        if ('test_card' in data) {
            document.getElementById(`test-card`).innerHTML = makeCardContent(data.test_card);
        }

        msgProgress.innerHTML = data.num_trials;

        for (let btn of buttons) {
            btn.disabled = '';
        }

    }

    document.addEventListener("DOMContentLoaded", function (event) {
        liveSend({});
    });

</script>

{{ endblock }}

wisconsin/Results.html

From otree-more-demos


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <p><i>See the admin data table for the user's stats</i></p>

{{ endblock }}

wisconsin/__init__.py

From otree-more-demos


from pprint import pprint  # noqa
from otree.api import *
import random

doc = """
Wisconsin card sorting test: https://doi.org/10.1093/cercor/1.1.62
"""


class Constants(BaseConstants):
    name_in_url = 'wisconsin'
    players_per_group = None
    num_rounds = 1

    SWITCH_THRESHOLD = 6
    NUM_TRIALS = 30

    RULES = ['color', 'shape', 'number']
    COLORS = ['red', 'blue', 'green', 'yellow']
    SHAPES = ['circle', 'triangle', 'star', 'plus']
    NUMBERS = [1, 2, 3, 4]


C = Constants


class Subsession(BaseSubsession):
    matching_rules = models.LongStringField()


def creating_session(subsession: Subsession):
    for p in subsession.get_players():
        p.layout = random_layout()
        p.rule = random.choice(C.RULES)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    # how many they have selected from each deck
    num_trials = models.IntegerField(initial=0)
    num_correct = models.IntegerField(initial=0)
    num_correct_in_block = models.IntegerField(initial=0)
    rule = models.StringField(doc="Either color, shape, or number")
    layout = models.StringField()
    is_finished = models.BooleanField(initial=False)


def random_layout():
    # color, number, shape, and 0 (which means nothing matches)
    layout = list('cns0')
    random.shuffle(layout)
    return ''.join(layout)


def generate_decks(test_card: dict, layout):
    cs = [c for c in C.COLORS if c != test_card['color']]
    ss = [c for c in C.SHAPES if c != test_card['shape']]
    ns = [c for c in C.NUMBERS if c != test_card['number']]
    random.shuffle(cs)
    random.shuffle(ss)
    random.shuffle(ns)

    decks = []
    for letter in layout:
        if letter == 'c':
            card = dict(color=test_card['color'], shape=ss.pop(), number=ns.pop())
        elif letter == 's':
            card = dict(shape=test_card['shape'], color=cs.pop(), number=ns.pop())
        elif letter == 'n':
            card = dict(number=test_card['number'], shape=ss.pop(), color=cs.pop())
        else:
            card = dict(color=cs.pop(), shape=ss.pop(), number=ns.pop())

        decks.append(card)
    return decks


def live_method(player: Player, data):
    print('player.rule', player.rule)
    print('player.layout', player.layout)
    my_id = player.id_in_group

    # guard
    if player.is_finished:
        return {my_id: dict(finished=True)}

    resp = {}
    if 'deck_number' in data:
        deck = data['deck_number']
        # [0] means the first letter of the rule.
        is_correct = player.layout[deck] == player.rule[0]
        player.num_trials += 1
        player.num_correct += is_correct
        player.num_correct_in_block += is_correct

        if player.num_correct_in_block == C.SWITCH_THRESHOLD:
            other_rules = [r for r in C.RULES if r != player.rule]
            player.rule = random.choice(other_rules)
            player.num_correct_in_block = 0

        # layout changes each turn. otherwise, the user could just keep
        # clicking on the same box for the rest of the block.
        player.layout = random_layout()
        feedback = '✓' if is_correct else '✗'
        resp.update(feedback=feedback)

    test_card = dict(
        color=random.choice(C.COLORS),
        shape=random.choice(C.SHAPES),
        number=random.choice(C.NUMBERS),
    )
    decks = generate_decks(test_card, player.layout)
    resp.update(test_card=test_card, decks=decks)

    if player.num_trials == C.NUM_TRIALS:
        player.is_finished = True
        resp.update(finished=True)

    resp.update(num_trials=player.num_trials)

    return {my_id: resp}


class Play(Page):
    live_method = live_method

    @staticmethod
    def vars_for_template(player: Player):
        return dict(deck_numbers=range(4))

    @staticmethod
    def error_message(player: Player, values):
        if not player.is_finished:
            return "Game not finished"


class Results(Page):
    pass


page_sequence = [Play, Results]

read_mind_in_eyes/instructions.html

From otree-more-demos


<h3>
    Instructions
</h3>

<p>
    This test will investigate your ability to read emotion from the eyes. You will be shown a pair of eyes with four
    emotion labels around it. You are to select which one of the four emotion words best describes the emotion that the
    eyes are showing. Please provide one best guess for each item.
</p>

read_mind_in_eyes/Play.html

From otree-more-demos


{{ block title }}
    Face {{ subsession.round_number }} of {{ Constants.num_rounds }}
    {{ if is_practice }}(practice){{ endif}}
{{ endblock }}
{{ block content }}

    <img src="{{ static image_path }}" style="padding-bottom: 100px">

    {{ formfields }}

    {{ next_button }}

    {{ if is_practice }}
        {{ include Constants.instructions_template }}
    {{ endif}}

{{ endblock }}

read_mind_in_eyes/PracticeFeedback.html

From otree-more-demos


{{ block title }}
    Practice feedback
{{ endblock }}

{{ block content }}

    {{ if player.is_correct }}
        <p>You got the practice question correct!</p>
    {{ else }}
        <p>The correct answer was actually 'panicked'.</p>
    {{ endif }}

    <p>You will receive no feedback for the remaining questions.</p>

    {{ next_button }}

{{ endblock }}

read_mind_in_eyes/Results.html

From otree-more-demos


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <p>You answered {{ score }} out of 36 images correctly.</p>

{{ endblock }}

read_mind_in_eyes/__init__.py

From otree-more-demos


from otree.api import *


doc = """
Reading the Mind in the Eyes Test (Baron-Cohen et al. 2001).
See here: http://socialintelligence.labinthewild.org/mite/
"""


def read_csv():
    import csv

    f = open(__name__ + '/stimuli.csv', encoding='utf-8-sig')
    rows = [row for row in csv.DictReader(f)]
    for row in rows:
        row['image_path'] = 'read_mind_in_eyes/{}.png'.format(row['image'])

    return {row['image']: row for row in rows}


class Constants(BaseConstants):
    name_in_url = 'read_mind_in_eyes'
    players_per_group = None
    images = read_csv()
    num_rounds = len(images)
    instructions_template = __name__ + '/instructions.html'


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    choice = models.StringField(
        label="What emotion are the eyes showing?", widget=widgets.RadioSelect
    )
    is_correct = models.BooleanField()


def choice_choices(player: Player):
    trial = get_current_trial(player)
    return [
        ['A', trial['A']],
        ['B', trial['B']],
        ['C', trial['C']],
        ['D', trial['D']],
    ]


def get_current_trial(player: Player):
    if player.round_number == 1:
        image_name = 'practice'
    else:
        image_name = str(player.round_number - 1)
    return Constants.images[image_name]


class Play(Page):
    form_model = 'player'
    form_fields = ['choice']

    @staticmethod
    def vars_for_template(player: Player):
        trial = get_current_trial(player)
        return dict(image_path=trial['image_path'], is_practice=player.round_number == 1)

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        trial = get_current_trial(player)
        player.is_correct = player.choice == trial['solution']


class PracticeFeedback(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == 1


class Results(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.round_number == Constants.num_rounds

    @staticmethod
    def vars_for_template(player: Player):
        participant = player.participant

        score = sum(p.is_correct for p in player.in_rounds(2, Constants.num_rounds))
        participant.read_mind_in_eyes_score = score

        return dict(score=score)


page_sequence = [Play, PracticeFeedback, Results]

go_no_go/Introduction.html

From otree-more-demos


{{ block title }}
  Instructions
{{ endblock }}

{{ block content }}

  <p>
    You will be shown a series of images in quick succession.
    If the image is green, press the '1' key as quickly as possible.
    If the image is red, don't press anything. Instead, wait for the next image.
  </p>

  <p>When you are ready to start, press the button below.</p>
  <button class="btn btn-primary">Start</button>
{{ endblock }}

go_no_go/Results.html

From otree-more-demos


{{ block title }}
  Results
{{ endblock }}

{{ block content }}

You made {{ player.num_errors }} errors.
{{ if avg_reaction_ms }}
  Your average reaction time was {{ avg_reaction_ms }} ms.
{{ endif }}
{{ endblock }}

go_no_go/__init__.py

From otree-more-demos


from otree.api import *

doc = """
Go/No-go
"""


class Constants(BaseConstants):
    name_in_url = 'go_no_go'
    players_per_group = None
    num_rounds = 1
    red_images = [0, 4, 8, 17]
    num_images = 10  # actually there are 20 images but we just show 10 for brevity


class Subsession(BaseSubsession):
    pass


def creating_session(subsession: Subsession):
    for p in subsession.get_players():
        participant = p.participant
        image_ids = generate_ordering()
        for stim in image_ids:
            is_red = stim in Constants.red_images
            Trial.create(player=p, image_id=stim, is_red=is_red)

        participant.reaction_times = []


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    num_completed = models.IntegerField(initial=0)
    num_errors = models.IntegerField(initial=0)
    avg_reaction_ms = models.FloatField()


def get_current_trial(player: Player):
    return Trial.filter(player=player, is_error=None)[0]


def is_finished(player: Player):
    return player.num_completed == Constants.num_images


class Trial(ExtraModel):
    player = models.Link(Player)
    reaction_ms = models.IntegerField()
    image_id = models.IntegerField()
    is_red = models.BooleanField()
    is_error = models.BooleanField()
    pressed = models.BooleanField()


def generate_ordering():
    import random

    numbers = list(range(Constants.num_images))
    random.shuffle(numbers)
    return numbers


# PAGES
class Introduction(Page):
    pass


class Task(Page):
    @staticmethod
    def live_method(player: Player, data):
        participant = player.participant
        if 'pressed' in data:
            trial = get_current_trial(player)
            # this is necessary because the timeout will cause duplicates to be sent
            if data['image_id'] != trial.image_id:
                return
            trial.is_error = trial.is_red == data['pressed']
            if trial.is_error:
                feedback = '✗'
                player.num_errors += 1
            else:
                feedback = '✓'
                if not trial.is_red:
                    trial.reaction_ms = data['answered_timestamp'] - data['displayed_timestamp']
                    participant.reaction_times.append(trial.reaction_ms)
            player.num_completed += 1
        else:
            feedback = ''

        if is_finished(player):
            return {player.id_in_group: dict(is_finished=True)}

        trial = get_current_trial(player)
        return {
            player.id_in_group: dict(image_id=trial.image_id, feedback=feedback, trialId=trial.id)
        }

    @staticmethod
    def vars_for_template(player: Player):
        image_paths = [
            'go_no_go/{}.png'.format(image_id) for image_id in range(Constants.num_images)
        ]

        return dict(image_paths=image_paths)

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        participant = player.participant
        import statistics

        # if the participant never pressed, this list will be empty
        if participant.reaction_times:
            avg_reaction = statistics.mean(participant.reaction_times)
            player.avg_reaction_ms = int(avg_reaction)


def get_or_none(obj, fieldname):
    """oTree will raise an error for null fields"""
    try:
        return getattr(obj, fieldname)
    except TypeError:
        return None


class Results(Page):
    @staticmethod
    def vars_for_template(player: Player):
        return dict(avg_reaction_ms=get_or_none(player, 'avg_reaction_ms'))


page_sequence = [Introduction, Task, Results]

go_no_go/Task.html

From otree-more-demos


{{ block title }}
{{ endblock }}

{{ block content }}

  {{ for path in image_paths }}
      <img class="img-stimulus" src="{{ static path }}" style="display: none"></img>
  {{ endfor }}

  <div id="feedback" style="font-size: 100px"></div>
  <div id="loading">Get ready...</div>

  <script>
      let images = document.getElementsByClassName('img-stimulus');
      let feedback = document.getElementById('feedback');
      let displayed_timestamp;
      let loading = document.getElementById('loading');
      let image_id_global = null;

      // time before we unhideDiv the first image (give time to get hands ready on keyboard)
      const INITIAL_DELAY = 2000;

      // time in between showing showing ✓ or ✗, and showing the next image
      const IN_BETWEEN_DELAY = 1000;

      function liveRecv(data) {
          for (let image of images) {
              image.style.display = 'none';
          }
          if (data.feedback) {
            feedback.innerHTML = data.feedback;
            feedback.style.display = 'block';
          }
          if (data.is_finished) {
              document.getElementById('form').submit();
          } else {
            image_id_global = data.image_id;
            setTimeout(() => loadImage(data.image_id), IN_BETWEEN_DELAY);
          }
      }

      function loadImage(image_id) {
          feedback.style.display = 'none';
          images[image_id].style.display = 'block';
          isRefractoryPeriod = false;
          displayed_timestamp = performance.now();
          setTimeout(() => {
            liveSend({'image_id': image_id, 'pressed': false})
          }, 3000);
      }

      let isRefractoryPeriod = false;

      document.addEventListener("keypress", function (event) {
        if (event.key === '1') {
          if (isRefractoryPeriod) return;
          isRefractoryPeriod = true;
          liveSend({
              'image_id': image_id_global,
              'pressed': true,
              'displayed_timestamp': displayed_timestamp,
              'answered_timestamp': performance.now()
          })
        }
      });

      document.addEventListener('DOMContentLoaded', function (event) {
          setTimeout(function () {
              loading.style.display = 'none';
              liveSend({});
          }, INITIAL_DELAY);
      });
  </script>


{{ endblock }}

iowa_gambling/instructions.html

From otree-more-demos


<div class="card">
  <div class="card-body">
    <h5 class="card-title">Instructions</h5>
    <p class="card-text">
        In this task, you are presented with 4 decks of cards.
        Each card you turn over has a reward and may have a cost also.
        Your task is to turn over {{ Constants.NUM_TRIALS }} cards,
        with the goal of making the highest payoff.
        Note: Each deck has a different distribution of rewards and costs.
    </p>
  </div>
</div>

iowa_gambling/Play.html

From otree-more-demos


{{ block title}}
Progress: <span id="task-progress"></span> / {{ Constants.NUM_TRIALS }}
{{ endblock }}

{{ block content }}

<div class="container">
    <table class="table">
        <colgroup>
            <col style="width: 50%"/>
            <col style="width: 50%"/>
        </colgroup>
        <tr>
            <td>Your total payoff</td>
            <td id="cum_payoff"></td>
        </tr>
        <tr>
            <td>Last card reward</td>
            <th id="reward" style="color: green"></th>
        </tr>
        <tr>
            <td>Last card cost</td>
            <th id="cost" style="color: red"></th>
        </tr>
    </table>


    <div class="row">
        {{ for letter in 'ABCD' }}
        <div class="col">
            <button type="button" onclick="selectDeck(this)" value="{{ letter }}" class="btn-card">
                <!-- it's just a coincidence that we use the bootstrap 'card' element to represent a card :) -->
                <div class="card" style="width: 10rem; height: 14rem">
                    <div class="card-body">
                        <h2 class="card-title">Deck</h2>
                        <h1 class="card-title">{{ letter }}</h1>
                    </div>
                </div>
            </button>
        </div>
        {{ endfor }}

    </div>

    <br><br>
    {{ include 'iowa_gambling/instructions.html' }}
</div>

<script>
    let buttons = document.getElementsByClassName('btn-card');
    let msgCost = document.getElementById('cost');
    let msgReward = document.getElementById('reward');
    let msgCumPayoff = document.getElementById('cum_payoff');
    let msgProgress = document.getElementById('task-progress');

    function selectDeck(btn) {
        liveSend({'letter': btn.value});
        for (let btn of buttons) {
            btn.disabled = 'disabled';
        }
    }

    function liveRecv(data) {
        if ('finished' in data) {
            document.getElementById('form').submit();
            return;
        }
        console.log(data);
        if ('reward' in data) {
            // unpack
            let cost = data.cost;
            let reward = data.reward;

            msgReward.innerHTML = cu(reward);
            msgCost.innerHTML = cost === 0 ? '' : cu(cost);
        }

        msgCumPayoff.innerHTML = cu(data.cum_payoff);
        msgProgress.innerHTML = data.num_trials;

        for (let btn of buttons) {
            btn.disabled = '';
        }

    }

    function cu(amount) {
        return `${amount} points`;
    }

    document.addEventListener("DOMContentLoaded", function (event) {
        liveSend({});
    });

</script>

{{ endblock }}

iowa_gambling/Results.html

From otree-more-demos


{{ block title }}
    Results
{{ endblock }}

{{ block content }}

    <p><i>See the admin data table for the user's stats</i></p>

{{ endblock }}

iowa_gambling/__init__.py

From otree-more-demos


from otree.api import *


doc = """
Iowa Gambling Task. See: "Insensitivity to future consequences following damage to human prefrontal cortex"
(Bechara et al, 1994)
"""


class Constants(BaseConstants):
    name_in_url = 'iowa'
    players_per_group = None
    num_rounds = 1

    # randomization is done within a block of 10 trials, see figure 1 of the 1994 paper
    BLOCK_SIZE = 10
    NUM_BLOCKS = 10

    # should be greater than block_size * num_blocks
    NUM_TRIALS = 50  # in the classic game it is 100

    # these are the rewards for each deck, which are constant
    REWARDS = [100, 100, 50, 50]


C = Constants


class Subsession(BaseSubsession):
    pass


def generate_block():
    import random

    costs = [[150, 200, 250, 300, 350], [1250], [50, 50, 50, 50, 50], [250]]
    for ele in costs:
        # add zeroes until it has 10 elements
        ele += [0] * (C.BLOCK_SIZE - len(ele))
        random.shuffle(ele)
    return costs


def creating_session(subsession: Subsession):
    session = subsession.session
    import random

    costs = [[], [], [], []]
    for i in range(C.NUM_BLOCKS):
        block = generate_block()

        costs[0] += block[0]
        costs[1] += block[1]
        costs[2] += block[2]
        costs[3] += block[3]

    session.iowa_costs = costs

    for p in subsession.get_players():
        deck_layout = ['A', 'B', 'C', 'D']
        random.shuffle(deck_layout)
        p.deck_layout = ''.join(deck_layout)


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    # ho