from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range, ) import random # なにかコードについて質問がある場合は、長瀬までご連絡ください。 # メールアドレスは、odenshacho@icloud.comです class Constants(BaseConstants): # 実験appと同じ名前にする name_in_url = 'carbon_b_copy' # 何人1組にするか設定 players_per_group = 4 # ラウンド数の設定 # なおターンという概念がotreeにないので、ラウンドごとに処理を分けて実質ターンを作れば良い。 num_rounds = 6 # 実験説明用にincludeするテンプレートファイルの場所を指定 # 通常は、実験appの名前/instructions.htmlとし、instructions.htmlというテンプレートファイルをつくる instructions_template = 'carbon_b_copy/instructions.html' # 政府から配布される排出枠の上限 2022ver gov_co2 = [400,400,300,300,200,200] # 政府から配布される排出枠の上限 2022年度バージョン。最終的にはこっちを使いたい Jyogen_list = [400,400,300,300,200,200] #売り上げ生産能力 2022年度追加機能 2022ver Unit_price_list = [150,150,120,120] # 各入札者の資本金 shihonkin = 10000 # 技術導入に対する投資額 invest_price = 3500 # 生産における必要枠数それぞれ expand_need = 120 normal_need = 100 shrink_need = 80 non_need = 0 # 生産における必要枠数それぞれ(チケット使用時) expand_need_ticket = 96 normal_need_ticket = 80 shrink_need_ticket = 64 non_need_ticket = 0 # 辞書の指定した値のキーをリストとして取り出す関数 def get_keys_by_value(dict, value): return [key for key, val in dict.items() if val == value] class Subsession(BaseSubsession): game_app = models.StringField(initial="差別") Jyogen = models.IntegerField() def creating_session(self): self.Jyogen = Constants.Jyogen_list[self.round_number-1] import random Unit_list = random.sample(Constants.Unit_price_list, 4) for p in self.get_players(): if self.round_number == 1: p.unit_price_ability = Unit_list[p.id_in_group-1] else: p.unit_price_ability = p.in_round(1).unit_price_ability p.expand_profit = Constants.expand_need* p.unit_price_ability p.normal_profit = Constants.normal_need* p.unit_price_ability p.shrink_profit = Constants.shrink_need* p.unit_price_ability p.non_profit = 0 class Group(BaseGroup): # 1200枠超えてたらここをTrueに変える can_haibun = models.BooleanField(initial=False) # 全員で何枠入札してるか数えたやつ total_purchase_num = models.IntegerField(initial=0) # 政府から配布される排出枠の上限 current_gov_co2 = models.IntegerField(initial=Constants.gov_co2) # 枠トレードが起こったかどうかフラグ waku_trade_flag = models.BooleanField(initial=False) # 企業ごとの初期配分量を決定するための処理 def do_shoki_haibun(self): # データベースからプレイヤーのデータを持ってくる players = self.get_players() # playersの中身[P1, P2, P3, P4] # 1200枠を超えてるか判定用リスト purchase_num_lst = [] # この時点でpurchase_num_lstは空のリスト # ループでそれぞれのプレイヤーの購入希望排出枠数をリストに追加 for player in players: purchase_num_lst.append(player.purchase_num) # この時点でpurchase_num_lstの中身は、[P1の希望枠数,P2の希望枠数,P3の希望枠数,P4の希望枠数] # sum関数を使ってリストの要素をすべて足し合わせ、total_purchase_numに代入 self.total_purchase_num = sum(purchase_num_lst) # 1200枠以上あれば初期配分可能、そうじゃなかったらやり直し。 # pages.pyでcan_haibunがTrueのとき初期配分ページをすべてスキップするようにしている。 if self.total_purchase_num >=[400,400,300,300,200,200]: self.can_haibun = True else: # このFalseに特に意味はないのですが、こうするとこれ以下の関数の処理を強制的に行わないようにできる。 return False # 1200枠以上になってて配分が可能だったら下の処理が走る。 # 入札価格用のリストを作成 purchase_price_lst = [] # この時点では、まだpurchase_price_lstは空のリスト # ループでそれぞれのプレイヤーの入札価格をリストに追加 for player in players: purchase_price_lst.append(player.purchase_price) # この時点でpurchase_price_lstの中身は、[P1の入札価格,P2の入札価格,P3の入札価格,P4の入札価格] # 入札価格が低い順に並び替え purchase_price_hikuijun = sorted(purchase_price_lst) # これでpurchase_price_hikuijunの中身が[一番低い入札価格, ....., 一番高い入札価格] # まず何が重複してるか調べる必要がある # print(purchase_price_hikuijun) # リストの中の要素がなくなるまでループ処理 while len(purchase_price_hikuijun) > 0: # 現状一番高い入札価格をリストから取り出す。 # popは取り出す関数。 highest_price = purchase_price_hikuijun[-1] # 同じ値段をつけたプレイヤーが何人いるか数える # count関数は要素を数えられる same_highprice_count = purchase_price_hikuijun.count(highest_price) # 同じhighest_priceをつけてる他のプレイヤーがいない場合 if same_highprice_count==0: highest_price = purchase_price_hikuijun.pop() # 一番高い価格をつけているプレイヤーのインデックスをしらべる。 # index関数は要素がリストの何番目か調べる関数 player_idx = purchase_price_lst.index(highest_price) # そのインデックス使ってどのプレイヤーがbuyerが決定する。 buyer = players[player_idx] # 購入量を決定する buy_amount = buyer.purchase_num # 政府の残り排出枠が購入量よりも多ければ if self.current_gov_co2 >= buy_amount: # 合計代金の計算 total_cost = buy_amount * highest_price # 購入者の所持金の計算 buyer.shojikin -= total_cost # 購入者の初期配分を決定 buyer.shoki_haibun = buy_amount # 政府の残り排出枠を計算 self.current_gov_co2 -= buy_amount # 政府の残り排出枠が購入量よりも少ないとき、政府の残り枠分しか購入できない else: # 購入料を政府の残り枠数と同じにする buy_amount = self.current_gov_co2 # 合計代金の計算 total_cost = buy_amount * highest_price # 購入者の所持金の計算 buyer.shojikin -= total_cost # 購入者の初期配分を決定 buyer.shoki_haibun = buy_amount # 政府の残り排出枠を計算 self.current_gov_co2 -= buy_amount # 同じ価格をつけているプレイヤーが他にいる場合 # ちなみにこのカウントが1のとき2人いるという意味 elif same_highprice_count>0: # 空リスト定義 buyers = [] buy_amounts = [] while same_highprice_count > 0: highest_price = purchase_price_hikuijun.pop() # 一番高い価格をつけている1人目のプレイヤーのインデックスをしらべる。 # index関数は要素がリストの何番目か調べる関数 player_idx = purchase_price_lst.index(highest_price) # これしないと2回目のindex関数でまた同じプレイヤーを引っ張って来てしまう。 purchase_price_lst[player_idx] = None # 購入者を設定 buyer = players[player_idx] # リストに追加 buyers.append(buyer) # 購入量を設定 buy_amount = buyer.purchase_num # リストに追加 buy_amounts.append(buy_amount) # カウントを一つを減らす same_highprice_count -= 1 # デバッグ用 print("buyers") print(buyers) # 政府の残り排出枠が購入量よりも多ければ if self.current_gov_co2 >= sum(buy_amounts): # enumerateはfor文でインデックスつけながらループさせるときに便利なやつ for idx, buy_amount in enumerate(buy_amounts): # 購入者の合計代金の計算 total_cost = buy_amount * highest_price # 購入者の所持金の計算 buyers[idx].shojikin -= total_cost # 購入者の初期配分を決定 buyers[idx].shoki_haibun = buy_amount # 政府の残り排出枠を計算 self.current_gov_co2 -= buy_amount # 政府の残り排出枠が購入量よりも少ないとき、政府の残り枠分しか購入できない else: # 分配比率計算用の分母を計算 bunbo = sum(buy_amounts) num_can_buy = self.current_gov_co2 for idx, buy_amount in enumerate(buy_amounts): print("num_can_buy") print(num_can_buy) print("current_gov_co2") print(self.current_gov_co2) # それぞれの配分量を設定 buy_amount_haibun = num_can_buy * buy_amount // bunbo # あまり計算用 self.current_gov_co2 -= buy_amount_haibun # 購入者の合計代金の計算 total_cost = highest_price * buy_amount_haibun # 購入者の所持金の計算 buyers[idx].shojikin -= total_cost # 購入者の初期配分を決定 buyers[idx].shoki_haibun = buy_amount_haibun # 同じ価格をつけているプレイヤーにランダムで余りを売らせる if self.current_gov_co2 > 0: p = random.choice(buyers) p.shoki_haibun += self.current_gov_co2 p.shojikin -= p.purchase_price * self.current_gov_co2 self.current_gov_co2 -= self.current_gov_co2 # 初期配分量を現在の枠数のところに代入 for player in players: player.waku_num = player.shoki_haibun def do_emission_trade(self): # 枠トレード前の状態を保存 self.save_desire() players = self.get_players() # playersの中身[P1, P2, P3, P4,] # 買いたい人と売りたい人がそれぞれ一人以上いないとトレードできないので関数を抜けるようにする emission_lst = [] for player in players: player_choice = player.emission emission_lst.append(player_choice) if not ('買う' in emission_lst and '売る' in emission_lst): return False # 買う側の変数定義 buy_price_lst = [] buy_player_id = [] buy_priceSum_num_dict = {} for player in players: if player.buy_num > 0: buy_price_lst.append(player.buy_price) buy_player_id.append(player.id_in_group) if player.buy_price in buy_priceSum_num_dict: buy_priceSum_num_dict[player.buy_price] += player.buy_num else: buy_priceSum_num_dict[player.buy_price] = player.buy_num # 買う数に0を入れた場合リストが空になって後にエラーが出るのでここで回避 if len(buy_price_lst) == 0: return False # 売る側の変数定義 sell_price_lst = [] sell_player_id = [] sell_priceSum_num_dict = {} for player in players: if player.sell_num > 0: sell_price_lst.append(player.sell_price) sell_player_id.append(player.id_in_group) if player.sell_price in sell_priceSum_num_dict: sell_priceSum_num_dict[player.sell_price] += player.sell_num else: sell_priceSum_num_dict[player.sell_price] = player.sell_num # 売る数に0を入れた場合リストが空になって後にエラーが出るのでここで回避 if len(sell_price_lst) == 0: return False # 辞書作成 buy_id_price_dict = {} for idx, price in enumerate(buy_price_lst): buy_id_price_dict[buy_player_id[idx]] = price # price高い順でソート buy_priceSum_num_takaijun_dict = dict(sorted(buy_priceSum_num_dict.items(), key=lambda x:x[0], reverse=True)) # デバッグ用 print("デバッグ用買う側") print("buy_price_lst") print(buy_price_lst) print("buy_player_id") print(buy_player_id) print("buy_id_price_dict") print(buy_id_price_dict) print("buy_priceSum_num_takaijun_dict") print(buy_priceSum_num_takaijun_dict) # 辞書作成 sell_id_price_dict = {} for idx, price in enumerate(sell_price_lst): sell_id_price_dict[sell_player_id[idx]] = price # price低い順でソート sell_priceSum_num_hikuijun_dict = dict(sorted(sell_priceSum_num_dict.items(), key=lambda x:x[0])) # デバッグ用 print("デバッグ用売る側") print("sell_price_lst") print(sell_price_lst) print("sell_player_id") print(sell_player_id) print("sell_id_price_dict") print(sell_id_price_dict) print("sell_priceSum_num_hikuijun_dict") print(sell_priceSum_num_hikuijun_dict) buy_num_priceSumTakaijun_lst = list(buy_priceSum_num_takaijun_dict.values()) sell_num_priceSumHikuijun_lst = list(sell_priceSum_num_hikuijun_dict.values()) buy_price_priceSumTakaijun_lst = list(buy_priceSum_num_takaijun_dict.keys()) sell_price_priceSumHikuijun_lst = list(sell_priceSum_num_hikuijun_dict.keys()) # 必要条件を満たしているかチェック if sell_price_priceSumHikuijun_lst[0] > buy_price_priceSumTakaijun_lst[0]: return False ''' # 必要条件について 例えば、最低価格100円、120円で売りたいと言ってる人が1人ずついて、 買いたい2人が、最高価格90円、80円で買いたいといっているとする。 買いたいと言っている人たちが提示している最高価格の最大値(この場合90円)が、 売りたいと言っている人たちが提示している最低価格の最小値(この場合100円)を上回らないと絶対成立しない。 売り手の提示価格の最小値<買い手の提示価格の最大値がTrueのときだけ処理をする。 ''' sell_idx = 0 buy_idx = 0 count = 1 # 売る量と買う量のリストの値の最後の要素がどちらか片方0になるまでループ while sell_num_priceSumHikuijun_lst[-1] > 0 and buy_num_priceSumTakaijun_lst[-1] > 0: # デバッグ用 print("whileループ"+str(count)+"週目") print("sell_num_priceSumHikuijun_lst") print(sell_num_priceSumHikuijun_lst) print("buy_num_priceSumTakaijun_lst") print(buy_num_priceSumTakaijun_lst) # 売る量が買う量よりも少ない場合 if sell_num_priceSumHikuijun_lst[sell_idx] <= buy_num_priceSumTakaijun_lst[buy_idx]: # 買い手、売り手の提示価格 buy_price_offer = list(buy_priceSum_num_takaijun_dict.keys())[buy_idx] sell_price_offer = list(sell_priceSum_num_hikuijun_dict.keys())[sell_idx] # 取引をする枠数 trade_num = sell_num_priceSumHikuijun_lst[sell_idx] trade_num_shoki = trade_num # 取引価格 trade_price = (sell_price_priceSumHikuijun_lst[sell_idx] + buy_price_priceSumTakaijun_lst[buy_idx])//2 # 買い手と売り手それぞれのリスト buy_player_ids = Constants.get_keys_by_value(dict=buy_id_price_dict, value=buy_price_offer) sell_player_ids = Constants.get_keys_by_value(dict=sell_id_price_dict, value=sell_price_offer) buy_players = [self.get_player_by_id(p_id) for p_id in buy_player_ids] sell_players = [self.get_player_by_id(p_id) for p_id in sell_player_ids] # トレードに際しての比率を計算するようの分母を設定 bunbo = buy_num_priceSumTakaijun_lst[buy_idx] print("buy_players") print(buy_players) # 購入側の処理 for buy_player in buy_players: true_buy_num = trade_num_shoki * buy_player.buy_num // bunbo print("true_buy_num") print(true_buy_num) buy_player.waku_num += true_buy_num buy_player.shojikin -= trade_price * true_buy_num buy_player.buy_num -= true_buy_num trade_num -= true_buy_num # 売る側の処理 for sell_player in sell_players: sell_player.waku_num -= sell_player.sell_num sell_player.shojikin += trade_price * sell_player.sell_num sell_player.sell_num = 0 # 購入するときの比率計算で余りが出ている場合 if trade_num > 0: # 同じ価格をつけているプレイヤーにランダムで余りを付与する p = random.choice(buy_players) p.waku_num += trade_num p.buy_num -= trade_num p.shojikin -= trade_price * trade_num buy_num_priceSumTakaijun_lst[buy_idx] -= trade_num_shoki sell_idx += 1 # 売る量が買う量よりも多い場合 elif sell_num_priceSumHikuijun_lst[sell_idx] > buy_num_priceSumTakaijun_lst[buy_idx]: # 買い手、売り手の提示価格 buy_price_offer = list(buy_priceSum_num_takaijun_dict.keys())[buy_idx] sell_price_offer = list(sell_priceSum_num_hikuijun_dict.keys())[sell_idx] # 取引をする枠数 trade_num = buy_num_priceSumTakaijun_lst[buy_idx] trade_num_shoki = trade_num # 取引価格 trade_price = (sell_price_priceSumHikuijun_lst[sell_idx] + buy_price_priceSumTakaijun_lst[buy_idx])//2 # 買い手と売り手それぞれのリスト buy_player_ids = Constants.get_keys_by_value(dict=buy_id_price_dict, value=buy_price_offer) sell_player_ids = Constants.get_keys_by_value(dict=sell_id_price_dict, value=sell_price_offer) buy_players = [self.get_player_by_id(p_id) for p_id in buy_player_ids] sell_players = [self.get_player_by_id(p_id) for p_id in sell_player_ids] # トレードに際しての比率を計算するようの分母を設定 bunbo = sell_num_priceSumHikuijun_lst[sell_idx] # 購入側の処理 for buy_player in buy_players: buy_player.waku_num += buy_player.buy_num buy_player.shojikin -= buy_player.buy_num * trade_price buy_player.buy_num = 0 # 売る側の処理 for sell_player in sell_players: true_sell_num = trade_num_shoki * sell_player.sell_num // bunbo print("true_sell_num") print(true_sell_num) sell_player.waku_num -= true_sell_num sell_player.shojikin += trade_price * true_sell_num sell_player.sell_num -= true_sell_num trade_num -= true_sell_num # 売るときの比率計算で余りが出ている場合 if trade_num > 0: # 同じ価格をつけているプレイヤーにランダムで余りを売らせる p = random.choice(sell_players) p.waku_num -= trade_num p.sell_num -= trade_num p.shojikin += trade_price * trade_num sell_num_priceSumHikuijun_lst[sell_idx] -= trade_num_shoki buy_idx += 1 # 終了条件 if buy_idx == len(buy_num_priceSumTakaijun_lst): break if sell_idx == len(sell_num_priceSumHikuijun_lst): break count += 1 # 実際の購買量を計算 self.cal_nums() # 枠トレードが起こったことを保存 waku_trade_flag = True # 枠の売買のときの最初の希望価格、希望数を保存する関数 def save_desire(self): players = self.get_players() for p in players: p.desired_buy_num = p.buy_num p.desired_buy_price = p.buy_price p.desired_sell_num = p.sell_num p.desired_sell_price = p.sell_price p.shojikin_before_trade = p.shojikin # 実際に買った量や売った量を計算する関数 def cal_nums(self): players = self.get_players() for p in players: p.bought_num = p.desired_buy_num - p.buy_num p.sold_num = p.desired_sell_num - p.sell_num class Player(BasePlayer): # 資産(いわゆる最終の利得になる部分) shisan = models.IntegerField(initial=0) # 排出枠に対する入札量 purchase_num = models.IntegerField(label='') # 排出枠に対する入札価格 purchase_price = models.IntegerField(label='') # 初期配分量 shoki_haibun = models.IntegerField() # 各入札者の所持金 shojikin = models.IntegerField(initial=Constants.shihonkin) # 所持金枠トレード前 shojikin_before_trade=models.IntegerField() # 現在の枠数 waku_num = models.IntegerField() #売上金 expand_profit =models.IntegerField() normal_profit = models.IntegerField() shrink_profit = models.IntegerField() non_profit = models.IntegerField() '''枠トレード前の状態を保存するフィールド''' # 枠を買う量(保存用) desired_buy_num=models.IntegerField() # 枠を買う価格(保存用) desired_buy_price=models.IntegerField() # 枠を売る量(保存用) desired_sell_num=models.IntegerField() # 枠を売る価格(保存用) desired_sell_price=models.IntegerField() # 実際買った量 bought_num = models.IntegerField(initial=0) # 実際売った量 sold_num = models.IntegerField(initial=0) # 枠を買う量 buy_num=models.IntegerField(min=0,label='',) # 枠を買う価格 buy_price=models.IntegerField(min=0,label='',) # 枠を売る量 sell_num=models.IntegerField(min=0,label='',) # 枠を売る価格 sell_price=models.IntegerField(min=0,label='',) invest = models.StringField( choices=[['はい', 'はい'], ['いいえ', 'いいえ']], label='', widget=widgets.RadioSelectHorizontal, ) seisan = models.StringField( choices=[['拡大', '拡大生産'], ['通常', '通常生産'], ['縮小', '縮小生産'], ['しない', '生産しない']], label='', widget=widgets.RadioSelectHorizontal, ) emission = models.StringField( choices=[['買う', '排出枠を買う'], ['しない', '売買しない'], ['売る', '排出枠を売る'],], label='', widget=widgets.RadioSelectHorizontal, )