from otree.api import * c = cu doc = '' class C(BaseConstants): # built-in constants NAME_IN_URL = 'sequentialsearch_live' PLAYERS_PER_GROUP = None NUM_ROUNDS = 76 # user-defined constants NFCLONG = 0 HIVALUE = 30 BASELINEROUNDS = 6 MIDROUND = 80 PRACTICEROUNDS = 5 DISCOUNTFACTOR = 0 DISPLAYALGO = True class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): session = subsession.session # https://otree.readthedocs.io/en/latest/treatments.html#creating-session if subsession.round_number == 1: import itertools import random # create 5 different random orderings indices1 = [i for i in range(1,C.NUM_ROUNDS+1)] random.shuffle(indices1) indices2 = [i for i in range(1,C.NUM_ROUNDS+1)] random.shuffle(indices2) indices3 = [i for i in range(1,C.NUM_ROUNDS+1)] random.shuffle(indices3) indices4 = [i for i in range(1,C.NUM_ROUNDS+1)] random.shuffle(indices4) indices5 = [i for i in range(1,C.NUM_ROUNDS+1)] random.shuffle(indices5) # cycle the players between the random orderings index_cycle = itertools.cycle([indices1,indices2,indices3,indices4,indices5]) for player in subsession.get_players(): participant = player.participant participant.indexlist = next(index_cycle) class Group(BaseGroup): pass class Player(BasePlayer): boxvalues = models.StringField() chosencard = models.IntegerField(initial=0) stopped = models.IntegerField(initial=0) chosenboxvalue = models.FloatField(initial=0) recommendation = models.IntegerField() reachedend = models.IntegerField(initial=0) prolificcode = models.StringField(blank=True) reward = models.FloatField(initial=0) bonus = models.FloatField() dectimes = models.StringField(initial='') polyareas = models.StringField(initial='') startleft = models.StringField(initial='') touchtimes = models.StringField(initial='') xs = models.StringField(initial='') ys = models.StringField(initial='') dohmen = models.IntegerField(choices=[[1, '1 = not at all willing to take risks'], [2, '2'], [3, '3'], [4, '4'], [5, '5'], [6, '6'], [7, '7'], [8, '8'], [9, '9'], [10, '10 = very willing to take risks']], widget=widgets.RadioSelect) nfcscore = models.IntegerField() nfcall = models.StringField() affectbased = models.IntegerField(choices=[[1, '1 = Strongly disagree'], [2, '2 = Moderately disagree'], [3, '3 = Slightly disagree'], [4, '4 = Slightly agree'], [5, '5 = Moderately agree'], [6, '6 = Strongly agree']], label='

I solved the task on a gut level

', widget=widgets.RadioSelect) deliberative = models.IntegerField(choices=[[1, '1 = Strongly disagree'], [2, '2 = Moderately disagree'], [3, '3 = Slightly disagree'], [4, '4 = Slightly agree'], [5, '5 = Moderately agree'], [6, '6 = Strongly agree']], label='

I tried to solve the task mathematically

', widget=widgets.RadioSelect) handedness = models.StringField(choices=[['Right hand', 'Right hand'], ['Left hand', 'Left hand']], label='

Which hand do you typically use to browse your mobile device?

', widget=widgets.RadioSelect) computer_randomround1 = models.IntegerField() computer_randomround2 = models.IntegerField() checkup1 = models.IntegerField(choices=[[2, 'Points earned from all rounds'], [1, 'Points earned from two randomly chosen rounds'], [3, 'Points of random box values']], label='

My bonus payments from the study consists of ...

', widget=widgets.RadioSelect) checkup2 = models.IntegerField(choices=[[1, 'Then the last box will determine my points'], [2, 'Then my points will be zero'], [3, 'Then my points will be randomly determined']], label='

If I swipe left through all boxes, ...

', widget=widgets.RadioSelect) checkup3 = models.IntegerField(choices=[[1, 'The box values change randomly from round to another'], [2, 'The box values are always the same between rounds, only their order changes']], label='

Which of the below statements is true?

