Part 2: 公共財ゲーム¶
YouTube のビデオチュートリアル(英語)も参照してください。
ここでは、 公共財ゲーム を作成します。公共財ゲームは経済学の古典的なゲームです。
3人1組でプレイされる公共財ゲームを考えましょう。各プレイヤーには初期保有として1000ポイントが与えられます。プレイヤーは、グループに貢献したいポイント数を個別に決定します。3人のプレイヤーによる貢献額の合計は2倍された後、3人に均等に分割され、プレイヤーに再分配されます。
以下で解説する公共財ゲームの完全なコードは ここ にあります。
アプリの作成¶
新たにアプリを作成し、名前を my_public_goods とします。
定数¶
定数を定義するクラス class C に移動します。(詳細については、 Constants を参照してください。)
- 埋め込み変数
PLAYERS_PER_GROUPを3に設定します。oTreeは自動的にプレイヤーを3人1組にグループ分けします。 - 各プレイヤーへの初期保有は1000ポイントです。新たに
ENDOWMENTなる変数を定義してその値を1000に設定しましょう。 - プレイヤーの貢献額は2倍されます。倍率として、新たに
MULTIPLIERなる変数を定義してその値を2に設定しましょう。
class C の中身は以下のようになります。
PLAYERS_PER_GROUP = 3
NUM_ROUNDS = 1
ENDOWMENT = cu(1000)
MULTIPLIER = 2
モデル (記録すべき実験データの定義)¶
実験参加者の行動を後々分析するために、各プレイヤーが貢献したポイント数を記録しなければなりません。データベースに記録する変数(フィールド)でプレイヤー一人ひとりについて定義されるものは class Player (プレイヤーモデルと呼ばれます)の中で定義します。class Player に移動し、 contribution なるフィールドを以下のように定義します。
class Player(BasePlayer):
contribution = models.CurrencyField(
min=0,
max=C.ENDOWMENT,
label="How much will you contribute?"
)
実験参加者への謝金の支払いのためには、各プレイヤーの利得を記録する必要がありますが、oTreeではあらかじめプレイヤーモデルで payoff なるフィールドが定義されているため、改めてフィールドを明示的に定義する必要はありません。
各プレイヤーの意思決定データだけでなく、各グループについて、グループへの貢献額の合計と、各プレイヤーに分配されるポイント数を記録したい場合は、グループモデル class Group でこれら2つのフィールドを定義します。
class Group(BaseGroup):
total_contribution = models.CurrencyField()
individual_share = models.CurrencyField()
ページ¶
このゲームでは3つのページを表示します。
- ページ1: プレイヤーはグループへの貢献額を決定し入力します。
- ページ2: 待機ページ。プレイヤーはグループ内の他の参加者の操作が終わるまで待機します。
- ページ3: プレイヤーに結果が通知されます。
ページ1: Contribute¶
貢献額を入力するページの名前は Contribute としましょう。まず、 クラス class Contribute を定義します。Contribute ページには貢献額の入力フォームが含まれているため、 form_model と form_fields を定義する必要があります。貢献額 contribution はプレイヤーモデルのフィールドなので、 form_model は player と設定します。form_fields には具体的なフィールド( contribution )をリストで渡します。(詳細については、 フォーム を参照してください。)
class Contribute(Page):
form_model = 'player'
form_fields = ['contribution']
次に、HTMLテンプレートを作成します。
{{ title block }} ブロック内にタイトルとして Contribute と入力し、 {{ content block }} ブロック内を以下のように設定します。
<p>
This is a public goods game with
{{ C.PLAYERS_PER_GROUP }} players per group,
an endowment of {{ C.ENDOWMENT }},
and a multiplier of {{ C.MULTIPLIER }}.
</p>
{{ formfields }}
{{ next_button }}
ページ2: ResultsWaitPage¶
すべてのプレイヤーが Contribute ページを完了した後、各プレイヤーの利得を計算します。利得の計算は、set_payoffs なる関数を定義して、そこで実行することにしましょう。関数 set_payoffs はグループモデルの変数や関数を利用するため、引数として group なるオブジェクトを受け取ることにします。関数 set_payoffs は具体的に以下のように定義します。
def set_payoffs(group):
players = group.get_players()
contributions = [p.contribution for p in players]
group.total_contribution = sum(contributions)
group.individual_share = group.total_contribution * C.MULTIPLIER / C.PLAYERS_PER_GROUP
for player in players:
player.payoff = C.ENDOWMENT - player.contribution + group.individual_share
プレイヤーが貢献額を意思決定した後、他のプレイヤーが意思決定し終わるのを待ってから関数 set_payoffs を呼び出す必要があります。グループ内の他のプレイヤーが意思決定を完了するまで待機するページ WaitPage を追加します。ここでは WaitPage の名前を ResultsWaitPage としましょう( class ResultsWaitPage (WaitPage): とクラスを定義する)。プレイヤーが ResultsWaitPage ページに到達すると、グループ内の他のすべてのプレイヤーが ResultsWaitPage ページに到達するまで待機します(詳細は、 Wait page を参照してください)。
class ResultsWaitPage 内で、動かしたい関数の名前( set_payoffs )を組み込み変数 after_all_players_arrive に渡します。この設定により、すべてのプレイヤーが ResultsWaitPage ページに到達したことをトリガーとして関数 set_payoffs が呼び出されます。
after_all_players_arrive = 'set_payoffs'
ページ3: Results¶
次に、 結果を表示する Results という名前のページを作成します(クラス class Results を定義する)。テンプレートの {{ content block }} ブロックを以下のように設定します。
<p>
You started with an endowment of {{ C.ENDOWMENT }},
of which you contributed {{ player.contribution }}.
Your group contributed {{ group.total_contribution }},
resulting in an individual share of {{ group.individual_share }}.
Your profit is therefore {{ player.payoff }}.
</p>
{{ next_button }}
セッション構成の定義¶
settings.py の SESSION_CONFIGS に 新しいセッションを追加し、 app_sequence にアプリ名 my_public_goods をリストとして渡します。
プログラムの実行¶
oTreeを(再)起動し、ブラウザを開いて http://localhost:8000 に接続してください。
print()を利用したトラブルシューティング¶
プログラミングフォーラムで、「プログラムが機能していません。コードを何時間も見ても、間違いを見つけることができません」などのメッセージがしばしば投稿されます。
エラーが見つかるまでコードをじっくり目で追っていてはいけません。プログラムをインタラクティブにテストすることが肝心です。
最も簡単な方法は、 print() を使用することです。このテクニックを学ばないと、ゲームを効果的にプログラムすることができません。
たとえば、利得を計算する関数 set_payoffs の中で、次のような print 文を挿入してみましょう。
print('group.total_contribution is', group.total_contribution)
機能していないコードの部分にこの print 文を配置します。ブラウザでゲームをプレイし、そのコードが実行されると、 print コマンドによる出力がコンソールログに表示されます(Webブラウザでは表示されません)。
print 文はいくらでも挿入することができます。
print('in payoff function')
contributions = [p.contribution for p in players]
print('contributions:', contributions)
group.total_contribution = sum(contributions)
group.individual_share = group.total_contribution * C.MULTIPLIER / C.PLAYERS_PER_GROUP
print('individual share', group.individual_share)
if group.individual_share > 100:
print('inside if statement')
for player in players:
player.payoff = C.ENDOWMENT - p.contribution + group.individual_share
print('payoff after', p.payoff)
printによる出力がコンソールログに表示されない¶
コンソールログに print の出力が表示されない場合は、その行が実行されていないことを意味します。その箇所に、プログラムが機能しない理由があります。
コードが return の後などの到達できない場所にあるか、あるいは常に False である if 文の中にあるなどの原因が考えられます。関数の先頭から print 文を配置していくことで、問題の箇所を特定できます。
または、コードが呼び出されることのない関数に含まれている可能性があります。 creating_session や before_next_page のようなoTreeに組み込まれている関数は自動的に実行されますが、 set_payoffs のような独自に定義した関数は、その関数を明示的に呼び出さなければならないことを覚えておく必要があります。