import time from otree import settings from otree.api import * from . import task_matrix from .image_utils import encode_image # note: Set to False for real experiment run DEBUG = False doc = """ Environmental Screening with 7er Task. Participants with even participant IDs complete the 7er task first. """ class C(BaseConstants): NAME_IN_URL = 'Environmental_Screening_Task' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 DSGVO_PAGE = __name__ + '/Datenschutz.html' NUMBER_OF_TYPES = 6 # environmental types # added for the 7er task HEIGHT = 10 WIDTH = 6 IGNORED_CHARS = "012345689" COUNTED_CHARS = "7" DEVIATION_GROUP = 2 # Deviation for Group stuff INSTRUCTION_TEMPLATE = __name__ + "/instructions.html" class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): # for the 7er task session = subsession.session defaults = dict( retry_delay=1.0, puzzle_delay=1.0, attempts_per_puzzle=1, max_iterations=None ) session.params = {} for param in defaults: session.params[param] = session.config.get(param, defaults[param]) # logic for the order of 7er task and environmental screening for player in subsession.get_players(): if player.id_in_group % 2 == 0: player.IS_TYPE_FIRST = False else: player.IS_TYPE_FIRST = True class Group(BaseGroup): # added for the 7er task solution = models.IntegerField(initial=0) response = models.IntegerField(initial=0) is_correct = models.BooleanField(initial=False) class Player(BasePlayer): IS_TYPE_FIRST = models.BooleanField(initial=False) einwilligung1 = models.BooleanField( label="Ich willige in die Teilnahme an der oben beschriebenen Studie ein.", choices=[ [1, "Ja"], [0, "Nein"], ], widget=widgets.RadioSelect, ) einwilligung2 = models.BooleanField( label = "Ich willige hiermit ein (Art. 6 Abs. 1 lit. a DSGVO), dass meine übermittelten persönlichen Daten gespeichert und verarbeitet werden dürfen. Ich versichere, dass ich über 16 Jahre alt bin bzw. die Zustimmung der / des Sorgeberechtigten zur Nutzung des Kontaktes und Weitergabe der Daten vorliegt. Die Datenschutzerklärung zum Formular habe ich gelesen. Das Recht des Widerrufs ist mir bekannt.", choices=[ [1, "Ja"], [0, "Nein"], ], widget=widgets.RadioSelect, ) type = models.IntegerField( label="", choices=[i for i in range(1, C.NUMBER_OF_TYPES + 1)], ) prolificID = models.StringField( label="Bitte tragen Sie hier Ihre Prolific ID ein:", ) typeQ1 = models.IntegerField( label="Fanden Sie die Erklärungen zu den 6 Umwelteinstellungen verständlich?", choices=[ [1, "Ja"], [0, "Nein"], ], widget=widgets.RadioSelect, ) typeQ1_comment = models.StringField( blank=True, label="Falls nicht, warum?", ) typeQ2 = models.IntegerField( label="Waren die Unterschiede zwischen den 6 Umwelteinstellungen für Sie verständlich?", choices=[ [1, "Ja"], [0, "Nein"], ], widget=widgets.RadioSelect, ) typeQ2_comment = models.StringField( blank=True, label="Falls nicht, warum?", ) taskQ1 = models.IntegerField( label="Empfanden Sie die Aufgabe (das Zählen der 7er) als schwierig?", choices=[ [1, "Ja"], [0, "Nein"], ], widget=widgets.RadioSelect, ) taskQ2 = models.IntegerField( label="Haben Sie während der gesamten 4 Minuten durchgehend gezählt?", choices=[ [1, "Ja"], [0, "Nein"], ], widget=widgets.RadioSelect, ) taskQ2_comment = models.StringField( blank=True, label="Falls nicht, warum?", ) # toAsk: Is the max=300 needed here? example = models.IntegerField( label='Gesamtzahl der 7er im angezeigten Zahlenblock:', min=0, max=300, ) # added for the 7er task iteration = models.IntegerField(initial=0) num_trials = models.IntegerField(initial=0) num_correct = models.IntegerField(initial=0) num_failed = models.IntegerField(initial=0) payoffECU = models.IntegerField(initial=0) class Puzzle(ExtraModel): player = models.Link(Player) iteration = models.IntegerField(initial=0) attempts = models.IntegerField(initial=0) timestamp = models.FloatField(initial=0) text = models.LongStringField() # entw. Text oder Json (wie in Slider) solution = models.LongStringField() # genauso wie text response = models.LongStringField() response_timestamp = models.FloatField() is_correct = models.BooleanField() def gen_puzzle(player: Player) -> Puzzle: """ generate new puzzle for a player""" fields = task_matrix.generate_puzzle_fields(ignored_chars=C.IGNORED_CHARS, counted_char=C.COUNTED_CHARS, width=C.WIDTH, height=C.HEIGHT) ## Schriftgröße kann hier mitgegeben werden player.iteration += 1 return Puzzle.create( player=player, iteration=player.iteration, timestamp=time.time(), **fields # text & solution ) def get_cur_puzzle(player: Player): puzzles = Puzzle.filter(player=player, iteration=player.iteration) if puzzles: [puzzle] = puzzles return puzzle return None def enc_puzzle(puzzle: Puzzle): image = task_matrix.render_image(puzzle) return dict(image=encode_image(image)) def get_progess(player:Player): return dict( num_trials= player.num_trials, num_correct = player.num_correct, num_incorrect = player.num_failed, iteration = player.iteration ) def play_game(player: Player, msg: dict): ## siehe slider session = player.session my_id = player.id_in_group params = session.params now = time.time() current = get_cur_puzzle(player) msg_type = msg['type'] if msg_type == 'load': prog = get_progess(player) if current: return { my_id: dict(type='status',progress=prog, puzzle=enc_puzzle(current)) } else: return {my_id: dict(type='status',progress=prog)} if msg_type == 'cheat' and settings.DEBUG: return {my_id: dict(type='solution', solution=current.solution)} if msg_type == 'next': if current is not None: if current.response is None: raise RuntimeError('trying to skip over unsolved puzzle') if now < current.timestamp + params["puzzle_delay"]: raise RuntimeError('retrying too fast') if current.iteration == params['max_iterations']: return { my_id: dict(type='status', progess=get_progess(player), iterations_left=0) } puzz = gen_puzzle(player) prog = get_progess(player) return {my_id: dict(type='puzzle', puzzle=enc_puzzle(puzz), progress=prog)} if msg_type == 'answer': if current is None: raise RuntimeError('trying to answer no puzzle') if current.response is not None: if current.attempts >= params['attempts_per_puzzle']: raise RuntimeError('no more attempts allowed') if now < current.response_timestamp + params['retry_delay']: raise RuntimeError('retrying too fast') player.num_trials -=1 if current.is_correct: player.num_correct-= 1 else: player.num_failed -= 1 answer = msg['answer'] if answer == '' or answer is None: raise ValueError('Answer is empty ') current.response = answer current.is_correct = task_matrix.is_correct(answer,current) current.response_timestamp = now current.attempts +=1 if current.is_correct: player.num_correct += 1 else: player.num_failed +=1 player.num_trials += 1 tries_left = params['attempts_per_puzzle'] - current.attempts prog = get_progess(player) return { my_id: dict( type='feedback', is_correct=current.is_correct, retries_left = tries_left, progress=prog, ) } raise RuntimeError("unrecognized message from client") def prolificID_error_message(player, value): if len(value) != 24: return "Ihre Prolific ID muss 24 Zeichen (Buchstaben & Zahlen) lang sein." return None class Welcome(Page): form_model = 'player' form_fields = ['einwilligung1', 'einwilligung2'] class ProlificID(Page): form_model = 'player' form_fields = ['prolificID'] class Instructions_FIRST(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.IS_TYPE_FIRST class Type_FIRST(Page): form_model = 'player' form_fields = ['type'] @staticmethod def is_displayed(player: Player): return player.IS_TYPE_FIRST # def before_next_page(player: Player, timeout_happened): # if player.player_Type <= 4: # player.user_Role = "Employee" # else: # player.user_Role = "Employer" # # print('UserRole', player.user_Role) class Instructions_7er(Page): form_model = 'player' class Example(Page): timeout_seconds = 30 timer_text = 'Verbleibende Zeit für die Bearbeitung dieser Aufgabe:' form_model = 'player' form_fields = ['example'] class ExampleSolution(Page): pass class Spiel(Page): timeout_seconds = 240 timer_text = 'Verbleibende Zeit für die Bearbeitung dieser Aufgabe:' live_method = play_game @staticmethod def js_vars(player: Player): return dict(params=player.session.params) @staticmethod def vars_for_template(player: Player): return dict(DEBUG=settings.DEBUG, input_type=task_matrix.INPUT_TYPE, placeholder=task_matrix.INPUT_HINT) @staticmethod def before_next_page(player: Player, timeout_happened): if not timeout_happened and not player.session.params['max_iterations']: raise RuntimeError("malicious page submission") if C.PLAYERS_PER_GROUP is not None and C.PLAYERS_PER_GROUP > 1: ### An der Stelle Zusammenrechnen der Gruppenberechnung machen, bzw in der Gruppe speichern current = get_cur_puzzle(player) player.group.solution += current.solution player.group.response += current.response pass def before_next_page(player: Player, timeout_happened): player.payoffECU = player.field_maybe_none('num_correct')*15 class Results(Page): pass class Instructions(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return not player.IS_TYPE_FIRST class Type(Page): form_model = 'player' form_fields = ['type'] @staticmethod def is_displayed(player: Player): return not player.IS_TYPE_FIRST class Questions(Page): form_model = 'player' form_fields = ['typeQ1', 'typeQ1_comment', 'typeQ2', 'typeQ2_comment', 'taskQ1', 'taskQ2', 'taskQ2_comment'] class FinalPage(Page): form_model = 'player' page_sequence = [Welcome, ProlificID, Instructions_FIRST, Type_FIRST, Instructions_7er, Example, ExampleSolution, Spiel, Results, Instructions, Type, Questions, FinalPage, ]