from otree.api import ( models, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, widgets ) import time from settings import MY_DEFAULT_TIMER import random from random import randrange import numpy as np import json import csv doc = '' class Constants(BaseConstants): name_in_url = 'dobble_game' players_per_group = None num_rounds = 60 base_image_dimensions_mapping = { 4: 140, 6: 100, 8: 90 } cloud_images = 'https://storage.googleapis.com/sorting-game/icons/' class Group(BaseGroup): pass def creating_sequence(self, items_number): if self.round_number == 1: self.session.cards[items_number] = create_dobble_cards(items_number) random.shuffle(self.session.cards[items_number]) self.session.round_cards[items_number] = {} card_1 = self.session.cards[items_number][(self.round_number - 1) % len(self.session.cards[items_number])] card_2 = self.session.cards[items_number][self.round_number % len(self.session.cards[items_number])] # Only one item should exist correct_answer = list(set(card_1) & set(card_2))[0] random.shuffle(card_1) random.shuffle(card_2) base_image_dimension = Constants.base_image_dimensions_mapping[items_number] card_1 = list(map((lambda x: {"number": x, "rotation": randrange(360), "width": int(randrange(80, 120) * base_image_dimension / 100)}), card_1)) card_2 = list(map((lambda x: {"number": x, "rotation": randrange(360), "width": int(randrange(80, 120) * base_image_dimension / 100)}), card_2)) self.session.round_cards[items_number][self.round_number] = { "card_1": card_1, "card_2": card_2, "correct_answer": correct_answer } def load_csv_pool(): pool = {'Animal': [], 'Food': [], 'Summer': []} with open('control_pool.csv') as f: for row in csv.DictReader(f, skipinitialspace=True): line_dict = {k: v for k, v in row.items()} pool[line_dict['choice']].append(line_dict['grade']) return pool def create_group_feedback_for_player(self, player): try: animal_size_arr = json.loads(self.session.config['animal_size']) except: animal_size_arr = self.session.config['animal_size'] try: food_size_arr = json.loads(self.session.config['food_size']) except: food_size_arr = self.session.config['food_size'] try: summer_size_arr = json.loads(self.session.config['summer_size']) except: summer_size_arr = self.session.config['summer_size'] if animal_size_arr is not 'C': self.animal_feedback_size = max(round(np.random.normal(animal_size_arr[0], animal_size_arr[1])), 0) if food_size_arr is not 'C': self.food_feedback_size = max(round(np.random.normal(food_size_arr[0], food_size_arr[1])), 0) if summer_size_arr is not 'C': self.summer_feedback_size = max(round(np.random.normal(summer_size_arr[0], summer_size_arr[1])), 0) if animal_size_arr is 'C': self.animal_feedback_size = self.session.config['population_size'] - self.food_feedback_size - self.summer_feedback_size elif food_size_arr is 'C': self.food_feedback_size = self.session.config['population_size'] - self.summer_feedback_size - self.animal_feedback_size elif summer_size_arr is 'C': self.summer_feedback_size = self.session.config['population_size'] - self.food_feedback_size - self.animal_feedback_size animal_feedback_indexes = random.sample(range(0, len(self.session.vars.pool['Animal'])), self.animal_feedback_size) food_feedback_indexes = random.sample(range(0, len(self.session.vars.pool['Food'])), self.food_feedback_size) summer_feedback_indexes = random.sample(range(0, len(self.session.vars.pool['Summer'])), self.summer_feedback_size) # sorting only for debugging purpose sorting_key = lambda item: float(item) player.others_animal_feedback = ' '.join(sorted([self.session.vars.pool['Animal'][i] for i in animal_feedback_indexes], key=sorting_key)) player.others_food_feedback = ' '.join(sorted([self.session.vars.pool['Food'][i] for i in food_feedback_indexes], key=sorting_key)) player.others_summer_feedback = ' '.join(sorted([self.session.vars.pool['Summer'][i] for i in summer_feedback_indexes], key=sorting_key)) class Subsession(BaseSubsession): animal_feedback_size = models.IntegerField() food_feedback_size = models.IntegerField() summer_feedback_size = models.IntegerField() def creating_session(self): if self.round_number == 1: self.initialize_session(self.session.config['card_items_number']) for player in self.get_players(): create_group_feedback_for_player(self, player) creating_sequence(self, self.session.config['card_items_number']) # Run once on first round creation def initialize_session(self, items_number): self.session.num_rounds = self.session.config['rounds'] * self.session.config['periods'] self.session.vars.pool = load_csv_pool() self.session.round_cards = {} self.session.cards = {} self.session.feedback = { 'Animal': [], 'Food': [], 'Summer': [] } self.session.cards[items_number] = create_dobble_cards(items_number) random.shuffle(self.session.cards[items_number]) self.session.round_cards[items_number] = {} for player in self.get_players(): player.participant.period_score = 0 player.participant.feedback = { 'Animal': [], 'Food': [], 'Summer': [] } class Player(BasePlayer): def waiting_too_long(self): if not hasattr(self.participant, 'wait_page_arrival'): self.participant.wait_page_arrival = time.time() return time.time() - self.participant.wait_page_arrival > MY_DEFAULT_TIMER dobble_answer = models.IntegerField(initial=-1) correct_answer = models.BooleanField() round_score = models.IntegerField() answer_time = models.IntegerField() selection = models.StringField( choices=['Animal', 'Food', 'Summer'], widget=widgets.RadioSelect, label='', ) is_dropout = models.BooleanField(default=False) timeout_happened = models.BooleanField(initial=False) error_answers_count = models.IntegerField(initial=0) correct_answers_count = models.IntegerField(initial=0) mean_grade = models.FloatField(initial=0.0) others_animal_feedback = models.StringField() others_food_feedback = models.StringField() others_summer_feedback = models.StringField() def calculate_answer(self, template): self.correct_answer = self.dobble_answer == self.session.round_cards[template][self.round_number]['correct_answer'] self.round_score = 0 if not self.correct_answer else self.answer_time self.participant.period_score += self.round_score if self.correct_answer: self.participant.correct_answers_count += 1 else: self.participant.error_answers_count += 1 self.correct_answers_count = self.participant.correct_answers_count self.error_answers_count = self.participant.error_answers_count self.mean_grade = self.participant.period_score / (((self.round_number-1) % self.session.config['rounds']) + 1) def new_period(self): self.participant.correct_answers_count = 0 self.participant.error_answers_count = 0 self.participant.period_score = 0 # n-1 must be prime def create_dobble_cards(n): cards = [] # first card and first category for crd in range(0, n): symbols = [0] for sym in range(1, n): symbols.append(crd * (n-1) + sym) cards.append(symbols.copy()) # other categories for cat in range(1, n): for crd in range(0, n-1): symbols = [cat] for sym in range(1, n): symbols.append(1 + sym * (n-1) + ((cat-1) * (sym-1) + crd) % (n-1)) cards.append(symbols.copy()) return cards class DobbleCard: items = []