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`
`; 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`
`; 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`Awaiting your partner's selection
` 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`
${left_goal.charAt(0).toUpperCase() + left_goal.slice(1)}
${right_goal.charAt(0).toUpperCase() + right_goal.slice(1)}
`; 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' ? 1400 : 1000 const partner_go_delay = jsPsych.randomization.sampleExGaussian(go_delay, 500, 1/100, true); const partner_teleport_delay = jsPsych.randomization.sampleExGaussian(1400, 300, 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`
Go
`; 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;