from otree.api import ( models, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, widgets ) import time from settings import MY_DEFAULT_TIMER import random from random import randrange, shuffle import numpy as np import json import csv import math 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_into_player_grouped_list(): my_dict = {} 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()} if line_dict['PID'] not in my_dict: my_dict[line_dict['PID']] = [line_dict] else: my_dict[line_dict['PID']].append(line_dict) my_list = [] for key, value in my_dict.items(): dict_to_append = {key: value} my_list.append(dict_to_append) return my_list def transform_player_pool_to_themes(player_grouped_list): pool = {'Animal': [], 'Food': [], 'Summer': []} for line_dict in player_grouped_list: for key, value in line_dict.items(): for raw_line_round in value: pool[raw_line_round['choice']].append(raw_line_round['grade']) return pool def create_group_feedback_for_player(self, player, current_period): numeral_animal_feedback_size = 0 numeral_food_feedback_size = 0 numeral_summer_feedback_size = 0 seed_number = current_period + player.participant_id np.random.seed(seed_number) random.seed(seed_number) if player.participant.animal_feedback_size != 'C': numeral_animal_feedback_size = max(round(np.random.normal(player.participant.animal_feedback_size[0], player.participant.animal_feedback_size[1])), 0) if player.participant.food_feedback_size != 'C': numeral_food_feedback_size = max(round(np.random.normal(player.participant.food_feedback_size[0], player.participant.food_feedback_size[1])), 0) if player.participant.summer_feedback_size != 'C': numeral_summer_feedback_size = max(round(np.random.normal(player.participant.summer_feedback_size[0], player.participant.summer_feedback_size[1])), 0) if player.participant.animal_feedback_size == 'C': numeral_animal_feedback_size = self.session.config['population_size'] - numeral_food_feedback_size - numeral_summer_feedback_size elif player.participant.food_feedback_size == 'C': numeral_food_feedback_size = self.session.config['population_size'] - numeral_summer_feedback_size - numeral_animal_feedback_size elif player.participant.summer_feedback_size == 'C': numeral_summer_feedback_size = self.session.config['population_size'] - numeral_food_feedback_size - numeral_animal_feedback_size animal_feedback_indexes = random.sample(range(0, len(player.participant.other_players_pool['Animal'])), numeral_animal_feedback_size) food_feedback_indexes = random.sample(range(0, len(player.participant.other_players_pool['Food'])), numeral_food_feedback_size) summer_feedback_indexes = random.sample(range(0, len(player.participant.other_players_pool['Summer'])), numeral_summer_feedback_size) # sorting only for debugging purpose sorting_key = lambda item: float(item) player.others_animal_feedback = ' '.join(sorted([player.participant.other_players_pool['Animal'][i] for i in animal_feedback_indexes], key=sorting_key)) player.others_food_feedback = ' '.join(sorted([player.participant.other_players_pool['Food'][i] for i in food_feedback_indexes], key=sorting_key)) player.others_summer_feedback = ' '.join(sorted([player.participant.other_players_pool['Summer'][i] for i in summer_feedback_indexes], key=sorting_key)) player.numeral_animal_feedback_size = numeral_animal_feedback_size player.numeral_food_feedback_size = numeral_food_feedback_size player.numeral_summer_feedback_size = numeral_summer_feedback_size def get_current_period_number(self): return int(math.ceil(self.round_number / self.session.config['rounds'])) class Subsession(BaseSubsession): 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, get_current_period_number(self)) 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_into_player_grouped_list() 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] = {} feedback_theme_size = json.loads(self.session.config['feedback_theme_size']) for player in self.get_players(): other_chosen_players = random.sample(self.session.vars.pool, self.session.config['population_size']) player.participant.other_players_pool = transform_player_pool_to_themes(other_chosen_players) # for debugging only, display the chosen players in feedback player.participant.other_chosen_players = [] for line_dict in other_chosen_players: for key in line_dict.keys(): player.participant.other_chosen_players.append(key) player.participant.period_score = 0 player.participant.feedback = { 'Animal': [], 'Food': [], 'Summer': [] } player_sizes_shuffled = feedback_theme_size.copy() shuffle(player_sizes_shuffled) player.participant.vars['animal_feedback_size'] = player_sizes_shuffled[0] player.participant.vars['food_feedback_size'] = player_sizes_shuffled[1] player.participant.vars['summer_feedback_size'] = player_sizes_shuffled[2] 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.RadioSelectHorizontal, 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() numeral_animal_feedback_size = models.IntegerField() numeral_food_feedback_size = models.IntegerField() numeral_summer_feedback_size = models.IntegerField() 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 = []