from otree.api import * doc = """ This is an attempt at integrating live pages into the basic cascade environment. """ class Constants(BaseConstants): name_in_url = 'information_cascade_treatment' players_per_group = None num_rounds = 30 instructions_template = 'information_cascade_treatment/instructions.html' max_players = 17 timeout_seconds = 30 earnings_multiplier = 0.25 main_template = __name__ + '/Prediction.html' class Subsession(BaseSubsession): pass class Group(BaseGroup): world_state = models.IntegerField( doc="""The urn can be A(=1) or B(=2), random for each round""" ) string_state = models.StringField() num_decided = models.IntegerField(initial=0) live_player_id = models.IntegerField() class Player(BasePlayer): made_decision = models.BooleanField(initial=False) # Indicates has made choice for calling completed group is_in_queue = models.BooleanField(initial=True) string_choice = models.StringField( doc="""Makes it easier to print prediction""" ) player_id = models.IntegerField( doc="""Draw order in the cascade""" ) color_of_ball_signal = models.IntegerField( doc="""The color of ball drawn (black=A=1 white=B=2)""" ) state_of_world_guess = models.IntegerField( doc="""Selected Urn prediction""", choices=[['1', 'Urn A'], ['2', 'Urn B']], label='Which urn do you think was selected this round?', widget=widgets.RadioSelect, ) earnings_from_guess = models.IntegerField( doc="""Earnings from the IC""" ) # Functions # These two functions come from the trust metric to set the state of the world def creating_session(subsession: Subsession): # Setting the state of the world for g in subsession.get_groups(): import random world_state = random.uniform(1, 2) # Randomly choosing up or down g.world_state = int(round(world_state, 0)) if g.world_state == 1: g.string_state = 'Urn A' else: g.string_state = 'Urn B' from itertools import cycle from random import shuffle ps = subsession.get_players() # Get list of players and shuffle it shuffle(ps) # Change the max to the number of experiment players slots = [*range(1, 17, 1)] shuffled_slots = cycle(slots) # shuffle the slot order for p in ps: # Assign the shuffled players to the shuffled positions p.player_id = next(shuffled_slots) def generate_signal_received(group: Group): import random if group.world_state == 1: # Set proportion of black balls based on experiment signal precision proportion_black = 0.6 else: proportion_black = 0.4 draw_received = random.uniform(0, 1) # Simulating a draw if draw_received <= proportion_black: # Assigning the result based on the defined proportions ball_color = 1 else: ball_color = 2 return ball_color def other_players(player: Player): return sorted(player.get_others_in_group()[0]) def set_payoffs(group: Group): players = group.get_players() for player in players: if player.state_of_world_guess == group.world_state: # Give 1 EU if guessed correctly player.payoff = 1 else: player.payoff = 0 def get_or_none(instance, field): try: return getattr(instance, field) except TypeError: return None def live_method(player: Player, data): import time print('in live_method', data) group = player.group my_id = player.player_id broadcast = {} # this should get run whether the current player is the live one or not live_player_id = get_or_none(group, 'live_player_id') if live_player_id: # test if the other player is expired live_player = group.get_player_by_id(live_player_id) if live_player.expiry < time.time(): live_player.is_live = Falts live_player.timeout_happened = True group.live_player_id = None broadcast[live_player.player_id] = dict(finished=True) if not (player.is_in_queue or player.is_live): return {my_id: dict(finished=True)} if player.is_live and 'state_of_world_guess' in data: player.state_of_world_guess = data['state_of_world_guess'] player.is_in_queue = False player.is_live = False #set the next player group.num_decided += 1 group.live_player_id = None return {0: dict(reload=True)} if player.is_in_queue and get_or_none(group, 'live_player_id') is None: waiting_players = [p for p in group.get_players() if p.is_in_queue] next_player = min(waiting_players, key=lambda p: p.player_id) next_player.expiry = time.time() + Constants.timeout_seconds next_player.is_live = True group.live_player_id = next_player.id_in_group next_player.is_in_queue = False broadcast[next_player.id_in_group] = dict(reload=True) if player.is_live: broadcast[my_id] = dict(signal=player.color_of_ball_signal, is_first=group.num_decided == 0) else: broadcast[my_id] = dict(wait=True) return broadcast # PAGES class BeforeWaitPage(WaitPage): pass class Introduction(Page): @staticmethod def is_displayed(player): # Only show this page the first round return player.round_number == 1 class Ordering(Page): @staticmethod def before_next_page(player: Player, timeout_happened): group = player.group player.color_of_ball_signal = generate_signal_received(group) # Assign ball signal print('set_ball_signal', player.player_id) class IntroductionWaitPage(WaitPage): pass class Guess(Page): @staticmethod def is_displayed(player: Player): return player.is_in_queue or player.is_live @staticmethod def js_vars(player: Player): return dict(timeout_seconds=Constants.timeout_seconds) live_method = live_method class Results(Page): pass class ResultsWaitPage(WaitPage): pass page_sequence = [BeforeWaitPage, Introduction, Ordering, IntroductionWaitPage, Guess, Results, ResultsWaitPage]