/***************** * Numgroup * *****************/ import { core, data, sound, util, visual, hardware } from './lib/psychojs-2024.1.0.js'; const { PsychoJS } = core; const { TrialHandler, MultiStairHandler } = data; const { Scheduler } = util; //some handy aliases as in the psychopy scripts; const { abs, sin, cos, PI: pi, sqrt } = Math; const { round } = util; // store info about the experiment session: let expName = 'numgroup'; // from the Builder filename that created this script let expInfo = { 'participant': `${util.pad(Number.parseFloat(util.randint(0, 999999)).toFixed(0), 6)}`, 'session': '001', }; // Start code blocks for 'Before Experiment' // init psychoJS: const psychoJS = new PsychoJS({ debug: true }); // open window: psychoJS.openWindow({ fullscr: true, color: new util.Color([0,0,0]), units: 'height', waitBlanking: true, backgroundImage: '', backgroundFit: 'none', }); // schedule the experiment: psychoJS.schedule(psychoJS.gui.DlgFromDict({ dictionary: expInfo, title: expName })); const flowScheduler = new Scheduler(psychoJS); const dialogCancelScheduler = new Scheduler(psychoJS); psychoJS.scheduleCondition(function() { return (psychoJS.gui.dialogComponent.button === 'OK'); }, flowScheduler, dialogCancelScheduler); // flowScheduler gets run if the participants presses OK flowScheduler.add(updateInfo); // add timeStamp flowScheduler.add(experimentInit); flowScheduler.add(counterbalance_testRoutineBegin()); flowScheduler.add(counterbalance_testRoutineEachFrame()); flowScheduler.add(counterbalance_testRoutineEnd()); flowScheduler.add(trialRoutineBegin()); flowScheduler.add(trialRoutineEachFrame()); flowScheduler.add(trialRoutineEnd()); flowScheduler.add(quitPsychoJS, '', true); // quit if user presses Cancel in dialog box: dialogCancelScheduler.add(quitPsychoJS, '', false); psychoJS.start({ expName: expName, expInfo: expInfo, resources: [ // resources: ] }); psychoJS.experimentLogger.setLevel(core.Logger.ServerLevel.WARNING); var currentLoop; var frameDur; async function updateInfo() { currentLoop = psychoJS.experiment; // right now there are no loops expInfo['date'] = util.MonotonicClock.getDateStr(); // add a simple timestamp expInfo['expName'] = expName; expInfo['psychopyVersion'] = '2024.1.0rc11'; expInfo['OS'] = window.navigator.platform; // store frame rate of monitor if we can measure it successfully expInfo['frameRate'] = psychoJS.window.getActualFrameRate(); if (typeof expInfo['frameRate'] !== 'undefined') frameDur = 1.0 / Math.round(expInfo['frameRate']); else frameDur = 1.0 / 60.0; // couldn't get a reliable measure so guess // add info from the URL: util.addInfoFromUrl(expInfo); psychoJS.experiment.dataFileName = (("." + "/") + `data/${expInfo["participant"]}_${expName}_${expInfo["date"]}`); psychoJS.experiment.field_separator = '\t'; return Scheduler.Event.NEXT; } var trialClock; var text; var mouse; var globalClock; var routineTimer; async function experimentInit() { // Initialize components for Routine "trial" trialClock = new util.Clock(); text = new visual.TextStim({ win: psychoJS.window, name: 'text', text: '', font: 'Open Sans', units: undefined, pos: [0, 0], height: 0.05, wrapWidth: undefined, ori: 0.0, languageStyle: 'LTR', color: new util.Color('white'), opacity: undefined, depth: 0.0 }); mouse = new core.Mouse({ win: psychoJS.window, }); mouse.mouseClock = new util.Clock(); // Create some handy timers globalClock = new util.Clock(); // to track the time since experiment started routineTimer = new util.CountdownTimer(); // to track time remaining of each (non-slip) routine return Scheduler.Event.NEXT; } var counterbalance_test; function counterbalance_testRoutineBegin(snapshot) { return async function () { // create uniform conditions for counterbalance_test let counterbalance_testConditions = []; for (let n = 0; n < 3; n++) { counterbalance_testConditions.push({ 'group': n, 'probability': 1/3, 'cap': 10 }); } // get counterbalancing group counterbalance_test = await psychoJS.shelf.counterbalanceSelect({ key: ['counterbalance_test', '@designer', '@experiment'], groups: counterbalance_testConditions.map(row => row.group), groupSizes: counterbalance_testConditions.map(row => row.cap), }); // if slots and repeats are fully depleted, end the experiment now if (counterbalance_test.finished) { quitPsychoJS('No more slots remaining for this study.', true) } psychoJS.experiment.addData('counterbalance_test.group', counterbalance_test.group) for (let _key in counterbalance_test.params) { psychoJS.experiment.addData(`counterbalance_test.${_key}`, counterbalance_test.params[_key]) } psychoJS.experiment.addData('counterbalance_test.remaining', counterbalance_test.remaining) return Scheduler.Event.NEXT; } } function counterbalance_testRoutineEachFrame(snapshot) { return async function () { return Scheduler.Event.NEXT; } } function counterbalance_testRoutineEnd(snapshot) { return async function () { return Scheduler.Event.NEXT; } } var t; var frameN; var continueRoutine; var gotValidClick; var trialComponents; function trialRoutineBegin(snapshot) { return async function () { TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date //--- Prepare to start Routine 'trial' --- t = 0; trialClock.reset(); // clock frameN = -1; continueRoutine = true; // until we're told otherwise // update component parameters for each repeat psychoJS.experiment.addData('trial.started', globalClock.getTime()); text.setText((("You are in group " + counterbalance_test.group.toString()) + "\n\n Click anywhere to end")); // setup some python lists for storing info about the mouse // current position of the mouse: mouse.x = []; mouse.y = []; mouse.leftButton = []; mouse.midButton = []; mouse.rightButton = []; mouse.time = []; gotValidClick = false; // until a click is received // keep track of which components have finished trialComponents = []; trialComponents.push(text); trialComponents.push(mouse); for (const thisComponent of trialComponents) if ('status' in thisComponent) thisComponent.status = PsychoJS.Status.NOT_STARTED; return Scheduler.Event.NEXT; } } var prevButtonState; var _mouseButtons; var _mouseXYs; function trialRoutineEachFrame() { return async function () { //--- Loop for each frame of Routine 'trial' --- // get current time t = trialClock.getTime(); frameN = frameN + 1;// number of completed frames (so 0 is the first frame) // update/draw components on each frame // *text* updates if (t >= 0.0 && text.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later text.tStart = t; // (not accounting for frame time here) text.frameNStart = frameN; // exact frame index text.setAutoDraw(true); } // *mouse* updates if (t >= 0.0 && mouse.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later mouse.tStart = t; // (not accounting for frame time here) mouse.frameNStart = frameN; // exact frame index mouse.status = PsychoJS.Status.STARTED; mouse.mouseClock.reset(); prevButtonState = mouse.getPressed(); // if button is down already this ISN'T a new click } if (mouse.status === PsychoJS.Status.STARTED) { // only update if started and not finished! _mouseButtons = mouse.getPressed(); if (!_mouseButtons.every( (e,i,) => (e == prevButtonState[i]) )) { // button state changed? prevButtonState = _mouseButtons; if (_mouseButtons.reduce( (e, acc) => (e+acc) ) > 0) { // state changed to a new click _mouseXYs = mouse.getPos(); mouse.x.push(_mouseXYs[0]); mouse.y.push(_mouseXYs[1]); mouse.leftButton.push(_mouseButtons[0]); mouse.midButton.push(_mouseButtons[1]); mouse.rightButton.push(_mouseButtons[2]); mouse.time.push(mouse.mouseClock.getTime()); // end routine on response continueRoutine = false; } } } // check for quit (typically the Esc key) if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) { return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false); } // check if the Routine should terminate if (!continueRoutine) { // a component has requested a forced-end of Routine return Scheduler.Event.NEXT; } continueRoutine = false; // reverts to True if at least one component still running for (const thisComponent of trialComponents) if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) { continueRoutine = true; break; } // refresh the screen if continuing if (continueRoutine) { return Scheduler.Event.FLIP_REPEAT; } else { return Scheduler.Event.NEXT; } }; } function trialRoutineEnd(snapshot) { return async function () { //--- Ending Routine 'trial' --- for (const thisComponent of trialComponents) { if (typeof thisComponent.setAutoDraw === 'function') { thisComponent.setAutoDraw(false); } } psychoJS.experiment.addData('trial.stopped', globalClock.getTime()); // store data for psychoJS.experiment (ExperimentHandler) if (mouse.x) { psychoJS.experiment.addData('mouse.x', mouse.x[0])}; if (mouse.y) { psychoJS.experiment.addData('mouse.y', mouse.y[0])}; if (mouse.leftButton) { psychoJS.experiment.addData('mouse.leftButton', mouse.leftButton[0])}; if (mouse.midButton) { psychoJS.experiment.addData('mouse.midButton', mouse.midButton[0])}; if (mouse.rightButton) { psychoJS.experiment.addData('mouse.rightButton', mouse.rightButton[0])}; if (mouse.time) { psychoJS.experiment.addData('mouse.time', mouse.time[0])}; // the Routine "trial" was not non-slip safe, so reset the non-slip timer routineTimer.reset(); // Routines running outside a loop should always advance the datafile row if (currentLoop === psychoJS.experiment) { psychoJS.experiment.nextEntry(snapshot); } return Scheduler.Event.NEXT; } } function importConditions(currentLoop) { return async function () { psychoJS.importAttributes(currentLoop.getCurrentTrial()); return Scheduler.Event.NEXT; }; } async function quitPsychoJS(message, isCompleted) { // Check for and save orphaned data if (psychoJS.experiment.isEntryEmpty()) { psychoJS.experiment.nextEntry(); } await psychoJS.shelf.counterbalanceConfirm( ['counterbalance_test', '@designer', '@experiment'], counterbalance_test.participantToken, isCompleted ); psychoJS.window.close(); psychoJS.quit({message: message, isCompleted: isCompleted}); return Scheduler.Event.QUIT; }