diff --git a/src/index.ts b/src/index.ts index 2949be5..7e2883a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,7 @@ export default defineInstrument({ } translator.init(); - translator.changeLanguage(settingsParseResult.data.language as Language); + translator.changeLanguage(settingsParseResult.data.language); await pictureNamingTask(done); }, }, diff --git a/src/main.ts b/src/main.ts index 61de61f..1ee5e91 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,3 @@ -import type { Language } from "@opendatacapture/runtime-v1/@opendatacapture/runtime-core/index.js"; import { experimentSettingsJson } from "./experimentSettings.ts"; import { pictureNamingTask } from "./pictureNamingTask"; @@ -17,6 +16,6 @@ if (!settingsParseResult.success) { } translator.init(); -translator.changeLanguage(settingsParseResult.data.language as Language); +translator.changeLanguage(settingsParseResult.data.language); await pictureNamingTask(); diff --git a/src/pictureNamingTask.ts b/src/pictureNamingTask.ts index 74236e6..523e384 100644 --- a/src/pictureNamingTask.ts +++ b/src/pictureNamingTask.ts @@ -100,6 +100,9 @@ experimentStimuli function getRandomElementWithSeed( array: ExperimentImage[], ): ExperimentImage[] { + if (array.length === 0) { + throw new Error("no stimuli available for the requested difficulty/language"); + } let randomIndex: number; let foundUnique = false; @@ -153,6 +156,19 @@ experimentStimuli jsPsych.pluginAPI.keyUp(key); } + // returns on_start/on_finish hooks that share one listener reference so it can actually be removed + function clickAsKeyPress(jsPsych: JsPsych) { + const handleClick = () => simulateKeyPress(jsPsych, "a"); + return { + on_start: function () { + document.addEventListener("click", handleClick, { once: true }); + }, + on_finish: function () { + document.removeEventListener("click", handleClick); + }, + }; + } + //**************************** //********EXPERIMENT********** //**************************** @@ -186,14 +202,7 @@ experimentStimuli }); const welcome = { - on_start: function () { - const handleClick = () => simulateKeyPress(jsPsych, "a"); - document.addEventListener("click", handleClick, { once: true }); - }, - on_finish: function () { - const handleClick = () => simulateKeyPress(jsPsych, "a"); - document.removeEventListener("click", handleClick); - }, + ...clickAsKeyPress(jsPsych), stimulus: translator.t("welcome"), type: HtmlKeyboardResponsePlugin, }; @@ -261,14 +270,7 @@ experimentStimuli type: HtmlKeyboardResponsePlugin, }; const showImg = { - on_start: function () { - const handleClick = () => simulateKeyPress(jsPsych, "a"); - document.addEventListener("click", handleClick, { once: true }); - }, - on_finish: function () { - const handleClick = () => simulateKeyPress(jsPsych, "a"); - document.removeEventListener("click", handleClick); - }, + ...clickAsKeyPress(jsPsych), stimulus: jsPsych.timelineVariable("stimulus"), stimulus_height: 600, type: ImageKeyboardResponsePlugin, @@ -298,18 +300,8 @@ experimentStimuli const html = `

${translator.t("logResponse")}

- - + +
@@ -330,6 +322,7 @@ experimentStimuli }, }; jsPsych.finishTrial(trialData); + return; } const submitButton = document.getElementById( "jspsych-survey-html-form-next", @@ -337,14 +330,22 @@ experimentStimuli const resultOutput = document.getElementById( "result", ) as HTMLOutputElement; + const resultAsNumberInput = document.getElementById( + "resultAsNumber", + ) as HTMLInputElement; submitButton.disabled = true; - document.querySelectorAll('input[type="button"]').forEach((button) => { - button.addEventListener("click", () => { - if (resultOutput.value) { + document + .querySelectorAll( + "input[type='button'][data-result-as-number]", + ) + .forEach((button) => { + button.addEventListener("click", () => { + resultOutput.value = button.value; + resultOutput.style.color = button.dataset.color ?? ""; + resultAsNumberInput.value = button.dataset.resultAsNumber ?? ""; submitButton.disabled = false; - } + }); }); - }); }, preamble: function () { const html = `

${translator.t("correctResponse")}

@@ -410,16 +411,16 @@ experimentStimuli const lastTrialResults: ParticipantResponse = loggingResponseArray[lastTrialIndex]!.response; - if (lastTrialResults.result === "Correct") { + if (lastTrialResults.resultAsNumber === "1") { numberOfCorrectAnswers++; clearSet = false; - } else if (lastTrialResults.result === "Incorrect") { + } else if (lastTrialResults.resultAsNumber === "0") { numberOfCorrectAnswers = 0; } // difficulty level logic if (numberOfCorrectAnswers === advancementSchedule) { - if (numberOfCorrectAnswers <= numberOfLevels) { + if (currentDifficultyLevel < numberOfLevels) { currentDifficultyLevel++; numberOfCorrectAnswers = 0; clearSet = true; diff --git a/src/schemas.ts b/src/schemas.ts index 40550fb..e552302 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -44,22 +44,31 @@ export const $ExperimentResultsUnion = z.union([ $ExperimentResults, $ParticipantIDResult, ]); -export const $Settings = z.object({ - advancementSchedule: z.coerce.number().positive().int(), - downloadOnFinish: z.coerce.boolean(), - initialDifficulty: z.coerce.number().positive().int(), - language: z.string(), - numberOfLevels: z.coerce.number().positive().int(), - regressionSchedule: z.coerce.number().int(), - seed: z.preprocess( - (val) => - val === "" || val === null || val === 0 || val === undefined - ? undefined - : val, - z.coerce.number().positive().int().optional(), - ), - totalNumberOfTrialsToRun: z.coerce.number().positive().int(), -}); +export const $Settings = z + .object({ + advancementSchedule: z.coerce.number().positive().int(), + // z.coerce.boolean() would turn the string "false" into true + downloadOnFinish: z.preprocess( + (val) => (val === "false" ? false : val === "true" ? true : val), + z.boolean(), + ), + initialDifficulty: z.coerce.number().positive().int(), + language: $Language, + numberOfLevels: z.coerce.number().positive().int(), + regressionSchedule: z.coerce.number().nonnegative().int(), + seed: z.preprocess( + (val) => + val === "" || val === null || val === 0 || val === undefined + ? undefined + : val, + z.coerce.number().positive().int().optional(), + ), + totalNumberOfTrialsToRun: z.coerce.number().positive().int(), + }) + .refine((settings) => settings.initialDifficulty <= settings.numberOfLevels, { + message: "initialDifficulty must not exceed numberOfLevels", + path: ["initialDifficulty"], + }); export const $ExperimentImage = z.object({ correctResponse: z.string(),