313 lines
No EOL
10 KiB
Python
313 lines
No EOL
10 KiB
Python
from otree.api import *
|
|
import random
|
|
import math
|
|
import json
|
|
import numpy as np
|
|
import random
|
|
import itertools
|
|
|
|
|
|
doc = """Template"""
|
|
|
|
class C(BaseConstants):
|
|
NAME_IN_URL = 'Decision'
|
|
PLAYERS_PER_GROUP = None
|
|
|
|
# Rounds
|
|
NUM_PRACTICE_ROUNDS = 2
|
|
NUM_PAYMENT_ROUNDS = 3
|
|
NUM_ROUNDS = NUM_PRACTICE_ROUNDS + NUM_PAYMENT_ROUNDS
|
|
|
|
# Captchas
|
|
max_captcha_time = 60
|
|
captcha_rounds = [1, 2, ] # The rounds in which captchas occur
|
|
captcha_text = ["bonus", "payment", ] # The text the participants need to input (can use upper or lowercase)
|
|
|
|
treatments_between_list = [0, 1]
|
|
cost_list = [5, 15, 25, 35, 45, 55, 65, 75, 85, 95]
|
|
value_list = [10, 30, 50, 70, 90]
|
|
payoffs_list = []
|
|
for cost in cost_list:
|
|
for value in value_list:
|
|
payoffs_list.append([value, cost])
|
|
|
|
|
|
|
|
|
|
class Subsession(BaseSubsession):
|
|
pass
|
|
|
|
|
|
class Group(BaseGroup):
|
|
pass
|
|
|
|
|
|
class Player(BasePlayer):
|
|
practice = models.IntegerField()
|
|
player_round = models.IntegerField()
|
|
|
|
decision = models.IntegerField()
|
|
belief_1 = models.FloatField(min=0, max=100)
|
|
|
|
# Between-Participant Treatment
|
|
treatment_between = models.IntegerField()
|
|
|
|
# Within-Participant Treatment
|
|
treatment_index_within = models.IntegerField()
|
|
value = models.FloatField()
|
|
cost = models.FloatField()
|
|
|
|
# Time fields
|
|
time_spent = models.IntegerField()
|
|
time_spent_instructions = models.IntegerField()
|
|
|
|
# Captcha
|
|
captcha_attempt_1 = models.StringField(label="", initial="")
|
|
captcha_attempt_2 = models.StringField(label="", initial="")
|
|
captcha_attempt_3 = models.StringField(label="", initial="")
|
|
|
|
# Instructions
|
|
incorrect_instruction_answers = models.StringField(blank=True)
|
|
errors_quiz = models.IntegerField()
|
|
|
|
instructions_clicked = models.BooleanField()
|
|
|
|
prolific_id = models.StringField(default=str(" "))
|
|
|
|
|
|
|
|
# FUNCTIONS
|
|
def creating_session(subsession: Subsession):
|
|
# Between-participant treatment
|
|
# Extend between treatment list * Total participants in session /2 /Nr Between treatments and shuffle
|
|
total_number_participants = 10 # max([1,math.floor(len(player.session.get_participants())/2/len(C.treatments_between_list))])
|
|
between_treatment_list_shuffled = C.treatments_between_list * total_number_participants
|
|
random.shuffle(between_treatment_list_shuffled)
|
|
|
|
# Within-participant treatment
|
|
reps = len(C.payoffs_list) // C.NUM_PAYMENT_ROUNDS
|
|
remainder = len(C.payoffs_list) % C.NUM_PAYMENT_ROUNDS
|
|
|
|
for player in subsession.get_players():
|
|
|
|
player.prolific_id = player.participant.label
|
|
player.player_round = player.round_number
|
|
|
|
player.participant.vars['failed_captcha'] = False
|
|
|
|
# Between-participant treatment assignment
|
|
player.treatment_between = between_treatment_list_shuffled[len(between_treatment_list_shuffled) % player.participant.id_in_session-1]
|
|
|
|
if player.round_number == 1:
|
|
# Within-participant treatment assignment
|
|
treatments_indices_within = list(range(len(C.payoffs_list))) * reps
|
|
remaining_treatments = list(range(len(C.payoffs_list)))
|
|
random.shuffle(remaining_treatments)
|
|
treatments_indices_within = treatments_indices_within + remaining_treatments[:remainder]
|
|
random.shuffle(treatments_indices_within)
|
|
for r in range(C.NUM_PAYMENT_ROUNDS):
|
|
player.in_round(C.NUM_PRACTICE_ROUNDS+1+r).treatment_index_within = treatments_indices_within[r]
|
|
|
|
if player.player_round <= C.NUM_PRACTICE_ROUNDS:
|
|
player.practice = 1
|
|
player.value = random.choice(C.value_list)
|
|
player.cost = np.round(random.choice(C.cost_list)+10*(random.random()-.5),1)
|
|
if player.player_round > C.NUM_PRACTICE_ROUNDS:
|
|
player.practice = 0
|
|
player.value = C.payoffs_list[player.treatment_index_within][0]
|
|
player.cost = np.round(C.payoffs_list[player.treatment_index_within][1]+10*(random.random()-.5),1)
|
|
|
|
|
|
|
|
# PAGES
|
|
class Instructions(Page):
|
|
form_model = 'player'
|
|
form_fields = ['incorrect_instruction_answers', 'errors_quiz', 'time_spent_instructions',]
|
|
|
|
@staticmethod
|
|
def is_displayed(player):
|
|
return player.round_number == 1
|
|
|
|
@staticmethod
|
|
def vars_for_template(player: Player):
|
|
return dict(
|
|
participation_fee=player.session.config['participation_fee'],
|
|
min_bonus_payment=player.session.config['min_bonus_payment'],
|
|
max_bonus_payment=player.session.config['max_bonus_payment'],
|
|
min_payment=player.session.config['min_payment'],
|
|
max_payment=player.session.config['max_payment'],
|
|
)
|
|
|
|
@staticmethod
|
|
def before_next_page(player, timeout_happened):
|
|
player.incorrect_instruction_answers = player.field_maybe_none('incorrect_instruction_answers')
|
|
|
|
|
|
class TaskIntro(Page):
|
|
@staticmethod
|
|
def is_displayed(player):
|
|
return (
|
|
player.round_number in [1,C.NUM_PRACTICE_ROUNDS+1] and
|
|
not player.participant.vars.get('failed_captcha', False)
|
|
)
|
|
|
|
|
|
class Task(Page):
|
|
form_model = 'player'
|
|
form_fields = ['decision','belief_1','time_spent',]
|
|
|
|
@staticmethod
|
|
def is_displayed(player):
|
|
return (
|
|
not player.participant.vars.get('failed_captcha', False)
|
|
)
|
|
|
|
@staticmethod
|
|
def vars_for_template(player):
|
|
return dict(
|
|
payment_round_number = player.round_number-C.NUM_PRACTICE_ROUNDS,
|
|
practice_round_number = player.round_number,
|
|
round_number = player.round_number,
|
|
)
|
|
|
|
class TaskFeedback(Page):
|
|
@staticmethod
|
|
def is_displayed(player):
|
|
return (
|
|
player.round_number <= C.NUM_PRACTICE_ROUNDS and
|
|
not player.participant.vars.get('failed_captcha', False)
|
|
)
|
|
@staticmethod
|
|
def vars_for_template(player):
|
|
return dict(
|
|
payment_round_number = player.round_number-C.NUM_PRACTICE_ROUNDS,
|
|
practice_round_number = player.round_number,
|
|
round_number = player.round_number,
|
|
belief_A = player.belief_1,
|
|
belief_B = 100-player.belief_1,
|
|
paymentbelief_A = 2*player.belief_1-player.belief_1**2/100,
|
|
paymentbelief_B = 100-player.belief_1**2/100,
|
|
)
|
|
|
|
@staticmethod
|
|
def js_vars(player):
|
|
return dict(
|
|
decision = player.decision,
|
|
belief_1 = player.belief_1,
|
|
)
|
|
|
|
|
|
|
|
class CaptchaFailed(Page):
|
|
@staticmethod
|
|
def is_displayed(player):
|
|
return (
|
|
player.participant.vars.get('failed_captcha', True)
|
|
)
|
|
|
|
class Captcha1(Page):
|
|
template_name = 'Task1/Captcha.html'
|
|
form_model = 'player'
|
|
form_fields = ['captcha_attempt_1']
|
|
|
|
@staticmethod
|
|
def is_displayed(player):
|
|
if not (
|
|
player.round_number in C.captcha_rounds
|
|
and not player.participant.vars.get('failed_captcha', False)
|
|
and player.subsession.session.config['use_captchas'] == 1
|
|
):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
@staticmethod
|
|
def vars_for_template(player):
|
|
max_captcha_time = C.max_captcha_time
|
|
return {
|
|
'image_path': "images/captchas/captcha{}.png".format(C.captcha_rounds.index(player.round_number) + 1),
|
|
'attempt': 1,
|
|
'max_captcha_time': max_captcha_time,
|
|
}
|
|
|
|
|
|
class Captcha2(Page):
|
|
template_name = 'Task1/Captcha.html'
|
|
form_model = 'player'
|
|
form_fields = ['captcha_attempt_2']
|
|
|
|
@staticmethod
|
|
def is_displayed(player):
|
|
if not (
|
|
player.round_number in C.captcha_rounds
|
|
and not player.participant.vars.get('failed_captcha', False)
|
|
and player.subsession.session.config['use_captchas'] == 1
|
|
):
|
|
return False
|
|
else:
|
|
return player.captcha_attempt_1.lower() != C.captcha_text[C.captcha_rounds.index(player.round_number)].lower()
|
|
|
|
@staticmethod
|
|
def vars_for_template(player):
|
|
max_captcha_time = C.max_captcha_time
|
|
return {
|
|
'image_path': "images/captchas/captcha{}.png".format(C.captcha_rounds.index(player.round_number) + 1),
|
|
'attempt': 2,
|
|
'max_captcha_time': max_captcha_time,
|
|
}
|
|
|
|
|
|
class Captcha3(Page):
|
|
template_name = 'Task1/Captcha.html'
|
|
form_model = 'player'
|
|
form_fields = ['captcha_attempt_3']
|
|
|
|
@staticmethod
|
|
def is_displayed(player):
|
|
if not (
|
|
player.round_number in C.captcha_rounds
|
|
and not player.participant.vars.get('failed_captcha', False)
|
|
and player.subsession.session.config['use_captchas'] == 1
|
|
):
|
|
return False
|
|
else:
|
|
return (
|
|
player.captcha_attempt_1.lower() != C.captcha_text[
|
|
C.captcha_rounds.index(player.round_number)
|
|
].lower()
|
|
and player.captcha_attempt_2.lower() != C.captcha_text[
|
|
C.captcha_rounds.index(player.round_number)
|
|
].lower()
|
|
)
|
|
@staticmethod
|
|
def vars_for_template(player):
|
|
max_captcha_time = C.max_captcha_time
|
|
return {
|
|
'image_path': "images/captchas/captcha{}.png".format(C.captcha_rounds.index(player.round_number) + 1),
|
|
'attempt': 3,
|
|
'max_captcha_time': max_captcha_time,
|
|
}
|
|
|
|
@staticmethod
|
|
def before_next_page(player, timeout_happened):
|
|
correct_text = C.captcha_text[C.captcha_rounds.index(player.round_number)].lower()
|
|
if (
|
|
player.round_number in C.captcha_rounds
|
|
and player.captcha_attempt_1.lower() != correct_text
|
|
and player.captcha_attempt_2.lower() != correct_text
|
|
and player.captcha_attempt_3.lower() != correct_text
|
|
):
|
|
player.participant.vars['failed_captcha'] = True
|
|
|
|
|
|
# Update page_sequence
|
|
page_sequence = [
|
|
Instructions,
|
|
Captcha1,
|
|
Captcha2,
|
|
Captcha3,
|
|
CaptchaFailed,
|
|
TaskIntro,
|
|
Task,
|
|
TaskFeedback,
|
|
] |