first commit

This commit is contained in:
duartegoncalvesds 2026-03-12 21:05:38 +00:00
commit 73fc2121bf
30 changed files with 13308 additions and 0 deletions

88
Task1/Captcha.html Normal file
View file

@ -0,0 +1,88 @@
{{ extends "_static/global/Page.html" }}
{{ load otree static }}
{{ block title }}
Bot Detection
{{ endblock }}
{{ block styles}}
<style>
#timer_display {
font-size: 2.2rem;
font-weight: 600;
color: #333;
position: fixed;
bottom: 1rem;
right: 1rem;
}
</style>
{{ endblock }}
{{ block scripts}}
<script src="{{ static 'js/vue.js' }}"></script>
<script src="{{ static 'js/lodash.min.js' }}"></script>
<script>
var vm = new Vue({
el: '#main_container',
delimiters: ['${', '}'],
data: {
attempt: {{ attempt }},
time_now: null,
start_time: null,
end_time: null,
time_limit: {{ max_captcha_time }},
time_until_end: {{ max_captcha_time }},
},
computed: {
time_until_end_display: function () {
s = _.clone(this.time_until_end)
if (s>=60) {return (s-(s%=60))/60+(9<s?':':':0')+s}
else if (s>=0) {return s}
else {return '0'}
}
},
mounted: function () {
this.start_time = _.now()/1000
this.end_time = this.start_time + this.time_limit
window.setInterval(() => {
this.time_now = _.now()/1000;
this.time_until_end = (this.end_time - this.time_now).toFixed(0);
if (this.time_until_end <= 0) {
if (document.querySelectorAll("input[type=text]")[0].value == "") {
document.querySelectorAll("input[type=text]")[0].value = "NULL TIMEOUT"
}
document.getElementById("submit_button").click();
}
}
, 1000)
},
})
</script>
{{ endblock }}
{{ block content }}
<div id="main_container" v-cloak>
<br>
<div id="captcha">
<h3>Bot Detection - Attempt ${attempt}</h3>
<p>
Type the following word or phrase into the box below, then press 'Next'. Answers are not case-sensitive.
<br>
You have three attempts. If you fail all three attempts, the task will end and you will not be paid.
<br>
You have one minute per attempt.
</p>
<div id="captcha_image">
<img src="{{ static image_path }}" style='width:300px;'/>
</div>
<br>
{{ formfields }}
<button class="btn btn-primary" id="submit_button">Next</button>
</div>
<div id="timer_display"><span>${time_until_end_display}</span></div>
</div>
{{ endblock }}

19
Task1/CaptchaFailed.html Normal file
View file

@ -0,0 +1,19 @@
{{ extends "_static/global/Page.html" }}
{{ load otree static }}
{{ block title }}
Bot Detection Failed
{{ endblock }}
{{ block styles}}
{{ endblock }}
{{ block scripts}}
{{ endblock }}
{{ block content }}
<p>
You failed the transcription task three times. <br>
You will not be able to continue the experiment.
</p>
{{ endblock }}

195
Task1/Instructions.html Normal file
View file

