Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ class CorsiBlocksPlugin {
.querySelector(`.jspsych-corsi-block[data-id="${id}"]`)
.animate(correct_animation, animation_timing);
}
else if (!correct) {
else if (!correct && !trial.disable_animation) {
display_element
.querySelector(`.jspsych-corsi-block[data-id="${id}"]`)
.animate(incorrect_animation, animation_timing);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 29 additions & 8 deletions task-launcher/src/tasks/matrix-reasoning/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
createPreloadTrials,
getRealTrials,
batchTrials,
batchMediaAssets,
batchMediaAssets,
checkFallbackCriteria,
} from '../shared/helpers';
import { downexInstructions1, downexInstructions2, downexInstructions3, downexInstructions4, downexInstructions5, instructions } from './trials/instructions';
import { downexStimulus } from './trials/downexStimulus';
Expand Down Expand Up @@ -130,12 +131,31 @@ export default function buildMatrixTimeline(config: Record<string, any>, mediaAs
}
}

const repeatInstructions = {
timeline: [repeatInstructionsMessage, ...instructions],
const secondPhaseIndex = 5;
let fellBack = false;

// give older kids the downex items if they meet fall back criteria
const fallbackBlock = {
timeline: [
repeatInstructionsMessage,
downexInstructions1,
...downexCorpus.slice(0, secondPhaseIndex).map((trial) => [{...fixationOnly, stimulus: ''}, downexStimulus(layoutConfigMap, true, trial), ifRealTrialResponse]).flat(),
downexInstructions2,
downexInstructions3,
practiceTransition(undefined, true),
...downexCorpus.slice(secondPhaseIndex).map((trial) => [{...fixationOnly, stimulus: ''}, downexStimulus(layoutConfigMap, false, trial), ifRealTrialResponse]).flat(),
downexInstructions4,
downexInstructions5,
],
conditional_function: () => {
return taskStore().numIncorrect >= 2;
const run = checkFallbackCriteria() && !fellBack && !heavyInstructions;
if (run) {
fellBack = true;
}

return run;
},
};
}

