Your first experiment¶
This tutorial walks you through building a complete prisoner's dilemma experiment from scratch. By the end, you'll understand how uproot pages, forms, groups, and synchronization work together.
What we're building¶
A two-player prisoner's dilemma where participants are paired, each choose to cooperate or defect, and then see the outcome. The payoff matrix:
| Partner cooperates | Partner defects | |
|---|---|---|
| You cooperate | 10 | 0 |
| You defect | 15 | 3 |
1. Create a project¶
If you haven't already, create a new uproot project:
uv run --with 'uproot-science[dev] @ git+https://github.com/mrpg/uproot.git@main' uproot setup my_project
cd my_project
2. Create the app¶
Create a new app for the experiment:
This creates a prisoners_dilemma/ directory with an __init__.py and a starter template.
3. Register the config¶
Open main.py and add a config that loads your app:
4. Define the app¶
Replace the contents of prisoners_dilemma/__init__.py with:
from uproot.fields import *
from uproot.smithereens import *
DESCRIPTION = "Prisoner's dilemma"
SUGGESTED_MULTIPLE = 2
class C:
PAYOFF_MATRIX = {
(True, True): 10,
(True, False): 0,
(False, True): 15,
(False, False): 3,
}
DESCRIPTION shows up in the admin interface. SUGGESTED_MULTIPLE tells the admin that sessions should have a multiple of 2 players. C holds constants accessible in templates.
Group formation¶
Add a wait page that pairs participants:
When a participant reaches this page, they wait until another participant arrives. Then both are grouped together and advance.
The decision page¶
Add a page where participants choose to cooperate or defect:
class Dilemma(Page):
fields = dict(
cooperate=RadioField(
label="Do you wish to cooperate?",
choices=[(True, "Yes"), (False, "No")],
),
)
The fields dictionary defines the form. After submission, the choice is stored as player.cooperate.
Synchronization¶
Add a wait page so both players' decisions are in before showing results:
The results page¶
Add an empty results page (the template will handle display):
PlayerContext for computed values¶
Add a Context class to compute payoffs without writing a templatevars method:
class Context(PlayerContext):
@property
def payoff(self):
return C.PAYOFF_MATRIX[
self.player.cooperate,
self.player.other_in_group.cooperate,
]
This makes player.context.payoff available in templates.
Page order¶
Define the sequence:
5. Create the templates¶
Dilemma.html¶
Create prisoners_dilemma/Dilemma.html:
{% extends "Base.html" %}
{% block title %}
Dilemma
{% endblock title %}
{% block main %}
{{ fields() }}
{% endblock main %}
The {{ fields() }} call renders all form fields defined on the page.
Results.html¶
Create prisoners_dilemma/Results.html:
{% extends "Base.html" %}
{% block title %}
Results
{% endblock title %}
{% block main %}
{% if player.cooperate %}
<p>You cooperated.</p>
{% else %}
<p>You did not cooperate.</p>
{% endif %}
{% if player.other_in_group.cooperate %}
<p>Your partner cooperated.</p>
{% else %}
<p>Your partner did not cooperate.</p>
{% endif %}
<p>Your payoff is <b>{{ player.context.payoff }}</b>.</p>
{% endblock main %}
Templates have access to player, C, and player.context automatically.
6. Run the experiment¶
Start the server:
Open the admin at http://127.0.0.1:8000/admin/ and create a new session:
- Select the prisoners_dilemma config
- Set 2 players
- Click Create
Open the two player links in separate browser tabs. Both players will wait at the grouping page until the other arrives. After making their choices and submitting, they wait at the sync page until both have decided. Then they see the results.
The complete code¶
from uproot.fields import *
from uproot.smithereens import *
DESCRIPTION = "Prisoner's dilemma"
SUGGESTED_MULTIPLE = 2
class C:
PAYOFF_MATRIX = {
(True, True): 10,
(True, False): 0,
(False, True): 15,
(False, False): 3,
}
class Context(PlayerContext):
@property
def payoff(self):
return C.PAYOFF_MATRIX[
self.player.cooperate,
self.player.other_in_group.cooperate,
]
class GroupPlease(GroupCreatingWait):
group_size = 2
class Dilemma(Page):
fields = dict(
cooperate=RadioField(
label="Do you wish to cooperate?",
choices=[(True, "Yes"), (False, "No")],
),
)
class Sync(SynchronizingWait):
pass
class Results(Page):
pass
page_order = [
GroupPlease,
Dilemma,
Sync,
Results,
]
See the full prisoners_dilemma example
What's next?¶
- Project structure — Understand the files in your project
- Pages and templates — Learn about page lifecycle methods
- Collecting data with forms — Explore all available field types
- Grouping participants — More on multiplayer experiments