from otree.api import * author = 'Knowlton Chan' doc = """ Cheap Talk Covert-Verifiable """ import random import json class Constants(BaseConstants): name_in_url = 'cheap_talk_covert_verifiable' players_per_group = 2 num_rounds = 41 Duties_list = ['A','B'] class Subsession(BaseSubsession): tot_matches = models.IntegerField(initial=41) def creating_session(subsession: Subsession): subsession.tot_matches = subsession.session.config['total_match_number'] subsession.group_randomly(fixed_id_in_group=True) for group in subsession.get_groups(): group.local_seed = random.randint(0, 9999) group.value_X = round(random.uniform(0.0, 100.0), 1) for player in subsession.get_players(): player.duty = Constants.Duties_list[player.id_in_group-1] class Group(BaseGroup): rounds = models.LongStringField(initial='{}') value_X = models.FloatField(initial=-1) choice_N = models.IntegerField(initial=1, min=1, max=10) guess_Y = models.FloatField(min=0.0, max=100.0) yellow_min = models.FloatField(initial=25, min=0.0, max=99.9) yellow_max = models.FloatField(initial=80, min=0.1, max=100.0) blue_index = models.IntegerField(initial=-1) local_seed = models.IntegerField(initial=-1) piece_min = models.FloatField(initial=-1) piece_max = models.FloatField(initial=101) bargain_stage = models.StringField(initial='rejected, wait_guess') seller_guesses = models.LongStringField(initial='{}') seller_offers = models.LongStringField(initial='{}') class Player(BasePlayer): duty = models.StringField() points_A = models.FloatField(initial=0) points_B = models.FloatField(initial=0) read = models.IntegerField(initial=-1) payoff_final = models.IntegerField(initial=-1) chosen_match = models.IntegerField(initial=-1) q1 = models.IntegerField(choices=[ [1, '[BLANK]'], [2, '[answer]'], [3, '[BLANK]'], [4, '[BLANK]'] ], widget=widgets.RadioSelect, label='QUESTION 1: Which of the followings is NOT TRUE?') q2 = models.IntegerField(choices=[ [1, '[BLANK]'], [2, '[BLANK]'], [3, '[answer]'], [4, '[BLANK]'] ], widget=widgets.RadioSelect, label='QUESTION 2: Which of the followings is TRUE?') q3 = models.IntegerField(choices=[ [1, '[answer]'], [2, '[BLANK]'], [3, '[BLANK]'], [4, '[BLANK]'] ], widget=widgets.RadioSelect, label='QUESTION 3: Which of the followings CAN HAPPEN?') q4 = models.IntegerField(choices=[ [1, '[BLANK]'], [2, '[BLANK]'], [3, "[BLANK]"], [4, "[answer]"]], widget=widgets.RadioSelect, label='QUESTION 4: Which of the followings is TRUE?') class Instruction(Page): def is_displayed(player: Player): return player.subsession.round_number == 1 def vars_for_template(player: Player): read = -1 return { 'read': read, 'tot_matches': player.subsession.tot_matches-1 } class Quiz(Page): form_model = 'player' form_fields = ['q1','q2','q3', 'q4'] def live_method(player: Player, data): if data['read'] == 1: if player.read == -1: read = 1 return {player.id_in_group: {'read': read}} def is_displayed(player: Player): return player.subsession.round_number == 1 def error_message(player: Player, value): correct_answers = {"q1": 2, "q2": 3, "q3": 1, "q4": 4 } list_answers = list(value.items())[0:] list_correct_answers = list(correct_answers.items()) if list_answers != list_correct_answers: Text = 'Some of your answers are incorrect. It indicates that your understanding may not be fully accurate. ' \ 'Please try it again. You can always go back to the experimental instructions if you find anything unclear.' return Text # live_method = "live_instruct" def vars_for_template(player: Player): return { 'read' : player.read, 'tot_matches': player.subsession.tot_matches-1 } class Instruction2(Page): def is_displayed(player: Player): return player.subsession.round_number == 1 def vars_for_template(player: Player): return { 'tot_matches': player.subsession.tot_matches-1 } class PleaseWait(Page): def is_displayed(player: Player): return player.subsession.round_number == 1 pass class Usher(Page): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): return dict( roundnum = player.subsession.round_number - 1, role = player.duty ) class Page1_ChooseN(Page): form_model = 'group' form_fields = ['choice_N'] def is_displayed(player: Player): return player.duty == 'A' and player.subsession.round_number <= player.subsession.tot_matches @staticmethod def vars_for_template(player: Player): roundnum = player.subsession.round_number - 1 choice_N = player.group.choice_N return { "roundnum": roundnum, "choice_N": choice_N } def before_next_page(player: Player, timeout_happened): choice_N = player.group.choice_N value_X = player.group.value_X pieces = [[0.0] * 2 for _ in range(choice_N)] for i in range(choice_N): if i == 0: pass else: pieces[i][0] = round(pieces[i - 1][1] + 0.1, 1) pieces[i][1] = round((pieces[i][0] + (100.0 - pieces[i][0]) / (choice_N - i)), 1) j_index = 0 k_index = 0 for j in range(choice_N): for k in range(2): if value_X < pieces[j][k]: j_index = j k_index = k break else: continue break if value_X == 100: player.group.blue_index = choice_N else: if k_index==0: player.group.blue_index = j_index - 1 else: player.group.blue_index = j_index player.group.piece_min = pieces[player.group.blue_index][0] player.group.piece_max = pieces[player.group.blue_index][1] class Page2_Report(Page): def live_method(player: Player, data): if data['type'] == "seller_guess": guess_dict = json.loads(player.group.seller_guesses) guess_dict[len(guess_dict)] = {'min': data['guess_min'], 'max': data['guess_max']} player.group.seller_guesses = json.dumps(guess_dict) player.group.bargain_stage = "guess_made,wait_offer" prob_percent = 0.8 message = {'type': 'seller_guess_made', 'guess_min': data['guess_min'], 'guess_max': data['guess_max'], 'roundnum': len(guess_dict) + 1, 'prob_percent': prob_percent } return {0: message} def is_displayed(player: Player): return player.duty == 'A' and player.subsession.round_number <= player.subsession.tot_matches @staticmethod def vars_for_template(player: Player): guess_dict = json.loads(player.group.seller_guesses) if len(guess_dict)>0: guessed_max = int(guess_dict[str(len(guess_dict) - 1)]['max']) guessed_min = int(guess_dict[str(len(guess_dict) - 1)]['min']) else: guessed_max = 400 guessed_min = 50 roundnum = player.subsession.round_number - 1 choice_N = player.group.choice_N piece_min = player.group.piece_min piece_max = player.group.piece_max return dict( random_initial = 1+ 350 +50, matchnum = player.subsession.round_number - 1, roundnum= roundnum, prob_percent = 0.8, p_value = 1, bargain_stage = player.group.bargain_stage, latest_offer = 1, B_value = 1, guessed_min = guessed_min, guessed_max = guessed_max, latest_draw = 1, outside_option = 1, choice_N = choice_N, piece_min = piece_min, buyer_bar_left = piece_min * 8, piece_max = piece_max, buyer_bar_width = (piece_max - piece_min) * 8, right_min = round(piece_max * 4, 1), left_max = round(piece_min * 4, 1) ) class Waiting_for_A(WaitPage): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches def after_all_players_arrive(group: Group): pass class Page3_GuessY(Page): def live_method(player: Player, data): if data['type'] == "seller_offers": offer_dict = json.loads(player.group.seller_offers) offer_dict[len(offer_dict)] = data['offer'] player.group.seller_offers = json.dumps(offer_dict) player.group.bargain_stage = "offer_made,wait_action" prob_percent = 0.8 message = {'type': 'seller_offer_made', 'offer': data['offer'], 'roundnum': len(offer_dict) + 1, 'prob_percent': prob_percent } return {0: message} def is_displayed(player: Player): return player.id_in_group == 2 and player.subsession.round_number <= player.subsession.tot_matches @staticmethod def vars_for_template(player: Player): offer_dict = json.loads(player.group.seller_offers) if len(offer_dict) > 0: latest_offer = int(offer_dict[str(len(offer_dict) - 1)]) else: latest_offer = 0 guess_dict = json.loads(player.group.seller_guesses) guessed_max = round(float(guess_dict[str(len(guess_dict) - 1)]['max']), 1) guessed_min = round(float(guess_dict[str(len(guess_dict) - 1)]['min']), 1) return dict( random_initial=1 + 350 + 50, matchnum=player.subsession.round_number - 1, roundnum=1, prob_percent=0.8, p_value=1, bargain_stage=player.group.bargain_stage, latest_offer=1, B_value=1, guessed_min= guessed_min , guessed_max= guessed_max , latest_draw=1, outside_option=1 ) class Waiting_for_B(WaitPage): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches def after_all_players_arrive(group: Group): offer_dict = json.loads(group.seller_offers) latest_offer = round(float(offer_dict[str(len(offer_dict) - 1)]), 1) group.guess_Y = round(0.25 * latest_offer, 1) guess_dict = json.loads(group.seller_guesses) guessed_max = round(float(guess_dict[str(len(guess_dict) - 1)]['max']), 1) guessed_min = round(float(guess_dict[str(len(guess_dict) - 1)]['min']), 1) group.yellow_max = round(100 - (100 - (0.25 * guessed_max)), 1) group.yellow_min = round(0.25 * guessed_min, 1) class Summary(Page): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches @staticmethod def vars_for_template(player: Player): roundnum = player.subsession.round_number - 1 value_X = player.group.value_X choice_N = player.group.choice_N piece_min = player.group.piece_min piece_max = player.group.piece_max yellow_min = player.group.yellow_min yellow_max = player.group.yellow_max guess_Y = player.group.guess_Y if player.duty == 'A': player.points_A = round(max((100 - (((guess_Y - value_X + 20)*(guess_Y - value_X + 20)) / 50)) + (100 - 10 * choice_N), 0), 1) else: player.points_B = round(max(150 - (((guess_Y - value_X)*(guess_Y - value_X)) / 50) , 0), 1) points_A = player.points_A points_B = player.points_B return { "roundnum": roundnum, "value_X": value_X, "choice_N": choice_N, "piece_min": piece_min, "piece_max": piece_max, "yellow_min": yellow_min, "yellow_max": yellow_max, "guess_Y": guess_Y, "points_A": points_A, "points_B": points_B } page_sequence = [Instruction, Quiz, Instruction2, PleaseWait, Usher, Page1_ChooseN, Page2_Report, Waiting_for_A, Page3_GuessY, Waiting_for_B, Summary] # page_sequence = [Page1_ChooseN, Page2_Report, Waiting_for_A, Page3_GuessY, Waiting_for_B, Summary]