from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range, ) author = 'Jake ZHANG' doc = """ An attemp to do experiment on positive negative selection with Otree. """ import random import json class Constants(BaseConstants): name_in_url = 'pn_selection' players_per_group = 2 num_rounds = 13 Roles=['Buyer','Seller'] class Subsession(BaseSubsession): prob_continuation = models.FloatField(initial=0.8) outside_option = models.IntegerField(initial = 25) tot_matches = models.IntegerField(initial=13) def creating_session(self): # self.prob_continuation = self.session.config['prob_continuation'] self.outside_option = self.session.config['outside_option'] self.tot_matches = self.session.config['total_match_number'] self.group_randomly(fixed_id_in_group=True) for group in self.get_groups(): group.local_seed = random.randint(0, 9999) for player in self.get_players(): player.roles= Constants.Roles[player.id_in_group-1] if player.roles=="Buyer": player.value = random.randint(50,400) else: player.value = 0 class Group(BaseGroup): rounds = models.LongStringField(initial='{}') seller_offers = models.LongStringField(initial='{}') buyer_actions = models.LongStringField(initial='{}') seller_guesses = models.LongStringField(initial='{}') lucky_draws = models.LongStringField(initial='{}') bargain_stage = models.StringField(initial='Nill') local_seed = models.IntegerField() class Player(BasePlayer): roles = models.StringField() value = models.IntegerField(initial = 0) def live_bargain(self, data): if data['type'] == "seller_offers": offer_dict = json.loads(self.group.seller_offers) offer_dict[len(offer_dict)] = data['offer'] self.group.seller_offers = json.dumps(offer_dict) self.group.bargain_stage = "offer_made,wait_action" prob_percent = int(round(self.subsession.prob_continuation ** (len(offer_dict) + 1), 2) * 100) if prob_percent < 10: prob_percent = "Less than 10%" message = {'type': 'seller_offer_made', 'offer':data['offer'], 'roundnum': len(offer_dict) + 1, 'prob_percent': prob_percent } return {0: message} if data['type'] == "buyer_actions": draw = 0 action_dict = json.loads(self.group.buyer_actions) action_dict[len(action_dict)] = data['action'] self.group.buyer_actions = json.dumps(action_dict) terminated = False offer_dict = json.loads(self.group.seller_offers) if data['action']=="Outside": terminated =True self.group.bargain_stage = "outside, end" for player in self.group.get_players(): if player.roles=="Buyer": player.payoff = player.subsession.outside_option if player.roles=="Seller": player.payoff = 0 if data['action'] == "Accept": terminated = True self.group.bargain_stage = "accepted, end" for player in self.group.get_players(): if player.roles == "Buyer": player.payoff =player.value - int(offer_dict[str(len(offer_dict)-1)]) if player.roles == "Seller": player.payoff = int(offer_dict[str(len(offer_dict)-1)]) if data['action'] == "Reject": draw = random.randint(1, 100) draw_dict = json.loads(self.group.lucky_draws) draw_dict[len(draw_dict)] = draw self.group.lucky_draws = json.dumps(draw_dict) if draw>self.subsession.prob_continuation*100: terminated = True self.group.bargain_stage = "rejected, end" self.payoff = 0 else: self.group.bargain_stage = "rejected, wait_guess" prob_percent = int(round(self.subsession.prob_continuation ** (len(offer_dict) + 1), 2) * 100) if prob_percent < 10: prob_percent = "Less than 10%" message = {'type': 'buyer_action_made', 'action': data['action'], 'terminated': terminated, 'pre_offer':offer_dict[str(len(offer_dict)-1)], 'roundnum': len(offer_dict) + 1, 'prob_percent': prob_percent, 'draw' : (100-draw)*3.6-36, } return {0: message} if data['type'] == "seller_guess": guess_dict = json.loads(self.group.seller_guesses) guess_dict[len(guess_dict)] = {'min': data['guess_min'], 'max': data['guess_max']} self.group.seller_guesses = json.dumps(guess_dict) self.group.bargain_stage = "guess_made,wait_offer" prob_percent = int(round(self.subsession.prob_continuation ** (len(guess_dict) + 1), 2) * 100) if prob_percent < 10: prob_percent = "Less than 10%" 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 live_instruct(self, data): if data['read'] == 1: if self.read == -1: self.read = 1 return {self.id_in_group: {'read': self.read}} read = models.IntegerField(initial=-1) q1 = models.IntegerField(choices=[ [1, 'I do not know how much the buyer values the asset.'], [2, 'If the buyer takes an outside option, I earn 25 tokens.'], [3, 'If I offer 200 tokens, and the buyer accepts it, then I earn 200 tokens.'], [4, 'If the buyer rejects my offer, then I can make a new offer with an 80% chance.'] ], widget=widgets.RadioSelect, label='Suppose you are a seller. Which of the followings is NOT TRUE?') q2 = models.IntegerField(choices=[ [1, 'If I accept a price offer of 200 tokens, I earn 200 tokens.'], [2, 'If I take an outside option, I earn 250 tokens.'], [3, 'If I accept a price offer of 200 tokens, I earn 100 tokens.'], [4, 'In Round 2 of this match, the value of the asset will be different from 300.'] ], widget=widgets.RadioSelect, label='Suppose you are a buyer, and the value of the asset is 300. 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?') def set_error_message(self, 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