等待页面

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

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

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

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

after_all_players_arrive

after_all_players_arrive 允许你在所有玩家都到达等待页面之后进行一些计算。这是个设置玩家的收益或者决定获胜者的好地方。你应当首先定义一个小组函数,它实现了你想要的功能。例如:

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

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

class MyWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs

如果你设置了 wait_for_all_groups = True, 那么 after_all_players_arrive 应当是一个 子会话 函数。

如果你使用的是文本编辑器,after_all_players_arrive 可直接在等待页面中定义:

class MyWaitPage(WaitPage):
    @staticmethod
    def after_all_players_arrive(group: Group):
        for p in group.get_players():
            p.payoff = 100

它也可是一个字符串:

class MyWaitPage(WaitPage):
    after_all_players_arrive = 'set_payoffs'

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名女性组成。

如果你定义了一个 group_by_arrival_time_method 函数,它会在每一次新玩家到达等待页面时被执行。这一方法的参数是所有处在本等待页面的玩家组成的列表。如果你选择了其中一些玩家并且返回了他们组成的列表,那么这些玩家会被分为一组,并且继续进行下面的游戏。如果你没有返回任何东西,那么组队不会发生。

下面是一个每一组中有2名男性和2名女性的例子。这里假定了在上一个应用中,你设置了每一位参加者的 participant.category

# note: this function goes at the module level, not inside the WaitPage.
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.category == 'M']
    f_players = [p for p in waiting_players if p.participant.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')

等待页面上的超时

你也可以使用 group_by_arrival_time_method 为等待页面设置一个超时行为,例如允许参与人单独继续进行游戏,如果他们已经等待了超过5分钟。首先,你必须在使用了 group_by_arrival_time 的应用之前的最后一个页面记录 time.time() 。将其存入 participant field

然后定义一个玩家函数:

def waiting_too_long(player):
    participant = player.participant

    import time
    # assumes you set wait_page_arrival in PARTICIPANT_FIELDS.
    return time.time() - participant.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

下面是一个组合使用了上面一些技巧的例子,此时即使某位玩家退出了,他仍可以继续自动游戏,就如机器人一样。首先定义一个 participant field 命名为 is_dropout,并将其初始值在 creating_session 中设为 False 。即像下面这样在每一个页面上使用 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.is_dropout:
            return 1  # instant timeout, 1 second
        else:
            return 5*60

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

        if timeout_happened:
            player.contribution = cu(100)
            participant.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"

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