init
This commit is contained in:
302
scripts/plugin-object-moving.js
Normal file
302
scripts/plugin-object-moving.js
Normal file
@@ -0,0 +1,302 @@
|
||||
import { ParameterType } from 'jspsych';
|
||||
import { html } from './text-stimuli';
|
||||
import getColourMap from './colours';
|
||||
|
||||
const info = {
|
||||
name: 'object-moving',
|
||||
version: "1.1",
|
||||
parameters: {
|
||||
selector: {
|
||||
type: ParameterType.STRING,
|
||||
pretty_name: 'Selector',
|
||||
},
|
||||
together_side: {
|
||||
type: ParameterType.STRING,
|
||||
pretty_name: 'together side',
|
||||
},
|
||||
location: {
|
||||
type: ParameterType.INT,
|
||||
pretty_name: 'Location',
|
||||
},
|
||||
together_colour: {
|
||||
type: ParameterType.STRING,
|
||||
pretty_name: 'Together colour',
|
||||
},
|
||||
},
|
||||
// prettier-ignore
|
||||
citations: '__CITATIONS__'
|
||||
};
|
||||
|
||||
class jsPsychObjectMoving {
|
||||
constructor(jsPsych) {
|
||||
this.jsPsych = jsPsych;
|
||||
}
|
||||
static {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
trial(display_element, trial) {
|
||||
|
||||
const colourMap = getColourMap(trial.together_colour);
|
||||
|
||||
const left_goal = trial.together_side === 'left' ? 'together' : 'alone';
|
||||
const right_goal = trial.together_side === 'left' ? 'alone' : 'together';
|
||||
|
||||
const left_colour = trial.together_side === 'left' ? colourMap.get('togetherbg') : colourMap.get('alonebg');
|
||||
const right_colour = trial.together_side === 'left' ? colourMap.get('alonebg') : colourMap.get('togetherbg');
|
||||
|
||||
display_element.innerHTML = html`
|
||||
<div class="flex flex-row min-w-screen absolute top-0 left-0 min-h-screen">
|
||||
<div id="${left_goal}-goal" class="w-1/12 ${left_colour}"></div>
|
||||
<div id="workspace" class="w-10/12"></div>
|
||||
<div id="${right_goal}-goal" class="w-1/12 ${right_colour}"></div>
|
||||
<div id="info-box" class="z-10 absolute bottom-1/40 min-w-screen justify-center items-center"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const workspace = document.getElementById('workspace');
|
||||
const info_box = document.getElementById('info-box');
|
||||
const workspace_width = workspace.clientWidth;
|
||||
const viewport_width = window.visualViewport.width;
|
||||
const goal_offset = viewport_width * (1/12);
|
||||
const box_width = viewport_width * (3/100);
|
||||
const workspace_offset = workspace_width * trial.location/100;
|
||||
|
||||
const offset_left = workspace_offset + goal_offset - box_width/2;
|
||||
|
||||
const viewport_height = window.visualViewport.height;
|
||||
const top_offset = viewport_height * .5;
|
||||
const separation = box_width * 1.5;
|
||||
|
||||
const objects = html`
|
||||
<div id="object-partner" class="object-moving-box ${colourMap.get('object-disabled')} absolute z-10" style="left: ${offset_left}px; top: ${top_offset - separation}px"></div>
|
||||
<div id="object-participant" class="object-moving-box ${colourMap.get('object-disabled')} absolute z-10" style="left: ${offset_left}px; top: ${top_offset + separation}px"></div>
|
||||
`;
|
||||
|
||||
workspace.innerHTML = objects;
|
||||
|
||||
const object_partner = document.getElementById('object-partner');
|
||||
const object_participant = document.getElementById('object-participant');
|
||||
|
||||
object_partner.addEventListener('click', () => issue_alert('partner_click'));
|
||||
object_participant.addEventListener('click', () => issue_alert('early_click_choice'));
|
||||
|
||||
const partner_turn = html`<span class="font-semi-bold text-gray-800 text-xl">Awaiting your partner's selection</span>
|
||||
<div class='loading'></div>`
|
||||
|
||||
let choice;
|
||||
let go_clicked = false;
|
||||
let partner_finished = false;
|
||||
let participant_finished = false;
|
||||
let partner_choice_made = false;
|
||||
|
||||
const jsPsych = this.jsPsych;
|
||||
|
||||
let start_time = 0;
|
||||
let response_time = 0;
|
||||
|
||||
if (trial.selector === 'participant') {
|
||||
display_choices();
|
||||
|
||||
} else {
|
||||
info_box.innerHTML = partner_turn;
|
||||
|
||||
const partner_choice_delay = jsPsych.randomization.sampleExGaussian(2000, 250, 1/100, true);
|
||||
|
||||
let random = Math.random();
|
||||
|
||||
|
||||
//These probabilities are based on Azaad and Sebanz (2025)
|
||||
|
||||
if (trial.location > 48 && trial.location < 52) {
|
||||
choice = random < 0.64 ? 'together' : 'alone';
|
||||
} else if (trial.location > 31 && trial.location < 35) {
|
||||
choice = random < 0.58 ? 'together' : 'alone';
|
||||
} else if (trial.location > 64 && trial.location < 68) {
|
||||
choice = random < 0.67 ? 'together' : 'alone';
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
info_box.innerHTML = '';
|
||||
clear_boxes();
|
||||
display_go();
|
||||
initiate_partner_action();
|
||||
}, partner_choice_delay);
|
||||
}
|
||||
|
||||
function display_choices(){
|
||||
info_box.innerHTML = html`<div class="flex flex-row justify-center items-center gap-2">
|
||||
<div id="${left_goal}-button" class="w-1/12 py-1 text-slate-50 ${left_colour}">${left_goal.charAt(0).toUpperCase() + left_goal.slice(1)}</div>
|
||||
<div id="${right_goal}-button" class="w-1/12 py-1 text-slate-50 ${right_colour}">${right_goal.charAt(0).toUpperCase() + right_goal.slice(1)}</div>
|
||||
</div>`;
|
||||
|
||||
const left_goal_button = document.getElementById(`${left_goal}-button`);
|
||||
const right_goal_button = document.getElementById(`${right_goal}-button`);
|
||||
|
||||
start_time = Date.now();
|
||||
|
||||
left_goal_button.addEventListener('click', () => {
|
||||
choice = left_goal;
|
||||
handle_choice_made(left_goal_button,right_goal_button);
|
||||
});
|
||||
|
||||
right_goal_button.addEventListener('click', () => {
|
||||
choice = right_goal;
|
||||
handle_choice_made(left_goal_button,right_goal_button);
|
||||
});
|
||||
}
|
||||
|
||||
function handle_choice_made(left_goal_button,right_goal_button){
|
||||
left_goal_button.removeEventListener('click', handle_choice_made);
|
||||
right_goal_button.removeEventListener('click', handle_choice_made);
|
||||
left_goal_button.remove();
|
||||
right_goal_button.remove();
|
||||
clear_boxes();
|
||||
display_go();
|
||||
response_time = Date.now() - start_time;
|
||||
if (choice === 'together') {
|
||||
initiate_partner_action();
|
||||
}
|
||||
}
|
||||
|
||||
function initiate_partner_action(){
|
||||
const go_delay = trial.selector === 'participant' ? 1200 : 800
|
||||
const partner_go_delay = jsPsych.randomization.sampleExGaussian(go_delay, 300, 1/100, true);
|
||||
const partner_teleport_delay = jsPsych.randomization.sampleExGaussian(1200, 200, 1/100, true);
|
||||
|
||||
setTimeout(() => {
|
||||
change_object_colour(object_partner);
|
||||
partner_choice_made = true;
|
||||
}, partner_go_delay);
|
||||
|
||||
setTimeout(() => {
|
||||
teleport_object(object_partner);
|
||||
partner_finished = true;
|
||||
check_finished();
|
||||
}, partner_teleport_delay + partner_go_delay);
|
||||
}
|
||||
|
||||
function clear_boxes() {
|
||||
if (choice === 'together') {
|
||||
return;
|
||||
}
|
||||
if (trial.selector === 'participant') {
|
||||
object_partner.remove();
|
||||
} else {
|
||||
object_participant.remove();
|
||||
}
|
||||
}
|
||||
|
||||
let go_button;
|
||||
|
||||
function display_go() {
|
||||
if (choice === 'alone' && trial.selector === 'partner') {
|
||||
return;
|
||||
}
|
||||
info_box.innerHTML = html`<div id="go-button" class="mx-auto font-semi-bold ${colourMap.get('gobg')} text-slate-50 text-xl w-1/20 p-2">Go</div>`;
|
||||
go_button = document.getElementById('go-button');
|
||||
object_participant.addEventListener('click', () => issue_alert('early_click_go'));
|
||||
go_button.addEventListener('click', () => handle_go_click(go_button));
|
||||
}
|
||||
|
||||
function handle_go_click() {
|
||||
go_button.removeEventListener('click', handle_go_click);
|
||||
go_button.removeEventListener('click', () => issue_alert('early_click_go'));
|
||||
go_clicked = true;
|
||||
go_button.remove();
|
||||
change_object_colour(object_participant);
|
||||
object_participant.addEventListener('click', () => handle_teleport_click());
|
||||
|
||||
}
|
||||
|
||||
function handle_teleport_click(){
|
||||
teleport_object(object_participant);
|
||||
participant_finished = true;
|
||||
check_finished();
|
||||
}
|
||||
|
||||
|
||||
function change_object_colour(object){
|
||||
object.classList.remove(colourMap.get('object-disabled'));
|
||||
object.classList.add(colourMap.get('object-enabled'));
|
||||
}
|
||||
|
||||
function teleport_object(object){
|
||||
let goal_side = 'left';
|
||||
if (choice === 'together' && trial.together_side === 'right') {
|
||||
goal_side = 'right';
|
||||
}
|
||||
if (choice === 'alone' && trial.together_side === 'left') {
|
||||
goal_side = 'right';
|
||||
}
|
||||
const mid_goal_offset = viewport_width * (1/24);
|
||||
const new_left_offset = goal_side === 'left' ? mid_goal_offset - box_width/2 : viewport_width - mid_goal_offset - box_width/2;
|
||||
object.style.transition = 'left 0.35s';
|
||||
object.style.left = `${new_left_offset}px`;
|
||||
}
|
||||
|
||||
function check_finished(){
|
||||
let finished = false;
|
||||
if (choice === 'together') {
|
||||
if (partner_finished && participant_finished) {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
if (choice === 'alone') {
|
||||
if (partner_finished || participant_finished) {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
if (finished) {
|
||||
object_partner.removeEventListener('click', () => issue_alert('partner_click'));
|
||||
object_participant.removeEventListener('click', () => issue_alert('early_click_choice'));
|
||||
|
||||
const save_data = {
|
||||
choice: choice,
|
||||
selector: trial.selector,
|
||||
together_side: trial.together_side,
|
||||
response_time: response_time,
|
||||
}
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
jsPsych.finishTrial(save_data);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
async function show_alert(message) {
|
||||
const dialog = document.createElement("dialog");
|
||||
document.body.appendChild(dialog);
|
||||
dialog.className = "z-20 fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 p-4 bg-slate-100 text-red-700 rounded-lg shadow-lg";
|
||||
dialog.innerText = message;
|
||||
dialog.show();
|
||||
setTimeout(function () {
|
||||
dialog.close();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function issue_alert(issue){
|
||||
if (issue === 'partner_click'){
|
||||
show_alert(`That is your partner's object. Please click the lower object.`)
|
||||
}
|
||||
if (issue === 'early_click_go' && !go_clicked){
|
||||
show_alert(`You need to click 'Go' before clicking your object.`)
|
||||
}
|
||||
if (issue === 'early_click_choice'){
|
||||
if (!partner_choice_made && trial.selector === 'partner'){
|
||||
show_alert(`Please wait for your partner to make a choice.`)
|
||||
}
|
||||
if (!choice && trial.selector === 'participant'){
|
||||
show_alert(`Please make a choice using the buttons below before clicking your object.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default jsPsychObjectMoving;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user