from pprint import pprint # noqa from otree.api import * import random from settings import SESSION_CONFIG_DEFAULTS, PARTICIPANT_FIELDS doc = """ Wisconsin card sorting test: https://doi.org/10.1093/cercor/1.1.62 """ class C(BaseConstants): NAME_IN_URL = 'card' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 ## Instructions contants CONVERSION_FACTOR = SESSION_CONFIG_DEFAULTS['conversion_factor'] CORRECT_ANSWER_FEE = cu(SESSION_CONFIG_DEFAULTS['wisconsin_fee'] * CONVERSION_FACTOR) SWITCH_THRESHOLD = 6 NUM_TRIALS = 30 RULES = ['color', 'shape', 'number'] COLORS = ['red', 'blue', 'green', 'yellow'] SHAPES = ['circle', 'triangle', 'star', 'plus'] NUMBERS = [1, 2, 3, 4] class Subsession(BaseSubsession): matching_rules = models.LongStringField() def creating_session(subsession: Subsession): for p in subsession.get_players(): p.layout = random_layout() p.rule = random.choice(C.RULES) class Group(BaseGroup): pass class Player(BasePlayer): ## Session indices day = models.IntegerField() app_sequence = models.IntegerField() inf_sequence = models.IntegerField() intervention = models.IntegerField() # how many they have selected from each deck num_trials = models.IntegerField(initial=0) num_correct = models.IntegerField(initial=0) num_correct_in_block = models.IntegerField(initial=0) rule = models.StringField(doc="Either color, shape, or number") layout = models.StringField() is_finished = models.BooleanField(initial=False) # Response time response_time = models.LongStringField(initial='') class Trial(ExtraModel): player = models.Link(Player) reaction_ms = models.FloatField() def random_layout(): # color, number, shape, and 0 (which means nothing matches) layout = list('cns0') random.shuffle(layout) return ''.join(layout) def generate_decks(test_card: dict, layout): cs = [c for c in C.COLORS if c != test_card['color']] ss = [c for c in C.SHAPES if c != test_card['shape']] ns = [c for c in C.NUMBERS if c != test_card['number']] random.shuffle(cs) random.shuffle(ss) random.shuffle(ns) decks = [] for letter in layout: if letter == 'c': card = dict(color=test_card['color'], shape=ss.pop(), number=ns.pop()) elif letter == 's': card = dict(shape=test_card['shape'], color=cs.pop(), number=ns.pop()) elif letter == 'n': card = dict(number=test_card['number'], shape=ss.pop(), color=cs.pop()) else: card = dict(color=cs.pop(), shape=ss.pop(), number=ns.pop()) decks.append(card) return decks ## Create response time array response_time_list = [] def live_method(player: Player, data): import json # to convert dict to json print('player.rule', player.rule) print('player.layout', player.layout) my_id = player.id_in_group trial = 1 # guard if player.is_finished: return {my_id: dict(finished=True)} resp = {} if 'deck_number' in data: trial += 1 deck = data['deck_number'] # [0] means the first letter of the rule. is_correct = player.layout[deck] == player.rule[0] player.num_trials += 1 player.num_correct += is_correct player.num_correct_in_block += is_correct if player.num_correct_in_block == C.SWITCH_THRESHOLD: other_rules = [r for r in C.RULES if r != player.rule] player.rule = random.choice(other_rules) player.num_correct_in_block = 0 # layout changes each turn. otherwise, the user could just keep # clicking on the same box for the rest of the block. player.layout = random_layout() feedback = '✓' if is_correct else '✗' # trial.reaction_ms = data['answered_timestamp'] - data['displayed_timestamp'] response_time_list.append(json.dumps({ "trial number":player.num_trials, "displayed time":data['displayed_timestamp'], "answered time":data['answered_timestamp'] })) player.response_time = json.dumps({player.num_trials:response_time_list}) print(player.response_time) resp.update(feedback=feedback) test_card = dict( color=random.choice(C.COLORS), shape=random.choice(C.SHAPES), number=random.choice(C.NUMBERS), ) decks = generate_decks(test_card, player.layout) resp.update(test_card=test_card, decks=decks) if player.num_trials == C.NUM_TRIALS: player.is_finished = True resp.update(finished=True) resp.update(num_trials=player.num_trials) return {my_id: resp} class Intro(Page): live_method = live_method @staticmethod def vars_for_template(player: Player): return dict(deck_numbers=range(4)) class Play(Page): live_method = live_method @staticmethod def vars_for_template(player: Player): return dict(deck_numbers=range(4)) @staticmethod def error_message(player: Player, values): if not player.is_finished: return "Game not finished" class Results(Page): pass @staticmethod def vars_for_template(player: Player): reward = player.num_correct * C.CORRECT_ANSWER_FEE return dict( num_correct=player.num_correct, reward=reward ) @staticmethod def before_next_page(player: Player, timeout_happened): player.participant.wisconsin = (player.num_correct * C.CORRECT_ANSWER_FEE) print('participant.wisconsin: ', player.participant.wisconsin) page_sequence = [ Intro, Play, Results ]