Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
788dcd3
initial plugin skeleton
LoicGrobol Jun 9, 2025
6e679a5
This is now a light spr plugin
LoicGrobol Jun 10, 2025
fe77c2d
we have maze, captain
LoicGrobol Jun 10, 2025
119da04
fix alignment, add waiting time
LoicGrobol Jun 10, 2025
b23313f
bump readme
LoicGrobol Jun 10, 2025
7d72fa7
fix test
LoicGrobol Jun 11, 2025
c0c6c28
add question
LoicGrobol Jun 13, 2025
8be19bd
the tests! they run!
LoicGrobol Jun 13, 2025
5cb4ae2
make canvas style completely configurable
LoicGrobol Jun 13, 2025
9b7ded0
log question rt
LoicGrobol Jun 16, 2025
10ae309
add option to halt on error
LoicGrobol Jun 16, 2025
3128056
split instruction display and answer loggin logics
LoicGrobol Jun 16, 2025
a45de9b
fix first word, add spaghetti code
LoicGrobol Jun 16, 2025
99bec06
fix step_display
LoicGrobol Jun 16, 2025
aeb4adc
typing
LoicGrobol Jun 16, 2025
45d6115
consolidate the font style in a single parameter
LoicGrobol Jun 16, 2025
39f96ba
start documenting
LoicGrobol Jun 16, 2025
cdb8203
More doc
LoicGrobol Jun 17, 2025
28a1bd8
improve doc types
LoicGrobol Jun 17, 2025
8bb1592
typo
LoicGrobol Jun 17, 2025
6247000
typo
LoicGrobol Jun 18, 2025
baccad5
stop logging word_number
LoicGrobol Jun 18, 2025
dde0c18
fix types in doc
LoicGrobol Jun 18, 2025
56c6eaf
doc typing
LoicGrobol Jun 18, 2025
fc8381e
refactor (more typescript-y) to prepare moving from canvas to DOM
LoicGrobol Jun 18, 2025
ebc47e2
restore font align
LoicGrobol Jun 18, 2025
baa1aeb
fix rt for the first word
LoicGrobol Jun 18, 2025
14e3554
add test for waiting time
LoicGrobol Jun 18, 2025
d463ea1
add inter-word and before-question interval
LoicGrobol Jun 18, 2025
09a23ab
start working on dom port
LoicGrobol Jun 18, 2025
69e5424
pure dom display
LoicGrobol Jun 18, 2025
34e49ce
split off style and remove obsolete params
LoicGrobol Jun 18, 2025
168c4e3
cleanup
LoicGrobol Jun 18, 2025
5d3753c
move all style to example
LoicGrobol Jun 18, 2025
8a17207
move the questions to html
LoicGrobol Jun 18, 2025
a49d204
remove refs to end_interval
LoicGrobol Jun 18, 2025
a36af8d
Merge pull request #1 from LoicGrobol/maze-dom
LoicGrobol Jun 19, 2025
ea8ea3a
fix question eval in the example
LoicGrobol Jun 19, 2025
fe50cdf
fix question data
LoicGrobol Jun 19, 2025
e2aaeb1
use compareKeys to compare keys
LoicGrobol Jun 19, 2025
a44d89b
remove obsolete param
LoicGrobol Jun 19, 2025
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
1,243 changes: 1,036 additions & 207 deletions package-lock.json

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions packages/plugin-maze/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Maze

## Overview

A jsPsych plugin for running Maze experiments

## Loading

### In browser

```js
<script src="https://unpkg.com/@jspsych-contrib/[email protected]"></script>
```

### Via NPM

```
npm install @jspsych-contrib/plugin-maze
```

```js
import jsPsychMaze from '@jspsych-contrib/plugin-maze';
```

## Compatibility

jsPsych 8.0.0

## Documentation

See [documentation](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-maze/docs/jspsych-maze.md)

## Author / Citation

