oTree Forum >

Synchronise time within group in continuous time games

#1 by Floria

Hi all,

I am working on a continuous-time public goods game, where players could change the contribution, but the payoff is accumulated at each specific time (e.g. 10s, 20s, 30s,...). I understand that the livepage works for the dynamics. The problem I face is how to synchronise the time within group, e.g., ideally, the contributions at 10s should affect the payoff, requiring a check if the current time on the client side is 10s. However, there may be discrepancies at client side timers, so the exact time of determining payoff may vary across group members. For example, it is possible that for player 1, the payoff is affected by the contributions at 10.1s, while for player 2, the payoff is affected by the contributions at 10.5s. I am fine with all group members' payoffs being affected by contributions at the same time point, even if this time point is not exactly at 10s. I was wondering if there are any approaches to achieve this.

Thanks in advance!

#2 by BonnEconLab

> For example, it is possible that for player 1, the payoff is affected by the contributions at 10.1s, while for player 2, the payoff is affected by the contributions at 10.5s.

The only way to avoid this, I think, is by using the server time. That is, you would have to live with the fact that there are both systematic and random delays between the clients’ time and the server time (and probably explain this to your participants). That is, a submission by one participant with a slow internet connection after 9.95 seconds and a submission by another participant with a faster internet connection after 10.03 seconds may have the same effect. Also, two submissions at the same time may have different effects, depending on network speed.

I don’t know how one could implement a client-side-only timer that is both accurate and non-manipulable. What I mean is that if you implement a timer via JavaScript, then a participant’s timer will start anew each time they refresh the page. Hence, at least when the page loads, you will have to synchronize the client-side time with some universal time anyway.

I would thus suggest to use a live method to transmit the offers to oTree and let oTree do all calculations of payoffs based on receipt of the offers according to the server time. To show the participants a timer that is as accurate as possible, you can synchronize the clients’ time with the server time whenever the live method is called.

#3 by Floria

Thanks @BonnEconLab! Yes the server side timer works synchronously for group members. I am still unclear about how to make the server check if the current time is the exact time specified (e.g. 10s, 20s, 30s,...). The live method cannot closely monitor every second as it is invoked only when the data is sent from the client side. The only solution I can think of is to have the client side send messages back to the server frequently to invoke the live method often. But it seems a heavy work and I am looking for some elegant approaches.

#4 by BonnEconLab

>  I am still unclear about how to make the server check if the current time is the exact time specified (e.g. 10s, 20s, 30s,...).

I don’t really understand the question. It would be enough to check what the current server time is whenever the live method is triggered by a participant, wouldn’t it? And whenever the live method is triggered you can also automatically send back information to everyone else in the same group. See https://otree.readthedocs.io/en/latest/live.html.

#5 by BonnEconLab

Here is some code that transmits input to the server as soon as a “transmit” button is clicked and then displays that input, including the time at which it was made, on the other group members’ screens on the fly:


***** __init__.py *****


from otree.api import *
import time


doc = """
An app to demonstrate live send, receive, and display of participants' inputs.
"""


class C(BaseConstants):

    NAME_IN_URL = 'zzz_live_send_receive_display'
    PLAYERS_PER_GROUP = 3
    NUM_ROUNDS = 1


class Subsession(BaseSubsession):

    pass


class Group(BaseGroup):

    pass


class Player(BasePlayer):
    
    some_input = models.StringField(initial="")
    round_started = models.FloatField()


# FUNCTIONS


# None


# PAGES


class Initialization(Page):

    timeout_seconds = 2

    @staticmethod
    def before_next_page(player, timeout_happened):
        round_started = time.time()
        for p in player.group.get_players():
            p.round_started = round_started


class MyPage(Page):

    form_model = "player"
    form_fields = ["some_input"]

    @staticmethod
    def js_vars(player):
        return{
            "secs_since_round_started": round(time.time() - player.round_started, 1),
        }
    
    @staticmethod
    def live_method(player, data):
        if "some_input" in data:
            new_interval = time.time() - player.round_started
            player.some_input = str(data["some_input"])
            all_inputs = {}
            for p in player.group.get_players():
                others_inputs = {}
                for o in p.group.get_players():
                    if o.id_in_group != p.id_in_group:
                        others_inputs.update({o.id_in_group: o.some_input})
                all_inputs.update({p.id_in_group: [new_interval, p.some_input, others_inputs]})
            print(all_inputs)  # For debugging only
            return all_inputs            


# PAGE SEQUENCE


page_sequence = [
    Initialization,
    MyPage,
]


***** MyPage.html *****


{{ block title }}

Transmit, receive, and display input on the fly

{{ endblock }}


{{ block content }}

<div class="alert alert-warning">
  Round started <span id="timer">0</span> seconds ago.
</div>

{{ formfields }}

<p>My own most recent input: <b id="my_input">none yet.</b></p>

<p>My partners’ most recent input: <b id="partner_input"></b></p>

<p id="transmit_button_container">
  <button id="transmit_button" type="button" onclick="sendInput()" class="btn btn-warning">Transmit input</button>
</p>

<p style="margin-top: 200px;">
  <button class="btn btn-danger"><b>Leave page</b></button>
</p>

{{ endblock }}


{{ block scripts }}

<script>
  var lastSubmitTime = Date.now();
  var secs_since_round_started = js_vars.secs_since_round_started;
  document.getElementById("timer").innerHTML = secs_since_round_started.toFixed(1);
  // CAVEAT: At least Chrome on Mac pauses setInterval if the browser tab is not the frontmost tab!
  setInterval(() => {
    secs_since_round_started += 0.1;
    document.getElementById("timer").innerHTML = secs_since_round_started.toFixed(1);
  }, 100)
  function liveRecv(data) {
    document.getElementById("my_input").innerHTML =
      data[1] + " at " + data[0].toFixed(1) + "&nbsp;seconds after start of this round. ";
    for (const [key, value] of Object.entries(data[2])) {
      document.getElementById("partner_input").innerHTML +=
        "<br>Player " + key + "’s input: " + value + " at " + data[0].toFixed(1) + "&nbsp;seconds after start of this round.";
    };
  };
  function sendInput() {
    liveSend({
      "some_input": document.getElementById('id_some_input').value,
    });
  };
</script>

{{ endblock }}

Write a reply

Set forum username