@ -0,0 +1,195 @@
{{ extends "_static/global/Page.html" }}
{{ load otree static }}
{{ block styles}}
{{ endblock }}
{{ block title }}
Instructions
{{ endblock }}
{{ block content }}
<input name="time_spent_instructions" type="hidden" id="time_spent_instructions">
{{ include "Task1/InstructionsBase.html" }}
<div id="main_container" v-cloak>
<div id="questions_container">
<h3>Questions</h3>
<p>
You must answer the following questions correctly before you can proceed.
</p>
<ol>
<question v-for="(question, question_index) in questions" :question="question" :question_index="question_index" v-on:update_response="question.r=$event">
</question>
</ol>
<div id="check_answers">
<button type="button" class="btn btn-primary" @click="check_answers">Check Answers</button>
<br>
<p v-if="incorrect_answer_numbers != null && incorrect_answer_numbers.length > 0">
<!-- The following answers are incorrect: <span v-for="q in incorrect_answer_numbers.slice(0, -1)">${q}, </span><span>${incorrect_answer_numbers[incorrect_answer_numbers.length-1]}.</span>
<br> -->
Some of the questions are unanswered or incorrect.
You must answer all questions correctly before you can proceed.
<br>
Update your answers and try again.
</p>
<p v-if="incorrect_answer_numbers != null && incorrect_answer_numbers.length == 0">
All answers are correct! Click 'Next' to proceed.
</p>
<button type="button" @click="instructions_time" v-if="incorrect_answer_numbers != null && incorrect_answer_numbers.length == 0" class="btn btn-primary">Next</button>
</div>
</div>
<input type="hidden" v-model="incorrect_answers_string" name="incorrect_instruction_answers">
<input type="hidden" v-model="errors_quiz_count" name="errors_quiz">
<input type="hidden" id="time_spent_instructions" name="time_spent_instructions">
</div>
{{ endblock }}
{{ block scripts }}
<script>
let startTime,submitTime
window.addEventListener('load', function() {
startTime=Date.now()
})
</script>
<!-- <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script> -->
<!--Loads vue.js-->
<script src="{{ static 'js/vue.js' }}"></script>
<!--Defines what a question looks like-->
<script type="text/x-template" id="question-template">
<li>
<br>
<label class="question_text"><span v-html="question.q"></span></label><br>
<template v-for="answer in question.a">
<input type="radio" :value="answer" v-model="response" @change="$emit('update_response', $event.target.value)">
<label v-html="answer" class="question_text" :for="answer"></label>
<br>
</template>
<br>
</li>
</script>
<script>
Vue.component('question', {
props: ['question','question_index'],
template: '#question-template',
name: 'question',
delimiters: ['${', '}'],
data: function () {
return {
response: null
}
},
methods: {
update_response: function() {
console.log(this.question_index,this.response)
},
}
});
var vm = new Vue({
el: '#main_container',
delimiters: ['${', '}'],
data: {
incorrect_answers: [], errors_quiz: 0,
questions: [
{
'q': 'True or False? You and your opponent always have the same baseline points.',
'a': [
'True.',
'False.',
],
'c': 'False.',
'r': null
},
{
'q': 'True or False? The asset is worth the same for you and your opponent.',
'a': [
'True.',
'False.',
],
'c': 'True.',
'r': null
},
{
'q': 'True or False? If you choose Option A (the asset), you always get the points associated with the asset.',
'a': [
'True.',
'False.'
],
'c': 'False.',
'r': null
},
{
'q': 'True or False? If you choose Option B (baseline points), you get the points only if your opponent also chooses Option B.',
'a': [
'True.',
'False.'
],
'c': 'False.',
'r': null
},
{
'q': 'True or False? If you choose Option B (baseline points), you get the points only if your opponent also chooses Option B.',
'a': [
'True.',
'False.'
],
'c': 'False.',
'r': null
},
{
'q': 'Choose the correct answer if you want to maximise the probability of winning the bonus payment of {{ session.config.currency_symbol }}{{ session.config.bonus | to2 }}.',
'a': [
'Your guess does not matter for earning the bonus.',
'It is always better to make the best guess possible.'
],
'c': 'It is always better to make the best guess possible.',
'r': null
}
]
},
computed: {
incorrect_answer_numbers: function() {
ia = this.incorrect_answers
if (ia.length === 0) {return null}
else {return ia[ia.length-1].map((a) => a[0])}
},
incorrect_answers_string: function() {
return JSON.stringify(this.incorrect_answers)
},
errors_quiz_count: function(){
return this.errors_quiz-1
},
questions: function() {
questions = this.common_questions
}
},
methods: {
check_answers: function() {
incorrect_answers = [];
this.questions.forEach((q,i) => {if (q.c !== q.r) {incorrect_answers.push([i+1,q.r])}})
this.incorrect_answers.push(incorrect_answers)
this.errors_quiz = this.errors_quiz + 1
},
instructions_time: function(){
submitTime = Date.now()
if (document.getElementById("time_spent_instructions").value) {
document.getElementById("time_spent_instructions").value
}
else {
document.getElementById("time_spent_instructions").value = submitTime - startTime;
}
document.getElementById("form").submit();
}
}
});
</script>
{{ endblock }}

