from otree.api import * c = cu doc = '' class C(BaseConstants): NAME_IN_URL = 'columbiacardtask' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1728 NUM_BLOCKS = 54 BOXES = 32 LOSSCARDS = (1, 2, 3) GAINAMOUNTS = (10, 10, 10, 20, 20, 20, 30, 30, 30) LOSSAMOUNTS = (250, 250, 250, 250, 250, 250, 250, 250, 250, 500, 500, 500, 500, 500, 500, 500, 500, 500, 750, 750, 750, 750, 750, 750, 750, 750, 750) NFCLONG = 0 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_BLOCKS+1)] random.shuffle(indices1) indices2 = [i for i in range(1,C.NUM_BLOCKS+1)] random.shuffle(indices2) indices3 = [i for i in range(1,C.NUM_BLOCKS+1)] random.shuffle(indices3) indices4 = [i for i in range(1,C.NUM_BLOCKS+1)] random.shuffle(indices4) indices5 = [i for i in range(1,C.NUM_BLOCKS+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): prolificcode = models.StringField() block = models.IntegerField(initial=1) round_in_block = models.IntegerField(initial=1) blockends = models.IntegerField(initial=0) blocklosscards = models.IntegerField() blockgain = models.IntegerField() blockloss = models.IntegerField() firstlosscard = models.IntegerField() losscardlist = models.StringField() accept = models.IntegerField() gaincardsturned = models.IntegerField() blockendround = models.IntegerField() y = models.StringField(blank=True) x = models.StringField(blank=True) reward = models.FloatField() jsdectime_start = models.FloatField(blank=True) jsdectime_end = models.FloatField(blank=True) jsdectime_touches = models.StringField(blank=True) jsdectime_firsttouch = models.FloatField(blank=True) dectime = models.FloatField() lookingtime = models.FloatField() 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() nfcscales = models.StringField() nfcall = models.StringField() browser = models.StringField(blank=True, initial='') screenwidth = models.IntegerField(blank=True, initial=0) screenheight = models.IntegerField(blank=True, initial=0) ismobile_touchstart = models.IntegerField(blank=True, initial=0) computer_randomblock1 = models.IntegerField() computer_randomblock2 = models.IntegerField() randomprofit1 = models.IntegerField() randomprofit2 = models.IntegerField() checkup1 = models.StringField(choices=[['2', 'Profits earned from all blocks'], ['1', 'Profits earned from two randomly selected blocks after the practice blocks'], ['3', 'Profits earned from the highest profit blocks']], label='

My bonus payments for the study consist of ...

', widget=widgets.RadioSelect) checkup2 = models.StringField(choices=[['2', 'True'], ['1', 'False']], label='

The order in which candidates are presented is the same in all blocks

', widget=widgets.RadioSelect) checkup3 = models.StringField(choices=[['1', 'Then the same candidate will not be shown again in the same block'], ['2', 'Then the same candidate may be shown again in the same block']], label='

If I reject a candidate, ...

', widget=widgets.RadioSelect) polyarea = models.FloatField() profit = models.IntegerField() def checkup1_error_message(player: Player, value): if value != '1': return "Your answer is wrong. Hint: as the instructions state, two blocks will be randomly selected at the end of the study and the profits from these blocks paid to you." def checkup2_error_message(player: Player, value): if value != '1': return "Your answer is wrong. The order in which candidates are presented is randomised in all blocks and therefore is different between blocks." def checkup3_error_message(player: Player, value): if value != '1': return "Your answer is wrong. After rejecting a candidate there will not be another opportunity to evaluate the candidate in the same block." def poly_area(player: Player): x = player.x y = player.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] < 237: if x[jj] >= x[0]-150: xy.append([x[jj],y[jj]]) jjj = jj # stopping index else: if x[jj] < x[0]+150: xy.append([x[jj],y[jj]]) jjj = jj # stopping index # close the curve to determine the polyarea # startleft value (initial position of the grey bid box) is 237, is this across all devices? if not, # then needs to be returned by the app # bid is accepted or rejected when its x-coordinate is 150 px from startleft 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 """ Calculates polygon area. """ # checked 29/12/2022 => OK! 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]) return -0.5*s class Mobilecheck(Page): form_model = 'player' form_fields = ['ismobile_touchstart', 'screenheight', 'screenwidth', 'browser'] @staticmethod def is_displayed(player: Player): session = player.session return player.round_number == 1 and session.config['pilot'] == 0 class Welcome(Page): form_model = 'player' form_fields = ['prolificcode'] @staticmethod def is_displayed(player: Player): session = player.session return player.round_number == 1 and session.config['pilot'] == 0 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( icons = session.config['icons'] ) class BlockStart(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): if player.round_number > 1: prev_player = player.in_round(player.round_number - 1) if prev_player.blockends == 1: display = True else: display = False else: display = True return display @staticmethod def vars_for_template(player: Player): session = player.session participant = player.participant # player.block is set on Decision page if player.round_number == 1: blockk = 1 else: prev_player = player.in_round(player.round_number - 1) blockk = prev_player.block + 1 # THE FOLLOWING VARIABLES ARE SET ONLY AT THE START OF THE BLOCK # AND THEN ON Decision PAGE COPIED FROM PREVIOUS ROUND IN EACH ROUND OF THE BLOCK # determine the amount of loss cards, gainamount, and lossamount trialnumber = participant.indexlist[blockk-1] # vector of how many loss cards in each block [1,2,3,1,2,3,1,2,3,...] losscardsvec = int(C.NUM_BLOCKS / len(C.LOSSCARDS)) * C.LOSSCARDS # how many loss cards in this specific block player.blocklosscards = losscardsvec[trialnumber - 1] # which specific card numbers are loss cards in this block import random numbs = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32] losscardlist = [] num1 = random.choice(numbs) losscardlist.append(num1) numbs.remove(num1) if (player.blocklosscards >= 2): num2 = random.choice(numbs) losscardlist.append(num2) numbs.remove(num2) if (player.blocklosscards == 3): num3 = random.choice(numbs) losscardlist.append(num3) # which one is the first loss card because the block ends in this card player.firstlosscard = min(losscardlist) player.losscardlist = str(losscardlist) # gain amount in this block based on trialnumber (excel file) 10,10,10,20,20,20,30,30,30,10,10,10,... gainsvec = int(C.NUM_BLOCKS / len(C.GAINAMOUNTS)) * C.GAINAMOUNTS player.blockgain = gainsvec[trialnumber - 1] # loss amount in this block based on trialnumber (excel file) 250,250,250,250,250,250,250,250,250,500,500,... lossvec = int(C.NUM_BLOCKS / len(C.LOSSAMOUNTS)) * C.LOSSAMOUNTS player.blockloss = lossvec[trialnumber - 1] return dict(blockk = blockk, pilot = session.config['pilot'], icons = session.config['icons']) class Decision(Page): form_model = 'player' form_fields = ['y', 'x', 'jsdectime_start', 'jsdectime_end', 'jsdectime_touches', 'jsdectime_firsttouch', 'accept'] @staticmethod def vars_for_template(player: Player): session = player.session # assign block number for rounds > 1 if player.round_number > 1: prev_player = player.in_round(player.round_number - 1) if prev_player.blockends == 1: player.block = prev_player.block + 1 else: player.block = prev_player.block # assign player.round_in_block if player.round_number == 1: player.round_in_block = 1 else: prev_player = player.in_round(player.round_number - 1) if prev_player.blockends == 1: player.round_in_block = 1 else: player.round_in_block = prev_player.round_in_block + 1 # assign number of loss cards for this block # and the specific cards that are loss cards out of the 32 cards if player.round_in_block > 1: prev_player = player.in_round(player.round_number - 1) player.blocklosscards = prev_player.blocklosscards player.firstlosscard = prev_player.firstlosscard player.losscardlist = prev_player.losscardlist player.blockgain = prev_player.blockgain player.blockloss = prev_player.blockloss return dict( pilot = session.config['pilot'], icons = session.config['icons'] ) @staticmethod def js_vars(player: Player): session = player.session # create blockround for js_vars if player.round_number == 1: blockround = 1 else: prev_player = player.in_round(player.round_number - 1) if prev_player.blockends == 1: blockround = 1 else: blockround = prev_player.round_in_block + 1 return dict( blockround = blockround, boxes = C.BOXES, icons = session.config['icons'], losscards = player.blocklosscards ) @staticmethod def before_next_page(player: Player, timeout_happened): # the block can end only if the player stops (accepts) or turns over a loss card # this is used in BlockEnd page if player.accept == 1 or player.round_in_block == player.firstlosscard: player.blockends = 1 player.gaincardsturned = player.round_in_block - 1 if player.round_in_block == player.firstlosscard: player.profit = max(0,player.gaincardsturned * player.blockgain - player.blockloss) else: player.profit = player.gaincardsturned * player.blockgain player.dectime = (player.jsdectime_end - player.jsdectime_firsttouch)/1000 player.lookingtime = (player.jsdectime_firsttouch - player.jsdectime_start)/1000 player.polyarea = poly_area(player) class BlockEnd(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.blockends == 1 @staticmethod def vars_for_template(player: Player): session = player.session player.blockendround = player.round_number # table of block results tabledata = "" for j in range(player.round_number): j_player = player.in_round(j+1) if j_player.blockends == 1: if session.config['icons'] == 'smileys': tabledata += "" + str(j_player.block) + "" + str(j_player.gaincardsturned) + "£" + str(format(j_player.profit, '.2f')) + "" else: if j_player.firstlosscard == j_player.round_in_block and j_player.accept == 0: tabledata += "" + str(j_player.block) + "0" else: tabledata += "" + str(j_player.block) + "" + str(j_player.gaincardsturned) + "" return dict(tabledata = tabledata, icons = session.config['icons']) @staticmethod def js_vars(player: Player): session = player.session # create blockround for js_vars if player.round_number == 1: blockround = 1 else: prev_player = player.in_round(player.round_number - 1) if prev_player.blockends == 1: blockround = 1 else: blockround = prev_player.round_in_block + 1 endedinloss = 0 if blockround == player.firstlosscard and player.accept == 0: endedinloss = 1 losscardlist = player.losscardlist losscardlist = losscardlist[1:] losscardlist = losscardlist[:-1] losscardlist = losscardlist.split(',') return dict(blockround = blockround, boxes = C.BOXES, endedinloss = endedinloss, losscardlist = losscardlist, firstlosscard = player.firstlosscard, icons = session.config['icons']) class RewardPage(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.block == C.NUM_BLOCKS and player.blockends == 1 #return player.block == 6 and player.blockends == 1 @staticmethod def vars_for_template(player: Player): import random blockendrounds = [] for j in range(player.round_number): j_player = player.in_round(j+1) if j_player.field_maybe_none('blockendround') != None: blockendrounds.append(j_player.blockendround) del blockendrounds[0] # remove the first item because this was the practice block del blockendrounds[0] # remove the second item because this was the practice block if player.field_maybe_none('computer_randomblock1') == None and player.field_maybe_none('computer_randomblock2') == None: # randomly chosen rounds randomround1 = random.choice(blockendrounds) blockendrounds.remove(randomround1) # make sure that randomround2 is not the same as randomround1 randomround2 = random.choice(blockendrounds) rewardplayer1 = player.in_round(randomround1) rewardplayer2 = player.in_round(randomround2) player.computer_randomblock1 = rewardplayer1.block player.computer_randomblock2 = rewardplayer2.block player.reward = rewardplayer1.profit + rewardplayer2.profit player.randomprofit1 = rewardplayer1.profit player.randomprofit2 = rewardplayer2.profit profitzero = False if player.reward == 0: profitzero = True return dict( profitzero = profitzero ) class NFC(Page): form_model = 'player' form_fields = ['dohmen', 'nfcscore', 'nfcscales', 'nfcall'] @staticmethod def is_displayed(player: Player): return player.block == C.NUM_BLOCKS and player.blockends == 1 #return player.round_number == 1 @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.block == C.NUM_BLOCKS and player.blockends == 1 @staticmethod def vars_for_template(player: Player): session = player.session return dict( prolificurl = session.config['prolificurl'] ) page_sequence = [Mobilecheck, Welcome, Instructions, BlockStart, Decision, BlockEnd, RewardPage, NFC, FinalPage]