Part 3: 信頼ゲーム

ここでは、2人でプレイされる 信頼ゲーム を作成し、oTreeの機能をさらに学びましょう。

まず、プレイヤー1(P1)は10ポイントを獲得します。この時点でプレイヤー2(P2)は何も受け取りません。P1は、ポイントの一部またはすべてをP2に渡すことができます。P2は、P1が渡したポイントの3倍を受け取ります。P2がポイントを受け取った後、受け取ったポイントの一部またはすべてをP1に渡すことができます。

以下で解説する信頼ゲームの完全なコードは ここ にあります。

アプリの作成

新たにアプリを作成し、名前を my_trust とします。

定数

定数を定義するクラス class C に移動します。

初期保有は10ポイントです。 ENDOWMENT なる変数を定義してその値を 10 に設定しましょう。P1からP2へポイントを渡すときの倍増率は3です。 MULTIPLICATION_FACTOR なる変数を定義してその値を 3 に設定しましょう。class C の中身は以下のようになります。

class C(BaseConstants):
    NAME_IN_URL = 'my_trust'
    PLAYERS_PER_GROUP = 2
    NUM_ROUNDS = 1

    ENDOWMENT = cu(10)
    MULTIPLICATION_FACTOR = 3

モデル (記録すべき実験データの定義)

次に、プレイヤーとグループにフィールドを定義します。分析のために必要な意思決定データは、P1が渡したポイント数( sent_amount )とP2が返したポイント数( sent_back_amount )です。この2つのデータを記録するためにフィールドを定義しなければなりません。

あなたは直観的に、次のようにプレイヤーのフィールドを定義してしまうかもしれません。

# Don't copy paste this
class Player(BasePlayer):

    sent_amount = models.CurrencyField()
    sent_back_amount = models.CurrencyField()

上のコードは賢い実装ではありません。sent_amount は、P1にのみ適用され、P2の場合には空白となります。sent_back_amount はP2にのみ適用され、P1の場合には空白となります。データモデルをより正確に記述するためにはどうすればよいのでしょうか?

sent_amountsent_back_amount のフィールドはグループのレベルで定義すると良いでしょう。以下のように、グループモデル( class Group )でこれら2つのフィールドを定義します。

class Group(BaseGroup):

    sent_amount = models.CurrencyField(
        label="How much do you want to send to participant B?"
    )
    sent_back_amount = models.CurrencyField(
        label="How much do you want to send back?"
    )

sent_back_amount をドロップダウンメニューで選択する形式で回答させる実装を考えましょう。oTreeには、選択肢を動的に生成するための関数が用意されています。これは {field_name}_choices と呼ばれる機能であり、 動的なフォームの検証 で説明されています。ここでは以下のように sent_back_amount_choices なる関数を定義します。使われている currency_range はポイント数の等差数列を返すoTreeの組み込み関数です。

def sent_back_amount_choices(group):
    return currency_range(
        0,
        group.sent_amount * C.MULTIPLICATION_FACTOR,
        1
    )

ページ

このゲームでは3つのページを表示します。

  • ページ1: P1がP2に渡すポイント数( sent_amount )を回答します。
  • ページ2: P2がP1に返すポイント数( sent_back_amount )を回答します。
  • ページ3: P1とP2の両方に結果が通知されます。

ページ1: Send

class Send(Page):

    form_model = 'group'
    form_fields = ['sent_amount']

    @staticmethod
    def is_displayed(player):
        return player.id_in_group == 1

クラス class Send を定義します。 form_fields には sent_amount を指定します。Send ページをP1にのみ表示するために、組み込みの is_displayed() を使用します。P2は Send ページをスキップします。id_in_group の詳細については グループ を参照してください。

HTMLテンプレートファイルを設定します。{{ title block }} ブロック内にタイトルとして Trust Game: Your Choice と入力します。本文は {{ content block }} ブロック内を以下のように設定します。

<p>
You are Participant A. Now you have {{C.ENDOWMENT}}.
</p>

{{ formfields }}

{{ next_button }}

ページ2: SendBack

HTMLテンプレートファイルを設定します。{{ title block }} ブロック内にタイトルとして Trust Game: Your Choice と入力します。本文は {{ content block }} ブロック内を以下のように設定します。

<p>
    You are Participant B. Participant A sent you {{group.sent_amount}}
    and you received {{tripled_amount}}.
</p>

{{ formfields }}

{{ next_button }}

クラス class SendBack でこれまでと同様に form_fieldsis_displayed などを設定します。テンプレートの中で、P2が受け取るポイント数(P1が渡したポイントの3倍)を {{tripled_amount}} として表示していることに注目してください。変数 tripled_amountclass SendBack の中で定義する必要があります。

  • テンプレートに渡す変数 tripled_amountvars_for_template() を使用して設定します。HTMLコードでは、ポイント数を3倍する、というような計算を直接行えないため、Pythonコードで計算してからテンプレートに渡す必要があります。
class SendBack(Page):

    form_model = 'group'
    form_fields = ['sent_back_amount']

    @staticmethod
    def is_displayed(player):
        return player.id_in_group == 2

    @staticmethod
    def vars_for_template(player):
        group = player.group

        return dict(
            tripled_amount=group.sent_amount * C.MULTIPLICATION_FACTOR
        )

ページ3: Results

結果を表示する Results ページのHTMLテンプレートファイルを設定します。{{ title block }} ブロック内にタイトルとして Results と入力します。本文は {{ content block }} ブロック内を設定します。P1とP2で表示する内容を変える必要があるため、 {{ if }} 文を使用して、 id_in_group による条件分岐を行います。

{{ if player.id_in_group == 1 }}
    <p>
        You sent Participant B {{ group.sent_amount }}.
        Participant B returned {{ group.sent_back_amount }}.
    </p>
{{ else }}
    <p>
        Participant A sent you {{ group.sent_amount }}.
        You returned {{ group.sent_back_amount }}.
    </p>

{{ endif }}

<p>
Therefore, your total payoff is {{ player.payoff }}.
</p>
class Results(Page):
    pass

WaitPageとページの順番

2つの待機ページを追加します。

  • WaitForP1 : P1が意思決定している間、P2は待機します。
  • ResultsWaitPage : P2が意思決定している間、P1は待機します。

2番目のWaitPage ( ResultsWaitPage ) の後 Results ページへ遷移する前に、利得を計算する必要があります。利得を計算する関数 set_payoffs を定義します。

def set_payoffs(group):
    p1 = group.get_player_by_id(1)
    p2 = group.get_player_by_id(2)
    p1.payoff = C.ENDOWMENT - group.sent_amount + group.sent_back_amount
    p2.payoff = group.sent_amount * C.MULTIPLICATION_FACTOR - group.sent_back_amount

関数 set_payoffs をP2の意思決定が完了したタイミングで呼び出すために、 class ResultsWaitPage の中で、 after_all_players_arrive を設定します。

after_all_players_arrive = set_payoffs

ページの順番は page_sequence にページ名のリストを渡すことで設定してください。

page_sequence = [
    Send,
    WaitForP1,
    SendBack,
    ResultsWaitPage,
    Results,
]

SESSION_CONFIGS をエントリーに追加

settings.py の SESSION_CONFIGS に 新しいセッションを追加し、 app_sequence にアプリ名 my_trust をリストとして渡します。

プログラムの実行

oTreeを(再)起動し、ブラウザを開いて http://localhost:8000 に接続してください。