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, ]