View file

@ -0,0 +1,94 @@
<h3>
Your Task
</h3>
<p>
This task has {{ C.NUM_PAYMENT_ROUNDS }} rounds.
<br>
Every round, you and other participants (1) make a choice and (2) guess the choices of other participants.
Your choice and guess will determine how likely you are to get a bonus of {{ player.session.config.currency_symbol }}{{ player.session.config.bonus | to2 }}.
<br>
At the end of the task, you will be paid the participation fee of {{ player.session.config.currency_symbol }}{{ player.session.config.participation_payment | to2 }} and one round will be chosen randomly to determine your chance of getting the bonus.
</p>
<p>
In each round, you will be randomly matched with another participant (your opponent). Both you and your opponent must choose between an asset (Option A) and the assigned baseline points (Option B).
</p>
<ul>
<li>
<strong>Option A:</strong> You receive the asset's value if your opponent chooses Option B. If both you and your opponent choose Option A, both of you receive 0 points.
</li>
<li>
<strong>Option B:</strong> You receive your assigned baseline points, regardless of what your opponent chooses.
</li>
</ul>
<h3>Key Details</h3>
<ul>
<li>
<strong>Asset's Value:</strong> The asset is worth the same amount of points for both you and your opponent.
</li>
<li>
<strong>Baseline Points:</strong> At the start of each round, every participant is assigned a random number of baseline points between 0 and 100. All values are equally likely and have been randomised for each participant.<br>
<strong>You will not know your opponent's baseline points and your opponent will not know your baseline points.</strong>
</li>
<li>
<strong>Same Incentives for Everyone:</strong> Everyone else in the task follows these same rules and has the same incentives as you.
</li>
<li>
<strong>Independence:</strong> The asset value, your baseline points, and your opponent's baseline points vary each round. Your choices across rounds are independent and do not affect one another.
</li>
</ul>
<h3>Example</h3>
<p>
Suppose the asset (Option A) is worth 50 points and your baseline points (Option B) are 38.4.
</p>
<ul>
<li>
If you choose Option B, you are guaranteed to receive your baseline points of 38.4.
</li>
<li>
If you choose Option A and your opponent chooses Option B, you receive the asset value of 50.
</li>
<li>
If both you and your opponent choose Option A, both of you receive 0 points.
</li>
</ul>
<h3>Guesses</h3>
<p>
In addition, we will ask you what you believe other participants will do.
The more accurate your guess is, the more likely you are to get the bonus.
</p>
<h3>Bonus Payment Rule</h3>
<p>
Your choice and guess will determine how likely you are to get the bonus.
The payment rule is designed so that
(1) you choose to maximise what your points given how likely you think others are to invest or not;
(2) you tell us what you believe the probability is that your opponent chooses each action.
<br>
To ensure this, your payment from guesses is determined as follows:
<ul>
<li>At the end of the study, one round will be randomly selected, and either your choice or your guess is chosen to determine payment, both equally likely.</li>
<li>If your choice is selected for payment, then the probability you get the bonus equals the number of points you got from your choice, as described above.</li>
<li>If your guess is selected for payment, we draw two random choices according to your guess.
You get 50 points for each choice that matches the choice of your opponent.
In addition, you get 50 points if the two drawn choices are different.
The probability you get the bonus equals the resulting total number of points.</li>
</ul>
</p>
<h3>Practice Rounds</h3>
<p>
In order to get familiarised with the task, you will begin with {{ C.NUM_PRACTICE_ROUNDS }} practice rounds.
The practice rounds do not count toward your chance of getting the bonus.
<br>
There is also a brief questionnaire at the end.
</p>
<!-- Treatments:
Baseline
N samples with replacement, forced and free
Sample as many times as they want
-->

