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
]