from otree import settings from otree.api import * import random from PIL import Image, ImageDraw, ImageFont from pathlib import Path import io, base64 #SESSION class C(BaseConstants): NAME_IN_URL = 'sg_practice' PLAYERS_PER_GROUP = None NUM_ROUNDS = 2 #INSTRUCTIONS spectator_instructions = __name__ + "/shortinstructions.html" #SUBSESSION class Subsession(BaseSubsession): pass #GROUP class Group(BaseGroup): pass #PLAYER class Player(BasePlayer): task = models.IntegerField(label='Task', choices=[[0, 'Productivity. A:6; B:0'], [1, 'Productivity. A:4; B:2'], [2, 'Productivity. A:3; B:3'], [3, 'Productivity. A:2; B:4'], [4, 'Productivity. A:0; B:6'], [5, 'Luck. A:6; B:0'], [6, 'Luck. A:4; B:2'], [7, 'Luck. A:3; B:3'], [8, 'Luck. A:2; B:4'], [9, 'Luck. A:0; B:6']], ) reason = models.IntegerField(label='Reason', choices=[[0, 'Productivity'], [1, 'Luck']], ) statusquo = models.IntegerField(label='Initial bonus for Agent B', choices=[[0, 'A:6; B:0'], [2, 'A:4; B:2'], [3, 'A:3; B:3'], [4, 'A:2; B:4'], [6, 'A:0; B:6']], ) game_alloc0 = models.IntegerField(min=0,max=6,blank=True) game_alloc1 = models.IntegerField(min=0,max=6,blank=True) digit = models.IntegerField(label='Shown number',initial=0) recall = models.IntegerField(label='Which number was shown at the beginning of this round?',blank=True) math_num1 = models.IntegerField(label='Number 1') math_num2 = models.IntegerField(label='Number 2') math_result = models.IntegerField(label='Addition result') math_answer = models.IntegerField(label='What is the result?',blank=True) # FUNCTIONS def random_digit_low(player): random_number = random.randint(1, 9) player.digit = random_number def random_digit_high(player): random_number = random.randint(100000, 999999) player.digit = random_number def math(player): random_num1 = random.choice([64, 66, 67, 68, 69, 74, 76, 77, 78, 79]) random_num2 = random.choice([4, 6, 7, 8, 9]) first = random.choice([True, False]) num1 = random_num1 if first==True else random_num2 num2 = random_num2 if first==True else random_num1 result = num1 * num2 player.math_num1 = num1 player.math_num2 = num2 player.math_result = result def agents_label(player): agent0= 'A' agent1= 'B' return agent0, agent1 def generate_code_image(code: int) -> str: TEXT_FONT = Path(__file__).parent / "assets" / "FreeSansBold.otf" font_big = ImageFont.truetype(str(TEXT_FONT), 50) code_str = str(code) img = Image.new("RGB", (300, 50), color="#ece6f5") d = ImageDraw.Draw(img) d.text((0, 0), code_str, font=font_big, fill='black') buffer = io.BytesIO() img.save(buffer, format="PNG") encoded = base64.b64encode(buffer.getvalue()).decode("utf-8") return f"data:image/png;base64,{encoded}" def generate_math_image(math_num1: int, math_num2: int) -> str: TEXT_FONT = Path(__file__).parent / "assets" / "FreeSansBold.otf" font_big = ImageFont.truetype(str(TEXT_FONT), 18) code_str = f"What is the result of {math_num1} x {math_num2}?" # 1. measure text size using textbbox dummy_img = Image.new("RGB", (1, 1)) d_dummy = ImageDraw.Draw(dummy_img) bbox = d_dummy.textbbox((0, 0), code_str, font=font_big) text_width = bbox[2] - bbox[0] text_height = bbox[3] - bbox[1] # 2. define padding padding = 4 # pixels around text # 3. create image with padding img_width = text_width + 2 * padding img_height = text_height + 2 * padding img = Image.new("RGB", (img_width, img_height), color="#dfe5eb") # applied .03 opacity to #e6ecf2 d = ImageDraw.Draw(img) # 4. draw text at padding offset x_position = padding y_position = padding d.text((x_position, y_position), code_str, font=font_big, fill='black') buffer = io.BytesIO() img.save(buffer, format="PNG") encoded = base64.b64encode(buffer.getvalue()).decode("utf-8") return f"data:image/png;base64,{encoded}" # PAGES class Instructions(Page): @staticmethod def is_displayed(player): instruction_round = {1} return player.round_number in instruction_round class Digit(Page): form_model = 'player' @staticmethod def vars_for_template(player): participant = player.participant # TASK ORDER task_num = player.round_number - 1 player.task = participant.practice_list[task_num] if participant.treatment == 'T1': if player.round_number == 1: player.digit = 7 else: player.digit = 3 if participant.treatment == 'T2': if player.round_number == 1: player.digit = 345678 else: player.digit = 875631 img_data = generate_code_image(player.digit) return dict(round=player.round_number,digit=player.digit,treatment=participant.treatment,code_img=img_data) def is_displayed(player): digit_round = {'T1','T2'} participant = player.participant return participant.treatment in digit_round class NonDigit(Page): form_model = 'player' @staticmethod def vars_for_template(player): participant = player.participant task_num = player.round_number - 1 player.task = participant.practice_list[task_num] return dict(round=player.round_number,digit=player.digit,treatment=participant.treatment) def is_displayed(player): participant = player.participant return participant.treatment == 'T0' class Prior(Page): form_model = 'player' @staticmethod def vars_for_template(player): player.reason = ( 0 if player.task in {0, 1, 2, 3, 4} else 1 if player.task in {5, 6, 7, 8, 9} else None ) player.statusquo = ( 0 if player.task in {0, 5} else 2 if player.task in {1, 6} else 3 if player.task in {2, 7} else 4 if player.task in {3, 8} else 6 if player.task in {4, 9} else None ) return dict(round=player.round_number,reason=player.reason,statusquo=player.statusquo) @staticmethod def before_next_page(player, timeout_happened): math_round = {2} if player.round_number in math_round: math(player) class Game(Page): form_model = 'player' form_fields = ['game_alloc0','game_alloc1'] @staticmethod def vars_for_template(player): agents= agents_label(player) agent0= agents[0] agent1= agents[1] return dict(round=player.round_number,reason=player.reason,statusquo=player.statusquo, agent0=agent0,agent1=agent1) class Math(Page): form_model = 'player' form_fields = ['math_answer'] @staticmethod def vars_for_template(player): img_data = generate_math_image(player.math_num1, player.math_num2) return dict(round=player.round_number,num1=player.math_num1,num2=player.math_num2,math_img=img_data) @staticmethod def is_displayed(player): math_round = {2} return player.round_number in math_round class MathFeedback(Page): @staticmethod def vars_for_template(player): return dict(round=player.round_number,result=player.math_result) @staticmethod def is_displayed(player): math_round = {2} return player.round_number in math_round class Recall(Page): form_model = 'player' form_fields = ['recall'] @staticmethod def vars_for_template(player): return dict(round=player.round_number) @staticmethod def is_displayed(player): participant = player.participant digit_recall = {'T1','T2'} return participant.treatment in digit_recall class Feedback(Page): timeout_seconds = 5 @staticmethod def vars_for_template(player): return dict(round=player.round_number,feedback=player.correct,digit=player.digit) @staticmethod def is_displayed(player): participant = player.participant digit_recall = {'T1','T2'} return participant.treatment in digit_recall class Result(Page): @staticmethod def is_displayed(player): result_round = {2} return player.round_number in result_round page_sequence = [Instructions, Digit,NonDigit, Prior, Game, Math, #MathFeedback, Recall, #Feedback, Result]