この記事は、一般交換ゲームの実験画面を作成するための__init__.py
およびHTMLテンプレートの書き方に関する記録・紹介を行います。
実験画面は主に五つのページになります。
- 紹介ページ(
Introduction.html
) - 役割配分ページ(
Role.html
) - 報酬分配ページ(
Contribute.html
, senderのみ) - 待ち画面(
ResultsWaitPage
) - 結果の表示画面(
Results.html
)
この五つのページのHTMLテンプレートの書き方について順次解説していきます。
紹介ページIntroduction.html
このページでは、参加者に実験の内容を説明します。
注意点として、oTreeでは各ページがデフォルトで全ラウンドに表示される設定になっています。
そのため、Introduction
ページが第1ラウンドのみに表示されるように設定する必要があります。
class Introduction(Page):
@staticmethod
#round1でしか提示しない
def is_displayed(player: Player):
return player.round_number == 1
ここでの @staticmethod は、is_displayed
メソッドを指します。
その意味は 「インスタンスやクラス自体を引数として受け取らないメソッド」 です。
今回の場合、is_displayed
の第1引数は Player
オブジェクトであり、Introduction
クラス(ページクラス)自体ではないため、staticmethod
として定義されています。
is_displayed
メソッドでは、return player.round_number == 1
と設置することで、このページを第1ラウンドでしか提示しないように設置しています。
役割配分ページRole.html
このページでは、各プレイヤーに自分の役割を提示するページです。
まずは、__init__.py
でテンプレートに渡す変数を定義します。
class Role(Page):
@staticmethod
#templateに渡す変数を定義する
def vars_for_template(player: Player):
players = player.group.get_players()
sender = next(p for p in players if p.roles == 'sender')
receiver = next(p for p in players if p.roles == 'receiver')
return{
"sender" : sender.id_in_group,
"receiver": receiver.id_in_group
}
vars_for_template は oTree が提供する特別なメソッド で、ページの HTML テンプレートに渡す変数を定義するために使います。
このメソッドがreturn
で返した辞書(dict)は、HTML テンプレート内で変数として直接参照できます。
例えば、ここで "sender" をキーとして返せば、テンプレート内で {{ sender }} と書くことで、その値が表示されます。
テンプレート側の書き方は以下となります:
{{ block title }}
役割のランダム配分
{{ endblock }}
{{ block content }}
{{ if player.id_in_group == sender }}
<p>あなたは<b>提供者(sender)</b>として選ばれました。</p>
{{ elif player.id_in_group == receiver}}
<p>あなたは<b>受取者(receiver)</b>として選ばれました。</p>
{{ else }}
<p>あなたは今回プレイヤーとして選ばれなかった。他のプレイヤーの操作が完了するまでお待ちください。<p>
{{ endif }}
{{ formfields }}
{{ next_button }}
{{ endblock }}
プレイヤーの役割に応じて表示内容を切り替えるため、テンプレート内で if
文を用いて条件分岐を行っています。
これにより、sender、receiver、あるいは非参加者のいずれに該当するかに応じて、異なるメッセージを表示できます。
報酬分配ページ(Contribute.html
, senderのみ)
__init__.py
でContribute.html
に必要な変数を定義する。
class Contribute(Page):
form_model = 'player'
form_fields = ['contribute']
@staticmethod
#このページはsenderにしか提示されない
def is_displayed(player: Player):
return player.roles == 'sender'
@staticmethod
#Contribute.htmlに必要な変数
def vars_for_template(player: Player):
players = player.group.get_players()
#receiverの今まで貢献回数を取り出す(reputationの情報として提示する)
receiver = next(p for p in players if p.roles == 'receiver')
receiver_history = receiver.participant.vars.get('contribute_times',0)
return {
#相手のplayer IDを提示する
'receiver_id': player.partner_id,
'receiver_history': receiver_history
}
Contribute.html
はsenderにしか提示されないページです。
前回の記事で書いた通り、一般交換が成立する前提は過去の行動に基づいた他者の評判を参照して意思決定が行われることです。そのため、このページでは、今回の相手のIDおよびその相手がこれまでに他者へポイントを提供した回数(履歴)が提示されます。
HTMLテンプレートの書き方は以下の通りです。
{{ block title }}
貢献するか否かの選択
{{ endblock }}
{{ block content }}
<p>あなたはM<strong>sender(提供者)</strong>として選ばれました。</p>
<p>あなたの相手はプレイヤー{{ receiver_id }}です。</p>
<p>プレイヤー{{ receiver_id }}は今まで他のプレイヤーに{{ receiver_history }}回ポイントを提供した</p>
{{ formfields }}
{{ next_button }}
{{ endblock }}
__init__.py
側で返したreceiver_id
とreceiver_history
二つの変数をここで呼び出します。
待ち画面(ResultsWaitPage
)
Sender が意思決定している間、他のプレイヤーは待ちページに到達して待機します。
全員がこの待ちページに集まったタイミングで、after_all_players_arrive
が グループごとに1回だけ 実行されます。
class ResultsWaitPage(WaitPage):
@staticmethod
def after_all_players_arrive(group: Group):
set_payoffs(group)
ここでは、after_all_players_arrive
内で set_payoffs(group)
を呼び出し、そのラウンドの意思決定がすべて確定した後に各プレイヤーの利得、累積報酬、貢献回数と参加回数を更新する。
以降の Results
ページでは、ここで計算した値をそのまま表示できます。
結果の表示画面(Results.html
)
このページは、毎ラウンドの結果を全プレイヤーに表示します。vars_for_template
では、participant.vars
に保持してきた履歴情報(累積報酬や貢献回数など)を安全に取得し、テンプレートへ渡しています。
__init__.py
で必要な変数を定義する。
class Results(Page):
@staticmethod
def vars_for_template(player: Player):
players = player.group.get_players()
sender = next(p for p in players if p.roles == 'sender')
receiver = next(p for p in players if p.roles == 'receiver')
#senderのこれまでの貢献回数
sender.sender_contribute_times = sender.participant.vars.get('contribute_times',0)
#累積報酬
player.cumulative_payoff = player.participant.vars.get('cumulative_payoff', 0)
#今回の利得の計算
if sender.contribute == True:
result = "貢献する"
received_amount = C.FIXED_AMOUNT*C.MULTIPLIER
sent_amount = C.FIXED_AMOUNT
elif sender.contribute == False:
result = "貢献しない"
received_amount = 0
sent_amount = 0
return{
'sender': sender.id_in_group,
'receiver': receiver.id_in_group,
'result': result,
'sent_amount': sent_amount,
'received_amount': received_amount,
'cumulative_payoff': player.cumulative_payoff,
'contribute_times': sender.sender_contribute_times
}
participant.vars
はラウンドやアプリをまたいで値を保持できますが、CSVエクスポートには直接含まれません。分析で使う値は、あわせて Player
のフィールド(例:sender_contribute_times
, cumulative_payoff
)にも保存しておくと、出力ファイルからそのまま集計できます。
なお、participant.vars
にまだ値がない可能性があるキーは、dict.get('key', 0)
のようにデフォルト値を指定して取り出すと、初回ラウンドでもエラーなく動作します。
HTMLテンプレートの書き方は以下です。
{{ block title }}
結果
{{ endblock }}
{{ block content }}
<p>プレイヤー{{ sender }}がプレイヤー{{ receiver }}に{{ sent_amount }}ポイントを渡した。</p>
<p>プレイヤー{{ receiver }}が{{ received_amount }}ポイントをもらいました。</p>
<p>プレイヤー{{ sender }}は今まで{{ contribute_times }}回貢献した。</p>
<p>あなたのこれまでの累積報酬は{{ cumulative_payoff }}です。</p>
{{ next_button }}
{{ endblock }}
本記事および前回の記事の内容の一部は、ChatGPT(OpenAI)による生成結果を参考にしています。
今回の説明・記録は以上です。
次回は、累積報酬を各ページで常に表示できるようにするための HTML テンプレートの書き方について解説します。