', widget=widgets.RadioSelect) 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 starttime = data['starttime'] # this is a timestamp recorded on page, indicating when box moving started opened = data['opened'] # this is zero if swiped right player.chosencard += opened player.dectimes += str(int(time) - int(starttime)) + ';' player.xs += data['x'] + ';' player.ys += data['y'] + ';' # this should correspond to decision times player.touchtimes += touchtimes + ';' player.startleft += data['startleft'] + ';' # this is recorded just in case the polyarea calculations are wrong ### 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) + ';' response = {'openedcards': player.chosencard} # how many cards have been opened so far return {player.id_in_group: response} # this has been changed from 0: response -- reason for blinking?? def checkup1_error_message(player: Player, value): if value != 1: return "Your answer is wrong. Hint: as the instructions state, two rounds will be randomly selected at the end of the study to determine the bonus." def checkup2_error_message(player: Player, value): if value != 1: return "Your answer is wrong. Hint: as the instructions state, if you do not choose any box then the last box will determine your points." def checkup3_error_message(player: Player, value): if value != 1: return "Your answer is wrong. Hint: as the instructions state, the box values are randomly generated on each new round." 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 Welcome(Page): form_model = 'player' form_fields = ['prolificcode'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Middle(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == C.MIDROUND class Instructions(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number <= C.NUM_ROUNDS - C.BASELINEROUNDS @staticmethod def vars_for_template(player: Player): session = player.session return dict( boxes = session.config['boxes'], fullinfo = session.config['fullinfo'], recommendation = player.round_number >= C.MIDROUND and C.DISPLAYALGO, practiceround = player.round_number <= C.PRACTICEROUNDS or (player.round_number >= C.MIDROUND and player.round_number <= C.MIDROUND + C.PRACTICEROUNDS - 1), rounds = C.NUM_ROUNDS - C.BASELINEROUNDS, realround = player.round_number == C.PRACTICEROUNDS + 1, discount = C.DISCOUNTFACTOR > 0, discountpercent = C.DISCOUNTFACTOR*100, ) class Decision(Page): form_model = 'player' form_fields = ['reachedend'] @staticmethod def is_displayed(player: Player): return player.round_number <= C.NUM_ROUNDS - C.BASELINEROUNDS @staticmethod def vars_for_template(player: Player): session = player.session # create the number of boxes opened for js_vars (same as blockround in the old app) import random if player.field_maybe_none('boxvalues') == None: values = [round(random.uniform(0,C.HIVALUE),1) for i in range(session.config['boxes'])] player.boxvalues = str(values).split('[')[1].split(']')[0] import ast values = player.boxvalues #ast.literal_eval(player.boxvalues) valuess = ast.literal_eval(player.boxvalues) N = session.config['boxes'] V = [0] * (N+1) V[N] = 0 for i in range(N-1, -1, -1): cumprob = V[i+1] / C.HIVALUE conditionaldensity = (C.HIVALUE*C.HIVALUE - (V[i+1])*(V[i+1])) / (2*C.HIVALUE) V[i] = conditionaldensity + V[i+1] * cumprob for j in range(len(valuess)): if valuess[j] > V[j]: break recommendation = j + 1 player.recommendation = recommendation return dict( values = values, #str(values).split('[')[1].split(']')[0], pilot = session.config['pilot'], fullinfo = session.config['fullinfo'], recommendation = recommendation ) @staticmethod def js_vars(player: Player): session = player.session boxes = session.config['boxes'] return dict( fullinfo = session.config['fullinfo'], openedcards = player.chosencard, # this is only used to display the initial row (NOT NEEDED IN js_vars?) roundi = player.round_number, boxes = boxes, showrecommendation = True# C.DISCOUNTFACTOR and player.round_number >= C.MIDROUND ) @staticmethod def before_next_page(player: Player, timeout_happened): session = player.session # add 1 to chosencard because unlike in CCT, here the player chooses the box that she swipes right player.chosencard += 1 if player.reachedend == 1: # if ended round by opening the last box player.chosencard = session.config['boxes'] import ast boxvalues = ast.literal_eval(player.boxvalues) player.chosenboxvalue = boxvalues[player.chosencard-1] live_method = 'live_open' class RoundEnd(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number <= C.NUM_ROUNDS - C.BASELINEROUNDS @staticmethod def vars_for_template(player: Player): session = player.session reached = "" if player.reachedend == 1: reached = "You reached the end without stopping. Therefore your chosen box was the last box." if player.round_number <= C.PRACTICEROUNDS: player.reward = 0 elif player.round_number == C.PRACTICEROUNDS + 1: # first round to pay reward player.reward = player.chosenboxvalue elif player.round_number >= C.MIDROUND and player.round_number <= C.MIDROUND + C.PRACTICEROUNDS - 1: prev_player = player.in_round(player.round_number - 1) player.reward = round(prev_player.reward + 0, 1) # do not accumulate reward from middle practice rounds else: prev_player = player.in_round(player.round_number - 1) player.reward = round(prev_player.reward + player.chosenboxvalue, 1) # accumulate reward # create the table of block results tabledata = "" if player.round_number <= C.PRACTICEROUNDS or (player.round_number >= C.MIDROUND and player.round_number <= C.MIDROUND + C.PRACTICEROUNDS - 1): practiceround = 1 else: practiceround = 0 for j in range(player.round_number): if (j+1 > C.PRACTICEROUNDS and j+1 < C.MIDROUND) or (j+1 > C.MIDROUND + C.PRACTICEROUNDS - 1): j_player = player.in_round(j+1) points = round((1/pow(1+C.DISCOUNTFACTOR,j_player.chosencard))*(j_player.chosenboxvalue),1) tabledata += "" + str(j_player.round_number) + "" + str(j_player.chosencard) + "" + str(points) + "" return dict( reached = reached, practiceround = practiceround, notpracticeround = practiceround == 0, tabledata = tabledata, pilot = session.config['pilot'], points = round((1/pow(1+C.DISCOUNTFACTOR,player.chosencard))*player.chosenboxvalue,1), discount = C.DISCOUNTFACTOR > 0 ) @staticmethod def js_vars(player: Player): session = player.session boxes = session.config['boxes'] import ast values = ast.literal_eval(player.boxvalues) return dict( values = values, openedcards = player.chosencard, # this is only used to display the initial row of cards roundi = player.round_number, boxes = boxes #wasclose = player.chosenboxvalue <= 3 ) class Checkup(Page): form_model = 'player' form_fields = ['checkup1', 'checkup2', 'checkup3'] @staticmethod def is_displayed(player: Player): return player.round_number == 5 class BaselineRoundStart(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 1 + C.NUM_ROUNDS - C.BASELINEROUNDS class BaselineDecision(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number > C.NUM_ROUNDS - C.BASELINEROUNDS @staticmethod def vars_for_template(player: Player): if player.round_number % 2 == 0: leftright = 'left' else: leftright = 'right' return dict( leftright = leftright ) @staticmethod def js_vars(player: Player): if player.round_number % 2 == 0: left = 1 else: left = 0 return dict( left = left, boxes = 10 ) live_method = 'live_open' class NFC(Page): form_model = 'player' form_fields = ['dohmen', 'nfcscore', 'nfcall', 'affectbased', 'deliberative', 'handedness'] @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS @staticmethod def js_vars(player: Player): group = player.group import random nfcorder = [ ['nfc1','I think that having clear rules and order at work is essential for success.'], ['nfc2','Even after I have made up my mind about something, I am always eager to consider a different opinion.'], ['nfc3','I do not like situations that are uncertain.'], ['nfc4','I dislike questions which could be answered in many different ways.'], ['nfc5','I like to have friends who are unpredictable.'], ['nfc6','I find that a well ordered life with regular hours suits my temperament.'], ['nfc7','When dining out, I like to go to places where I have been before so that I know what to expect.'], ['nfc8','I feel uncomfortable when I do not understand the reason why an event occurred in my life.'], ['nfc9','I feel irritated when one person disagrees with what everyone else in a group believes.'], ['nfc10','I hate to change my plans at the last minute.'], ['nfc11','I do not like to go into a situation without knowing what I can expect from it.'], ['nfc12','When I have made a decision, I feel relieved.'], ['nfc13','When I am confronted with a problem, I''m dying to reach a solution very quickly.'], ['nfc14','When I am confused about an important issue, I feel very upset.'], ['nfc15','I would quickly become impatient and irritated if I would not find a solution to a problem immediately.'], ['nfc16','I would rather make a decision quickly than sleep over it.'], ['nfc17','Even if I get a lot of time to make a decision, I still feel compelled to decide quickly.'], ['nfc18','I think it is fun to change my plans at the last moment.'], ['nfc19','I enjoy the uncertainty of going into a new situation without knowing what might happen.'], ['nfc20','My personal space is usually messy and disorganised.'], ['nfc21','In most social conflicts, I can easily see which side is right and which is wrong.'], ['nfc22','I almost always feel hurried to reach a decision, even when there is no reason to do so.'], ['nfc23','I believe that orderliness and organisation are among the most important characteristics of a good student.'], ['nfc24','When considering most conflict situations, I can usually see how both sides could be right.'], ['nfc25','I do not like to be with people who are capable of unexpected actions.'], ['nfc26','I prefer to socialise with familiar friends because I know what to expect from them.'], ['nfc27','I think that I would learn best in a class that lacks clearly stated objectives and requirements.'], ['nfc28','When thinking about a problem, I consider as many different opinions on the issue as possible.'], ['nfc29','I like to know what people are thinking all the time.'], ['nfc30','I dislike it when a person''s statement could mean many different things.'], ['nfc31','It is annoying to listen to someone who cannot seem to make up his or her mind.'], ['nfc32','I find that establishing a consistent routine enables me to enjoy life more.'], ['nfc33','I enjoy having a clear and structured mode of life.'], ['nfc34','I prefer interacting with people whose opinions are very different from my own.'], ['nfc35','I like to have a place for everything and everything in its place.'], ['nfc36','I feel uncomfortable when someone''s meaning or intention is unclear to me.'], ['nfc37','I always see many possible solutions to problems I face.'], ['nfc38','I would rather know bad news than stay in a state of uncertainty.'], ['nfc39','I do not usually consult many different opinions before forming my own view.'], ['nfc40','I dislike unpredictable situations.'], ['nfc41','I dislike the routine aspects of my work (studies).'] ] random.shuffle(nfcorder) # NFC Short includes nfc3, nfc4, nfc6, nfc8, nfc9, nfc11, nfc12, nfc13, nfc15, nfc25, nfc30, nfc32, nfc33, nfc39, nfc40 # N = not reversed, R = reversed reversedscoring = ['N','R','N','N','R','N','N','N','N','N','N','N','N','N','N','N','N','R','R','R','N','N','N','R','N','N','R','R','N','N','N','N','N','R','N','N','R','N','N','N','R'] # the varianle player.nfcall returns the list of answers in the order nfc1, nfc2, nfc3, ... so with reverse scoring included # subscales a,b,c,d,e subscales = ['a','e','d','e','b','a','b','d','e','a','b','c','c','d','c','c','c','b','b','a','d','c','a','e','b','b','a','e','d','d','d','a','a','e','a','d','e','d','e','b','a'] responses = [' 1 = Strongly disagree',' 2 = Moderately disagree',' 3 = Slightly disagree',' 4 = Slightly agree', ' 5 = Moderately agree', ' 6 = Strongly agree'] return dict( nfcorder = nfcorder, reversedscoring = reversedscoring, responses = responses, subscales = subscales, nfclong = C.NFCLONG ) class FinalPage(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 # randomly choose two rounds import random if player.field_maybe_none('computer_randomround1') == None: if C.DISCOUNTFACTOR==0: # in the discount factor study there are no algorithmic recommendations player.computer_randomround1 = random.randrange(C.PRACTICEROUNDS+1, C.MIDROUND) player.computer_randomround2 = random.randrange(C.MIDROUND + C.PRACTICEROUNDS, C.NUM_ROUNDS - C.BASELINEROUNDS + 1) else: player.computer_randomround1 = random.randrange(C.PRACTICEROUNDS+1, C.PRACTICEROUNDS + 37) player.computer_randomround2 = random.randrange(C.PRACTICEROUNDS + 37, C.NUM_ROUNDS - C.BASELINEROUNDS + 1) randomround1 = player.computer_randomround1 randomround2 = player.computer_randomround2 player1 = player.in_round(randomround1) player2 = player.in_round(randomround2) #reward = player.in_round(player.round_number - C.BASELINEROUNDS).reward round1 = round((1/pow(1+C.DISCOUNTFACTOR,player1.chosencard))*(player1.chosenboxvalue), 1) round2 = round((1/pow(1+C.DISCOUNTFACTOR,player2.chosencard))*(player2.chosenboxvalue), 1) player.bonus = round((round1+round2)/10, 2) tabledata = '' for j in range(player.round_number - C.BASELINEROUNDS): if (j+1 > C.PRACTICEROUNDS and j+1 < C.MIDROUND) or (j+1 > C.MIDROUND + C.PRACTICEROUNDS - 1): j_player = player.in_round(j+1) points = round((1/pow(1+C.DISCOUNTFACTOR,j_player.chosencard))*(j_player.chosenboxvalue),1) tabledata += "" + str(j_player.round_number) + "" + str(j_player.chosencard) + "" + str(points) + "" return dict( prolificurl = session.config['prolificurl'], tabledata = tabledata ) page_sequence = [MobileCheck, Welcome, Middle, Instructions, Decision, RoundEnd, Checkup, BaselineRoundStart, BaselineDecision, NFC, FinalPage]