from otree.api import *
c = cu
doc = 'Accuracy task'
class C(BaseConstants):
# built-in constants
NAME_IN_URL = 'swipe_tap_nontimed'
PLAYERS_PER_GROUP = None
NUM_ROUNDS = 10
# user-defined constants
BLOCKS = 6
SWIPEROUNDS = (1, 2, 3, 7, 9)
PRACTICEROUNDS = (1, 2, 4, 5)
BONUSPAYMENTS = 'Your correctness is multiplied by £12, and from this we subtract your average time multiplied by £10. Example: if your correctness is 90% and your average time is 0.85 seconds, your bonus is £12×0.9 –\xa0£10×0.85 = £2.30.'
STIMSIZE = 80
class Subsession(BaseSubsession):
pass
class Group(BaseGroup):
pass
class Player(BasePlayer):
swipetimes = models.StringField(initial='')
responsetimes = models.StringField(initial='')
startleft = models.StringField(initial='')
touchtimes = models.StringField(initial='')
xs = models.StringField(initial='')
ys = models.StringField(initial='')
polyareas = models.StringField(initial='')
direction = models.StringField(initial='')
stimuli = models.StringField(initial='')
choicecounter = models.IntegerField(initial=0)
swipecheckchoicecounter = models.IntegerField(initial=0)
pageloads = models.StringField(initial='')
prolificcode = models.StringField()
correct = models.IntegerField(initial=0)
meanrt = models.FloatField()
test = models.IntegerField()
bonus = models.FloatField()
def live_open(player: Player, data):
group = player.group
time = data['time'] # this is a single timestamp indicating when the message was sent to server
touchtimes = data['touchtimes'] # these timestamps indicate when the card was touched
loadtime = data['loadtime'] # this is a timestamp recorded when the new stimulus was loaded, which happens in both frames after 1000 ms from the previous liveSend
starttime = data['starttime'] # this is a timestamp recorded on page, indicating when box moving started (empty in tap)
stimu = int(data['stimulus'])
player.test = stimu
# this is keeping track of how many times has been swiped/tapped
# only record if not swipecheck
if stimu != 0:
player.choicecounter += data['opened']
direction = data['direction'] # direction of the swipe
player.direction += direction
if data['direction']=='L' and stimu<65: player.correct += 1
if data['direction']=='R' and stimu>65: player.correct += 1
player.pageloads += str(data['pageloads']) + ';' # keeps track of possible refreshs of the page
else:
player.swipecheckchoicecounter += data['opened']
player.responsetimes += str(int(time) - int(loadtime)) + ';' # relevant in both swipe and tap frames
player.swipetimes += str(int(time) - int(starttime)) + ';' # only relevant in the swipe frame
if data['frame'] == 'swipe':
# comment off because these may become too large
#player.xs += data['x'] + ';'
#player.ys += data['y'] + ';' # this should correspond to decision times
player.touchtimes += touchtimes + ';'
# this is recorded just in case the polyarea calculations are wrong
player.startleft += data['startleft'] + ';'
### POLYAREA
x = data['x']
y = data['y']
xx = x.split(',')
xx = xx[:-1]
xxx = map(float,xx)
x = list(map(int,xxx))
yy = y.split(',')
yy = yy[:-1]
y = list(map(int,yy))
# flip the y coordinate
y = list(reversed(y))
xy = []
j = 0
# append all the points in the curve except the tail points
for jj in range(j,len(x)):
if x[-1] < int(data['startleft']): # rejected when final x-coordinate is 150 px lower than startleft
if x[jj] >= x[0]-150:
xy.append([x[jj],y[jj]])
jjj = jj # stopping index
else: # accepted when final x-coordinate is 150 px higher than startleft
if x[jj] < x[0]+150:
xy.append([x[jj],y[jj]])
jjj = jj # stopping index
# close the curve to determine the polyarea
xy.append([x[jjj],y[jjj]]) # append the end point
xy.append([x[jjj],y[0]]) # append the point that is vertically upwards from the end point
xy.append([x[0],y[0]]) # append the start point
l = len(xy)
s = 0.0
for i in range(l):
j = (i+1)%l # keep index in [0,l)
s += (xy[j][0] - xy[i][0])*(xy[j][1] + xy[i][1])
polyarea = -0.5*s
player.polyareas += str(polyarea) + ';'
if stimu > 0:
response = {'openedcards': player.choicecounter}
else:
response = {'openedcards': player.swipecheckchoicecounter}
return {player.id_in_group: response} # using 0: response is an error, broadcasts to the whole group!!!
def stimulus(player: Player):
session = player.session
import random
if session.config['lowvolatility']:
data = list(range(56, 75)) # FRYDMAN & JIN LOW VOLATILITY
data.remove(65)
stimulus = data+data+data+data+data # this has 5*18=90 numbers
else:
data = list(range(31, 100)) # FRYDMAN & JIN HIGH VOLATILITY
data.remove(65)
stimulus = data+data # this has 2*68=136 numbers
random.shuffle(stimulus)
if session.config['pilot'] == 1:
return stimulus[0:10]
else:
return stimulus[0:C.STIMSIZE] # 80 trials on each block for each volatility
class MobileCheck(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
@staticmethod
def vars_for_template(player: Player):
session = player.session
return dict( pilot = session.config['pilot'] )
class SwipeCheck(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
@staticmethod
def js_vars(player: Player):
return dict( howmanyswipes = 5 )
live_method = 'live_open'
class Welcome(Page):
form_model = 'player'
form_fields = ['prolificcode']
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
@staticmethod
def vars_for_template(player: Player):
session = player.session
return dict( bonuspaying = session.config['bonuspaying'] )
class Instructions(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
return player.round_number == 1
@staticmethod
def vars_for_template(player: Player):
session = player.session
return dict( swipe = player.round_number in C.SWIPEROUNDS,
bonuspaying = session.config['bonuspaying'])
class ChangedInstructions(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
return player.round_number == 4
@staticmethod
def vars_for_template(player: Player):
return dict( swipe = player.round_number in C.SWIPEROUNDS )
class PreparationPage(Page):
form_model = 'player'
@staticmethod
def vars_for_template(player: Player):
session = player.session
# set stimulus here because otherwise the data is corrupted if subject refreshes page
stimu = stimulus(player)
player.stimuli = str(stimu)
return dict( practice = player.round_number in C.PRACTICEROUNDS,
swipe = player.round_number in C.SWIPEROUNDS,
notroundthree = player.round_number > 3,
bonuspaying = session.config['bonuspaying'])
class Swipe(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
# this needs a specific list of rounds
return player.round_number in C.SWIPEROUNDS
@staticmethod
def js_vars(player: Player):
import ast
stimu = ast.literal_eval(player.stimuli)
return dict( stimulus = stimu,
practice = player.round_number in C.PRACTICEROUNDS,
howmanyswipes = len(stimu),
choicecounter = player.choicecounter
)
live_method = 'live_open'
class Tap(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
# this needs a specific list of rounds
return player.round_number not in C.SWIPEROUNDS
@staticmethod
def vars_for_template(player: Player):
session = player.session
return dict( pilot = session.config['pilot'] )
@staticmethod
def js_vars(player: Player):
import ast
stimu = ast.literal_eval(player.stimuli)
return dict( stimulus = stimu,
practice = player.round_number in C.PRACTICEROUNDS,
howmanyswipes = len(stimu),
choicecounter = player.choicecounter
)
live_method = 'live_open'
class Results(Page):
form_model = 'player'
@staticmethod
def vars_for_template(player: Player):
direction = ''
if player.direction == 'L':
direction = 'left'
if player.direction == 'R':
direction = 'right'
correct = ''
if player.correct == 1:
correct = 'correct. Well done!'
if player.correct == 0:
correct = 'incorrect.
If the number is larger than 65 the direction is right. If the number is smaller than 65 the direction is left.'
yyy = 0
rts = 0
meanrt = 0
if player.round_number not in C.PRACTICEROUNDS:
# determine responsetimes into one list
x = player.responsetimes
xx = x.split(';')[:-1]
xxx = list(map(float,xx))
rts = xxx
# check that there are no pageloads, if there are then the RT from that swipe is not valid
y = player.pageloads
yy = y.split(';')[:-1]
yyy = list(map(float,yy))
meanrt = 0
for i in range(len(rts)):
if yyy[i] == 0: meanrt += rts[i]
if yyy.count(0)>0:
meanrt = meanrt / (yyy.count(0))
else:
meanrt = 0
meanrt = meanrt / 1000
player.meanrt = meanrt
return dict( practice = player.round_number in C.PRACTICEROUNDS,
swipe = player.round_number in C.SWIPEROUNDS,
direction = direction, correct = correct, meanrt = meanrt, pageloads = str(yyy), rts = rts,
meanrtzero = meanrt == 0
)
class End(Page):
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
return player.round_number == C.NUM_ROUNDS
@staticmethod
def vars_for_template(player: Player):
session = player.session
stimulength = len(stimulus(player))
correctness = 0
meanrt = 0
for i in range(3,C.NUM_ROUNDS+1):
if i!=4 and i!=5:
correctness += player.in_round(i).correct
meanrt += player.in_round(i).meanrt
correctness = correctness / (stimulength*C.BLOCKS)
meanrt = meanrt / C.BLOCKS
#correctness = (player.in_round(3).correct + player.in_round(6).correct + player.in_round(7).correct + player.in_round(8).correct) / (stimulength*4)
#meanrt = (player.in_round(3).meanrt + player.in_round(6).meanrt + player.in_round(7).meanrt + player.in_round(8).meanrt) / 4
# Your correctness is multiplied by £12, and from this we subtract your average time multiplied by £10. Example: if your correctness is 90% and your average time is 0.85 seconds, your bonus is £12×0.9 – £10×0.85 = £2.30.
bonus = max(12*correctness - 10*meanrt, 0)
player.bonus = bonus
return dict( total = 100*correctness,
meanrt = meanrt,
bonus = bonus,
prolificurl = session.config['prolificurl'] )
page_sequence = [MobileCheck, SwipeCheck, Welcome, Instructions, ChangedInstructions, PreparationPage, Swipe, Tap, Results, End]