等待页面

当一位玩家需要等待其他玩家进行一些操作之后所有玩家才能继续进行游戏时,等待页面是必须的。举例来说,在最后通牒博弈中,玩家2在他看到玩家1的报价之前不能拒绝或者接受这一报价。

如果你在页面序列中放置了一个 WaitPage ,那么oTree会等待小组内的所有玩家都到达了序列中的那一点之后,再允许所有玩家继续下面的游戏。

如果子会话中有多组同时进行游戏,你可能会希望为所有组(也就是子会话中的所有玩家)设置一个等待页面,你可以在等待页面中设置 wait_for_all_groups = True 实现这一点。

获取更多关于小组的信息,参考 小组.

after_all_players_arrive

after_all_players_arrive lets you run some calculations once all players have arrived at the wait page. This is a good place to set the players’ payoffs or determine the winner. You should first define a Group function that does the desired calculations. For example:

def set_payoffs(group):
    for p in group.get_players():
        p.payoff = c(100)

然后通过下面的方法触发它:

class MyWaitPage(WaitPage):
    after_all_players_arrive = 'set_payoffs'

If you set wait_for_all_groups = True, then after_all_players_arrive must be a Subsession function.

after_all_players_arrive can also be defined directly in the WaitPge as a @staticmethod.

is_displayed()

与普通页面中的功能相同。

group_by_arrival_time

如果你在等待页面设置了 group_by_arrival_time = True ,玩家会按照到达等待页面的顺序组队:

class MyWaitPage(WaitPage):
    group_by_arrival_time = True

举例来说,假设 players_per_group = 2,那么首先到达等待页面的2位玩家将会被分为一组,然后接下来2名玩家一组,以此类推。

这在一些玩家可能会离开的情况下非常有用(例如在线实验或者有允许玩家提前离开的同意页面的实验),或在某些玩家会比其他玩家花费多得多的时间的情况下非常有用。

一种典型的使用 group_by_arrival_time 的方法是将其放在一个应用前面用来筛选参与人。举例来说,如果你的会话中有一个同意页面使得参与人有机会选择退出实验,你可以创建一个“consent”应用,它仅仅包含同意页面,然后 app_sequence 就像 ['consent', 'my_game'],并且 my_game 中使用 group_by_arrival_time。这意味着如果有人在 consent 中选择了退出,他们就不会在 my_game 中参与组队。

如果一个游戏有多轮,那么你可能只想在第一轮按照到达时间组队:

class MyWaitPage(WaitPage):
    group_by_arrival_time = True

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

如果你这样做,那么余下的轮次会保持第一轮的小组结构。不然的话,玩家在每一轮都会按照他们的到达时间重新组队。(group_by_arrival_time 复制了小组结构到下面的轮次。)

注意:

  • id_in_group 并不按照玩家到达页面的顺序被赋值。
  • group_by_arrival_time 仅在等待页面是 page_sequence 的第一个页面时可用
  • 如果你将 is_displayedgroup_by_arrival_time 一起使用,那么它应当仅依赖轮数。不要使用 is_displayed 来将页面展示给一部分玩家而不展示给其他玩家。
  • 如果 group_by_arrival_time = True 那么在 creating_session 时,所有玩家一开始均在同一组中。小组是在玩家到达等待页面的那“一瞬间”被创建的。

如果你需要进一步控制玩家如何组织成小组,参考 group_by_arrival_time_method().

group_by_arrival_time_method()

如果你使用了 group_by_arrival_time 并想要进一步控制哪些玩家被分配到一起,你可以使用 group_by_arrival_time_method().

我们假定除了按照到达时间组队之外,你还需要每一组由2名男性和2名女性组成。

If you define a subsession function called group_by_arrival_time_method, it will get called whenever a new player reaches the wait page. The method’s argument is the list of players who are currently waiting at your wait page. If you pick some of these players and return them as a list, those players will be assigned to a group, and move forward. If you don’t return anything, then no grouping occurs.

