import time from otree import settings from otree.api import * from .image_utils import encode_image #SESSION def get_task_module(player): from . import task_matrix session = player.session task = session.config.get("task") if task == "matrix": return task_matrix class Constants(BaseConstants): name_in_url = "CountingAs" players_per_group = None num_rounds = 1 instructions_template = __name__ + "/instructions_short.html" captcha_length = 3 #SUBSESSION class Subsession(BaseSubsession): pass #GROUP class Group(BaseGroup): pass #PLAYER class Player(BasePlayer): iteration = models.IntegerField(initial=0) num_trials = models.IntegerField(initial=0) num_correct = models.IntegerField(initial=0) num_failed = models.IntegerField(initial=0) num_halfway = models.IntegerField(initial=0) time_start = models.FloatField() time_pause = models.FloatField() time0 = models.FloatField() time1 = models.FloatField() time2 = models.FloatField() time3 = models.FloatField() time4 = models.FloatField() time5 = models.FloatField() time6 = models.FloatField() time7 = models.FloatField() time8 = models.FloatField() time9 = models.FloatField() time10 = models.FloatField() time11 = models.FloatField() time12 = models.FloatField() time13 = models.FloatField() time14 = models.FloatField() time15 = models.FloatField() time16 = models.FloatField() time17 = models.FloatField() time18 = models.FloatField() time19 = models.FloatField() time20 = models.FloatField() time21 = models.FloatField() time22 = models.FloatField() time23 = models.FloatField() time24 = models.FloatField() time25 = models.FloatField() time26 = models.FloatField() time27 = models.FloatField() time28 = models.FloatField() time29 = models.FloatField() time30 = models.FloatField() time31 = models.FloatField() time32 = models.FloatField() time33 = models.FloatField() time34 = models.FloatField() time35 = models.FloatField() time36 = models.FloatField() time37 = models.FloatField() time38 = models.FloatField() time39 = models.FloatField() time40 = models.FloatField() time41 = models.FloatField() time42 = models.FloatField() time43 = models.FloatField() time44 = models.FloatField() time45 = models.FloatField() time46 = models.FloatField() time47 = models.FloatField() time48 = models.FloatField() time49 = models.FloatField() time50 = models.FloatField() #PUZZLE class Puzzle(ExtraModel): """A model to keep record of all generated puzzles""" player = models.Link(Player) iteration = models.IntegerField(initial=0) attempts = models.IntegerField(initial=0) timestamp = models.FloatField(initial=0) # can be either simple text, or a json-encoded definition of the puzzle, etc. text = models.LongStringField() # solution may be the same as text, if it's simply a transcription task solution = models.LongStringField() response = models.LongStringField() response_timestamp = models.FloatField() is_correct = models.BooleanField() def generate_puzzle(player: Player) -> Puzzle: """Create new puzzle for a player""" task_module = get_task_module(player) fields = task_module.generate_puzzle_fields() player.iteration += 1 return Puzzle.create( player=player, iteration=player.iteration, timestamp=time.time(), **fields ) def get_current_puzzle(player): puzzles = Puzzle.filter(player=player, iteration=player.iteration) if puzzles: [puzzle] = puzzles return puzzle def encode_puzzle(puzzle: Puzzle): """Create data describing puzzle to send to client""" task_module = get_task_module(puzzle.player) # noqa # generate image for the puzzle image = task_module.render_image(puzzle) data = encode_image(image) return dict(image=data) def get_progress(player: Player): """Return current player progress""" 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, message: dict): session = player.session my_id = player.id_in_group total_games = 100 params = session.params task_module = get_task_module(player) now = time.time() current = get_current_puzzle(player) message_type = message['type'] max_correct = session.config['max_correct'] # page loaded if message_type == 'load': p = get_progress(player) if player.num_correct == 0: player.time0 = round(now,3) if current: return { my_id: dict(type='status', progress=p, puzzle=encode_puzzle(current)) } else: return {my_id: dict(type='status', progress=p)} if message_type == "cheat" and settings.DEBUG: return {my_id: dict(type='solution', solution=current.solution)} # client requested new puzzle if message_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 player.num_correct == total_games: return { my_id: dict( type='status', progress=get_progress(player), iterations_left=0 ) } # generate new puzzle z = generate_puzzle(player) p = get_progress(player) return {my_id: dict(type='puzzle', puzzle=encode_puzzle(z), progress=p)} # client gives an answer to current puzzle if message_type == "answer": if current is None: raise RuntimeError("trying to answer no puzzle") if current.response is not None: # it's a retry if now < current.response_timestamp + params["retry_delay"]: raise RuntimeError("retrying too fast") # check answer answer = message["answer"] if answer == "" or answer is None: raise ValueError("bogus answer") current.response = answer current.is_correct = task_module.is_correct(answer, current) current.response_timestamp = now current.attempts += 1 # update player progress if current.is_correct: player.num_correct += 1 task = player.num_correct if task == 1: player.time1 = round(now,3) elif task == 2: player.time2 = round(now,3) elif task == 3: player.time3 = round(now,3) elif task == 4: player.time4 = round(now,3) elif task == 5: player.time5 = round(now,3) elif task == 6: player.time6 = round(now,3) elif task == 7: player.time7 = round(now,3) elif task == 8: player.time8 = round(now,3) elif task == 9: player.time9 = round(now,3) elif task == 10: player.time10 = round(now,3) elif task == 11: player.time11 = round(now,3) elif task == 12: player.time12 = round(now,3) elif task == 13: player.time13 = round(now,3) elif task == 14: player.time14 = round(now,3) elif task == 15: player.time15 = round(now,3) elif task == 16: player.time16 = round(now,3) elif task == 17: player.time17 = round(now,3) elif task == 18: player.time18 = round(now,3) elif task == 19: player.time19 = round(now,3) elif task == 20: player.time20 = round(now,3) elif task == 21: player.time21 = round(now,3) elif task == 22: player.time22 = round(now,3) elif task == 23: player.time23 = round(now,3) elif task == 24: player.time24 = round(now,3) elif task == 25: player.time25 = round(now,3) elif task == 26: player.time26 = round(now,3) elif task == 27: player.time27 = round(now,3) elif task == 28: player.time28 = round(now,3) elif task == 29: player.time29 = round(now,3) elif task == 30: player.time30 = round(now,3) elif task == 31: player.time31 = round(now,3) elif task == 32: player.time32 = round(now,3) elif task == 33: player.time33 = round(now,3) elif task == 34: player.time34 = round(now,3) elif task == 35: player.time35 = round(now,3) elif task == 36: player.time36 = round(now,3) elif task == 37: player.time37 = round(now,3) elif task == 38: player.time38 = round(now,3) elif task == 39: player.time39 = round(now,3) elif task == 40: player.time40 = round(now,3) elif task == 41: player.time41 = round(now,3) elif task == 42: player.time42 = round(now,3) elif task == 43: player.time43 = round(now,3) elif task == 44: player.time44 = round(now,3) elif task == 45: player.time45 = round(now,3) elif task == 46: player.time46 = round(now,3) elif task == 47: player.time47 = round(now,3) elif task == 48: player.time48 = round(now,3) elif task == 49: player.time49 = round(now,3) elif task == 50: player.time50 = round(now,3) else: player.num_failed += 1 player.num_trials += 1 retries_left = params["attempts_per_puzzle"] - current.attempts p = get_progress(player) return { my_id: dict( type='feedback', is_correct=current.is_correct, retries_left=retries_left, progress=p, ) } raise RuntimeError("unrecognized message from client") #FUNCTIONS def creating_session(subsession: Subsession): session = subsession.session defaults = dict( retry_delay=20, puzzle_delay=1.0, attempts_per_puzzle=2, max_iterations=None ) session.params = {} for param in defaults: session.params[param] = session.config.get(param, defaults[param]) #PAGES class Instructions(Page): @staticmethod def before_next_page(player, timeout_happened): player.time_start = round(time.time(),3) def is_displayed(player): participant = player.participant return participant.treatment in ['MIXED1'] class Game(Page): timeout_seconds = 120 live_method = play_game @staticmethod def js_vars(player: Player): return dict(params=player.session.params) @staticmethod def vars_for_template(player: Player): task_module = get_task_module(player) return dict(counted_char="A", DEBUG=settings.DEBUG, input_type=task_module.INPUT_TYPE) @staticmethod def before_next_page(player, timeout_happened): player.time_pause = round(time.time(),3) player.num_halfway = player.num_correct @staticmethod def is_displayed(player): participant = player.participant return participant.treatment in ['MIXED1'] #SEQUENCE page_sequence = [Instructions, Game]