Displaying results¶
Results pages show participants their outcomes, payoffs, and other players' choices. uproot uses Jinja2 templates with full access to Python builtins, enabling calculations and logic directly in your templates.
Basic results display¶
Create a Results page and pass data via the context method:
class Results(Page):
@classmethod
def context(page, player):
return dict(
other=other_in_group(player),
)
In the template, access player data and context variables:
{% extends "Base.html" %}
{% block main %}
<p>You chose to {{ "cooperate" if player.cooperate else "defect" }}.</p>
<p>Your partner chose to {{ "cooperate" if other.cooperate else "defect" }}.</p>
<p>Your payoff is <b>{{ player.payoff }}</b>.</p>
{% endblock main %}
See the prisoners_dilemma example
Accessing other players' data¶
Two-player groups¶
Use other_in_group() to get the other player:
class Results(Page):
@classmethod
def context(page, player):
return dict(other=other_in_group(player))
See the trust_game example · ultimatum_game example
Larger groups¶
Pass a list of other players:
class Results(Page):
@classmethod
def context(page, player):
return dict(others=others_in_group(player))
<p>Other guesses:
{% for other in others %}
{{ other.guess }}{% if not loop.last %}, {% endif %}
{% endfor %}
</p>
See the beauty_contest example · minimum_effort_game example
Formatting numbers¶
The to filter¶
Format decimal places with the | to(n) filter:
<p>Your score: {{ player.score | to(1) }}</p> <!-- 7.3 -->
<p>Amount: ${{ player.amount | to(2) }}</p> <!-- $12.50 -->
<p>Percentage: {{ player.pct | to(0) }}%</p> <!-- 85% -->
See the big5_short example · focal_point example
The fmtnum filter¶
For currency and units with prefix/suffix:
{{ player.payoff | fmtnum(pre="$", places=2) }} <!-- $10.50 -->
{{ player.payoff | fmtnum(post=" EUR", places=2) }} <!-- 10.50 EUR -->
{{ player.change | fmtnum(pre="$", places=2) }} <!-- −$5.00 (uses minus sign) -->
Calculations in templates¶
uproot passes all Python builtins to templates. Perform calculations directly:
<!-- Arithmetic -->
<p>Total: {{ player.claim + other.claim }}</p>
<p>Tripled amount: {{ sent * 3 }}</p>
<p>Share: {{ player.contribution / total * 100 | to(1) }}%</p>
<!-- Comparisons -->
{% if player.claim + other.claim <= 100 %}
<p>The sum is $100 or less.</p>
{% else %}
<p>The sum exceeds $100.</p>
{% endif %}
Using Python builtins¶
Call sum(), max(), min(), len(), range(), enumerate(), zip(), and other builtins:
<!-- Sum contributions -->
<p>Group total: {{ sum(p.contribution for p in others) + player.contribution }}</p>
<!-- Find extremes -->
<p>Highest bid: {{ max(p.bid for p in others) }}</p>
<!-- Enumerate items -->
{% for i, item in enumerate(items) %}
<p>{{ i + 1 }}. {{ item }}</p>
{% endfor %}
<!-- Zip lists together -->
{% for option_a, option_b in zip(options_a, options_b) %}
<tr>
<td>{{ option_a }}</td>
<td>{{ option_b }}</td>
</tr>
{% endfor %}
Conditional display¶
Role-based results¶
Show different content based on player role:
{% if player.trustor %}
<p>You sent <b>{{ sent }}</b>, which was tripled to {{ tripled }}.</p>
<p>The other player returned <b>{{ returned }}</b> to you.</p>
{% else %}
<p>The other player sent {{ sent }}, tripled to <b>{{ tripled }}</b>.</p>
<p>You returned <b>{{ returned }}</b>.</p>
{% endif %}
See the trust_game example · dictator_game example
Outcome-based messages¶
{% if player.winner %}
<p><b>You won!</b></p>
{% else %}
<p>You did not win this round.</p>
{% endif %}
See the beauty_contest example
History tables¶
Using player.along()¶
For repeated games, iterate through all rounds with player.along():
<table class="table">
<thead>
<tr>
<th>Round</th>
<th>Your number</th>
</tr>
</thead>
<tbody>
{% for round, data in player.along("round") %}
<tr>
<td>{{ round }}</td>
<td>{{ data.number }}</td>
</tr>
{% endfor %}
</tbody>
</table>
The along("round") method returns tuples of (round_number, player_data_for_that_round).
Using player.within()¶
Access a specific round's data with player.within(round=n):
<table class="table">
<thead>
<tr>
<th>Round</th>
<th>You</th>
<th>Partner</th>
</tr>
</thead>
<tbody>
{% for round in rounds_so_far %}
<tr>
<td>{{ round }}</td>
<td>
{% if player.within(round=round).cooperate %}
Cooperated
{% else %}
Defected
{% endif %}
</td>
<td>
{% if other.within(round=round).cooperate %}
Cooperated
{% else %}
Defected
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
Pass rounds_so_far from context:
class Decision(Page):
@classmethod
def context(page, player):
return dict(
other=other_in_group(player),
rounds_so_far=range(1, player.round),
)
See the prisoners_dilemma_repeated example
Accessing constants¶
The C class is available in templates:
Available filters¶
| Filter | Purpose | Example |
|---|---|---|
to(n) |
Format to n decimal places | {{ x \| to(2) }} → 3.14 |
fmtnum(pre, post, places) |
Format with prefix/suffix | {{ x \| fmtnum(pre="$") }} |
tojson |
Convert to JSON | {{ data \| tojson }} |
repr |
Python repr | {{ x \| repr }} |
Summary¶
| Feature | Purpose |
|---|---|
context(page, player) |
Pass variables to template |
other_in_group(player) |
Get partner in 2-player group |
players(group) |
Get all group members |
player.along("round") |
Iterate all rounds |
player.within(round=n) |
Access specific round data |
{{ expression }} |
Output values |
{% if %}...{% endif %} |
Conditional display |
{% for %}...{% endfor %} |
Loops |
| Python builtins | sum(), max(), range(), etc. |