from otree.api import * import settings import time author = 'Patricia Zauchner (zauchner@uni-bremen.de)' doc = """ Effort task: Count letters in a sentence. """ # Models def get_unique_chars(string_to_guess: str) -> list: """ Create a sorted list of the unique characters in the string whose letters the participants have to count. The use of the set function in the creation process ensures that only one instance of each letter is returned after filtering out all non-alphabetic characters. :param string_to_guess: The string whose characters shall be counted :return: A sorted list of the unique characters in the input string """ return sorted(set(filter(str.isalpha, list(string_to_guess.lower())))) def get_char_counts(string_to_guess: str) -> list: """ Maps the alphabetic characters within a given string to their number of occurrences in that string. The return format -- forming a list of lists -- is the following: [["X_1/x_1", c_1], ["X_2/x_2", c_2], ... , ["X_n/x_n", c_n]] where X_i and x_i are the uppercase and lowercase variants of the i-th alphabetic character, while c_i is the respective count within the given string. As not all characters are necessarily contained within an input string there might be characters of the alphabet missing from the list. Note also that special latin characters like "ä" or "ö" only appear after all ASCII characters (i. e. a, b, c, ..., z). :param string_to_guess: The string whose characters shall be counted :return: A list of lists containing the unique characters of the string and their count. """ unique_chars = get_unique_chars(string_to_guess) return [[f'{y.upper()}/{y}', sum(map(lambda x: 1 if y in x else 0, string_to_guess.lower()))] for y in unique_chars] class C(BaseConstants): NAME_IN_URL = 'effort_counting' PLAYERS_PER_GROUP = 5 # Not really necessary in effort tasks. But so it looks better in the data STRING_TO_GUESS = "Die ältesten bekannten kieferlosen Fischartigen (z. B. die Pteraspidomorphi) stammen aus dem " \ "frühen Ordovizium vor rund 450–470 Millionen Jahren." SOLUTION = get_char_counts(STRING_TO_GUESS) NUM_ROUNDS = 1 TASK_TIMER = settings.task_timer POINTS_PER_CORRECT_ANSWER = 1 NUM_TASKS = len(SOLUTION) # print("solutions in count", NUM_TASKS) class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): # # Copy the group and player id structure of the first app if "id_matrix" in subsession.session.vars: subsession.set_group_matrix(subsession.session.vars['id_matrix']) else: subsession.group_randomly() # oTree function subsession.session.vars['id_matrix'] = subsession.get_group_matrix() print("ID Matrix created in app effort_counting", subsession.session.vars['id_matrix']) # # try: subsession.session.vars["do_effort_task"] = subsession.session.config["do_effort_task"] except KeyError: subsession.session.vars["do_effort_task"] = getattr(settings, "do_effort_task", True) # Set session vars (For counting number of effort apps in effort_intro) subsession.session.vars["DoEffort_Counting"] = True # class Group(BaseGroup): pass # Group Functions def get_question(index): return C.SOLUTION[index][0], C.SOLUTION[index][1] def live_answer_question(player, data): player.question_index = index = player.question_index + 1 if index > len(C.SOLUTION): player.final_round = True return old_letter_to_count, old_actual_count = get_question(index - 1) # Get new numbers new_letter_to_count, new_actual_count = get_question(index) # Get new numbers answer = int(data.get("answer", 0)) Answer.create(player=player, question_number=index - 1, letter=old_letter_to_count, answer=answer, correct_solution=old_actual_count) # Save answer in database # Check if the answer was correct (correct value is always 4) player.last_answer_correct = answer_correct = answer == old_actual_count player.number_of_attempts = number_of_attempts = player.number_of_attempts + 1 # Increment number of attempts # Retrieve old number of right guesses from the database number_of_correct_answers = player.number_of_correct_answers # If correct, increment number of correct guesses and, at the same time, write new value back to the database if answer_correct: player.number_of_correct_answers = number_of_correct_answers = number_of_correct_answers + 1 response = { "new_letter_to_count": new_letter_to_count, "correct_answer": new_actual_count, "answer_correct": answer_correct, "number_of_attempts": number_of_attempts, "number_of_correct_answers": number_of_correct_answers, } return {player.id_in_group: response} class Player(BasePlayer): question_index = models.IntegerField( doc="The number of the current task (for iterating through the list of values).", initial=0) number_of_correct_answers = models.IntegerField( doc="Number of correct answers in total.", initial=0) number_of_attempts = models.IntegerField( doc="Number of attempts in total.", initial=0) last_answer_correct = models.BooleanField( initial=0, doc="Did the user answer the last question correctly?") final_round = models.BooleanField(initial=False) class Answer(ExtraModel): """ Creates an extra dataframe output. Is called with custom_export """ player = models.Link(Player) question_number = models.IntegerField() letter = models.StringField() correct_solution = models.IntegerField() answer = models.IntegerField() def custom_export(players): """ Custom export xlsx/csv """ yield ['session_code', 'participant_code', 'question_number', "answer", "correct"] # Header row for answer in Answer.filter(): player = answer.player session = player.session participant = player.participant correct = 1 if 4 == answer.answer else 0 yield [session.code, participant.code, answer.question_number, answer.answer, correct] # Pages class EffortPage(Page): """ Handles if other pages are displayed """ @staticmethod def is_displayed(player): return player.session.vars.get("do_effort_task", True) class Intro(EffortPage): """ Introduction to the counting task """ @staticmethod def before_next_page(player, timeout_happened): # User has task_timer seconds to complete as many pages as possible player.participant.vars['expiry_timestamp'] = time.time() + C.TASK_TIMER class Task(EffortPage): """ Counting task """ live_method = 'live_answer_question' @staticmethod def get_timeout_seconds(player): return player.participant.vars['expiry_timestamp'] - time.time() timer_text = 'Verbleibende Zeit (in Sek.): ' @staticmethod def vars_for_template(player): index = player.question_index question_data = get_question(index) return { "string_to_guess": C.STRING_TO_GUESS, "letter": question_data[0], "correct_answer": question_data[1], "debug": False } @staticmethod def before_next_page(player, timeout_happened): correct_answers = player.number_of_correct_answers player.participant.vars["countingpoints"] = correct_answers player.participant.vars["countingmistakes"] = player.number_of_attempts - correct_answers class Results(EffortPage): """ Results of counting task """ pass page_sequence = [ Intro, Task, Results ]