# 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

A results page usually needs no Python code of its own:

```python
class Results(Page):
    pass
```

Its template reads everything it needs from `player`, which every template can access. In a two-player game, `player.other_in_group` is the other member of the group:

```html+jinja
{% 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>The other participant cooperated.</p>
{% else %}
<p>The other participant did not cooperate.</p>
{% endif %}

<p>Your payoff is <b>{{ player.payoff | fmtnum(pre="$", places=2) }}</b>.</p>

{% endblock main %}
```

The `fmtnum` filter prints the payoff as dollars with two decimal places (see [Formatting numbers](#formatting-numbers)).

[See the prisoners_dilemma example](https://github.com/mrpg/uproot-examples/tree/main/prisoners_dilemma)

## Accessing other players’ data

### Two-player groups

Access the other player via the virtual field `player.other_in_group` directly in templates, or pass it explicitly via `templatevars`:

```python
class Results(Page):
    @classmethod
    def templatevars(page, player):
        return dict(other=player.other_in_group)
```

```html+jinja
<p>Your partner sent {{ other.amount }}.</p>
```

[See the trust_game example](https://github.com/mrpg/uproot-examples/tree/main/trust_game) · [ultimatum_game example](https://github.com/mrpg/uproot-examples/tree/main/ultimatum_game)

### Larger groups

Pass a list of other players:

```python
class Results(Page):
    @classmethod
    def templatevars(page, player):
        return dict(others=player.others_in_group)
```

```html+jinja
<p>Other guesses:
{% for other in others %}
    {{ other.guess }}{% if not loop.last %}, {% endif %}
{% endfor %}
</p>
```

[See the beauty_contest example](https://github.com/mrpg/uproot-examples/tree/main/beauty_contest) · [minimum_effort_game example](https://github.com/mrpg/uproot-examples/tree/main/minimum_effort_game)

## Formatting numbers

### The `to` filter

Format decimal places with the `| to(n)` filter:

```html+jinja
<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 example](https://github.com/mrpg/uproot-examples/tree/main/big5) · [focal_point example](https://github.com/mrpg/uproot-examples/tree/main/focal_point)

### The `fmtnum` filter

For currency and units with prefix/suffix:

```html+jinja
{{ 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) -->
```

Large numbers use comma thousands separators by default. Use `sep` and `decsep`
to choose different thousands and decimal separators, or set `sep=""` to omit
the thousands separator:

```html+jinja
{{ player.payoff | fmtnum(pre="$", places=2) }}                  <!-- $1,234.50 -->
{{ player.payoff | fmtnum(sep=".", decsep=",", places=2) }}      <!-- 1.234,50 -->
{{ player.payoff | fmtnum(sep="", places=2) }}                   <!-- 1234.50 -->
```

`fmtnum` rounds half up, as on paper: 2.345 becomes 2.35 and 2.5 becomes 3 with `places=0`. It uses the number as written in decimal, so values such as 1.005 also round up.

The same function is available in JavaScript, for amounts that change while the page is open. It takes the same options and gives the same result:

```html+jinja
<p>Current bonus: <span id="bonus"></span></p>

<script>
document.getElementById("bonus").innerText =
    uproot.fmtnum(1234.5, { pre: "$", places: 2 });  // $1,234.50
</script>
```

## Calculations in templates

uproot passes all Python builtins to templates. Perform calculations directly:

```html+jinja
<!-- 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 %}
```

[See the focal_point example](https://github.com/mrpg/uproot-examples/tree/main/focal_point)

### Using Python builtins

Call `sum()`, `max()`, `min()`, `len()`, `range()`, `enumerate()`, `zip()`, and other builtins:

```html+jinja
<!-- 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 %}
```

[See the mpl example](https://github.com/mrpg/uproot-examples/tree/main/mpl)

## Conditional display

### Role-based results

Show different content based on player role:

```html+jinja
{% 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](https://github.com/mrpg/uproot-examples/tree/main/trust_game) · [dictator_game example](https://github.com/mrpg/uproot-examples/tree/main/dictator_game)

### Outcome-based messages

```html+jinja
{% if player.winner %}
<p><b>You won!</b></p>
{% else %}
<p>You did not win this round.</p>
{% endif %}
```

[See the beauty_contest example](https://github.com/mrpg/uproot-examples/tree/main/beauty_contest)

## History tables

### Using `player.along()`

For repeated games, iterate through all rounds with `player.along()`:

```html+jinja
<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)`. The data for each round is a live, read-only view.

You can narrow the data before iterating. This is useful when one player stores data for several apps:

```python
for round_number, round_data in player.within(app=__name__).along("round"):
    print(round_number, round_data.choice)
```

If your app has several [blocks](https://uproot.science/building/operators.md#blocks), each block has its own round 1, 2, …, so narrow by block as well. Otherwise, the rounds of all blocks get mixed together:

```python
for round_number, round_data in player.within(app=__name__, block="main").along("round"):
    print(round_number, round_data.choice)
```

[See the rounds example](https://github.com/mrpg/uproot-examples/tree/main/rounds)

### Using `player.within()`

Access a specific round’s data with `player.within(round=n)`:

```html+jinja
<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 `templatevars`:

```python
class Decision(Page):
    @classmethod
    def templatevars(page, player):
        return dict(
            other=player.other_in_group,
            rounds_so_far=range(1, player.round),
        )
```

[See the prisoners_dilemma_repeated example](https://github.com/mrpg/uproot-examples/tree/main/prisoners_dilemma_repeated)

By default, a view may carry forward a value that was stored before the requested context began. Pass `strict=True` when you only want fields written while that context held:

```python
round_data = player.within(round=3, strict=True)
choice = round_data.get("choice")

for round_number, round_data in player.along("round", strict=True):
    print(round_number, round_data.get("choice"))
```

## Accessing constants

The `C` class is available in templates:

```python
class C:
    ROUNDS = 5
    ENDOWMENT = 100
```

```html+jinja
<p>Round {{ player.round }} of {{ C.ROUNDS }}</p>
<p>You started with {{ C.ENDOWMENT }} points.</p>
```

## Available filters

| Filter | Purpose | Example |
|--------|---------|---------|
| `to(n)` | Format to n decimal places | `{{ x \| to(2) }}` → `3.14` |
| `fmtnum(pre, post, places, sep, decsep)` | Format with prefix/suffix and separators | `{{ x \| fmtnum(pre="$", sep=",") }}` |
| `tojson` | Convert to JSON | `{{ data \| tojson }}` |
| `repr` | Python repr | `{{ x \| repr }}` |

### `tojson` and `| safe`

`tojson` converts a Python value to a JSON string. Jinja2’s autoescaping then HTML-encodes the result. This is the right default: it keeps values safe in HTML attributes and element content.

**Most of the time, `tojson` alone is all you need.** Numbers, Booleans, and `None` produce JSON like `42`, `true`, or `null`—no characters that autoescaping would change. Even inside `<script>` blocks, autoescaping is a no-op for these types, so `| safe` is unnecessary:

```html+jinja
{# These all work without | safe, everywhere — including <script> blocks #}
{{ player.score | tojson }}       {# 42 #}
{{ player.is_buyer | tojson }}    {# true #}
{{ player.profit | tojson }}      {# null #}
```

`| safe` is **only** needed when you put a value that may contain strings into a `<script>` block, because autoescaping would turn `"` into `&#34;` and break JavaScript parsing:

```html+jinja
<script>
    {# results contains strings → must use | safe inside <script> #}
    let data = {{ results | tojson | safe }};
</script>
```

In HTML (attributes, element content), never add `| safe`. Autoescaping is what protects you:

```html+jinja
{# Safe: autoescaping prevents XSS even if player.response contains malicious strings #}
<div x-data="{ response: {{ player.response | tojson }} }">
```

!!! warning
    Only use `| safe` inside `<script>` blocks, and only when the value may contain strings. Adding `| safe` in HTML attributes or element content disables escaping and can introduce XSS vulnerabilities.

## Summary

| Feature | Purpose |
|---------|---------|
| `templatevars(page, player)` | Pass variables to template |
| `player.other_in_group` | Get partner in 2-player group |
| `player.others_in_group` | Get all other group members |
| `group.players` | 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. |