19
Task1/NewRound.html Normal file
View file

@ -0,0 +1,19 @@
{{ extends "_static/global/Page.html" }}
{{ load otree static }}
{{ block title }}
Main Task
{{ endblock }}
{{ block content }}
<div>
<p>
You will now make choices {{ C.NUM_PAYMENT_ROUNDS }} rounds.<br>
One of these rounds will be randomly selected to <b>determine your bonus payment</b> at the end of the task. <br>
You will not receive feedback.
</p>
</div>
<div style="justify-content: center; align-items: center; text-align: center;">
{{ next_button }}
</div>
{{ endblock }}

185
Task1/Task.html Normal file
View file

@ -0,0 +1,185 @@
{{ extends "_static/global/Page.html" }}
{{ load otree static }}
{{ block title }}
{{ if player.practice == 1 }}
Practice Round {{ practice_round_number }} out of {{ C.NUM_PRACTICE_ROUNDS }}
{{ endif }}
{{ if player.practice == 0 }}
Round {{ payment_round_number }} out of {{ C.NUM_PAYMENT_ROUNDS }}
{{ endif }}
{{ endblock }}
{{ block scripts }}
<script>
// (A) PREVENT CONTEXT MENU FROM OPENING
window.addEventListener("contextmenu", evt => evt.preventDefault(), false);
// (B) PREVENT CLIPBOARD COPYING
window.addEventListener("copy", evt => {
evt.clipboardData.setData("text/plain", "Copying is not allowed on this page");
evt.preventDefault();
}, false);
window.addEventListener("load", (event) => {
let startTime,submitTime
startTime=Date.now()
document.getElementById("instructions_clicked").value = 0
const div = document.querySelectorAll('div')
div.forEach(function(x){
if (x.classList.contains('otree-form-errors')){
x.innerHTML="Please make your choice and guess."
}
})
// SLIDER
const slider = document.getElementById('belief_slider');
const leftSpan = document.getElementById('left-value');
const rightSpan = document.getElementById('right-value');
const hidden = document.getElementById('id_belief_1');
const sliderContainer = document.getElementById('slider-container');
if (slider) {
slider.addEventListener('input', function() {
// Show the thumb on first move
sliderContainer.classList.remove('empty-slider');
sliderContainer.classList.add('interacted');
// Update text and hidden input
let val = parseFloat(this.value);
leftSpan.innerText = val.toFixed(1);
rightSpan.innerText = (100 - val).toFixed(1);
hidden.value = val.toFixed(1);
});
}
document.getElementById("ok_button").addEventListener('click', function () {
submitTime = Date.now()
if (document.getElementById("time_spent").value) {
document.getElementById("time_spent").value
} else {
document.getElementById("time_spent").value = submitTime - startTime;
}
document.getElementById("form").submit();
})
});
function show_Instructions() {
document.getElementById("instructions_clicked").value = 1
let elem1 = document.getElementById("instructions_button"), text1 = elem1.innerText;
if (text1 === "Show Instructions"){
elem1.innerHTML = "Hide Instructions";}
else{
elem1.innerHTML = "Show Instructions";
}
let x1 = document.getElementById("Instructions");
if (x1.style.display === "none") {
x1.style.display = "block";
}
else {
x1.style.display = "none";
}
};
// Delay choices
setTimeout(function () {
document.getElementById("interface").style.visibility = "visible";
document.getElementById("ok_button").style.visibility = "visible";
document.getElementById("instructions_button").style.visibility = "visible";
}, 2000);
</script>
{{ endblock }}
{{ block styles }}
<style>
/* Slider No-Value Start */
.empty-slider input[type=range]::-webkit-slider-thumb { visibility: hidden; }
.empty-slider input[type=range]::-moz-range-thumb { visibility: hidden; }
.interacted input[type=range]::-webkit-slider-thumb { visibility: visible; }
.interacted input[type=range]::-moz-range-thumb { visibility: visible; }
.form-range { width: 100%; cursor: pointer; }
</style>
{{ endblock }}
{{ block content }}
<input name="instructions_clicked" type="hidden" id="instructions_clicked">
<input name="time_spent" type="hidden" id="time_spent">
<br>
<div class="container">
<div class="center">
<p>
Asset's worth: {{ player.value | to0 }} points.
<br>
Your baseline points: {{ player.cost | to1 }}.
<br>
Opponent's baseline points: a random number between 0 and 100, all equally likely.
<br>
You will not know your opponent's baseline points and your opponent will not know your baseline points.
</p>
</div>
</div>
<br><br><br>
<div id="interface" style="visibility: hidden;">
<p>Which option do you choose?</p>
<table id="alternatives" class="center">
<tr>
<td style="width:10%">
<input class="form-check-input" type="radio" id="id_choice-0" name="decision" required="" value="0">
</td>
<td>Option A: Asset's worth if opponent chooses Option B.</td>
</tr>
<tr>
<td style="width:10%">
<input class="form-check-input" type="radio" id="id_choice-1" name="decision" required="" value="1">
</td>
<td>Option B: Baseline points.</td>
</tr>
</table>
<br><br>
<p>
What do you think is the probability that your opponent chooses each option:
</p>
<div class="center" style="max-width: 600px; margin: 40px auto; margin-top: 0px;">
<!-- <p>Please select a value by clicking on the bar below:</p> -->
<div id="slider-container" class="empty-slider">
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
<div style="width:80px; text-align:left;">
Option A <br>
<span id="left-value">--</span>%
<!-- <br>Asset<br> -->
</div>
<div style="flex:1;">
<input type="range" class="form-range" id="belief_slider" min="0" max="100" step="0.1" value="50">
</div>
<div style="width:80px; text-align:right;">
Option B <br>
<span id="right-value">--</span>%
<!-- <br>Opponent's <br> Baseline Points -->
</div>
</div>
</div>
<input type="hidden" name="belief_1" id="id_belief_1" value="">
</div>
{{ if player.practice == 1 }}
<div class="container"><div class="centering"><button type="button" class="btn btn-primary" style="visibility: hidden;" id="ok_button">Receive feedback</button></div></div>
{{ endif }}
{{ if player.practice == 0 }}
<div class="container"><div class="centering"><button type="button" class="btn btn-primary" style="visibility: hidden;" id="ok_button">Next</button></div></div>
{{ endif }}
<div class="container">
<div class="centering">
<button type="button" style="visibility: hidden;" class="btn btn-secondary" id="instructions_button" onclick="show_Instructions()">Show Instructions</button>
</div>
</div>
<div id="Instructions" style="display: none" class="card bg-light instructions">
<div class="card-body">
{{ include "Task1/InstructionsBase.html"}}
</div>
</div>
</div>
{{ endblock }}

