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]