from otree.api import BaseConstants, BaseGroup, BasePlayer, BaseSubsession from otree.api import Currency as c from otree.api import currency_range, models, widgets import random import logging author = 'Dr. Anne Barthel, Dr. Eric Hoffman, Walker Chesley' doc = """ You play the part of a farmer who has the choice of 4 different markets, Corn, wheat, rice or soybeans. Your goal is to maxamize your profit in your chosen market. The more sellers in the market, the less profit will be available. """ logger = logging.getLogger(__name__) class Constants(BaseConstants): name_in_url = 'free_entry_exit' players_per_group = None num_rounds = 20 class Subsession(BaseSubsession): def creating_session(self): # might have to set prices and demand here? since it's dependant on the number of players pass def vars_for_admin_report(self): group = self.get_groups()[0] player_data = [] corn_profit = [g.corn_profit for g in group.in_all_rounds()] player_data.append({ 'name':'Corn Profit', 'data':corn_profit }) wheat_profit = [g.wheat_profit for g in group.in_all_rounds()] player_data.append({ 'name':'Wheat Profit', 'data':wheat_profit }) rice_profit = [g.rice_profit for g in group.in_all_rounds()] player_data.append({ 'name':'Rice Profit', 'data':rice_profit }) soybean_profit = [g.soybean_profit for g in group.in_all_rounds()] player_data.append({ 'name':'Soybean Profit', 'data':soybean_profit }) return { 'units': player_data, 'categories': list(range(1, Constants.num_rounds, 1)) } class Group(BaseGroup): # Group variables for access in both set_price and set_payoff functions: corn_price = models.IntegerField(initial=11) total_corn = models.IntegerField(initial=0) corn_profit = models.IntegerField(verbose_name="Corn", initial=0) wheat_price = models.IntegerField(initial=10) total_wheat = models.IntegerField(initial=0) wheat_profit = models.IntegerField(verbose_name="Wheat", initial=0) rice_price = models.IntegerField(initial=11) total_rice = models.IntegerField(initial=0) rice_profit = models.IntegerField(verbose_name="Rice", initial=0) soybean_price = models.IntegerField(initial=0) total_soybean = models.IntegerField(initial=0) soybean_profit = models.IntegerField(verbose_name="Soybean", initial=0) highest_profit = models.IntegerField(initial=0) best_market = models.TextField() def set_markets(self): # If not already at 0, reset Group vars to 0 before we calculate price & profit: if(self.total_corn != 0): self.total_corn = 0 if(self.total_wheat != 0): self.total_wheat = 0 if(self.total_rice != 0): self.total_rice = 0 if(self.total_soybean != 0): self.total_soybean = 0 for p in self.get_players(): if(p.market_choice == 'Corn'): self.total_corn += 1 if(p.market_choice == 'Wheat'): self.total_wheat += 1 if(p.market_choice == 'Rice'): self.total_rice += 1 if(p.market_choice == 'Soybeans'): self.total_soybean += 1 logger.info(f'Player {p.id_in_group} has market: {p.market_choice}') total_market_count = self.total_corn + self.total_wheat + self.total_rice + self.total_soybean # Make it easier to see my null market bug: still to be fixed: if total_market_count != len(self.get_players()): logger.error("MARKETS ARE OFF SOMEONE IS NOT IN A MARKET!!!") logger.info(F"Total Market Counts: \nCorn: {self.total_corn}\nWheat: {self.total_wheat}\nRice: {self.total_rice}\nSoybean: {self.total_soybean}") # Lowest possible price for each market are the initial values for the model: def set_base_price(self): if(len(self.get_players()) <= 9): self.corn_price = self.corn_price - self.total_corn self.rice_price = self.rice_price - self.total_rice self.wheat_price = self.wheat_price - self.total_wheat self.soybean_price = (len(self.get_players()) + 6) - self.total_soybean if(10 <= len(self.get_players()) <= 14): self.corn_price = (self.corn_price + 1) - self.total_corn self.rice_price = (self.rice_price + 2) - self.total_rice self.wheat_price = (self.wheat_price + 1) - self.total_wheat self.soybean_price = (len(self.get_players()) + 2) - self.total_soybean if(15 <= len(self.get_players()) <= 19): self.corn_price = (self.corn_price + 2) - self.total_corn self.rice_price = (self.rice_price + 4) - self.total_rice self.wheat_price = (self.wheat_price + 2) - self.total_wheat self.soybean_price = (len(self.get_players()) - 2) - self.total_soybean if(20 <= len(self.get_players()) <= 24): self.corn_price = (self.corn_price + 3) - self.total_corn self.rice_price = (self.rice_price + 6) - self.total_rice self.wheat_price = (self.wheat_price + 3) - self.total_wheat self.soybean_price = (len(self.get_players()) - 6) - self.total_soybean if(25 <= len(self.get_players()) <= 29): self.corn_price = (self.corn_price + 4) - self.total_corn self.rice_price = (self.rice_price + 8) - self.total_rice self.wheat_price = (self.wheat_price + 4) - self.total_wheat self.soybean_price = (len(self.get_players()) - 10) - self.total_soybean if(30 <= len(self.get_players()) <= 34): self.corn_price = (self.corn_price + 5) - self.total_corn self.rice_price = (self.rice_price + 10) - self.total_rice self.wheat_price = (self.wheat_price + 5) - self.total_wheat self.soybean_price = (len(self.get_players()) - 14) - self.total_soybean if(35 <= len(self.get_players()) <= 39): self.corn_price = (self.corn_price + 6) - self.total_corn self.rice_price = (self.rice_price + 12) - self.total_rice self.wheat_price = (self.wheat_price + 6) - self.total_wheat self.soybean_price = (len(self.get_players()) - 18) - self.total_soybean if(40 <= len(self.get_players()) <= 44): self.corn_price = (self.corn_price + 7) - self.total_corn self.rice_price = (self.rice_price + 14) - self.total_rice self.wheat_price = (self.wheat_price + 7) - self.total_wheat self.soybean_price = (len(self.get_players()) - 22) - self.total_soybean def set_price_per_player(self): # Set price var on player: for p in self.get_players(): if(p.market_choice == 'Corn'): p.price = self.corn_price if(p.market_choice == 'Wheat'): p.price = self.wheat_price if(p.market_choice == 'Rice'): p.price = self.rice_price if(p.market_choice == 'Soybeans'): p.price = self.soybean_price logger.info(f'Player: {p.id_in_group} set price of {p.price} in {p.market_choice}') def set_payoff(self): # set group profit values: self.corn_profit = self.corn_price - 8 self.rice_profit = self.rice_price - 10 self.wheat_profit = self.wheat_price - 9 self.soybean_profit = self.soybean_price - 11 profit_array = [ self.corn_profit, self.rice_profit, self.wheat_profit, self.soybean_profit ] self.highest_profit = max(profit_array) logger.info(F"Profits:\nCorn: {self.corn_profit}\nRice: {self.rice_profit}\nWheat: {self.wheat_profit}\nSoybean: {self.soybean_profit}") if self.highest_profit == self.corn_profit: self.best_market = "Corn" elif self.highest_profit == self.rice_profit: self.best_market = "Rice" elif self.highest_profit == self.wheat_profit: self.best_market = "Wheat" elif self.highest_profit == self.soybean_profit: self.best_market = "Soybean" logger.info(F"best market for Best response: {self.best_market}\nProfit: {self.highest_profit}") def set_profit_per_player(self): # set profit values per player: for p in self.get_players(): if(p.market_choice == 'Corn'): p.profit = self.corn_profit if(p.market_choice == 'Rice'): p.profit = self.rice_profit if(p.market_choice == 'Wheat'): p.profit = self.wheat_profit if(p.market_choice == 'Soybeans'): p.profit = self.soybean_profit logger.info(f'Player {p.id_in_group} has profit of {p.profit}') def best_response(self): logger.info(F"Highest Profit: {self.in_round(self.round_number - 1).highest_profit}") # if the best market produced negative profit reshuffle the bots into random markets: # This is under the impression, that since highest profit was the largest profit in the # last round, all markets produced negative profit, therefore we need to shuffle into # new markets. This is in hopes of avoiding the two-market bug I encountered. Where the bots # would get stuck choosing between two markets, each round, then the other two open markets in the # next round. if(self.is_negative(self.in_round(self.round_number - 1).highest_profit)) == False: logger.info("Highest profit negative, reshuffle...") return self.random_market_choice() else: best_markets = [] selection = [] best_markets.append(self.best_market) # Make empty markets available to bots: if self.in_round(self.round_number - 1).total_corn == 0: best_markets.append("Corn") if self.in_round(self.round_number - 1).total_rice == 0: best_markets.append("Rice") if self.in_round(self.round_number - 1).total_soybean == 0: best_markets.append("Soybeans") if self.in_round(self.round_number - 1).total_wheat == 0: best_markets.append("Wheat") # Make positive markets available to bots: if self.is_negative(self.in_round(self.round_number -1).corn_profit) == True: best_markets.append("Corn") if self.is_negative(self.in_round(self.round_number -1).rice_profit) == True: best_markets.append("Rice") if self.is_negative(self.in_round(self.round_number -1).wheat_profit) == True: best_markets.append("Wheat") if self.is_negative(self.in_round(self.round_number -1).soybean_profit) == True: best_markets.append("Soybeans") best_markets.append(self.in_round(self.round_number - 1).best_market) # Ensure each market is there only once: for market in best_markets: if market not in selection: if market != None: selection.append(market) # GIve undue influence to best market? # Having the bots select an empty market at random, or any positive profit market, or the best market, # should help distribute the bots in a more human like behavior # if all markets had someone in them last round bots will choose the market with the highest profit in the previous round: return random.choice(selection) def is_negative(self, profit_last_round): if profit_last_round >= 0: return True else: return False def change_market_chance(self): chance_set = [1,2,3,4,5,6,7,8,9,10] chance = random.choice(chance_set) do_i_change = chance % 2 return do_i_change def random_market_choice(self): markets = [ 'Corn', 'Soybeans', 'Rice', 'Wheat' ] return random.choice(markets) class Player(BasePlayer): price = models.IntegerField( doc="""The price for the given item sold. """ ) market_choice = models.StringField( doc="Players selected market", choices=[ 'Corn', 'Soybeans', 'Rice', 'Wheat', ], widget=widgets.RadioSelect, ) profit = models.CurrencyField( doc=""" Players profit """ ) # Customize export of player data to make it easier to read: def custom_export(players): yield ['Player Number','Round Number', 'Market Choice', 'Price', 'Profit'] for p in players: yield [p.id_in_group, p.round_number, p.market_choice, p.price, p.profit]