225
Task1/TaskFeedback.html Normal file
View file

@ -0,0 +1,225 @@
{{ extends "_static/global/Page.html" }}
{{ load otree static }}
{{ block title }}
{{ if player.practice == 1 }}
Practice Round {{ practice_round_number }} out of {{ C.NUM_PRACTICE_ROUNDS }}
{{ endif }}
{{ if player.practice == 0 }}
Round {{ payment_round_number }} out of {{ C.NUM_PAYMENT_ROUNDS }}
{{ endif }}
{{ endblock }}
{{ block scripts }}
<script>
// (A) PREVENT CONTEXT MENU FROM OPENING
window.addEventListener("contextmenu", evt => evt.preventDefault(), false);
// (B) PREVENT CLIPBOARD COPYING
window.addEventListener("copy", evt => {
evt.clipboardData.setData("text/plain", "Copying is not allowed on this page");
evt.preventDefault();
}, false);
window.addEventListener("load", (event) => {
let startTime,submitTime
startTime=Date.now()
document.getElementById("instructions_clicked").value = 0
const div = document.querySelectorAll('div')
div.forEach(function(x){
if (x.classList.contains('otree-form-errors')){
x.innerHTML="Please make your choice and guess."
}
})
// SLIDER
const slider = document.getElementById('belief_slider');
const leftSpan = document.getElementById('left-value');
const rightSpan = document.getElementById('right-value');
const hidden = document.getElementById('id_belief_1');
const sliderContainer = document.getElementById('slider-container');
if (slider) {
slider.addEventListener('input', function() {
// Show the thumb on first move
sliderContainer.classList.remove('empty-slider');
sliderContainer.classList.add('interacted');
// Update text and hidden input
let val = parseFloat(this.value);
leftSpan.innerText = val.toFixed(1);
rightSpan.innerText = (100 - val).toFixed(1);
hidden.value = val.toFixed(1);
});
}
document.getElementById("ok_button").addEventListener('click', function () {
submitTime = Date.now()
if (document.getElementById("time_spent").value) {
document.getElementById("time_spent").value
} else {
document.getElementById("time_spent").value = submitTime - startTime;
}
document.getElementById("form").submit();
})
});
function show_Instructions() {
document.getElementById("instructions_clicked").value = 1
let elem1 = document.getElementById("instructions_button"), text1 = elem1.innerText;
if (text1 === "Show Instructions"){
elem1.innerHTML = "Hide Instructions";}
else{
elem1.innerHTML = "Show Instructions";
}
let x1 = document.getElementById("Instructions");
if (x1.style.display === "none") {
x1.style.display = "block";
}
else {
x1.style.display = "none";
}
};
// Delay choices
setTimeout(function () {
document.getElementById("interface").style.visibility = "visible";
document.getElementById("ok_button").style.visibility = "visible";
document.getElementById("instructions_button").style.visibility = "visible";
}, 0);
// Feedback
document.getElementById("id_choice-0").disabled = true;
document.getElementById("id_choice-1").disabled = true;
document.getElementById("belief_slider").disabled = true;
document.getElementById("belief_slider").value = js_vars.belief_1;
document.getElementById("left-value").innerText = parseFloat(js_vars.belief_1).toFixed(1);
document.getElementById("right-value").innerText = (100 - parseFloat(js_vars.belief_1)).toFixed(1);
if (js_vars.decision == 0){
document.getElementById("id_choice-0").checked = true;
}
if (js_vars.decision == 1){
document.getElementById("id_choice-1").checked = true;
}
</script>
{{ endblock }}
{{ block styles }}
<style>
/* Slider No-Value Start */
.empty-slider input[type=range]::-webkit-slider-thumb { visibility: hidden; }
.empty-slider input[type=range]::-moz-range-thumb { visibility: hidden; }
.interacted input[type=range]::-webkit-slider-thumb { visibility: visible; }
.interacted input[type=range]::-moz-range-thumb { visibility: visible; }
.form-range { width: 100%; cursor: pointer; }
</style>
{{ endblock }}
{{ block content }}
<input name="instructions_clicked" type="hidden" id="instructions_clicked">
<input name="time_spent" type="hidden" id="time_spent">
<br>
<div class="container">
<div class="center">
<p>
Asset's worth: {{ player.value | to0 }} points.
<br>
Your baseline points: {{ player.cost | to1 }}.
<br>
Opponent's baseline points: a random number between 0 and 100, all equally likely.
<br>
You will not know your opponent's baseline points and your opponent will not know your baseline points.
</p>
</div>
</div>
<br><br><br>
<div id="interface" style="visibility: hidden;">
<p>Which option do you choose?</p>
<table id="alternatives" class="center">
<tr>
<td style="width:10%">
<input class="form-check-input" type="radio" id="id_choice-0" name="decision" required="" value="0">
</td>
<td>Option A: Asset's worth if opponent chooses Option B.</td>
</tr>
<tr>
<td style="width:10%">
<input class="form-check-input" type="radio" id="id_choice-1" name="decision" required="" value="1">
</td>
<td>Option B: Baseline points.</td>
</tr>
</table>
<br><br>
<p>
What do you think is the probability that your opponent chooses each option:
</p>
<div class="center" style="max-width: 600px; margin: 40px auto; margin-top: 0px;">
<!-- <p>Please select a value by clicking on the bar below:</p> -->
<div id="slider-container" class="interacted">
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
<div style="width:80px; text-align:left;">
Option A <br>
<span id="left-value">--</span>%
<!-- <br>Asset<br> -->
</div>
<div style="flex:1;">
<input type="range" class="form-range" id="belief_slider" min="0" max="100" step="0.1" value="50">
</div>
<div style="width:80px; text-align:right;">
Option B <br>
<span id="right-value">--</span>%
<!-- <br>Opponent's <br> Baseline Points -->
</div>
</div>
</div>
<input type="hidden" name="belief_1" id="id_belief_1" value="">
</div>
<div class="feedback-text">
<p>
In this practice round, you chose Option {{ if player.decision == 0 }}A{{ endif }}{{ if player.decision == 1}}B{{ endif }} and guessed that your opponent chose Option A with {{ belief_A | to1 }}% probability and Option B with {{ belief_B | to1 }}% probability.
</p>
<p>
Recall that only one round is selected for payment and, in that round, only either your choice or your guess counts toward your payment.
</p>
<p>
<ul>
<li>
If your choice in this round were chosen for payment, you would get the {{ player.session.config.currency_symbol }}{{ player.session.config.bonus | to2 }} bonus with {{ if player.decision == 0 }}0% if your opponent chose Option A and {{ player.value | to0 }}% probability if your opponent chose Option B{{ endif }}{{ if player.decision == 1 }}{{ player.cost | to1 }}% probability, regardless of your opponent's choice{{ endif }}.
</li>
<li>
If your guess in this round were chosen for payment, you would get the {{ player.session.config.currency_symbol }}{{ player.session.config.bonus | to2 }} bonus with {{ paymentbelief_A | to1 }}% if your opponent chose Option A and {{ paymentbelief_B | to1 }}% if your opponent chose Option B.
</li>
</ul>
It is always best to submit your best guess to maximise the probability of getting paid the bonus.
</p>
<br>
<p>
Click 'Next' to see your instructions for this study.
</p>
</div>
{{ if player.practice == 1 }}
<div class="container"><div class="centering"><button type="button" class="btn btn-primary" style="visibility: hidden;" id="ok_button">Next</button></div></div>
{{ endif }}
<div class="container">
<div class="centering">
<button type="button" style="visibility: hidden;" class="btn btn-secondary" id="instructions_button" onclick="show_Instructions()">Show Instructions</button>
</div>
</div>
<div id="Instructions" style="display: none" class="card bg-light instructions">
<div class="card-body">
{{ include "Task1/InstructionsBase.html"}}
</div>
</div>
</div>
{{ endblock }}