[Morgan Grobol](https://lgrobol.bzh)
91 changes: 91 additions & 0 deletions packages/plugin-maze/docs/maze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
Maze
====

A jsPsych plugin for running Maze (Forster et al., 2009) experiments, a version of self-paced
reading that asks to choose between the correct next word and a distractor.

## Parameters

In addition to the [parameters available in all
plugins](https://www.jspsych.org/latest/overview/plugins/#parameters-available-in-all-plugins), this
plugin accepts the following parameters. Parameters with a default value of undefined must be
specified. Other parameters can be left unspecified if the default value is acceptable.

| Parameter | Type | Default Value | Description |
|-----------------------|---------------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| `sentence` | `Array<[string, string]>` | `undefined` | The sentence to use in the Maze, as `[word, foil]` pairs. |
| `canvas_size` | `[string, string]` | `["1280px", "960px"]` | The dimensions of the canvas. |
| `halt_on_error` | `boolean` | `false` | If true, any error ends the trial and sends the subject directly to the question (if any), then exit. |
| `inter_word_interval` | `number` | `0` | How long to wait on a blank screen before displaying the next word. |
| `keys` | `{left: string, right: string}` | `{left: "f", right: "j"}` | The choice/navigation keys. |
| `position_left` | `{x: number?, y: number?}` | `{x: null, y: null}` | The position of the left word. A null `x` is set to 1/3 of the canvas' width and null `y` is set to half of the canvas' height. |
| `position_right` | `{x: number?, y: number?}` | `{x: null, y: null}` | The position of the right word. A null `x` is set to 2/3 of the canvas' width and null `y` is set to half of the canvas' height. |
| `pre_answer_interval` | `number` | `0` | The minimum time (in ms) before the subject is allowed to chose a word. | |

## Data Generated

In addition to the [default data collected by all
plugins](https://www.jspsych.org/latest/overview/plugins/#data-collected-by-all-plugins), this
plugin collects the following data for each trial.

| Name | Type | Value |
|------------|---------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
| `sentence` | `string` | The sentence used in the trial (joined with spaces) |
| `events` | `Array<{correct: boolean, foil: string, rt: number, side: "left" | "right", word: string}>` | The parameters, choice and interaction time for each word of the sentence |

## Install

Using the CDN-hosted JavaScript file:

```js
<script src="https://unpkg.com/@jspsych-contrib/plugin-maze"></script>
```

Using the JavaScript file downloaded from a GitHub release dist archive:

```js
<script src="jspsych/plugin-maze.js"></script>
```

Using NPM:

```bash
npm install @jspsych-contrib/plugin-maze
```

```js
import Maze from '@jspsych-contrib/plugin-maze';
```

## Examples

```javascript

const trial = {
type: jsPsychMaze,
sentence: [
["After", "x-x-x"],
["a", "so"],
["bit", "pot"],
["of", "if"],
["success", "singing"],
["the", "ate"],
["stocks", "winter"],
["took", "walk"],
["a", "we"],
["dive", "toad"],
],
question: {
text: "Did the stocks take a dive?",
correct: "yes",
wrong: "no",
},
waiting_time: 200,
}
```

## Bibliography

- Forster, Kenneth I., Christine Guerrera, and Lisa Elliot. 2009. ‘The Maze Task: Measuring Forced
Incremental Sentence Processing Time’. Behavior Research Methods 41 (1): 163–71.
<https://doi.org/10.3758/BRM.41.1.163>.
64 changes: 64 additions & 0 deletions packages/plugin-maze/examples/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/jspsych"></script>
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<!-- The plugin is loaded here -->
<script src="https://unpkg.com/@jspsych/plugin-maze"></script>
<script src="../dist/index.browser.js"></script>

<!-- Ugly trick to keep the data separate while not having to think of how to fetch it -->
<script src="stimuli.js"></script>

<link rel="stylesheet" href="https://unpkg.com/jspsych/css/jspsych.css" />
<style>
.jspsych-display-element {
font: normal 24px monospace;
}
</style>
</head>

<body></body>
<script>
const jsPsych = initJsPsych({
on_finish: () => {
jsPsych.data.displayData("json");
},
});

const procedure = {
timeline: [
{
type: jsPsychMaze,
sentence: jsPsych.timelineVariable("sentence"),
inter_word_interval: 100,
pre_answer_interval: 200,
halt_on_error: true,
},
{
type: jsPsychHtmlKeyboardResponse,
stimulus: () => jsPsych.evaluateTimelineVariable("question").text,
choices: ["f", "j"],
prompt: () => `
<span style="margin: 5em 8em 0 8em; display: inline-block;">${
jsPsych.evaluateTimelineVariable("question").left
}</span>
<span style="margin: 5em 8em 0 8em; display: inline-block;">${
jsPsych.evaluateTimelineVariable("question").right
}</span>`,
on_finish: (data) => {
success: jsPsych.pluginAPI.compareKeys(
data.response,
"left" === jsPsych.evaluateTimelineVariable("question").correct
? "f"
: "j"
)
}
},
],
timeline_variables: data,
};

jsPsych.run(procedure);
</script>
</html>
41 changes: 41 additions & 0 deletions packages/plugin-maze/examples/stimuli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const data = [
{
sentence: [
["After", "x-x-x"],
["a", "so"],
["bit", "pot"],
["of", "if"],
["success", "singing"],
["the", "ate"],
["stocks", "winter"],
["took", "walk"],
["a", "we"],
["dive", "toad"],
],
question: {
text: "Did the stocks take a dive?",
left: "yes",
right: "no",
correct: "left",
},
},
{
sentence: [
["The", "x-x-x"],
["fashion", "realize"],
["model", "shirt"],
["was", "see"],
["very", "into"],
["self-conscious", "professionally"],
["of", "do"],
["her", "why"],
["appearance", "sandwiches"],
],
question: {
text: "Who was self-conscious?",
left: "The designer",
right: "The model",
correct: "right",
},
},
];
1 change: 1 addition & 0 deletions packages/plugin-maze/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname);
50 changes: 50 additions & 0 deletions packages/plugin-maze/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@jspsych-contrib/plugin-maze",
"version": "0.0.1",
"description": "A jsPsych plugin for running Maze experiments",
"type": "module",
"main": "dist/index.cjs",
"exports": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"typings": "dist/index.d.ts",
"unpkg": "dist/index.browser.min.js",
"files": [
"src",
"dist"
],
"source": "src/index.ts",
"scripts": {
"test": "jest",
"test:watch": "npm test -- --watch",
"tsc": "tsc",
"build": "rollup --config",
"build:watch": "npm run build -- --watch",
"dev-serve": "concurrently \"npm run build -- --watch\" \"serve\""
},
"repository": {
"type": "git",
"url": "git+https://github.com/jspsych/jspsych-contrib.git",
"directory": "packages/plugin-maze"
},
"author": {
"name": "Morgan Grobol",
"url": "https://lgrobol.bzh"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/jspsych/jspsych-contrib/issues"
},
"homepage": "https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-maze",
"peerDependencies": {
"jspsych": ">=8.0.0"
},
"devDependencies": {
"@jspsych/config": "^3.2.2",
"@jspsych/test-utils": "^1.0.0",
"concurrently": "^9.1.2",
"jspsych": "^8.0.0",
"serve": "^14.2.4"
}
}
3 changes: 3 additions & 0 deletions packages/plugin-maze/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { makeRollupConfig } from "@jspsych/config/rollup";

export default makeRollupConfig("jsPsychMaze");
59 changes: 59 additions & 0 deletions packages/plugin-maze/src/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { pressKey, startTimeline } from "@jspsych/test-utils";

import jsPsychMaze from ".";

jest.useFakeTimers();

describe("The maze jspsych plugin", () => {
it("Runs maze", async () => {
const sentence = [
["The", "x-x-x"],
["fashion", "realize"],
["model", "shirt"],
["was", "see"],
["very", "into"],
["self-conscious", "professionally"],
["of", "do"],
["her", "why"],
["appearance", "sandwiches"],
];
const { expectFinished, getHTML, getData, displayElement, jsPsych } = await startTimeline([
{
type: jsPsychMaze,
sentence: sentence,
inter_word_interval: 2,
end_interval: 50,
pre_answer_interval: 20,
},
]);

// Start
jest.advanceTimersByTime(100);
pressKey("f");

// This one is too fast and should be ignored
jest.advanceTimersByTime(10);
pressKey("f");

for (const [_word, _foil] of sentence) {
jest.advanceTimersByTime(100);
pressKey("f");
}

await expectFinished();

// Unclear to me why the values are wrapped in length 1 array
for (const [i, [word, foil], r] of getData()
.select("events")
.values[0].map((r: Response, i: number) => [i, sentence[i], r])) {
expect(r.word).toBe(word);
expect(r.foil).toBe(foil);

// FIXME: make this better, probably with different rts.

// We first waited 10s before the first word and for the other words, the inter-word interval
// is substracted from the elapsed time
expect(r.rt).toBe(0 === i ? 110 : 98);
}
});
});
Loading