from otree.api import (
Currency, cu, currency_range, models, widgets,
BaseConstants, BaseSubsession, BaseGroup,
BasePlayer, ExtraModel, WaitPage, Page, read_csv,
)
import random
import units
import shared_out
doc = ''
class C(BaseConstants):
NAME_IN_URL = 'd2_bret_otai_lucky'
PLAYERS_PER_GROUP = None
NUM_ROUNDS = 5
NUM_ROUNDS_lucky = 5
GRID_SIZE = 100
POINTS_PER_BOX = 500
LUCKY_NUMBERS_COST = cu(0.1)
FANCY_TIMEOUT_SECONDS = 60
BONUS_BOXES = 2
CLOSE_THRESHOLD = 5
REVEAL_DELAY_MS = 50
BOMB_REVEAL_DELAY_MS = 500
BOXES_PER_SECOND = 4
MS_PER_SECOND = 1000
TIMER_PAD_THRESHOLD = 10
SECONDS_PER_MINUTE = 60
SPEED_PHASE_1_BPS = 4
SPEED_PHASE_2_BPS = 4
SPEED_PHASE_3_BPS = 4
SPEED_PHASE_1_DURATION = 10
SPEED_PHASE_2_DURATION = 20
VARIANT_STANDARD = 'standard'
VARIANT_FANCY = 'fancy'
VARIANT_SPEED = 'speed'
VARIANT_STOPSTART = 'stopstart'
VARIANT_LUCKY = 'lucky'
LUCKY_NUMBERS_COUNT = 4
MIN_LUCKY_NUMBER = 40
EXTRA_BOXES = 5
WAITING_DELAY_SECONDS = 5
class Subsession(BaseSubsession):
pass
class Group(BaseGroup):
pass
def get_language(player):
return player.session.config.get('language', 'en') == 'en'
# ==========================================
# COMPREHENSION QUESTIONS
# ==========================================
def comprehension_lucky_1_choices(player):
return [
['1', '1'],
['2', '2'],
['3', '3'],
['4', '4'],
]
def comprehension_lucky_1_label(player):
return (
'How many lucky numbers can you choose?'
if get_language(player)
else 'Berapa banyak angka keberuntungan yang dapat Anda pilih?'
)
def comprehension_lucky_2_choices(player):
if get_language(player):
return [
[True, 'The statement is true'],
[False, 'The statement is false'],
]
return [
[True, 'Pernyataan tersebut benar'],
[False, 'Pernyataan tersebut salah'],
]
def comprehension_lucky_2_label(player):
if get_language(player):
return (
'Determine whether this statement is true or false:
'
'If you stop the box collecting process before hitting a bomb, '
'you can choose to add five (5) more boxes, '
'with the risk of collecting the bomb.'
)
return (
'Tentukan apakah pernyataan berikut benar atau salah:
'
'Jika Anda menghentikan proses pengumpulan kotak sebelum terkena bom, '
'Anda dapat memilih untuk menambahkan lima (5) kotak lagi, '
'dengan risiko terkena bom.'
)
class Player(BasePlayer):
boxes_collected = models.IntegerField(initial=0)
bomb_position = models.IntegerField(initial=0)
bomb_exploded = models.BooleanField(initial=False)
payoff_boxes = models.IntegerField(initial=0)
bonus_boxes_used = models.IntegerField(initial=0)
variant = models.StringField(initial=C.VARIANT_LUCKY)
stopped_by_player = models.BooleanField(initial=False)
# ==========================================
# LUCKY NUMBER CHOICE
# ==========================================
bought_lucky_numbers = models.BooleanField(
initial=False,
label=''
)
@staticmethod
def bought_lucky_numbers_choices(player):
if get_language(player):
return [
[True, 'Yes, I want to choose Lucky Numbers'],
[False, 'No, I do not want to choose Lucky Numbers'],
]
return [
[True, 'Ya, Saya ingin memilih Angka Keberuntungan'],
[False, 'Tidak, Saya tidak ingin memilih Angka Keberuntungan'],
]
# ==========================================
# LUCKY NUMBER INPUTS
# ==========================================
lucky_number_1 = models.IntegerField(
label='',
max=C.GRID_SIZE,
min=C.MIN_LUCKY_NUMBER
)
lucky_number_2 = models.IntegerField(
label='',
max=C.GRID_SIZE,
min=C.MIN_LUCKY_NUMBER
)
lucky_number_3 = models.IntegerField(
label='',
max=C.GRID_SIZE,
min=C.MIN_LUCKY_NUMBER
)
lucky_number_4 = models.IntegerField(
label='',
max=C.GRID_SIZE,
min=C.MIN_LUCKY_NUMBER
)
lucky_number_saved_player = models.BooleanField(
initial=False
)
# ==========================================
# COMPREHENSION QUESTIONS
# ==========================================
comprehension_lucky_1 = models.StringField(
choices=comprehension_lucky_1_choices,
label=comprehension_lucky_1_label,
widget=widgets.RadioSelect,
)
comprehension_lucky_2 = models.BooleanField(
choices=comprehension_lucky_2_choices,
label=comprehension_lucky_2_label,
widget=widgets.RadioSelect,
)
# ==========================================
# PAYOFF
# ==========================================
paying_round_lucky = models.IntegerField()
paying_round_lucky_payoff = models.CurrencyField()
payoff_lucky = models.CurrencyField(
initial=0
)
def creating_session(subsession):
for player in subsession.get_players():
player.variant = C.VARIANT_LUCKY
player.bomb_position = random.randint(1, C.GRID_SIZE)
player.boxes_collected = 0
player.bomb_exploded = False
player.payoff_boxes = 0
player.bonus_boxes_used = 0
player.stopped_by_player = False
if subsession.round_number == 1:
player.participant.vars['round_to_pay_lucky'] = random.randint(
1, C.NUM_ROUNDS_lucky
)
def get_lucky_numbers(player):
if player.variant == C.VARIANT_LUCKY and player.bought_lucky_numbers:
return [
player.lucky_number_1,
player.lucky_number_2,
player.lucky_number_3,
player.lucky_number_4,
]
return []
class Instructions(Page):
form_model = 'player'
form_fields = ['comprehension_lucky_1', 'comprehension_lucky_2']
@staticmethod
def is_displayed(player):
return player.round_number == 1
@staticmethod
def error_message(player, values):
err = (
'Your answer is incorrect. Please read the instructions again.'
if get_language(player)
else 'Jawaban Anda belum tepat. Silakan baca kembali instruksi permainan.'
)
errors = {}
if values['comprehension_lucky_1'] != '4':
errors['comprehension_lucky_1'] = err
if values['comprehension_lucky_2'] is not True:
errors['comprehension_lucky_2'] = err
return errors if errors else None
@staticmethod
def vars_for_template(player):
en = get_language(player)
return dict(
en=en,
C=C,
is_standard=player.variant == C.VARIANT_STANDARD,
is_fancy=player.variant == C.VARIANT_FANCY,
is_speed=player.variant == C.VARIANT_SPEED,
is_stopstart=player.variant == C.VARIANT_STOPSTART,
is_lucky=player.variant == C.VARIANT_LUCKY,
speed_phase_2_duration=C.SPEED_PHASE_2_DURATION - C.SPEED_PHASE_1_DURATION,
min_lucky_number=C.MIN_LUCKY_NUMBER,
extra_boxes=C.EXTRA_BOXES,
total_rounds_lucky=C.NUM_ROUNDS_lucky,
)
class LuckyNumbersChoice(Page):
form_model = 'player'
form_fields = ['bought_lucky_numbers']
@staticmethod
def is_displayed(player):
return player.variant == C.VARIANT_LUCKY
@staticmethod
def vars_for_template(player):
return dict(en=get_language(player))
class SelectLuckyNumbers(Page):
form_model = 'player'
form_fields = ['lucky_number_1', 'lucky_number_2', 'lucky_number_3', 'lucky_number_4']
@staticmethod
def is_displayed(player):
return player.variant == C.VARIANT_LUCKY and player.bought_lucky_numbers
@staticmethod
def vars_for_template(player):
return dict(en=get_language(player))
@staticmethod
def error_message(player, values):
nums = [values[f'lucky_number_{i}'] for i in range(1,5)]
for n in nums:
if n < C.MIN_LUCKY_NUMBER or n > C.GRID_SIZE:
return (
f'All numbers must be between {C.MIN_LUCKY_NUMBER} and {C.GRID_SIZE}'
if get_language(player)
else f'Semua angka harus berada di antara {C.MIN_LUCKY_NUMBER} dan {C.GRID_SIZE}'
)
if len(nums) != len(set(nums)):
return (
'All lucky numbers must be different'
if get_language(player)
else 'Semua angka keberuntungan harus berbeda'
)
class Task(Page):
form_model = 'player'
@staticmethod
def js_vars(player):
return dict(
bomb_position=player.bomb_position,
grid_size=C.GRID_SIZE,
variant=player.variant,
fancy_timeout_seconds=C.FANCY_TIMEOUT_SECONDS,
bonus_boxes=C.BONUS_BOXES,
close_threshold=C.CLOSE_THRESHOLD,
reveal_delay_ms=C.REVEAL_DELAY_MS,
bomb_reveal_delay_ms=C.BOMB_REVEAL_DELAY_MS,
boxes_per_second=C.BOXES_PER_SECOND,
ms_per_second=C.MS_PER_SECOND,
speed_phase_1_bps=C.SPEED_PHASE_1_BPS,
speed_phase_2_bps=C.SPEED_PHASE_2_BPS,
speed_phase_3_bps=C.SPEED_PHASE_3_BPS,
speed_phase_1_duration=C.SPEED_PHASE_1_DURATION,
speed_phase_2_duration=C.SPEED_PHASE_2_DURATION,
lucky_numbers=get_lucky_numbers(player),
seconds_per_minute=C.SECONDS_PER_MINUTE,
extra_boxes=C.EXTRA_BOXES,
waiting_delay_seconds=C.WAITING_DELAY_SECONDS,
total_rounds_lucky=C.NUM_ROUNDS_lucky,
points_per_box=C.POINTS_PER_BOX,
)
@staticmethod
def vars_for_template(player):
return dict(en=get_language(player), total_rounds_lucky=C.NUM_ROUNDS_lucky)
@staticmethod
async def live_method(player, data):
if data['type'] == 'collect':
boxes = data['boxes']
box_id = data.get('box_id')
player.boxes_collected = boxes
lucky_numbers = get_lucky_numbers(player)
bomb_protected = player.bomb_position in lucky_numbers
if not player.bomb_exploded:
if box_id == player.bomb_position:
if bomb_protected:
player.lucky_number_saved_player = True
player.payoff_boxes = boxes
else:
player.bomb_exploded = True
player.payoff_boxes = 0
else:
player.payoff_boxes = boxes
player.payoff_lucky = player.payoff_boxes * C.POINTS_PER_BOX
if player.bought_lucky_numbers:
player.payoff_lucky -= C.LUCKY_NUMBERS_COST
yield {
player.id_in_group: {
'bomb_exploded': player.bomb_exploded,
'bomb_protected': bomb_protected,
}
}
elif data['type'] == 'bonus':
player.bonus_boxes_used = data[
'bonus_used'
]
player.boxes_collected = data[
'final_boxes'
]
player.payoff_boxes = (
player.boxes_collected
)
player.payoff_lucky_trial = (
player.payoff_boxes
* C.POINTS_PER_BOX
)
if player.bought_lucky_numbers:
player.payoff_lucky_trial -= (
C.LUCKY_NUMBERS_COST
)
if player.bomb_exploded:
player.payoff_lucky = cu(0)
elif data['type'] == 'stop':
player.stopped_by_player = True
class Results(Page):
@staticmethod
def vars_for_template(player):
return dict(
en=get_language(player),
points_per_box=C.POINTS_PER_BOX,
boxes_collected=player.boxes_collected,
payoff_lucky=player.payoff_lucky,
total_rounds_lucky=C.NUM_ROUNDS_lucky,
)
class MultiRoundResults(Page):
@staticmethod
def is_displayed(player):
return player.round_number == C.NUM_ROUNDS_lucky
@staticmethod
def vars_for_template(player):
rn = player.participant.vars['round_to_pay_lucky']
paying_round = player.in_round(rn)
player.paying_round_lucky = rn
player.paying_round_lucky_payoff = paying_round.payoff_lucky
all_rounds_data = [
dict(
round_number=p.round_number,
boxes_collected=p.boxes_collected,
bomb_exploded=p.field_maybe_none('bomb_exploded') or False,
payoff_lucky=p.field_maybe_none('payoff_lucky') or cu(0),
)
for p in player.in_all_rounds()
]
player.payoff_lucky = paying_round.payoff_lucky
return dict(
en=get_language(player),
player_in_all_rounds=all_rounds_data,
round_to_pay_lucky=rn,
paying_round_lucky_payoff=paying_round.payoff_lucky,
bomb=paying_round.bomb_exploded,
total_rounds_lucky=C.NUM_ROUNDS_lucky,
)
page_sequence = [
Instructions,
LuckyNumbersChoice,
SelectLuckyNumbers,
Task,
Results,
MultiRoundResults,
]