30
Task1/TaskIntro.html Normal file
View file

@ -0,0 +1,30 @@
{{ extends "_static/global/Page.html" }}
{{ load otree static }}
{{ block title }}
Main Task
{{ endblock }}
{{ block content }}
<div>
{{ if player.practice == 1}}
<p>
You will now have {{C.NUM_PRACTICE_ROUNDS}} practice round{{ if C.NUM_PRACTICE_ROUNDS > 1 }}s{{ endif }} to familiarise yourself with the study.
<br>
You will receive feedback after your choices.
<br>
You will not be paid for {{ if C.NUM_PRACTICE_ROUNDS == 1 }}this round{{ endif }}{{ if C.NUM_PRACTICE_ROUNDS > 1 }}these rounds{{ endif }}.
</p>
{{ endif }}
{{ if player.practice == 0}}
<p>
You will now make choices {{ C.NUM_PAYMENT_ROUNDS }} rounds.<br>
One of these rounds will be randomly selected to <b>determine your bonus payment</b> at the end of the task. <br>
You will not receive feedback.
</p>
{{ endif }}
</div>
<div style="justify-content: center; align-items: center; text-align: center;">
{{ next_button }}
</div>
{{ endblock }}

313
Task1/__init__.py Normal file
View file

@ -0,0 +1,313 @@
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,
]