Here’s an example where each group has 2 men and 2 women. It assumes that in a previous app, you assigned participant.vars['category'] to each participant.

def group_by_arrival_time_method(subsession, waiting_players):
    print('in group_by_arrival_time_method')
    m_players = [p for p in waiting_players if p.participant.vars['category'] == 'M']
    f_players = [p for p in waiting_players if p.participant.vars['category'] == 'F']

    if len(m_players) >= 2 and len(f_players) >= 2:
        print('about to create a group')
        return [m_players[0], m_players[1], f_players[0], f_players[1]]
    print('not enough players yet to create a group')

Timeouts on wait pages

You can also use group_by_arrival_time_method to put a timeout on the wait page, for example to allow the participant to proceed individually if they have been waiting longer than 5 minutes. First, you must record time.time() on the final page before the app with group_by_arrival_time. Store it in participant.vars.

Then define a Player function:

def waiting_too_long(player):
    participant = player.participant

    import time
    return time.time() - participant.vars['wait_page_arrival'] > 5*60

现在使用下面的代码:

def group_by_arrival_time_method(subsession, waiting_players):
    if len(waiting_players) >= 3:
        return waiting_players[:3]
    for player in waiting_players:
        if waiting_too_long(player):
            # make a single-player group.
            return [player]

这样是可行的,因为等待页面会一分钟自动刷新1次或者2次,此时 group_by_arrival_time_method 就会被重新执行。

避免玩家卡在等待页面

一个在线实验中尤其常见的问题是玩家会因为等待一个已经退出或者太慢的玩家而卡住。

下面是一些减少此问题的方法:

使用 group_by_arrival_time

正如上文所述,你可以使用 group_by_arrival_time 使得那些大概同时完成页面的玩家在一起组队。

如果 group_by_arrival_time 放在一个“锁定”任务之后,它的效果会很好。换句话说,在你的多人游戏之前,你可以先有一个单人任务。这里的想法是如果一位参与人花了力气完成了第一个任务,那么他就有更少的可能在之后退出。

使用页面超时

在每个页面上使用 timeout_seconds ,这样当一位玩家很慢或者不再操作,他的页面会自动继续进行下去。或者,你可以手动强制超时,通过点击admin界面的 “Advance slowest participants” 按钮。

检查超时发生

你可以告知用户他们必须在 timeout_seconds 之前提交页面,否则就会被算作退出游戏。甚至可以添加一个页面,仅仅询问“点击继续按钮以确认继续进行游戏”。然后检查 timeout_happened.如果其值为True,你可以做一些事情如设置这位玩家/这个小组的某个字段以标记其已经退出,并跳过本轮余下的页面。

将退出的玩家替换为bot

下面是一个组合使用了上面一些技巧的例子,此时即使某位玩家退出了,他仍可以继续自动游戏,就如机器人一样。即像下面这样在每一个页面上使用 get_timeout_secondsbefore_next_page

class Page1(Page):
    form_model = 'player'
    form_fields = ['contribution']

    @staticmethod
    def get_timeout_seconds(player):
        participant = player.participant

        if participant.vars.get('is_dropout'):
            return 1  # instant timeout, 1 second
        else:
            return 5*60

    @staticmethod
    def before_next_page(player, timeout_happened):
        participant = player.participant

        if player.timeout_happened:
            player.contribution = c(100)
            participant.vars['is_dropout'] = True

注意:

  • 如果玩家未能按时提交页面,即将 is_dropout 设为 True.
  • 一旦 is_dropout 被设置,每个页面都会被立即自动提交。
  • 当页面被自动提交时,可使用 timeout_happened 来代替用户决定所要提交的值。

定制等待页面的外观

你可以定制出现在等待页面上的文本,通过设定 title_textbody_text 即可,举例如下:

class MyWaitPage(WaitPage):
    title_text = "Custom title text"
    body_text = "Custom body text"

也可参考: 自定义等待页面模板.