from otree.api import Currency as c, currency_range from asgiref.sync import async_to_sync from channels.layers import get_channel_layer from ._builtin import Page, WaitPage from .models import Constants from .models import Player from . import models from django.http import HttpResponseRedirect import hashlib import time import math from random import shuffle from random import uniform class Grouping(WaitPage): template_name = 'grouping/Grouping.html' group_by_arrival_time = True def is_displayed(self): if self.round_number == 1: try: arrival_set = self.participant.vars['wait_page_arrival'] except: self.participant.vars['wait_page_arrival'] = time.time() self.participant.vars['grouping_timeout'] = False return True def vars_for_template(self): now = time.time() time_left = Constants.TIME_GROUPING - (now - self.participant.vars['wait_page_arrival']) return dict( time_left=time_left, connected=self.participant.vars['connected'], ) class GroupingTimeout(Page): template_name = 'grouping/GroupingTimeout.html' def is_displayed(self): return self.participant.vars.get('grouping_timeout', True) & self.round_number == 1 def vars_for_template(self): return dict( accessCode = self.participant.code, ) def app_after_this_page(self, upcoming_apps): return upcoming_apps[1] class Introduction(Page): def is_displayed(player: Player): if player.round_number == 1: player.participant.vars['karma'] = Constants.INITIAL_KARMA return True def get_timeout_seconds(player): return Constants.TIME_INTRODUCTION def vars_for_template(player: Player): num_other_players = Constants.PLAYERS_PER_GROUP - 1 treatment = player.participant.vars['treatment_id'] if treatment == 1 or treatment == 3: low_urgency_value = Constants.URGENCY_VALUES_1[0] high_urgency_value = Constants.URGENCY_VALUES_1[1] low_urgency_probability = Constants.URGENCY_PROBABILITIES_1[0] high_urgency_probability = Constants.URGENCY_PROBABILITIES_1[1] else: low_urgency_value = Constants.URGENCY_VALUES_2[0] high_urgency_value = Constants.URGENCY_VALUES_2[1] low_urgency_probability = Constants.URGENCY_PROBABILITIES_2[0] high_urgency_probability = Constants.URGENCY_PROBABILITIES_2[1] rounds = Constants.NUM_ROUNDS - Constants.NUM_TEST_ROUNDS return dict( num_other_players=num_other_players, low_urgency_value=low_urgency_value, high_urgency_value=high_urgency_value, low_urgency_probability=low_urgency_probability, high_urgency_probability=high_urgency_probability, rounds=rounds, treatment = treatment ) class Instructions(Page): def is_displayed(player: Player): if player.round_number == 1: return True def get_timeout_seconds(player): return Constants.TIME_INSTRUCTION def vars_for_template(player: Player): num_other_players = Constants.PLAYERS_PER_GROUP - 1 treatment = player.participant.vars['treatment_id'] if treatment == 1 or treatment == 3: low_urgency_value = Constants.URGENCY_VALUES_1[0] high_urgency_value = Constants.URGENCY_VALUES_1[1] low_urgency_probability = Constants.URGENCY_PROBABILITIES_1[0] high_urgency_probability = Constants.URGENCY_PROBABILITIES_1[0] else: low_urgency_value = Constants.URGENCY_VALUES_2[0] high_urgency_value = Constants.URGENCY_VALUES_2[1] low_urgency_probability = Constants.URGENCY_PROBABILITIES_2[0] high_urgency_probability = Constants.URGENCY_PROBABILITIES_2[0] rounds = Constants.NUM_ROUNDS - Constants.NUM_TEST_ROUNDS return dict( num_other_players=num_other_players, low_urgency_value=low_urgency_value, high_urgency_value=high_urgency_value, low_urgency_probability=low_urgency_probability, high_urgency_probability=high_urgency_probability, rounds=rounds, treatment=treatment, ) class DecisionWaitPage(WaitPage): def after_all_players_arrive(self): ## START DEBUG ## print('') now = time.strftime('%H:%M:%S') print('[DEBUG] [DECISION WAIT] ALL PLAYERS ARRIVED AT: ' + now) ## END DEBUG ## players = self.group.get_players() # Treatment (only in first round) if self.round_number == 1: counter = self.subsession.counter if counter % 4 == 0: treatment = 1 elif counter % 4 == 1: treatment = 1 #only for pilot elif counter % 4 == 2: treatment = 3 else: treatment = 3 #only for pilot for p in players: p.participant.vars['karma'] = Constants.INITIAL_KARMA p.participant.vars['treatment_id'] = treatment p.treatment_id = treatment p.group_counter = counter self.group_counter = counter self.treatment_id = treatment self.subsession.counter = counter + 1 ## START DEBUG ## now = time.strftime('%H:%M:%S') print('[DEBUG] [DECISION WAIT] STARTED URGENCY SAMPLING AT: ' + now) ## END DEBUG ## # Urgency for p in players: p.urgency = urgency(p) ## START DEBUG ## now = time.strftime('%H:%M:%S') print('[DEBUG] [DECISION WAIT] FINISHED URGENCY SAMPLING AT: ' + now) print('') ## END DEBUG ## class DecisionTest(Page): form_model = 'player' form_fields = ['karma_input'] def is_displayed(player: Player): return player.round_number <= Constants.NUM_TEST_ROUNDS def get_timeout_seconds(player): participant = player.participant if participant.vars['is_dropout']: return 1 # instant timeout else: return Constants.TIME_TEST_ROUND def before_next_page(self): participant = self.player.participant if self.timeout_happened: self.player.karma_input = 0 timeout_count = participant.vars['timeout_count'] timeout_count = timeout_count + 1 participant.vars['timeout_count'] = timeout_count if timeout_count >= Constants.DROPOUT_COUNT: participant.vars['is_dropout'] = True ## START DEBUG ## now = time.strftime('%H:%M:%S') print(f'[DEBUG] [DECISION TEST] PLAYER {participant.id_in_session} DONE AT: {now}') ## END DEBUG ## def vars_for_template(self): if self.player.round_number == 1: self.player.participant.vars['test_payoff'] = 0 karma = self.player.participant.vars['karma'] karma_floor = math.floor(karma) self.player.karma = karma self.player.karma_int = karma_floor half_bid = math.floor(karma_floor / 2) karma_range = range(karma_floor) treatment = self.player.participant.vars['treatment_id'] if treatment <= 2: karma_slider_width = karma_floor / Constants.MAX_KARMA * 100 else: karma_slider_width = half_bid / Constants.MAX_KARMA * 100 return dict( treatment=treatment, karma=karma, karma_floor=karma_floor, half_bid=half_bid, karma_range=karma_range, karma_slider_width=karma_slider_width, ) class Decision(Page): form_model = 'player' form_fields = ['karma_input'] def is_displayed(player: Player): return player.round_number > Constants.NUM_TEST_ROUNDS def get_timeout_seconds(player): participant = player.participant if participant.vars['is_dropout']: return 1 # instant timeout else: return Constants.TIME_ROUND def before_next_page(self): participant = self.player.participant if self.timeout_happened: self.player.dropout = True self.player.karma_input = 0 timeout_count = participant.vars['timeout_count'] timeout_count = timeout_count + 1 participant.vars['timeout_count'] = timeout_count if timeout_count >= Constants.DROPOUT_COUNT: participant.vars['is_dropout'] = True ## START DEBUG ## now = time.strftime('%H:%M:%S') print(f'[DEBUG] [DECISION] PLAYER {participant.id_in_session} DONE AT: {now}') ## END DEBUG ## def vars_for_template(self): # Reset karma and payoff for first round if self.player.round_number == Constants.NUM_TEST_ROUNDS + 1: self.player.participant.vars['karma'] = Constants.INITIAL_KARMA self.player.participant.vars['payoff'] = 0 self.player.participant.payoff = 0 current_round = self.player.round_number - Constants.NUM_TEST_ROUNDS rounds = Constants.NUM_ROUNDS - Constants.NUM_TEST_ROUNDS karma = self.player.participant.vars['karma'] karma_floor = math.floor(karma) self.player.karma = karma self.player.karma_int = karma_floor half_bid = math.floor(karma_floor / 2) karma_range = range(karma_floor) treatment = self.player.participant.vars['treatment_id'] if treatment <= 2: karma_slider_width = karma_floor / Constants.MAX_KARMA * 100 else: karma_slider_width = half_bid / Constants.MAX_KARMA * 100 return dict( treatment=treatment, round=current_round, rounds=rounds, karma=karma, karma_floor=karma_floor, half_bid=half_bid, karma_range=karma_range, karma_slider_width=karma_slider_width, ) class ResultsWaitPage(WaitPage): def after_all_players_arrive(self): ## START DEBUG ## print('') now = time.strftime('%H:%M:%S') print('[DEBUG] [RESULTS WAIT] RESULTS WAIT ALL PLAYERS ARRIVED AT: ' + now) ## END DEBUG ## group_ids_range = range(1, Constants.PLAYERS_PER_GROUP + 1) group_ids = list(group_ids_range) shuffle(group_ids) print(group_ids) ## START DEBUG ## now = time.strftime('%H:%M:%S') print('[DEBUG] [RESULTS WAIT] STARTED MATCHING AT: ' + now) ## END DEBUG ## for p in group_ids: player = self.group.get_player_by_id(p) index = group_ids.index(p) if index % 2 == 0: player.partner_id = group_ids[index + 1] player.role_id = 1 else: player.partner_id = group_ids[index - 1] player.role_id = 2 ## START DEBUG ## now = time.strftime('%H:%M:%S') print('[DEBUG] [RESULTS WAIT] FINISHED MATCHING AT: ' + now) ## END DEBUG ## players = self.group.get_players() self.total_payment = 0 ## START DEBUG ## now = time.strftime('%H:%M:%S') print('[DEBUG] [RESULTS WAIT] STARTED WINS AND PAYMENTS AT: ' + now) ## END DEBUG ## for player in players: # Coin flip for ties random_value = uniform(0, 1) if player.role_id == 1: partner = self.group.get_player_by_id(player.partner_id) if random_value < 0.5: player.is_tied_win = True partner.is_tied_lose = True else: partner.is_tied_win = True player.is_tied_lose = True # Determine winner / payoff partner_input = partner.karma_input karma_input = player.karma_input if partner_input < karma_input: player.is_winner = True player.payoff = player.urgency elif partner_input == karma_input: player.is_tied = True partner.is_tied = True else: partner.is_winner = True partner.payoff = partner.urgency if player.is_tied: if player.is_tied_win: player.is_winner = True player.payoff = player.urgency else: partner.is_winner = True partner.payoff = partner.urgency # Karma payment if player.is_winner: self.total_payment = self.total_payment + karma_input player.participant.vars['karma'] -= player.karma_input else: self.total_payment = self.total_payment + partner_input partner.participant.vars['karma'] -= partner.karma_input ## START DEBUG ## now = time.strftime('%H:%M:%S') print('[DEBUG] [RESULTS WAIT] FINISHED WINS AND PAYMENTS AT: ' + now) ## END DEBUG ## ## START DEBUG ## now = time.strftime('%H:%M:%S') print('[DEBUG] [RESULTS WAIT] STARTED REDISTRIBUTION AT: ' + now) ## END DEBUG ## # Calculate new karma (redistributive scheme) self.redistribution = self.total_payment / Constants.PLAYERS_PER_GROUP to_redistribute = self.total_payment for player in players: player.k_pre_redist = player.participant.vars['karma'] while (to_redistribute > 1e-10): eligible_players = [] for player in players: if player.participant.vars['karma'] < Constants.MAX_KARMA: eligible_players.append(player) redistribution = to_redistribute / len(eligible_players) for player in eligible_players: k_prev = player.participant.vars['karma'] player.participant.vars['karma'] = min([player.participant.vars['karma'] + redistribution, Constants.MAX_KARMA]) redistribution_to_k = player.participant.vars['karma'] - k_prev to_redistribute -= redistribution_to_k for player in players: player.group_redistribution = self.redistribution player.redistribution = player.participant.vars['karma'] - player.k_pre_redist ## START DEBUG ## now = time.strftime('%H:%M:%S') print('[DEBUG] [RESULTS WAIT] FINISHED REDISTRIBUTION AT: ' + now) print('') ## END DEBUG ## class ResultsTest(Page): def is_displayed(player: Player): return player.round_number <= Constants.NUM_TEST_ROUNDS def get_timeout_seconds(player): participant = player.participant if participant.vars['is_dropout']: return 1 # instant timeout else: return Constants.TIME_TEST_RESULT ## START DEBUG ## def before_next_page(self): participant = self.player.participant now = time.strftime('%H:%M:%S') print(f'[DEBUG] [RESULTS TEST] PLAYER {participant.id_in_session} DONE AT: {now}') ## END DEBUG ## # def before_next_page(self): # participant = self.player.participant # if self.timeout_happened: # self.player.dropout = True # timeout_count = participant.vars['timeout_count'] # timeout_count = timeout_count + 1 # participant.vars['timeout_count'] = timeout_count # if timeout_count >= Constants.DROPOUT_COUNT: # participant.vars['is_dropout'] = True def vars_for_template(self): partner = self.player.group.get_player_by_id(self.player.partner_id) partner_input = partner.karma_input karma = self.player.participant.vars['karma'] karma_floor = math.floor(karma) group_redistribution = self.player.group_redistribution return dict( partner_input=partner_input, partner_id=self.player.partner_id, karma=karma, karma_floor=karma_floor, group_redistribution=f'{group_redistribution:.2f}' ) class Results(Page): def is_displayed(player: Player): return player.round_number > Constants.NUM_TEST_ROUNDS def get_timeout_seconds(player): participant = player.participant if participant.vars['is_dropout']: return 1 # instant timeout else: return Constants.TIME_RESULT ## START DEBUG ## def before_next_page(self): participant = self.player.participant now = time.strftime('%H:%M:%S') print(f'[DEBUG] [RESULTS] PLAYER {participant.id_in_session} DONE AT: {now}') ## END DEBUG ## # def before_next_page(self): # participant = self.player.participant # if self.timeout_happened: # self.player.dropout = True # timeout_count = participant.vars['timeout_count'] # timeout_count = timeout_count + 1 # participant.vars['timeout_count'] = timeout_count # if timeout_count >= Constants.DROPOUT_COUNT: # participant.vars['is_dropout'] = True def vars_for_template(self): round = self.player.round_number - Constants.NUM_TEST_ROUNDS rounds = Constants.NUM_ROUNDS - Constants.NUM_TEST_ROUNDS partner = self.player.group.get_player_by_id(self.player.partner_id) partner_input = partner.karma_input karma = self.player.participant.vars['karma'] karma_floor = math.floor(karma) group_redistribution = self.player.group_redistribution return dict( partner_input=partner_input, partner_id=self.player.partner_id, round=round, rounds=rounds, karma=karma, karma_floor=karma_floor, group_redistribution=f'{group_redistribution:.2f}' ) class TestEnd(Page): def is_displayed(player: Player): return player.round_number == Constants.NUM_TEST_ROUNDS def get_timeout_seconds(player): participant = player.participant if participant.vars['is_dropout']: return 1 # instant timeout else: return Constants.TIME_TEST_END def vars_for_template(player: Player): rounds = Constants.NUM_ROUNDS - Constants.NUM_TEST_ROUNDS return dict(rounds=rounds) class FinalPayoffs(Page): def is_displayed(player: Player): return player.round_number == Constants.NUM_ROUNDS def vars_for_template(player: Player): final_score = player.participant.payoff if player.participant.vars['is_dropout']: bonus_payment = 0.0 total_payment = 0.0 is_dropout = True else: bonus_payment = max(Constants.SCORE_TO_BONUS_FACTOR * float(final_score) + Constants.SCORE_TO_BONUS_OFFSET, 0.0) total_payment = Constants.BASE_PAYMENT + bonus_payment is_dropout = False return dict(final_score=final_score, bonus_payment=f'{bonus_payment:.2f}', total_payment=f'{total_payment:.2f}', is_dropout=is_dropout) # page_sequence = [Grouping, GroupingTimeout, DecisionWaitPage, Instructions, DecisionTest, Decision, ResultsWaitPage, ResultsTest, Results, TestEnd, FinalPayoffs] page_sequence = [Grouping, GroupingTimeout, DecisionWaitPage, DecisionTest, Decision, ResultsWaitPage, ResultsTest, Results, TestEnd, FinalPayoffs] # FUNCTIONS def karma_input_max(player): return player.participant.vars['karma'] def urgency(player: Player): random_value = uniform(0, 1) treatment = player.participant.vars['treatment_id'] if treatment == 1 or treatment == 3: if random_value < Constants.URGENCY_PROBABILITIES_1[0]: urgency = Constants.URGENCY_VALUES_1[0] else: urgency = Constants.URGENCY_VALUES_1[1] else: if random_value < Constants.URGENCY_PROBABILITIES_2[0]: urgency = Constants.URGENCY_VALUES_2[0] else: urgency = Constants.URGENCY_VALUES_2[1] return urgency