import time from otree.api import * author = 'Corey Scheinfeld' doc = """ Your app description """ class Constants(BaseConstants): name_in_url = 'coalition' players_per_group = 3 num_rounds = 4 instructions_template = 'coalition/instructions.html' second_chance = 'coalition/Contract.html' a_role = 'A' b_role = 'B' c_role = 'C' profits = {'AB': 90, 'AC': 70, 'BC': 40, 'ABC': 100, '': 0} class Subsession(BaseSubsession): pass class Group(BaseGroup): partner_match = models.BooleanField(initial=True) matching_contract = models.BooleanField(initial=False) finished_agreement = models.IntegerField(initial=0) num_chances = models.IntegerField(initial=0) profit = models.CurrencyField() class Player(BasePlayer): merged = models.BooleanField(initial=False) complete = models.BooleanField(initial=False) contract = models.StringField(initial='') firmA = models.IntegerField(label="Firm A Merger Profit:", initial=0, min=0) firmB = models.IntegerField(label="Firm B Merger Profit:", initial=0, min=0) firmC = models.IntegerField(label="Firm C Merger Profit:", initial=0, min=0) # FUNCTIONS def creating_session(subsession: Subsession): subsession.group_randomly(fixed_id_in_group=True) def live_agreement(player: Player, data): group = player.group id_in_group = player.id_in_group receive = [1, 2, 3] receive.remove(id_in_group) player.contract = data # moves all players forward after an agreement has been reached between two or more parties player.merged = True if data == 'ABC': msg = 0 else: group.finished_agreement += 1 msg = group.finished_agreement return { receive[0]: msg, receive[1]: msg, } def reset(group: Group): for player in group.get_players(): player.complete = False def chat_nickname(player: Player): return 'Company {}'.format(player.role) def chat_configs(player: Player): configs = [] for other in player.get_others_in_group(): if other.id_in_group < player.id_in_group: lower_id, higher_id = other.id_in_group, player.id_in_group else: lower_id, higher_id = player.id_in_group, other.id_in_group configs.append( { # make a name for the channel that is the same for all # channel members. That's why we order it (lower, higher) 'channel': '{}-{}-{}'.format(player.group.id, lower_id, higher_id), 'label': 'Chat with {}'.format(chat_nickname(other)), } ) return configs # PAGES class Introduction(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def before_next_page(player: Player, timeout_happened): player.participant.expiry = time.time() + 4 * 60 class IntroWait(WaitPage): @staticmethod def after_all_players_arrive(group: Group): pass def get_timeout_seconds(player: Player): participant = player.participant return participant.expiry - time.time() def is_sufficient_time(player: Player): return get_timeout_seconds(player) > 3 class Main(Page): live_method = live_agreement timer_text = 'Time left to complete this section:' get_timeout_seconds = get_timeout_seconds @staticmethod def vars_for_template(player: Player): options = dict(AB='A and B', AC='A and C', BC='B and C', ABC='A, B and C') for key in options: if player.role not in key: options.pop(key) return dict( configs=chat_configs(player), nickname=chat_nickname(player), options=options.items(), ) is_displayed = is_sufficient_time @staticmethod def before_next_page(player: Player, timeout_happened): group = player.group if player.merged: players = player.get_others_in_group() for p in players: if p.merged: if p.contract != player.contract: group.partner_match = False group.matching_contract = False return else: group.partner_match = True def get_form_fields(player: Player): return [f'firm{letter}' for letter in player.contract] class Contract(Page): @staticmethod def is_displayed(player: Player): return is_sufficient_time(player) and player.group.partner_match form_model = 'player' @staticmethod def vars_for_template(player: Player): return dict(profit=Constants.profits[player.contract]) get_form_fields = get_form_fields @staticmethod def error_message(player: Player, values): expected_profit = Constants.profits[player.contract] if sum(values.values()) != expected_profit: return f'The merger profit must total {expected_profit}' timer_text = 'Time left to complete this section:' @staticmethod def get_timeout_seconds(player: Player): return player.participant.expiry - time.time() @staticmethod def before_next_page(player: Player, timeout_happened): group = player.group if player.merged: player.complete = True players = player.get_others_in_group() for p in players: if p.merged and p.complete: if ( (p.firmA == player.firmA) and (p.firmB == player.firmB) and (p.firmC == player.firmC) ): group.matching_contract = True else: group.matching_contract = False group.num_chances += 1 if group.num_chances >= 2: group.matching_contract = False class WaitCheck(WaitPage): title_text = "Contract Finalization" body_text = "Please wait while players finalize their merger agreements." after_all_players_arrive = reset class SecondChance(Page): form_model = 'player' get_form_fields = get_form_fields @staticmethod def before_next_page(player: Player, timeout_happened): group = player.group if player.merged: player.complete = True players = player.get_others_in_group() for p in players: if p.merged and p.complete: if ( (p.firmA == player.firmA) and (p.firmB == player.firmB) and (p.firmC == player.firmC) ): group.matching_contract = True else: group.matching_contract = False group.num_chances += 1 if group.num_chances >= 4: group.matching_contract = False timer_text = 'Time left to complete this section:' @staticmethod def get_timeout_seconds(player: Player): participant = player.participant return participant.expiry - time.time() @staticmethod def is_displayed(player: Player): group = player.group if player.contract == 'ABC': num_chances_ok = group.num_chances <= 3 else: num_chances_ok = group.num_chances == 1 return ( num_chances_ok and not group.matching_contract and player.merged and is_sufficient_time(player) and group.partner_match ) class FinalWait(WaitPage): pass class Results(Page): @staticmethod def vars_for_template(player: Player): other_companies = player.contract.replace(player.role, '') if len(other_companies) == 1: other_companies_label = f'company {other_companies}' else: other_companies_label = f'companies ' + ' and '.join(other_companies) return dict( matching_companies=matching_companies, other_companies_label=other_companies_label, ) @staticmethod def before_next_page(player: Player, timeout_happened): player.participant.expiry = time.time() + 4 * 60 page_sequence = [ Introduction, IntroWait, Main, ContractWait, Contract, WaitCheck, SecondChance, FinalWait, Results, ]