function preloadBatch() {
timeline.push(createPreloadTrials(batchedMediaAssets[currPreloadBatch]).default);
Expand Down Expand Up @@ -181,7 +201,6 @@ export default function buildMatrixTimeline(config: Record<string, any>, mediaAs
timeline.push(unnormedBlock);
} else {
const numOfDownexTrials = taskStore().totalDownexTrials;
const secondPhaseIndex = 5; // stop animation after 5 trials

if (heavyInstructions) {
for (let i = 0; i < numOfDownexTrials; i++) {
Expand All @@ -203,12 +222,14 @@ export default function buildMatrixTimeline(config: Record<string, any>, mediaAs
const numOfTrials = taskStore().totalTrials;
taskStore('totalTestTrials', heavyInstructions ? getRealTrials(defaultCorpus) + getRealTrials(downexCorpus) : getRealTrials(defaultCorpus));

const numOfInitialPracticeTrials = defaultCorpus.filter((trial) => trial.assessmentStage === 'practice_response').length;
const fallbackIndex = numOfInitialPracticeTrials + 4;
for (let i = 0; i < numOfTrials; i += 1) {
if (i % batchSize === 0) {
preloadBatch();
}
if (i === 4) {
timeline.push(repeatInstructions);
if (i <= fallbackIndex) {
timeline.push(fallbackBlock);
}
timeline.push(stimulusBlock);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ let practiceResponses = []
let startTime: number;
let audioEnabled = false; // disable audio if the trial has changed since the loop started - prevent overlapping audio

export const downexStimulus = (layoutConfigMap: Record<string, LayoutConfigType>, animate: boolean) => {
export const downexStimulus = (layoutConfigMap: Record<string, LayoutConfigType>, animate: boolean, trial?: StimulusType) => {
return {
type: jsPsychHtmlMultiResponse,
data: () => {
const stim = taskStore().nextStimulus;
const stim = trial || taskStore().nextStimulus;
let isPracticeTrial = stim.assessmentStage === 'practice_response';
return {
// not camelCase because firekit
Expand All @@ -33,7 +33,7 @@ export const downexStimulus = (layoutConfigMap: Record<string, LayoutConfigType>
};
},
stimulus: () => {
const stim = taskStore().nextStimulus;
const stim = trial || taskStore().nextStimulus;
const t = taskStore().translations;
const imageSrc = mediaAssets.images[camelize(stim.item)];

Expand Down Expand Up @@ -66,7 +66,7 @@ export const downexStimulus = (layoutConfigMap: Record<string, LayoutConfigType>
},
prompt_above_buttons: true,
button_choices: () => {
const stim = taskStore().nextStimulus;
const stim = trial || taskStore().nextStimulus;
const itemLayoutConfig = layoutConfigMap?.[stim.itemId];
const choices = itemLayoutConfig.response.displayValues;

Expand All @@ -78,7 +78,7 @@ export const downexStimulus = (layoutConfigMap: Record<string, LayoutConfigType>
},
keyboard_choices: () => 'NO_KEYS',
button_html: () => {
const stim = taskStore().nextStimulus;
const stim = trial || taskStore().nextStimulus;
const itemLayoutConfig = layoutConfigMap?.[stim.itemId];
const classList = [...itemLayoutConfig.classOverrides.buttonClassList];
if (stim.assessmentStage === 'practice_response') {
Expand All @@ -92,7 +92,7 @@ export const downexStimulus = (layoutConfigMap: Record<string, LayoutConfigType>
on_load: async () => {
startTime = performance.now();

const stim = taskStore().nextStimulus;
const stim = trial || taskStore().nextStimulus;

// set up replay audio with animations
const trialAudio = stim.audioFile;
Expand Down Expand Up @@ -214,7 +214,7 @@ export const downexStimulus = (layoutConfigMap: Record<string, LayoutConfigType>
PageAudioHandler.stopAndDisconnectNode();
audioEnabled = false;

const stimulus = taskStore().nextStimulus;
const stimulus = trial || taskStore().nextStimulus;
const itemLayoutConfig = layoutConfigMap?.[stimulus.itemId];
const { corpus } = taskStore();

Expand Down Expand Up @@ -291,7 +291,7 @@ export const downexStimulus = (layoutConfigMap: Record<string, LayoutConfigType>
}
},
response_ends_trial: () => {
const stim = taskStore().nextStimulus;
const stim = trial || taskStore().nextStimulus;

return stim.assessmentStage !== 'practice_response';
},
Expand Down
66 changes: 47 additions & 19 deletions task-launcher/src/tasks/memory-game/timeline.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { initTimeline, initTrialSaving, createPreloadTrials, PageAudioHandler } from '../shared/helpers';
import { initTimeline, initTrialSaving, createPreloadTrials, checkFallbackCriteria, PageAudioHandler } from '../shared/helpers';
// setup
import { jsPsych } from '../taskSetup';
import { initializeCat } from '../taskSetup';
// trials
import { enterFullscreen, exitFullscreen, feedback, finishExperiment, taskFinished } from '../shared/trials';
import { enterFullscreen, exitFullscreen, feedback, finishExperiment, repeatInstructionsMessage, taskFinished } from '../shared/trials';
import { getCorsiBlocks } from './trials/stimulus';
import { readyToPlay, reverseOrderPrompt, reverseOrderInstructions, defaultInstructions, downexInstructions } from './trials/instructions';
import { taskStore } from '../../taskStore';
Expand Down Expand Up @@ -69,7 +69,7 @@ export default function buildMemoryTimeline(config: Record<string, any>) {

const corsiBlocksStimulus = {
timeline: [forwardTrial()],
repetitions: 20,
repetitions: 16,
};

// last forward trial by itself in order to reset sequence length back to 2 for backward phase
Expand Down Expand Up @@ -101,11 +101,11 @@ export default function buildMemoryTimeline(config: Record<string, any>) {
},
}

const downexFeedbackIncorrect = (reverse: boolean, seqlength: number, prompt: string) => {
const downexFeedbackIncorrect = (reverse: boolean, prompt: string) => {
return {
timeline: [
getCorsiBlocks(
{ reverse, mode: 'input', isPractice: true, customSeqLength: seqlength, animation: 'pulse', prompt }
{ reverse, mode: 'input', isPractice: true, animation: 'pulse', prompt }
),
],
conditional_function: () => {
Expand All @@ -115,41 +115,68 @@ export default function buildMemoryTimeline(config: Record<string, any>) {
}

const downexPracticeTrial = (
reverse: boolean,
currentSeqlength: number,
setNextSeqLength: number,
reverse: boolean,
seqlength: number,
animation?: 'pulse' | 'cursor',
) => {
return {
timeline: [
getCorsiBlocks({ reverse, mode: 'display', isPractice: true, customSeqLength: currentSeqlength }),
getCorsiBlocks({ reverse, mode: 'input', isPractice: true, customSeqLength: setNextSeqLength, animation }),
getCorsiBlocks({ reverse, mode: 'display', isPractice: true, customSeqLength: seqlength }),
getCorsiBlocks({ reverse, mode: 'input', isPractice: true, animation }),
downexFeedbackCorrect,
downexFeedbackIncorrect(reverse, setNextSeqLength, reverse ? 'memoryGameInstruct11Downex' : 'memoryGameFeedbackIncorrectDownex'),
downexFeedbackIncorrect(reverse, reverse ? 'memoryGameInstruct11Downex' : 'memoryGameFeedbackIncorrectDownex'),
]
}
}

const downexInstructionsTimeline = {
timeline: [
downexInstructions[0],
downexPracticeTrial(false, 1, 1, 'cursor'),
downexPracticeTrial(false, 1, 2),
downexPracticeTrial(false, 1, 'cursor'),
downexPracticeTrial(false, 1),
downexInstructions[1],
downexPracticeTrial(false, 2, 2, 'cursor'),
downexPracticeTrial(false, 2, 2),
downexPracticeTrial(false, 2, 2),
downexPracticeTrial(false, 2, 'cursor'),
downexPracticeTrial(false, 2),
downexPracticeTrial(false, 2),
downexInstructions[2],
downexInstructions[3],
downexInstructions[4],
]
}

let fellBack = false;
const fallbackBlock = {
timeline: [
repeatInstructionsMessage,
...downexInstructionsTimeline.timeline.slice(1)
],
conditional_function: () => {
const run = checkFallbackCriteria(true) && !fellBack;
if (run) {
fellBack = true;
taskStore('heavyInstructions', true);
taskStore('gridSize', 2);
taskStore('numOfBlocks', 4);
taskStore('blockSize', 50);
}

return run;
},
}

const firstFourTestTrials = {
timeline: [
forwardTrial(),
fallbackBlock,
],
repetitions: 4,
}

const downexCorsiBlocksPracticeReverse = {
timeline: [
downexPracticeTrial(true, 2, 2, 'cursor'),
downexPracticeTrial(true, 2, 2),
downexPracticeTrial(true, 2, 2),
downexPracticeTrial(true, 2, 'cursor'),
downexPracticeTrial(true, 2),
downexPracticeTrial(true, 2),
]
}

Expand All @@ -174,6 +201,7 @@ export default function buildMemoryTimeline(config: Record<string, any>) {
preloadTrials,
initialTimeline,
heavyInstructions ? downexInstructionsTimeline : defaultInstructionsTimeline,
firstFourTestTrials, // check for fallback criteria during first 4 trials
corsiBlocksStimulus,
forwardTrialResetSeq,
reverseOrderInstructions,
Expand Down
50 changes: 21 additions & 29 deletions task-launcher/src/tasks/memory-game/trials/stimulus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,34 @@ export function getCorsiBlocks(
type: jsPsychCorsiBlocks,
sequence: () => {
// On very first trial, generate initial sequence
if (!generatedSequence) {
if (mode === 'display') {
const numOfBlocks: number = Number(taskStore().numOfBlocks);
generatedSequence = generateRandomSequence(
{ numOfBlocks, sequenceLength: customSeqLength || sequenceLength, previousSequence: null }
);
// Avoid generating the same sequence twice in a row
let newSequence = generateRandomSequence({
numOfBlocks,
sequenceLength: customSeqLength || sequenceLength,
previousSequence: generatedSequence,
});

while (_isEqual(newSequence, generatedSequence)) {
newSequence = generateRandomSequence({
numOfBlocks,
sequenceLength: customSeqLength || sequenceLength,
previousSequence: generatedSequence,
});
}

generatedSequence = newSequence;
}

if (mode === 'input' && reverse) {
if (generatedSequence && mode === 'input' && reverse) {
return [...generatedSequence].reverse(); // Create a copy before reversing
} else {
return generatedSequence;
}
},
blocks: () => {
if (!grid) {
if (mode === 'display') {
const { numOfBlocks, blockSize, gridSize } = taskStore();
grid = createGrid({ x, y, numOfBlocks, blockSize, gridSize, blockSpacing });
}
Expand Down Expand Up @@ -214,26 +227,6 @@ export function getCorsiBlocks(

const numOfBlocks = taskStore().numOfBlocks;

// resuse the same sequence for incorrect downward extension trials
if (data.correct || !isPractice || !heavyInstructions) {
// Avoid generating the same sequence twice in a row
let newSequence = generateRandomSequence({
numOfBlocks,
sequenceLength: customSeqLength || sequenceLength,
previousSequence: generatedSequence,
});

while (_isEqual(newSequence, generatedSequence)) {
newSequence = generateRandomSequence({
numOfBlocks,
sequenceLength: customSeqLength || sequenceLength,
previousSequence: generatedSequence,
});
}

generatedSequence = newSequence;
}

if (!isPractice) {
timeoutIDs.forEach((id) => clearTimeout(id));
timeoutIDs = [];
Expand Down Expand Up @@ -356,9 +349,8 @@ function doOnLoad(
if (inputSequence !== null) {
const nextBlockIndex = inputSequence[clickCount];

if (i === nextBlockIndex) {
(event.target as HTMLDivElement).style.backgroundColor = HIGHLIGHT_COLOR
}
const color = isPractice && i !== nextBlockIndex ? INCORRECT_COLOR : HIGHLIGHT_COLOR;
(event.target as HTMLDivElement).style.backgroundColor = color;

Array.from(blocks).forEach((element, j) => {
if (i !== j) {
Expand Down
Loading