from otree.api import * c = cu doc = '' class C(BaseConstants): NAME_IN_URL = 'quality_control_game' PLAYERS_PER_GROUP = None NUM_ROUNDS = 40 STDEV = 30 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): x = models.IntegerField() y = models.IntegerField() marblex = models.IntegerField() marbley = models.IntegerField() score = models.FloatField() def random_xcoord(player: Player): import random # prevx, prevy are the location of the funnel in previous round if player.round_number == 1: prevx = 0 else: prev_player = player.in_round(player.round_number - 1) prevx = prev_player.x # xcoord, ycoord are where the marble rolls at the start of the current round count = 10 values = sum([random.randint(prevx - 150, prevx + 150) for z in range(count)]) xcoord = round(values/count) if xcoord > 150: xcoord = 150 if xcoord < -150: xcoord = -150 return xcoord def random_ycoord(player: Player): import random # prevx, prevy are the location of the funnel in previous round if player.round_number == 1: prevy = 0 else: prev_player = player.in_round(player.round_number - 1) prevy = prev_player.y # xcoord, ycoord are where the marble rolls at the start of the current round count = 10 values = sum([random.randint(prevy - 150, prevy + 150) for z in range(count)]) ycoord = round(values/count) if ycoord > 150: ycoord = 150 if ycoord < -150: ycoord = -150 return ycoord class InitialPage(Page): form_model = 'player' timeout_seconds = 1 @staticmethod def before_next_page(player: Player, timeout_happened): import math player.marblex = random_xcoord(player) player.marbley = random_ycoord(player) player.score = math.sqrt(player.marblex * player.marblex + player.marbley * player.marbley) class Game(Page): form_model = 'player' form_fields = ['x', 'y'] @staticmethod def vars_for_template(player: Player): # prevx, prevy are the location of the funnel in previous round # this is the starting location of the funnel in the new round if player.round_number == 1: # 1st round is an exception: we randomly locate the funnel around the centre prevx = 10 prevy = -20 else: prev_player = player.in_round(player.round_number - 1) prevx = prev_player.x prevy = prev_player.y return dict( prevx = prevx, prevy = prevy, ) @staticmethod def js_vars(player: Player): allycoord = [] allxcoord = [] cumulativescore = 0 i = 1 while i < player.round_number + 1: i_player = player.in_round(i) allxcoord.append(i_player.marblex) allycoord.append(i_player.marbley) cumulativescore += i_player.score i += 1 # prevx, prevy are the location of the funnel in previous round # this is the starting location of the funnel in the new round if player.round_number == 1: # 1st round is an exception: we randomly locate the funnel around the centre prevx = 10 prevy = -20 else: prev_player = player.in_round(player.round_number - 1) prevx = prev_player.x prevy = prev_player.y return dict( roundd = player.round_number, allxcoord = allxcoord, allycoord = allycoord, cumulativescore = round(cumulativescore,0), prevx = prevx, prevy = prevy ) class Results(Page): form_model = 'player' @staticmethod def is_displayed(player: Player): return player.round_number == 10 or player.round_number == 20 or player.round_number == 30 or player.round_number == 40 @staticmethod def vars_for_template(player: Player): import random import math cumulativescore = 0 controllimitbreach = 0 # counts how many times UCL or LCL is exceeded i = 1 while i < player.round_number + 1: i_player = player.in_round(i) cumulativescore += i_player.score if abs(i_player.marblex) > 3*C.STDEV: controllimitbreach += 1 if abs(i_player.marbley) > 3*C.STDEV: controllimitbreach += 1 i += 1 if controllimitbreach > 3: controllimitbreachtext = "You have exceeded the control limits " + str(controllimitbreach) + " times. Try to be more careful in the future rounds." if player.round_number == C.NUM_ROUNDS: controllimitbreachtext = "You have exceeded the control limits " + str(controllimitbreach) + " times." elif controllimitbreach > 0: controllimitbreachtext = "You have exceeded the control limits only " + str(controllimitbreach) + " times. Good job." else: controllimitbreachtext = "You have not exceeded the control limits even once. Excellent job!" # lowest score simulation, assuming funnel is kept at centre mclowscore = [] for mc in range(1000): lowscore = 0 for i in range(player.round_number): xcoord = round(random.randint(-150,150)) # cannot use numpy normal distribution so we use uniformly distributed random numbers ycoord = round(random.randint(-150,150)) lowscore += math.sqrt(xcoord * xcoord + ycoord * ycoord) mclowscore.append(lowscore) if min(mclowscore) < 0.8*cumulativescore: lowscoretext = "You can do much better." else: lowscoretext = "Great job." return dict( lowscore = min(mclowscore), lowscoretext = lowscoretext, cumulativescore = cumulativescore, controllimitbreach = controllimitbreach, controllimitbreachtext = controllimitbreachtext ) @staticmethod def js_vars(player: Player): # list of coordinates for the balls, starts from the centre of the area allycoord = [] allxcoord = [] cumulativescore = 0 controllimitbreach = 0 # counts how many times UCL or LCL is exceeded i = 1 while i < player.round_number + 1: i_player = player.in_round(i) allxcoord.append(i_player.marblex) allycoord.append(i_player.marbley) cumulativescore += i_player.score if abs(i_player.marblex) > 3*C.STDEV: controllimitbreach += 1 if abs(i_player.marbley) > 3*C.STDEV: controllimitbreach += 1 i += 1 return dict( roundi = player.round_number, allxcoord = allxcoord, allycoord = allycoord, stdev = C.STDEV ) page_sequence = [InitialPage, Game, Results]