from otree.api import * author = 'Knowlton Chan' doc = """ Updated Positive negative selection with Otree. """ import random import json def configured_int(obj, key, default): value = obj.session.config.get(key, default) if value in (None, ''): return default return int(value) def low_value_amount(obj): return configured_int(obj, 'v_L', 50) def configured_vm_value(obj): value = obj.session.config.get('v_M') if value in (None, ''): value = obj.session.config.get('vm_value') if value in (None, ''): return None return int(value) def resolved_vm_value(obj): value = configured_vm_value(obj) return value if value is not None else 350 def high_value_amount(obj): return configured_int(obj, 'v_H', 500) def low_value_display(obj): return str(low_value_amount(obj)) def vm_display(obj): value = configured_vm_value(obj) return str(value) if value is not None else 'vM' def high_value_display(obj): return str(high_value_amount(obj)) def low_offer_amount(obj): return configured_int(obj, 'p_L', 0) def middle_offer_amount(obj): configured = obj.session.config.get('p_M') if configured not in (None, ''): return int(configured) return resolved_vm_value(obj) - low_value_amount(obj) def middle_offer_display(obj): configured = obj.session.config.get('p_M') if configured not in (None, ''): return str(int(configured)) value = configured_vm_value(obj) return str(value - low_value_amount(obj)) if value is not None else 'vM - 50' def high_offer_amount(obj): return configured_int(obj, 'p_H', 450) def low_offer_display(obj): return str(low_offer_amount(obj)) def high_offer_display(obj): return str(high_offer_amount(obj)) def display_buyer_value(obj, buyer_value): if str(buyer_value) == '50': return low_value_display(obj) if str(buyer_value) == 'vM': return vm_display(obj) if str(buyer_value) == '500': return high_value_display(obj) return str(buyer_value) def display_seller_offer(obj, seller_offer): if seller_offer is None: return '' if seller_offer == "low": return low_offer_display(obj) if seller_offer == "middle": return middle_offer_display(obj) if seller_offer == "high": return high_offer_display(obj) return str(seller_offer) def seller_offer_maybe_none(group): return group.field_maybe_none('seller_offer') def can_accept_offer(player): seller_offer = seller_offer_maybe_none(player.group) if seller_offer is None: return False return offer_to_amount(player, seller_offer) <= buyer_value_to_amount(player, player.value) def seller_tick_position(obj, offer_amount): return round(90 * offer_amount / high_value_amount(obj), 1) def buyer_tick_position(obj, buyer_amount): low_value = low_value_amount(obj) high_value = high_value_amount(obj) if high_value == low_value: return 12.5 return round(12.5 + 87.5 * ((buyer_amount - low_value) / (high_value - low_value)), 1) def low_offer_tick_position(obj): return seller_tick_position(obj, low_offer_amount(obj)) def middle_offer_tick_position(obj): return seller_tick_position(obj, middle_offer_amount(obj)) def high_offer_tick_position(obj): return seller_tick_position(obj, high_offer_amount(obj)) def middle_offer_label_position(obj): return round(middle_offer_tick_position(obj) - 11, 1) def high_offer_label_position(obj): return round(high_offer_tick_position(obj) - 3, 1) def low_buyer_tick_position(obj): return 12.5 def vm_buyer_tick_position(obj): return buyer_tick_position(obj, resolved_vm_value(obj)) def high_buyer_tick_position(obj): return 100.0 def low_buyer_label_position(obj): return -8.0 def vm_buyer_label_position(obj): return round(vm_buyer_tick_position(obj) - 13, 1) def high_buyer_label_position(obj): return 92.0 def middle_offer_overlaps_low(obj): return low_price_enabled(obj) and middle_offer_label_position(obj) < 12 def middle_offer_overlaps_high(obj): return middle_offer_label_position(obj) > 67 def low_offer_transform(obj): if middle_offer_overlaps_low(obj): return 'translate(0px,-30px)' return 'translate(0px,-60px)' def middle_offer_transform(obj): if middle_offer_overlaps_low(obj) or middle_offer_overlaps_high(obj): return 'translate(0px,-105px)' return 'translate(0px,-60px)' def high_offer_transform(obj): if middle_offer_overlaps_high(obj): return 'translate(0px,-30px)' return 'translate(0px,-60px)' def outside_option_enabled(player): return player.session.config.get('outside_option_enabled', True) def outside_option_payoff(player): return Constants.outside_option_payoff if outside_option_enabled(player) else 0 def low_price_enabled(player): return player.session.config.get('low_price_enabled', True) def valid_seller_offers(player): offers = ["middle", "high"] if low_price_enabled(player): offers.insert(0, "low") return offers def shared_template_vars(player): return dict( outside_option_enabled=outside_option_enabled(player), outside_option_payoff=outside_option_payoff(player), low_price_enabled=low_price_enabled(player), low_value_display=low_value_display(player), vm_display=vm_display(player), high_value_display=high_value_display(player), low_offer_display=low_offer_display(player), middle_offer_display=middle_offer_display(player), high_offer_display=high_offer_display(player), low_offer_tick_position=low_offer_tick_position(player), middle_offer_tick_position=middle_offer_tick_position(player), high_offer_tick_position=high_offer_tick_position(player), middle_offer_label_position=middle_offer_label_position(player), high_offer_label_position=high_offer_label_position(player), low_buyer_tick_position=low_buyer_tick_position(player), vm_buyer_tick_position=vm_buyer_tick_position(player), high_buyer_tick_position=high_buyer_tick_position(player), low_buyer_label_position=low_buyer_label_position(player), vm_buyer_label_position=vm_buyer_label_position(player), high_buyer_label_position=high_buyer_label_position(player), low_offer_transform=low_offer_transform(player), middle_offer_transform=middle_offer_transform(player), high_offer_transform=high_offer_transform(player), ) def offer_to_amount(obj, seller_offer): if seller_offer == "low": return low_offer_amount(obj) if seller_offer == "middle": return middle_offer_amount(obj) if seller_offer == "high": return high_offer_amount(obj) return 0 def buyer_value_to_amount(obj, buyer_value): if buyer_value == "50": return low_value_amount(obj) if buyer_value == "vM": return resolved_vm_value(obj) if buyer_value == "500": return high_value_amount(obj) return int(buyer_value) def set_group_payoffs(group, buyer_action): seller = group.get_player_by_id(2) buyer = group.get_player_by_id(1) if buyer_action == "accept": price = offer_to_amount(group, group.seller_offer) buyer_value = buyer_value_to_amount(group, buyer.value) seller.payoff = price buyer.payoff = max(0, buyer_value - price) return if buyer_action == "outside": seller.payoff = 0 buyer.payoff = outside_option_payoff(buyer) return if buyer_action == "reject": seller.payoff = 0 buyer.payoff = 0 class Constants(BaseConstants): name_in_url = 'pn_selection' players_per_group = 2 num_rounds = 11 Duties_list = ['buyer','seller'] Bvalues = ['50', 'vM', '500'] prob_round2 = 0.9 outside_option_payoff = 50 def q1_choices(player): if not outside_option_enabled(player): return [ [1, 'I do not know how much the buyer values the asset.'], [2, 'If I offer 450 tokens, and the buyer accepts it, then I earn 450 tokens.'], [3, 'If I offer 450 tokens and the buyer rejects it, then the bargaining ends immediately and I earn 0 tokens.'], [4, 'If bargaining does not end in Round 1, then I can make a new offer with a 90% chance.'], ] return [ [1, 'I do not know how much the buyer values the asset.'], [2, f'If the buyer takes an outside option, I earn {outside_option_payoff(player)} tokens.'], [3, f'If I offer {high_offer_display(player)} tokens, and the buyer accepts it, then I earn {high_offer_display(player)} tokens.'], [4, 'If bargaining does not end in Round 1, then I can make a new offer with a 90% chance.'], ] def q2_choices(player): high_value = high_value_amount(player) if not outside_option_enabled(player): return [ [1, 'If I accept a price offer of 200 tokens, I earn 200 tokens.'], [3, f'If I accept a price offer of 200 tokens, I earn {high_value - 200} tokens.'], [4, f'In Round 2 of this match, the value of the asset will be different from {high_value}.'], ] return [ [1, 'If I accept a price offer of 200 tokens, I earn 200 tokens.'], [2, f'If I take an outside option, I earn {high_value + outside_option_payoff(player)} tokens.'], [3, f'If I accept a price offer of 200 tokens, I earn {high_value - 200} tokens.'], [4, f'In Round 2 of this match, the value of the asset will be different from {high_value}.'], ] class Subsession(BaseSubsession): tot_matches = models.IntegerField(initial=11) 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) for player in subsession.get_players(): player.duty = Constants.Duties_list[player.id_in_group-1] if player.duty == "buyer": player.value = random.choice(Constants.Bvalues) else: player.value = 0 class Group(BaseGroup): rounds = models.LongStringField(initial='{}') seller_offer_str = models.LongStringField(initial='{}') buyer_action_str = models.LongStringField(initial='{}') seller_offer = models.StringField(choices=["low", "middle", "high"], widget=widgets.RadioSelect, label="Choose your price offer") buyer_action = models.StringField(choices=["accept", "outside", "reject"], widget=widgets.RadioSelect, label="What is your response as the buyer?") seller_position = models.IntegerField(initial=0) buyer_position = models.IntegerField(initial=0) seller_belief_50 = models.IntegerField( min=0, max=100, label="% chance buyer has value 50 is " ) seller_belief_vM = models.IntegerField( min=0, max=100, label="% chance buyer has value vM is " ) seller_belief_500 = models.IntegerField( min=0, max=100, label="% chance buyer has value 500 is" ) lucky_draws = models.LongStringField(initial='{}') bargain_stage = models.StringField(initial='Nill') local_seed = models.IntegerField() continuetoRound2 = models.BooleanField(initial=False) draw_value = models.IntegerField(initial=0) class Player(BasePlayer): duty = models.StringField() value = models.StringField(initial=0) read = models.IntegerField(initial=-1) q1 = models.IntegerField(choices=q1_choices, widget=widgets.RadioSelect, label='Suppose you are a seller. Which of the followings is NOT TRUE?') q2 = models.IntegerField(choices=q2_choices, widget=widgets.RadioSelect, label='Suppose you are a buyer, and the value of the asset is the high value. Which of the followings is TRUE?') q3 = models.IntegerField(choices=[ [1, 'The match is terminated, and both the seller and the buyer earn 0 tokens.'], [2, 'The match is continued forever, even after continuous rejections.'], [3, 'The match is terminated, and each participant in the pair earns a half of value B.'], [4, 'The match initiates an open chat to negotiate.'] ], widget=widgets.RadioSelect, label='Suppose the price offer in Round 1 is rejected. Which of the followings CAN HAPPEN?') q4 = models.IntegerField(choices=[ [1, 'It is almost sure that I will be paired with the same participant in the first match.'], [2, 'I may play another role different from what I did in the first match.'], [3, "The buyer's value of the asset in the second match will be the same as the one in the first match."], [4, "My previous actions do not affect the value of the asset in the new match."]], widget=widgets.RadioSelect, label='Suppose the first match is done. Which of the followings is TRUE?') class Instruction(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": 3 if not outside_option_enabled(player) else 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 dict( read=player.read, outside_option=outside_option_payoff(player), q2_label=f"Suppose you are a buyer, and the value of the asset is {high_value_display(player)}. Which of the followings is TRUE?", tot_matches=player.subsession.tot_matches - 1, **shared_template_vars(player), ) class Instruction2(Page): def is_displayed(player: Player): return player.subsession.round_number == 1 def vars_for_template(player: Player): return dict( tot_matches=player.subsession.tot_matches - 1, **shared_template_vars(player), ) class PleaseWait(Page): def is_displayed(player: Player): return player.subsession.round_number == 2 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 Round1_11_Offer(Page): form_model = 'group' form_fields = ['seller_offer'] def is_displayed(player: Player): return player.duty == 'seller' and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): matchnum = player.subsession.round_number - 1 return dict( matchnum=matchnum, roundnum=1, **shared_template_vars(player), ) def before_next_page(player: Player, timeout_happened): offer_dict = json.loads(player.group.seller_offer_str) offer_dict[len(offer_dict)] = player.group.seller_offer player.group.seller_offer_str = json.dumps(offer_dict) if player.group.seller_offer == "low": player.group.seller_position = low_offer_amount(player) if player.group.seller_offer == "middle": player.group.seller_position = middle_offer_amount(player) if player.group.seller_offer == "high": player.group.seller_position = high_offer_amount(player) @staticmethod def error_message(player: Player, values): if values['seller_offer'] not in valid_seller_offers(player): return "This price offer is not available in this session." class Round1_12_BuyerInfo(Page): form_model = 'group' def is_displayed(player: Player): return player.duty == 'buyer' and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) if player.value == "vM": player.group.buyer_position = resolved_vm_value(player) else: player.group.buyer_position = int(player.value) matchnum = player.subsession.round_number - 1 return { "buyer_value": buyer_value, "buyer_value_display": display_buyer_value(player, buyer_value), "seller_offer_display": display_seller_offer(player, seller_offer), "matchnum": matchnum, "roundnum": 1, **shared_template_vars(player), } class Round1_13_BuyerWaitPage(WaitPage): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches pass @staticmethod def after_all_players_arrive(group: Group): pass class Round1_14_Response(Page): form_model = 'group' form_fields = ['buyer_action'] def is_displayed(player: Player): return player.duty == 'buyer' and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=1, buyer_value=buyer_value, buyer_value_display=display_buyer_value(player, buyer_value), seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), accept_offer_allowed=can_accept_offer(player), **shared_template_vars(player), ) @staticmethod def error_message(player: Player, values): if not outside_option_enabled(player) and values['buyer_action'] == "outside": return "Outside option is not available in this session." if values['buyer_action'] == "accept" and not can_accept_offer(player): return "You cannot accept an offer that exceeds your value." def before_next_page(player: Player, timeout_happened): action_dict = json.loads(player.group.buyer_action_str) action_dict[len(action_dict)] = player.group.buyer_action player.group.buyer_action_str = json.dumps(action_dict) if player.group.buyer_action == "accept": player.group.bargain_stage = "accepted, end" set_group_payoffs(player.group, "accept") if player.group.buyer_action == "outside": player.group.bargain_stage = "outside, end" set_group_payoffs(player.group, "outside") if player.group.buyer_action == "reject": player.group.bargain_stage = "rejected, end" class Round1_15_SellerWaitPage(WaitPage): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches pass @staticmethod def after_all_players_arrive(group: Group): pass # class LuckyWheelPage(Page): # def live_method(player: Player, data): # draw = random.randint(1, 100) # draw_dict = json.loads(player.group.lucky_draws) # draw_dict[len(draw_dict)] = draw # player.group.lucky_draws = json.dumps(draw_dict) # # if draw > Constants.prob_round2 * 100: # terminated = True # player.group.bargain_stage = "rejected, end" # player.payoff = 0 # else: # player.group.bargain_stage = "rejected, wait_guess" # terminated = False # # prob_percent = int(round(Constants.prob_round2 ** (1 + 1), 2) * 100) # if prob_percent < 10: # prob_percent = "Less than 10%" # # message = {'terminated': terminated, # 'roundnum': 1 + 1, # 'prob_percent': prob_percent, # 'draw': (100 - draw) * 3.6 - 36, # } # return {0: message} # # def is_displayed(player: Player): # return player.subsession.round_number <= player.subsession.tot_matches # # @staticmethod # def vars_for_template(player: Player): # draw_dict = json.loads(player.group.lucky_draws) # if len(draw_dict) > 0: # latest_draw = int(draw_dict[str(len(draw_dict) - 1)]) # else: # latest_draw = 0 # # prob_percent = int(round(Constants.prob_round2 ** (1 + 1), 2) * 100) # if prob_percent < 10: # prob_percent = "Less than 10%" # return dict( # matchnum=player.subsession.round_number - 1, # roundnum=1 + 1, # prob_percent=prob_percent, # p_value=player.value, # bargain_stage=player.group.bargain_stage, # latest_draw=(100 - latest_draw) * 3.6 - 36, # outside_option=Constants.outside_option_payoff # ) class LuckyWheelPage1(Page): def is_displayed(player: Player): return player.group.buyer_action == "reject" and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): seller_offer = seller_offer_maybe_none(player.group) draw_rotation = 180 if player.group.continuetoRound2 else 342 draw_outcome_text = ( "The match moves on to the next round." if player.group.continuetoRound2 else "The match terminates." ) return { "matchnum": player.subsession.round_number - 1, "roundnum": 1, "seller_offer_display": display_seller_offer(player, seller_offer), "draw_rotation": draw_rotation, "draw_outcome_text": draw_outcome_text, } class Round1_Accept(Page): def is_displayed(player: Player): return player.group.buyer_action == "accept" and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=1, buyer_value=buyer_value, buyer_value_display=display_buyer_value(player, buyer_value), seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), **shared_template_vars(player), ) class Round1_Outside(Page): def is_displayed(player: Player): return ( outside_option_enabled(player) and player.group.buyer_action == "outside" and player.subsession.round_number <= player.subsession.tot_matches ) def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=1, buyer_value=buyer_value, buyer_value_display=display_buyer_value(player, buyer_value), seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), **shared_template_vars(player), ) class Round1_Reject(Page): def is_displayed(player: Player): return player.group.buyer_action == "reject" and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=1, buyer_value=buyer_value, buyer_value_display=display_buyer_value(player, buyer_value), seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), **shared_template_vars(player), ) class Round2_0_DrawWaitPage(WaitPage): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches pass @staticmethod def after_all_players_arrive(group: Group): if group.buyer_action == "reject": draw_value = random.randint(1, 100) group.draw_value = draw_value if group.draw_value <= Constants.prob_round2 * 100: group.continuetoRound2 = True else: group.bargain_stage = "rejected, end" set_group_payoffs(group, "reject") class Round2_0_Continue(Page): form_model = 'group' def is_displayed(player: Player): return player.group.continuetoRound2 and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=1, buyer_value=buyer_value, buyer_value_display=display_buyer_value(player, buyer_value), seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), **shared_template_vars(player), ) class Round2_0_Stop(Page): form_model = 'group' def is_displayed(player: Player): player_group = player.group return player.group.buyer_action == "reject" and player_group.continuetoRound2 == False and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=1, buyer_value=buyer_value, seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), **shared_template_vars(player), ) class Round2_10_Belief(Page): form_model = 'group' form_fields = ['seller_belief_50', 'seller_belief_vM'] def is_displayed(player: Player): return player.duty == 'seller' and player.group.continuetoRound2 and player.subsession.round_number <= player.subsession.tot_matches @staticmethod def error_message(player, values): if values['seller_belief_50'] + values['seller_belief_vM'] > 100: return 'The first two numbers cannot add up to more than 100' def before_next_page(player: Player, timeout_happened): player.group.seller_belief_500 = 100 - ( player.group.seller_belief_50 + player.group.seller_belief_vM ) def vars_for_template(player: Player): matchnum = player.subsession.round_number - 1 return { "matchnum": matchnum, "roundnum": 2, "low_belief_label": f"% chance buyer has value {low_value_display(player)} is ", "vm_belief_label": f"% chance buyer has value {vm_display(player)} is ", "high_belief_label": f"% chance buyer has value {high_value_display(player)} is ", **shared_template_vars(player), } class Round2_11_Offer(Page): form_model = 'group' form_fields = ['seller_offer'] def is_displayed(player: Player): return player.duty == 'seller' and player.group.continuetoRound2 and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): matchnum = player.subsession.round_number - 1 return dict( matchnum=matchnum, roundnum=2, **shared_template_vars(player), ) @staticmethod def error_message(player: Player, values): if values['seller_offer'] not in valid_seller_offers(player): return "This price offer is not available in this session." class Round2_12_BuyerInfo(Page): form_model = 'group' def is_displayed(player: Player): return player.duty == 'buyer' and player.group.continuetoRound2 and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) matchnum = player.subsession.round_number - 1 return { "matchnum": matchnum, "roundnum": 2, "buyer_value": buyer_value, "buyer_value_display": display_buyer_value(player, buyer_value), "seller_offer_display": display_seller_offer(player, seller_offer), **shared_template_vars(player), } class Round2_13_BuyerWaitPage(WaitPage): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches pass @staticmethod def after_all_players_arrive(group: Group): pass class Round2_14_Response(Page): form_model = 'group' form_fields = ['buyer_action'] def is_displayed(player: Player): return player.duty == 'buyer' and player.group.continuetoRound2 and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=2, buyer_value=buyer_value, buyer_value_display=display_buyer_value(player, buyer_value), seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), accept_offer_allowed=can_accept_offer(player), **shared_template_vars(player), ) @staticmethod def error_message(player: Player, values): if not outside_option_enabled(player) and values['buyer_action'] == "outside": return "Outside option is not available in this session." if values['buyer_action'] == "accept" and not can_accept_offer(player): return "You cannot accept an offer that exceeds your value." def before_next_page(player: Player, timeout_happened): action_dict = json.loads(player.group.buyer_action_str) action_dict[len(action_dict)] = player.group.buyer_action player.group.buyer_action_str = json.dumps(action_dict) if player.group.buyer_action == "accept": player.group.bargain_stage = "accepted, end" set_group_payoffs(player.group, "accept") if player.group.buyer_action == "outside": player.group.bargain_stage = "outside, end" set_group_payoffs(player.group, "outside") if player.group.buyer_action == "reject": player.group.bargain_stage = "rejected, end" set_group_payoffs(player.group, "reject") class Round2_15_SellerWaitPage(WaitPage): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches @staticmethod def after_all_players_arrive(group: Group): pass class Round2_Accept(Page): def is_displayed(player: Player): return player.group.buyer_action == "accept" and player.group.continuetoRound2 and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=2, buyer_value=buyer_value, buyer_value_display=display_buyer_value(player, buyer_value), seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), **shared_template_vars(player), ) class Round2_Outside(Page): def is_displayed(player: Player): return ( outside_option_enabled(player) and player.group.buyer_action == "outside" and player.group.continuetoRound2 and player.subsession.round_number <= player.subsession.tot_matches ) def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=2, buyer_value=buyer_value, buyer_value_display=display_buyer_value(player, buyer_value), seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), **shared_template_vars(player), ) class Round2_Stop(Page): form_model = 'group' def is_displayed(player: Player): player_group = player.group return player.group.buyer_action == "reject" and player_group.continuetoRound2 and player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): buyer_value = player.value seller_offer = seller_offer_maybe_none(player.group) return dict( matchnum=player.subsession.round_number - 1, roundnum=1, buyer_value=buyer_value, buyer_value_display=display_buyer_value(player, buyer_value), seller_offer=seller_offer, seller_offer_display=display_seller_offer(player, seller_offer), **shared_template_vars(player), ) class Round2_Reject(Page): def is_displayed(player: Player): return player.group.buyer_action == "reject" and player.group.continuetoRound2 and player.subsession.round_number <= player.subsession.tot_matches class ResultsWaitPage(WaitPage): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches pass class Results(Page): def is_displayed(player: Player): return player.subsession.round_number <= player.subsession.tot_matches def vars_for_template(player: Player): offer_dict = json.loads(player.group.seller_offer_str) action_dict = json.loads(player.group.buyer_action_str) iterround = [r for r in range(0, len(offer_dict) - 1)] rounds = [r for r in range(1, len(offer_dict))] Y_N = ["N", "Y"] value_B = 0 for p in player.group.get_players(): if p.duty == "buyer": value_B = p.value return dict( match_num = player.subsession.round_number -1, round_num=len(offer_dict), iterround=iterround, offer_dict=player.group.seller_offer_str, action_dict=player.group.buyer_action_str, value_B = value_B, value_B_display=json.dumps(display_buyer_value(player, value_B)), match_gain = int(player.payoff) ) class RoundSyncWaitPage(WaitPage): wait_for_all_groups = True def is_displayed(player: Player): return player.subsession.round_number < player.subsession.tot_matches class Final_payment(Page): def cal_final_payoff(player: Player,local_seed): local_random = random.Random(local_seed+player.id_in_group) chosen_round = local_random.randint(2, player.subsession.tot_matches) playerround = player.in_round(chosen_round) player.participant.payoff = playerround.payoff return player.participant.payoff, chosen_round-1 def is_displayed(player: Player): return player.subsession.round_number == player.subsession.tot_matches def vars_for_template(player: Player): final_payment, chosen_match = cal_final_payoff(player.group.local_seed) Bvalue_dict = {} AcOffer_dict = {} Outcome_dict = {} Earning_dict = {} for m in range(2, player.subsession.tot_matches+1): player = player.in_round(m) Earning_dict[m-1] = int(player.payoff) group = player.in_round(m).group for p in group.get_players(): if p.duty == "buyer": Bvalue_dict[m-1]=display_buyer_value(player, p.value) if group.bargain_stage == "accepted, end": Outcome_dict[m-1] = "Offer Accepted" offer_dict = json.loads(group.seller_offer_str) AcOffer_dict[m-1] = offer_dict[str(len(offer_dict)-1)] if group.bargain_stage == "rejected, end": Outcome_dict[m - 1] = "Match Terminated" AcOffer_dict[m - 1] = "N/A" if group.bargain_stage == "outside, end": Outcome_dict[m - 1] = "Outside Option Taken" AcOffer_dict[m - 1] = "N/A" return dict(final_payment = final_payment, chosen_match = chosen_match, total_payment = player.participant.payoff_plus_participation_fee(), tot_match_num = player.subsession.tot_matches-1, Bvalue_dict = Bvalue_dict, AcOffer_dict = AcOffer_dict, Outcome_dict = Outcome_dict, Earning_dict = Earning_dict ) # page_sequence = [Instruction, Instruction2, PleaseWait, ResultsWaitPage,Usher, Bargain_page, Results, Final_payment ] # page_sequence = [Instruction2, PleaseWait, Usher, # Round1_11_Offer, Round1_12_BuyerInfo, Round1_13_BuyerWaitPage, Round1_14_ResponseTest, Round1_15_SellerWaitPage, # Round1_Accept, Round1_Outside, Round1_Reject, Round2_0_DrawWaitPage, Round2_0_Continue, Round2_0_Stop, Round2_10_Belief, # Round2_11_Offer, Round2_12_BuyerInfo, Round2_13_BuyerWaitPage, Round2_14_Response, Round2_15_SellerWaitPage, # Results, Final_payment # ] page_sequence = [Instruction, Instruction2, PleaseWait, Usher, Round1_11_Offer, Round1_12_BuyerInfo, Round1_13_BuyerWaitPage, Round1_14_Response, Round1_15_SellerWaitPage, Round2_0_DrawWaitPage, Round1_Reject, LuckyWheelPage1, Round1_Accept, Round1_Outside, Round2_10_Belief, Round2_11_Offer, Round2_12_BuyerInfo, Round2_13_BuyerWaitPage, Round2_14_Response, Round2_15_SellerWaitPage, Round2_Accept, Round2_Outside, Round2_Stop, Results, RoundSyncWaitPage] # Results, Final_payment # ]