About measuring response time online with timestamp

195 views
Skip to first unread message

傅韋銘

unread,
Mar 2, 2022, 6:21:33 PM3/2/22
to oTree help & discussion
Dear Chris and all,
I am now writing an app with otree for an online experiment that I need to record response time. We now use Heroku as a server and plan to implement the experiment on Amazon Turk. I face some issues that I hope to get help with.

1. I am now using the difference of two timestamps to record the response time on the client-side. Since we hope the accuracy of response time is at the level of milliseconds. I am not quite sure if there exists any possible delay getting timestamp from client to server. To be specific, I want to make sure even if there exists a latency, will the latency be equal in each timestamp or not.

2. I did a test using the Heroku server for my app. I found that if the subject close the Web Browser during the experiment, the time record function will still work even browser is closed. As they reopen the browser,  the timestamp will add on the duration when they are not in the task. which will lead to an extra longer and wrong response time.
I am wondering if there is any way I can avoid this problem through coding.

The following is my initial py. code:

class Player(BasePlayer):
is_winner = models.BooleanField(initial=False)
first_choice = models.IntegerField(initial=101)
old_choice = models.IntegerField(initial=101)
final_choice = models.IntegerField(initial=101)
switch = models.BooleanField()
confirm = models.BooleanField(initial=False)
game_over = models.BooleanField(initial=False)
open = models.BooleanField(initial=False)
second_stage = models.BooleanField(initial=False)
first_stage_correct = models.BooleanField(initial=False)
door_keep = models.IntegerField(initial=101)
commit = models.IntegerField(blank=True,
choices=[-1, 0, 1, 2],
widget=widgets.RadioSelect)
temp_payoff = models.CurrencyField(initial=cu(0))
hollow = models.BooleanField(initial=True)
first_door_timestamp = models.FloatField()
reveal_timestamp = models.FloatField()
submit_timestamp = models.FloatField()



class Game(Page):
def is_displayed(player):
# load commit from participant field
participant = player.participant
if player.round_number == 1 and player.game_over == False:
participant.vars['monty_hall_cmt'] = 0
player.commit = participant.vars['monty_hall_cmt']
return player

@staticmethod
def js_vars(player: Player):
subsession = player.subsession
return dict(num_doors = Constants.num_doors,
num_rows = Constants.num_rows,
answer = subsession.answer,
commit = player.commit)

def live_method(player: Player, data):
import random
subsession = player.subsession
participant = player.participant
print('data:', data)

if data:
if ('door' in data.keys()):
if (data and data['door'] in range(1,101)):
if (player.second_stage == False):
player.old_choice = player.first_choice
player.first_choice = data['door']
else:
player.old_choice = player.final_choice
player.final_choice = data['door']
player.confirm = True
player.hollow = False

if ('status' in data.keys()):
if (data and data['status'] == 'open'):
player.open = True
# temporarily set final_choice as first_choice, player can switch later
player.final_choice = player.first_choice
player.old_choice = player.first_choice
if player.first_choice == subsession.answer:
player.first_stage_correct = True
player.door_keep = player.first_choice
while player.door_keep == player.first_choice:
player.door_keep = random.randint(1, Constants.num_doors)
else:
player.door_keep = subsession.answer

if (data and data['status'] == 'opened'):
player.second_stage = True

if (data and data['status'] == 'final_choice'):
if player.final_choice == subsession.answer:
player.is_winner = True
player.payoff += Constants.win_payoff
if player.commit != 0:
player.payoff += Constants.commit_payoff

player.first_door_timestamp = data['first_door_timestamp']
player.reveal_timestamp = data['reveal_timestamp']
player.submit_timestamp = data['submit_timestamp'
]

player.game_over = True
# Record whether the player switched
if player.first_choice == player.final_choice:
player.switch = False
else:
player.switch = True


return {
player.id_in_group: dict(
game_over=player.game_over,
first_choice = player.first_choice,
final_choice = player.final_choice,
old_choice = player.old_choice,
open = player.open,
second_stage = player.second_stage,
door_keep = player.door_keep,
commit = participant.monty_hall_cmt,
confirm = player.confirm,
hollow = player.hollow
)
}


----------------------------------------------------------------------------------------------------------------------------------
The following is HTML and javascript code for the game page.


{{ block content }}

<link rel="stylesheet" href="{{ static 'CSS/Monty_hall_style.css' }}">

<p id='commit' style="color:red; font-size: 26px;"></p>

<p id='guide'>
Pick a door and click "Reveal a goat".
</p>

<div id="door_series"></div>

<button id="next" type="button" class="btn btn-primary" onclick="liveSend({'status': 'open'});reveal_timestamp = performance.now()">Reveal a goat</button>

<script>
var width_unit = (720*js_vars.num_rows)/(js_vars.num_doors*12)

// Create doors
for (let i = 1; i < js_vars.num_doors+1; i++) {
var bg = document.createElement("div");
if (i == js_vars.answer){
bg.className='answer'
} else {
bg.className='door_bg'
}
bg.style.width = width_unit*10 + 'px';
bg.style.height = width_unit*10 + 'px';
bg.style.marginLeft = width_unit + 'px';
bg.style.marginRight = width_unit + 'px';

var door = document.createElement("div");
door.className='door';
door.id = i;
door.style.width = width_unit*10 + 'px';
door.style.height = width_unit*10 + 'px';
door.onclick = function() { liveSend({'door': i}); }

bg.appendChild(door)
document.getElementById("door_series").appendChild(bg);
}

function toggleDoor() {
element.classList.toggle("doorOpen");
};

let next = document.getElementById('next');
let commit = document.getElementById('commit');
let first_door_timestamp;
let reveal_timestamp;
let second_door_timestamp;

let submit_timestamp;

if (js_vars.commit == 0) {
commit.innerText = ''
} else if (js_vars.commit == 1) {
commit.innerText = 'You have commited to \"switch\".'
} else {
commit.innerText = 'You have commited to \"stay\".'
}


// live section
function liveRecv(data) {
// open all doors when the game is over
if (data.game_over === true) {
next.btnDisabledStatus = 'disabled';
next.style.visibility = 'hidden';
for (let i = 1; i < js_vars.num_doors+1; i++){
document.getElementById(i).classList.add("doorOpen");
};
setTimeout(() => { document.getElementById('form').submit(); }, 2000);
};

let current_choice;
if (data.second_stage === false){
if (data.first_choice != 101 && data.old_choice == 101){
first_door_timestamp = performance.now()
};
current_choice = data.first_choice;
} else {
// if (final_choice == 101){
// second_door_timestamp = performance.now();
// };
current_choice = data.final_choice;
};

if (data.second_stage === false) {
if (data.first_choice == 101) {
next.disabled = 'disabled';
} else {
next.disabled = '';
}
} else {
next.textContent = 'Submit';
next.onclick = function(){ liveSend({
'status': 'final_choice',
'first_door_timestamp': first_door_timestamp,
'reveal_timestamp': reveal_timestamp,
'submit_timestamp': performance.now()
});}

{{ endblock }}

Sorry it is a second post since I did not add my code properly at first.
Thank you very much in advance for your help.

Sincerely,
Wei-ming
Reply all
Reply to author
Forward
0 new messages