from otree.api import * from json import dumps as json_dumps, loads as json_loads from decimal import * doc = """ Wait page implemented from scratch, using live pages. """ class C(BaseConstants): NAME_IN_URL = 'wait_page' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 GROUP_SIZE = 5 CRIT_NUM_CAT_ONE = 8 TIME_CAT_ONE = 3*60 # 2.5min from first player entering page. CRIT_NUM_CAT_TWO = 5 TIME_CAT_TWO = 5*60 DROP_OUT_OFFER_TIME = 4*60 # make it equal with Cat_TWO? but offer comes after personal time not group time class Subsession(BaseSubsession): pass class Group(BaseGroup): first_time = models.FloatField() class Player(BasePlayer): is_active = models.BooleanField(initial=False) proceed = models.BooleanField(initial=False) long_wait = models.BooleanField(initial=False) inactive = models.BooleanField(initial=True) free_exit = models.BooleanField(initial=False) excluded = models.BooleanField(initial=False) end_experiment = models.BooleanField(initial=False) waiting_earnings = models.IntegerField(initial=0) earnings_part2 = models.FloatField() def group_method(player: Player, data): # ----------------------- 1. Define Variables for Function ------------------------ # import random import time players = player.group.get_players() participant = player.participant # select active players active_ids = [p.id_in_subsession for p in players if p.is_active] active_players = [p for p in players if p.is_active] # define time of first person for determining how long the waitpage has been active overall if len(active_ids) == 1: player.group.first_time = participant.wait_page_arrival if player.group.first_time > 0: duration_waiting_page = time.time() - player.group.first_time # ----------------------- 2. Description of this Part ------------------------ # # If the number of potentially still available people is too low, we can end the waiting # Who is potentially eligible for grouping? # 1. All those who have began the quiz but not finished it yet, i.e., pipeline_players progress = [p.participant.progress for p in players] print(progress) pipeline_players = [p for p in players if 1 < p.participant.progress < 5] print(len(pipeline_players)) # 2. All those players who are on the waitpage but have neither proceeded, exited, or timed out stepone = [p for p in players if not (p.free_exit or p.proceed or (p.inactive and p.participant.progress == 5))] # if want to exclude inactive players: or (p.inactive and p.participant.progress == 5) players_on_waitpage = [p for p in stepone if p not in pipeline_players] print(len(players_on_waitpage)) # Merging these two lists: eligible_players = players_on_waitpage + pipeline_players if len(eligible_players) < C.GROUP_SIZE: for p in eligible_players: p.end_experiment = True # Define a dictionary of results so that the if conditions can run independent from each other results = {} # If Participant waited for long, we can offer to get out: if time.time() - participant.wait_page_arrival > C.DROP_OUT_OFFER_TIME: player.long_wait = True num_of_eligible_players = str(len(eligible_players)-1) num_of_quiz_players = str(len(pipeline_players)) num_of_wait_players = str(len(players_on_waitpage)-1) results[player.id_in_group] = {'long': True, 'eligible': num_of_eligible_players, 'pipeline': num_of_quiz_players, 'waiting': num_of_wait_players} # ----------------------- 2.b. Conditions for Grouping ------------------------ # # Implementing the first rule for proceeding if len(active_ids) >= C.CRIT_NUM_CAT_ONE and duration_waiting_page <= C.TIME_CAT_ONE: selected_players = random.sample(active_players, C.GROUP_SIZE) for p in selected_players: p.proceed = True p.is_active = False # otherwise they would be considered as still actively waiting results[p.id_in_group] = {'finished': True} # # Implementing the second rule for proceeding # elif len(active_ids) >= C.CRIT_NUM_CAT_TWO and (C.TIME_CAT_ONE < duration_waiting_page <= C.TIME_CAT_TWO): # selected_players = random.sample(active_players, C.GROUP_SIZE) # for p in selected_players: # p.proceed = True # p.is_active = False # results[p.id_in_group] = {'finished': True} # Implementing the third rule for proceeding elif len(active_ids) >= C.GROUP_SIZE and C.TIME_CAT_ONE < duration_waiting_page: selected_players = random.sample(active_players, C.GROUP_SIZE) for p in selected_players: p.proceed = True p.is_active = False results[p.id_in_group] = {'finished': True} print(results) return results class ScratchWaitPage(Page): @staticmethod def is_displayed(player: Player): return not player.proceed @staticmethod def live_method(player: Player, data): print(player.id_in_group) # get information on the activity of player on the wait page if data.get('active_time') is not None: active_time = data.get('active_time') print(active_time) player.waiting_earnings = int(active_time/20) player.participant.waiting_earnings = player.waiting_earnings print('Time Earnings') print(player.participant.waiting_earnings) if data.get('type') == 'participant_active': player.is_active = True player.inactive = False print(player.is_active) elif data.get('type') == 'participant_passive': player.is_active = False print(player.is_active) # Mark people as inactive elif data.get('type') == 'inactive': player.inactive = True # Indicate someone left the experiment if data.get('type') == 'exit': player.free_exit = True player.is_active = False player.inactive = False # Indicate someone was excluded from experiment due to inactivity if data.get('type') == 'excluded': player.excluded = True player.is_active = False player.inactive = False return group_method(player, data) class FreeExit(Page): @staticmethod def is_displayed(player: Player): return player.free_exit @staticmethod def vars_for_template(player: Player): participation_reward = player.session.config['participation_reward']*0.5 part1_reward = 1 player.earnings_part2 = player.waiting_earnings * 0.05 + participation_reward total_earnings = player.waiting_earnings * 0.05 + participation_reward + part1_reward return dict( wait_earnings=format(Decimal(player.waiting_earnings) * Decimal(0.05), '.2f'), participation_reward=format(Decimal(participation_reward), '.2f'), part1_reward=format(Decimal(part1_reward), '.2f'), total_earnings=format(Decimal(total_earnings), '.2f') ) class Excluded(Page): @staticmethod def is_displayed(player: Player): return player.excluded @staticmethod def vars_for_template(player: Player): return dict( wait_earnings=format(Decimal(player.waiting_earnings) * Decimal(0.05), '.2f') ) class Intro(Page): pass page_sequence = [ScratchWaitPage, FreeExit, Excluded]