第三部分:信任博弈

现在我们来创建一个2人 信任博弈,并学习oTree的一些特性。

开始时,玩家1获得10点数;玩家2无点数。玩家1可以将他点数的一部分或者全部给与玩家2。之后玩家2收到的点数会变为原来的3倍。当玩家2收到3倍的点数之后,他可以决定将部分或者全部的点数给予玩家1。

The completed app is here.

创建应用

正如教程的前一部分,创建另一个应用,命名为 my_trust

常量

转到本应用的常量。

首先我们定义应用的常量。初始点数为10,并且捐赠会变为3倍。

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

    ENDOWMENT = cu(10)
    MULTIPLICATION_FACTOR = 3

模型

现在我们添加player与group的字段。这里有两种关键的数据需要记录:玩家1的”捐赠“值与玩家2的“返还”值。

直觉上,你可能如下定义Player类中的字段:

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

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

此模型的问题在于, sent_amount 只对玩家1有意义, sent_back_amount 只对玩家2有意义。所以 sent_back_amount 字段对于玩家1无意义。我们如何才能使得模型更加精确呢?

我们可以将这些字段定义到 Group 层级。这非常合理,因为每一小组恰好有一个 sent_amount 与一个 sent_back_amount:

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_choices 的函数为下拉菜单动态地添加数据。这一特性称为 {field_name}_choices,在这里: 动态表单验证 被详细解释。

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

定义模板与页面

我们需要3个页面:

  • 玩家1的“Send”页面
  • 玩家2的“Send back”页面
  • 两位玩家都能看到的“Results”页面。

Send页面

class Send(Page):

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

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

我们使用 is_displayed() 让此页面仅显示给玩家1;玩家2跳过此页面。获取更多 id_in_group 的信息,可查看 小组

将模板的 title 设为 Trust Game: Your Choice,并且将 content 设置如下:

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

{{ formfields }}

{{ next_button }}

SendBack.html

这是玩家2需要查看的将钱返还的页面。将 title 设为 Trust Game: Your Choice,并将 content 设置如下:

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

{{ formfields }}

{{ next_button }}

下面是页面的代码。请注意:

  • 我们使用 vars_for_template() 将变量 tripled_amount 传递给模板。你不能直接在HTML代码中做计算,所以这一数字需要使用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
        )

Results

玩家1与玩家2所看到的结果页面有细微的不同。所以我们使用 {{ if }} 语句来判断当前玩家的 id_in_group。将 title 设置为 Results,并将内容部分设置如下:

{{ 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

等待页面与页面序列

添加2个等待页面:

  • WaitForP1 (玩家2需要等待玩家1决定捐赠值)
  • ResultsWaitPage (玩家1需要等待玩家2决定返还值)

在第二个等待页面之后,我们应当计算收益。所以我们定义一个函数,命名为 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

ResultsWaitPage 中,设置 after_all_players_arrive:

after_all_players_arrive = set_payoffs

确保在page_sequence中页面顺序是正确的:

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

SESSION_CONFIGS 中增加一个条目

在应用序列中创建一个属于 my_trust 的session config。

运行服务器

载入你的项目并打开浏览器,网址为 http://localhost:8000