等待页面¶
当一位玩家需要等待其他玩家进行一些操作之后所有玩家才能继续进行游戏时,等待页面是必须的。举例来说,在最后通牒博弈中,玩家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_displayed
与group_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_seconds
与 before_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
来代替用户决定所要提交的值。