from otree.api import * import numpy as np import time doc = """ Your app description """ class C(BaseConstants): NAME_IN_URL = 'study1D' PLAYERS_PER_GROUP = None NUM_ROUNDS = 81 # should be 2*num rounds per game + 1 class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): if subsession.round_number == 1: subsession.session.enough_passed = {1:False} subsession.session.actions = {1:{1:[],2:[]}} subsession.session.players_per_role = {1:0, 2:0} pid = 0 for p in subsession.get_players(): p.participant.dropped_tails = False p.participant.num_timeouts = 0 p.participant.prev_partner = -1 pid += 1 p.participant.pid = pid # p.participant.player_role = pid % 2 + 1 p.participant.player_role = -1 # This is just temporary p.participant.round_payoff = [] else: subsession.session.enough_passed[subsession.round_number] = False subsession.session.actions[subsession.round_number] = {1:[],2:[]} for p in subsession.get_players(): p.player_role = p.participant.player_role class Group(BaseGroup): pass class Player(BasePlayer): timeout = models.BooleanField(initial=False) opp_timeout = models.BooleanField(initial=False) other = models.IntegerField(initial=0) round = models.IntegerField() choice = models.StringField(initial='') opp_choice = models.StringField(initial='') player_role= models.IntegerField(initial=0) wait_time = models.FloatField(initial=0) join_time = models.FloatField(initial=0) rand_val = models.FloatField(initial=0) forgone_payoff = models.FloatField(initial=0) forgone_action = models.StringField(initial='') p1_tails = models.IntegerField(initial=0) p1_right = models.IntegerField(initial=0) p1_left = models.IntegerField(initial=0) p1_left_prob = models.FloatField(initial=0) p1_left_otherwise = models.IntegerField(initial=0) p2_tails = models.IntegerField(initial=0) p2_left = models.IntegerField(initial=0) p2_right = models.IntegerField(initial=0) def calc_payoff(player, choice, opp_choice): if player.player_role == 1: if choice == 'TAILS': payoff = player.p1_tails else: if opp_choice == 'RIGHT': payoff = player.p1_right else: if player.rand_val < player.p1_left_prob: payoff = player.p1_left else: payoff = player.p1_left_otherwise elif player.player_role == 2: if opp_choice == 'TAILS': payoff = player.p2_tails else: if choice == 'RIGHT': payoff = player.p2_right else: payoff = player.p2_left return payoff #Calcuate the payoff def set_payoff(player: Player): player.rand_val = np.random.rand() player.payoff = calc_payoff(player, player.choice, player.opp_choice) # action not taken if player.player_role == 1: player.forgone_action = 'TAILS' if player.choice == 'HEADS' else 'HEADS' else: player.forgone_action = 'RIGHT' if player.choice == 'LEFT' else 'LEFT' player.forgone_payoff = calc_payoff(player, player.forgone_action, player.opp_choice) player.participant.round_payoff.append(player.payoff) # if player.player_role == 1: # if player.choice == 'TAILS': # player.payoff = 0 # else: # if player.opp_choice == 'RIGHT': # player.payoff = -5 # else: # if player.rand_val < 0.9: # player.payoff = 5 # else: # player.payoff = -100 # elif player.player_role == 2: # if player.opp_choice == 'TAILS': # player.payoff = -5 # else: # if player.choice == 'RIGHT': # player.payoff = 10 # else: # player.payoff = 5 # PAGES class MyPage(Page): form_model = "player" @staticmethod def is_displayed(player: Player): if player.participant.game_stage == 1: player.p1_tails = player.session.config['p1_tails'] player.p1_right = player.session.config['p1_right'] player.p1_left = player.session.config['p1_left'] player.p1_left_prob = player.session.config['p1_left_prob'] player.p1_left_otherwise = player.session.config['p1_left_otherwise'] player.p2_tails = player.session.config['p2_tails'] player.p2_right = player.session.config['p2_right'] player.p2_left = player.session.config['p2_left'] elif player.participant.game_stage == 2: player.p1_tails = player.session.config['stage2_p1_tails'] player.p1_right = player.session.config['stage2_p1_right'] player.p1_left = player.session.config['stage2_p1_left'] player.p1_left_prob = player.session.config['stage2_p1_left_prob'] player.p1_left_otherwise = player.session.config['stage2_p1_left_otherwise'] player.p2_tails = player.session.config['stage2_p2_tails'] player.p2_right = player.session.config['stage2_p2_right'] player.p2_left = player.session.config['p2_left'] # return True if (player.other != -1): if len(player.group.get_players()) == 2: other_player = player.get_others_in_group()[0] if player.other != other_player.id_in_group: player.other = other_player.id_in_group else: player.other = -1 return player.round_number > 1 @staticmethod def live_method(player: Player, data): if data['type'] == 'act': if player.other != -1: other_player = player.group.get_player_by_id(player.other) other_player.opp_choice = data['choice'] player.choice = data['choice'] player.session.actions[player.round_number][player.player_role].append(player.choice) # if player.choice != "" and player.opp_choice != "" and other_player == -1: # set_payoff(player) if player.choice != "" and player.opp_choice != "": set_payoff(player) set_payoff(other_player) return {player.id_in_group: dict(type='act', choice=player.choice, opp_choice=player.opp_choice, payoff=player.payoff, forgone_action=player.forgone_action, forgone_payoff=player.forgone_payoff), other_player.id_in_group: dict(type='act', choice=other_player.choice, opp_choice=other_player.opp_choice, payoff=other_player.payoff, forgone_action=other_player.forgone_action, forgone_payoff=other_player.forgone_payoff)} elif player.choice != "" and player.other == -1: opp_choice = np.random.choice(player.session.actions[player.round_number][player.player_role % 2 + 1]) player.opp_choice = opp_choice set_payoff(player) return {player.id_in_group: dict(type='act', choice=player.choice, opp_choice=player.opp_choice, payoff=player.payoff, forgone_action=player.forgone_action, forgone_payoff=player.forgone_payoff)} else: return {player.id_in_group: dict(type='act', choice=player.choice, opp_choice=player.opp_choice)} elif data['type'] == 'load': return{player.id_in_group: dict(type='load', choice=player.choice, opp_choice=player.opp_choice, payoff=player.payoff, timeout=player.timeout, opp_timeout=player.opp_timeout, forgone_action=player.forgone_action, forgone_payoff=player.forgone_payoff)} elif data['type'] == 'timeout': player.payoff = 0 player.participant.num_timeouts += 1 player.timeout = True if player.participant.num_timeouts >= player.session.config['max_timeouts']: player.participant.dropped_tails = True if player.other != -1: other_player = player.group.get_player_by_id(player.other) other_player.payoff = 10 other_player.opp_timeout = True return{player.id_in_group: dict(type='timeout', num_timeouts=player.participant.num_timeouts), other_player.id_in_group: dict(type='opp_timeout', payoff=10)} else: return {player.id_in_group: dict(type='timeout', num_timeouts=player.participant.num_timeouts)} elif data['type'] == 'opp_timeout': player.payoff = 10 player.opp_timeout = True return{player.id_in_group: dict(type='opp_timeout', payoff=player.payoff)} @staticmethod def vars_for_template(player: Player): if player.player_role == 1: opt1 = "HEADS" opt2 = "TAILS" elif player.player_role == 2: opt1 = "LEFT" opt2 = "RIGHT" elif player.player_role == -1: opt1 = "WRONG" opt2 = "WRONG" return dict( player_role=player.player_role, opt1=opt1, opt2=opt2, ghost=player.other == -1, ) class DropOut(Page): @staticmethod def is_displayed(player: Player): return player.participant.dropped_tails class MatchWaitPage(WaitPage): group_by_arrival_time = True # body_text = "Waiting to be matched with another player. Remember that you are being paid for the time spent on this waiting page, so please be patient." template_name = 'oneshotDisaster/MyWaitPage.html' class GameChange(Page): @staticmethod def is_displayed(player: Player): if player.subsession.round_number == (C.NUM_ROUNDS -1)/2 +1: player.participant.vars['game_stage'] = 2 return True else: return False def group_by_arrival_time_method(subsession: Subsession, waiting_players): return_players = [] for p in waiting_players: if p.join_time == 0: p.join_time = time.time() # if subsession.round_number == 1: for p in waiting_players: if p.participant.player_role == -1: num_p1 = subsession.session.players_per_role[1] num_p2 = subsession.session.players_per_role[2] if num_p1 <= num_p2: p.participant.player_role = 1 subsession.session.players_per_role[1] += 1 else: p.participant.player_role = 2 subsession.session.players_per_role[2] += 1 p.player_role = p.participant.player_role p1s = list(filter(lambda p: p.participant.player_role == 1, waiting_players)) p2s = list(filter(lambda p: p.participant.player_role == 2, waiting_players)) if (len(p1s) >= subsession.session.config['min_group_size']/2) and (len(p2s) >= subsession.session.config['min_group_size']/2): subsession.session.enough_passed[subsession.round_number] = True if subsession.session.enough_passed[subsession.round_number]: if len(waiting_players) >= 2: iterations = 1 if (len(p1s) == 1) and (len(p2s) == 1) and (p1s[0].participant.prev_partner != p2s[0].participant.pid) and (p2s[0].participant.prev_partner != p1s[0].participant.pid): return_players = [p1s[0], p2s[0]] elif len(p1s) == 2 and len(p2s) == 2: to_match = True while to_match: if iterations > 100: return_players = [] to_match = False break np.random.shuffle(waiting_players) np.random.shuffle(p1s) np.random.shuffle(p2s) iterations += 1 if (p1s[0].participant.prev_partner != p2s[0].participant.pid) and (p2s[0].participant.prev_partner != p1s[0].participant.pid): return_players = [p1s[0], p2s[0]] to_match = False elif (len(p1s) >= 1) and (len(p2s) >= 1): to_match = True while to_match: if iterations > 100: return_players = [] to_match = False break np.random.shuffle(waiting_players) np.random.shuffle(p1s) np.random.shuffle(p2s) iterations += 1 # see if the first p1 and first p2 have played together in the previous round if p1s[0].participant.prev_partner != p2s[0].participant.pid: return_players = [p1s[0], p2s[0]] to_match = False elif subsession.round_number ==1: return_players = [waiting_players[0]] elif len(waiting_players) >= 1: p = waiting_players[0] if (time.time() - p.join_time > subsession.session.config['single_wait_time']) and (len(subsession.session.actions[subsession.round_number][1]) > 0) and (len(subsession.session.actions[subsession.round_number][2]) > 0): return_players = [p] p.other = -1 if len(return_players) == 2: return_players[0].participant.prev_partner = return_players[1].participant.pid return_players[1].participant.prev_partner = return_players[0].participant.pid return_players[0].other = return_players[1].id_in_group return_players[1].other = return_players[0].id_in_group elif len(return_players) == 1: return_players[0].participant.prev_partner = -1 return_players[0].other = -1 if len(return_players) > 0: for p in return_players: p.wait_time = time.time() - p.join_time p.participant.tot_wait_time += p.wait_time print(return_players) return return_players page_sequence = [MatchWaitPage, MyPage, GameChange]