from otree.api import * import random import numpy as np author = 'Kengo SUZUKI' doc = """ This game is originally designed by Sato K, Toda M, and Yamagishi T (1985). https://doi.org/10.4992/jjpsy.56.277 KS replicated this game as a web application for research and educational use. """ ########## 外生的に決めるパラメータを定義するクラス ########## ########## Class to define externally given variables ########## class Constants(BaseConstants): name_in_url = 'STY1985_new' # The name of game displayed on URL players_per_group = 3 # The number of players per game num_rounds = 50 # The number of rounds per game num_trees = 3 # The number of trees per game max_tree_size = 9 # Maximum value of tree size initial_tree_size = 9 # Initial value of tree size thinking_time = 30 # Thinking time per round ########## サブセッション=セッションを構成する各ゲームの集合のクラス(グループ分け,条件設定等) ########## ########## Class of a set of games consisting a session (group assignment etc) ########## class Subsession(BaseSubsession): def creating_session(subsession): labels = ['001', '002', '003', '004', '005', '006', '007', '008', '009' ] for p, label in zip(subsession.get_players(), labels): p.participant.label = label new_structure = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] subsession.set_group_matrix(new_structure) print(new_structure) ########## グループ変数を定義するクラス ########## ########## Class to define group valiables ########## class Group(BaseGroup): size_tree1 = models.IntegerField( # 1本目の木のサイズ max = Constants.max_tree_size, ) size_tree2 = models.IntegerField( # 2本目の木のサイズ max = Constants.max_tree_size, ) size_tree3 = models.IntegerField( # 3本目の木のサイズ max = Constants.max_tree_size, ) size_tree4 = models.IntegerField( # 4本目の木のサイズ max = Constants.max_tree_size, ) num_cut_trees_inputs = models.IntegerField( # 伐採を希望したプレイヤー数 initial=0, ) num_cut_trees_modify = models.IntegerField( # 実際に伐採できたプレイヤー数 initial=0, ) ave_size_of_cut_trees = models.FloatField( # 伐採された木の平均サイズ initial = 0, ) size_tree_average = models.FloatField( # 共有林にある木の平均サイズ(そのラウンドの伐採後=次ラウンドの伐採前) initial = 0, ) total_point_get = models.IntegerField( # 全プレイヤーの総獲得点数 initial = 0, ) total_point_accumulation = models.IntegerField( # 全プレイヤーの総累積獲得点数 initial = 0, ) ########## プレイヤー変数・メソッドを定義するクラス ########## ########## Class to define player valiables ########## class Player(BasePlayer): cut_input = models.IntegerField( # 伐採を希望する木の番号(0は伐採を希望しないことを意味する) initial = 0, min = 0, max =Constants.num_trees ) cut_modify = models.IntegerField( # 実際に伐採した木の番号(0は伐採しなかったことを意味する) initial = 0, min = 0, max =Constants.num_trees ) point_get = models.IntegerField( # 獲得点数 initial = 0 ) point_accumulation = models.IntegerField( # 累積獲得点数 initial = 0 ) ########## 関数(ResultWaitPageで使用する関数は,クラスの外に書く) ########## ########## Functions(Functions used in ResultWaitPage need to be written outside class) ########## def update(group): if group.round_number == 1: group.size_tree1 = Constants.initial_tree_size group.size_tree2 = Constants.initial_tree_size group.size_tree3 = Constants.initial_tree_size group.size_tree4 = Constants.initial_tree_size else: group.size_tree1 = group.in_round(group.round_number-1).size_tree1 group.size_tree2 = group.in_round(group.round_number-1).size_tree2 group.size_tree3 = group.in_round(group.round_number-1).size_tree3 group.size_tree4 = group.in_round(group.round_number-1).size_tree4 def solve(group): players = group.get_players() # Get list of players cut_inputs = [p.cut_input for p in players] # list of player's decisions cut_modify = [] for i in range(len(cut_inputs)): cut_modify.append(cut_inputs[i]) # list of tree size(木のサイズのリスト) size_trees = [] for i in range(Constants.num_trees): tree_number = i + 1 tree_name = 'group.size_tree' + str(tree_number) size_trees.append(eval(tree_name)) # count the number of players who decided to cut each tree(各木の伐採を希望する人数) count_cut = [] for i in range(Constants.num_trees): count_cut.append(cut_modify.count(i+1)) # list of players whose decisions are duplicated(他者と伐採希望が重複するプレイヤーにフラグを立てる) players_duplicate = [0]*Constants.players_per_group for i in range(Constants.players_per_group): for j in range(Constants.players_per_group): if (cut_modify[i] != 0 and cut_modify[i] == cut_modify[j] and i != j): players_duplicate[i] = 1 # modify the decisions of players(他者と伐採希望が重複するプレイヤーの入力を修正する) list_players = [] list_trees = [] while sum(players_duplicate)>0: for i in range(Constants.num_trees): # for each tree if count_cut[i] >1: # if more than one player try to cut the tree for j in range(Constants.players_per_group): # list of players who want to cut the tree if cut_inputs[j] == i+1: list_players.append(j+1) for k in range(Constants.num_trees): # list of trees with the same size as the tree if size_trees[k] == size_trees[i] and count_cut[k] != 1: list_trees.append(k+1) while len(list_trees) < len(list_players): list_trees.append(0) random.shuffle(list_trees) # randomize who can get the tree for l in range(len(list_players)): # modify cut cut_modify[list_players[l]-1] = list_trees[l] for m in range(Constants.players_per_group): # update players_duplicate for n in range(Constants.players_per_group): if (cut_modify[m] != 0 and cut_modify[m] == cut_modify[n] and m != n): players_duplicate[m] = 1 else: players_duplicate[m] = 0 for o in range(Constants.num_trees): # update count_cut count_cut[o] = (cut_modify.count(o + 1)) for i in range(Constants.players_per_group): # update Field cut_modify players[i].cut_modify = cut_modify[i] cut_modify_check = [p.cut_modify for p in players] # デバッグ用 # increase points of players(各プレイヤーの獲得点数と累積獲得点数を更新する) for i in range(Constants.players_per_group): if cut_modify[i]>0: players[i].point_get = 2**(size_trees[cut_modify[i]-1]-1) for i in range(Constants.players_per_group): if group.round_number == 1: players[i].point_accumulation = players[i].point_get else: players[i].point_accumulation = players[i].in_round(group.round_number-1).point_accumulation + players[i].point_get point_get_check = [p.point_get for p in players] # デバッグ用 point_accumulation_check = [p.point_accumulation for p in players] # デバッグ用 # record total point(全プレイヤーの総獲得点数と総累積獲得点数を更新する) group.total_point_get = sum(point_get_check) group.total_point_accumulation = sum(point_accumulation_check) # record the number of cut trees(伐採を希望したプレイヤー数と実際に伐採したプレイヤー数を更新する) for i in range(Constants.players_per_group): if cut_inputs[i] > 0: group.num_cut_trees_inputs = group.num_cut_trees_inputs +1 for i in range(Constants.players_per_group): if cut_modify[i] > 0: group.num_cut_trees_modify = group.num_cut_trees_modify +1 # record average size of cut trees(伐採された木の平均サイズを更新する;1本も伐採されなかったらゼロとなる) ave_size_of_cut_trees = [] for i in range(Constants.num_trees): if count_cut[i] != 0: ave_size_of_cut_trees.append(size_trees[i]) if len(ave_size_of_cut_trees)>0: group.ave_size_of_cut_trees = sum(ave_size_of_cut_trees) / len(ave_size_of_cut_trees) else: # group.ave_size_of_cut_trees = np.nan group.ave_size_of_cut_trees = 0 # reset the size of cut trees(伐採された木のサイズを0にする) size_trees_after = [] for i in range(Constants.num_trees): if count_cut[i]>0: size_trees_after.append(0) else: size_trees_after.append(size_trees[i]) # trees grow(すべての木のサイズを1増やす;最大に達したらそれ以上増えない) for i in range(Constants.num_trees): if size_trees_after[i] < Constants.max_tree_size: size_trees_after[i] = size_trees_after[i]+1 for i in range(Constants.num_trees): # update Field size_treeX tree_number = i + 1 trees_grow = 'group.size_tree' + str(tree_number) + '= size_trees_after[i]' exec(trees_grow) # record average tree size just after cut group.size_tree_average = sum(size_trees_after) / Constants.num_trees print('players is', players) # デバッグ用 print('size_trees is', size_trees) # デバッグ用 print('cut_inputs is', cut_inputs) # デバッグ用 print('count_cut is', count_cut) # デバッグ用 print('players_duplicate is', players_duplicate) # デバッグ用 print('list_players is', list_players) # デバッグ用 print('list_trees is', list_trees) # デバッグ用 print('cut_modify is', cut_modify) # デバッグ用 print('cut_modify_check is', cut_modify_check) print('point_get_check is', point_get_check) print('point_accumulation_check is', point_accumulation_check) print('total_point_get is', group.total_point_get) print('total_point_accumulation is', group.total_point_accumulation) print('num_cut_trees_inputs is', group.num_cut_trees_inputs) print('num_cut_trees_modify is', group.num_cut_trees_modify) print('ave_size_of_cut_trees is', ave_size_of_cut_trees) print('ave_size_of_cut_trees is', group.ave_size_of_cut_trees) print('size_trees_after is', size_trees_after) print('size_tree_average is', group.size_tree_average) print('size_tree1 is', group.size_tree1) # デバッグ用 print('size_tree2 is', group.size_tree2) # デバッグ用 print('size_tree3 is', group.size_tree3) # デバッグ用 print('size_tree4 is', group.size_tree4) # デバッグ用 ########## ページを定義するクラス ########## ########## Class to define pages ########## class Introduction(Page): @staticmethod def is_displayed(self): return self.round_number == 1 @staticmethod def vars_for_template(self): return dict( # num_rounds=self.session.config['num_rounds'], num_rounds=Constants.num_rounds, ) class ResultsWaitPage1(WaitPage): after_all_players_arrive = 'update' class Input1(Page): form_model = 'player' form_fields = ['cut_input'] @staticmethod def is_displayed(self): return self.round_number == 1 @staticmethod def vars_for_template(self): return dict( num_trees=Constants.num_trees, image_path1 = 'STY1985/tree_{}.jpg'.format(self.group.size_tree1), image_path2 = 'STY1985/tree_{}.jpg'.format(self.group.size_tree2), image_path3 = 'STY1985/tree_{}.jpg'.format(self.group.size_tree3), image_path4 = 'STY1985/tree_{}.jpg'.format(self.group.size_tree4), ) class Input2(Page): form_model = 'player' form_fields = ['cut_input'] timeout_seconds = Constants.thinking_time @staticmethod def is_displayed(self): return self.round_number > 1 @staticmethod def vars_for_template(self): return dict( round_number = self.round_number, num_trees = Constants.num_trees, cut_input_prev = self.in_round(self.round_number-1).cut_input, cut_modify_prev = self.in_round(self.round_number-1).cut_modify, point_get_prev = self.in_round(self.round_number-1).point_get, point_accumulation_prev = self.in_round(self.round_number-1).point_accumulation, image_path1 = 'STY1985/tree_{}.jpg'.format(self.group.size_tree1), image_path2 = 'STY1985/tree_{}.jpg'.format(self.group.size_tree2), image_path3 = 'STY1985/tree_{}.jpg'.format(self.group.size_tree3), image_path4 = 'STY1985/tree_{}.jpg'.format(self.group.size_tree4), ) @staticmethod def js_vars(self): ## 配列の作成に必要な定数をローカルに作成 round_number = self.round_number # 現在のラウンド数 players = self.group.get_players() # すべてのプレイヤーのリスト num_players = Constants.players_per_group # プレイヤーの人数 ## point_accumulation を出力する List を作成 List_point_accumulation = [] for p in players: # リストに含まれるプレイヤーについて id = p.id_in_group # id を取得 pname = 'Player' + str(id) # プレイヤー名の文字列 pdata = [] # データ格納用のリスト for j in range(round_number-1): # 1ラウンド目から直前のラウンドまでについて pdata.append(p.in_round(j+1).point_accumulation) # pdata リストに値を追加 pdict = {'name':pname,'data':pdata} # プレイヤー毎のリストを作成 List_point_accumulation.append(pdict) print('List_point_accumulation is', List_point_accumulation) # デバッグ用 return dict( round_number = self.round_number, # num_rounds = self.session.config['num_rounds'], num_rounds=Constants.num_rounds, List_point_accumulation = List_point_accumulation, ) class ResultsWaitPage2(WaitPage): after_all_players_arrive = 'solve' class GameOver(Page): @staticmethod def is_displayed(self): return self.round_number == Constants.num_rounds # return self.round_number == self.session.config['num_rounds'] @staticmethod def vars_for_template(self): ## 配列の作成に必要な定数をローカルに作成 round_number = self.round_number # 現在のラウンド数 players = self.group.get_players() # すべてのプレイヤーのリスト num_players = Constants.players_per_group # プレイヤーの人数 return dict( round_number = self.round_number, num_trees=Constants.num_trees, cut_input = self.cut_input, cut_modify = self.cut_modify, point_get = self.point_get, point_accumulation = self.point_accumulation, ) @staticmethod def js_vars(self): ## 配列の作成に必要な定数をローカルに作成 round_number = self.round_number # 現在のラウンド数 players = self.group.get_players() # すべてのプレイヤーのリスト num_players = Constants.players_per_group # プレイヤーの人数 ## point_accumulation を出力する List を作成 List_point_accumulation = [] for p in players: # リストに含まれるプレイヤーについて id = p.id_in_group # id を取得 pname = 'Player' + str(id) # プレイヤー名の文字列 pdata = [] # データ格納用のリスト for j in range(round_number-1): # 1ラウンド目から直前のラウンドまでについて pdata.append(p.in_round(j+1).point_accumulation) # pdata リストに値を追加 pdata.append(p.point_accumulation) # 最終ラウンドの値を追加 pdict = {'name':pname,'data':pdata} # プレイヤー毎のリストを作成 List_point_accumulation.append(pdict) ## num_cut_trees_inputs & _modifyを出力する List を作成 pname1 = '希望' # プレイヤー名の文字列 pdata1 = [] # データ格納用のリスト for j in range(round_number-1): # 1ラウンド目から直前のラウンドまでについて pdata1.append(self.group.in_round(j+1).num_cut_trees_inputs) # pdata リストに値を追加 pdata1.append(self.group.num_cut_trees_inputs) # 最終ラウンドの値を追加 dict1 = {'name':pname1,'data':pdata1} pname2 = '伐採' # プレイヤー名の文字列 pdata2 = [] # データ格納用のリスト for j in range(round_number-1): # 1ラウンド目から直前のラウンドまでについて pdata2.append(self.group.in_round(j+1).num_cut_trees_modify) # pdata リストに値を追加 pdata2.append(self.group.num_cut_trees_modify) # 最終ラウンドの値を追加 dict2 = {'name':pname2,'data':pdata2} List_num_cut_trees = [dict1,dict2] # プレイヤー毎のリストを作成 ## ave_size_of_cut_treesを出力する List を作成 pname1 = 'inputs' # プレイヤー名の文字列 pdata1 = [] # データ格納用のリスト for j in range(round_number-1): # 1ラウンド目から直前のラウンドまでについて pdata1.append(self.group.in_round(j+1).ave_size_of_cut_trees) # pdata リストに値を追加 pdata1.append(self.group.ave_size_of_cut_trees) # 最終ラウンドの値を追加 List_ave_size_of_cut_trees = [{'name':pname1,'data':pdata1}] ## size_tree_averageを出力する List を作成 pname1 = 'inputs' # プレイヤー名の文字列 pdata1 = [] # データ格納用のリスト for j in range(round_number-1): # 1ラウンド目から直前のラウンドまでについて pdata1.append(self.group.in_round(j+1).size_tree_average) # pdata リストに値を追加 pdata1.append(self.group.size_tree_average) # 最終ラウンドの値を追加 List_size_tree_average = [{'name':pname1,'data':pdata1}] print('List_point_accumulation is', List_point_accumulation) # デバッグ用 print('List_num_cut_trees is', List_num_cut_trees) # デバッグ用 print('List_ave_size_of_cut_trees is', List_ave_size_of_cut_trees) # デバッグ用 print('List_size_tree_average is', List_size_tree_average) # デバッグ用 return dict( round_number = self.round_number, # num_rounds = self.session.config['num_rounds'], num_rounds=Constants.num_rounds, List_point_accumulation = List_point_accumulation, List_num_cut_trees = List_num_cut_trees, List_ave_size_of_cut_trees = List_ave_size_of_cut_trees, List_size_tree_average = List_size_tree_average, ) page_sequence = [Introduction, ResultsWaitPage1, Input1, Input2, ResultsWaitPage2, GameOver]