{"_id":"@bjoernboss/mws-quiz-game","_rev":"3-21d646b4647a56a271eabfd8b7137920","name":"@bjoernboss/mws-quiz-game","dist-tags":{"latest":"1.2.0"},"versions":{"1.0.0":{"name":"@bjoernboss/mws-quiz-game","version":"1.0.0","author":{"name":"Bjoern Boss Henrichsen","email":"bjoernbossdev@gmail.com"},"license":"BSD-3-Clause","_id":"@bjoernboss/mws-quiz-game@1.0.0","maintainers":[{"name":"bjoernboss","email":"bjoernbossdev@gmail.com"}],"homepage":"https://github.com/BjoernBoss/mws-quiz-game#readme","bugs":{"url":"https://github.com/BjoernBoss/mws-quiz-game/issues"},"dist":{"shasum":"2ec889e9edcb31f04600641674398a75f421426e","tarball":"https://registry.npmjs.org/@bjoernboss/mws-quiz-game/-/mws-quiz-game-1.0.0.tgz","fileCount":19,"integrity":"sha512-sjhU0TX8eM6u/ktm9h/1FHhaEfU7qMtV7t8aUXpITUuBGCIIb+R7LZoerqoxv59+BPkKMNktTDUyIPiHi0LaIA==","signatures":[{"sig":"MEYCIQDNORyym/u9FNQ4DS7nfIWG8PXuWi9lSZprPoTkY/3qKQIhAMzLktaeVCHa5i6b6eiKbfGeSBrtfOdnv5BMWj0rivfo","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":159261},"main":"dist/quiz-game.js","type":"module","types":"dist/quiz-game.d.ts","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./dist/quiz-game.d.ts","default":"./dist/quiz-game.js"},"./package.json":"./package.json"},"gitHead":"cace171ee01b589cb6b279d8e0cb41d6d6c2e55b","scripts":{"prepare":"tsc"},"_npmUser":{"name":"bjoernboss","email":"bjoernbossdev@gmail.com"},"repository":{"url":"git+https://github.com/BjoernBoss/mws-quiz-game.git","type":"git"},"_npmVersion":"11.13.0","description":"Module in TypeScript to create and play a quiz-game against each other - For Modular Web Server","directories":{},"_nodeVersion":"26.2.0","dependencies":{"@bjoernboss/mws":"^1.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^6.0.3","@types/node":"^25.6.0"},"_npmOperationalInternal":{"tmp":"tmp/mws-quiz-game_1.0.0_1781750009121_0.9397461961610676","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@bjoernboss/mws-quiz-game","version":"1.1.0","author":{"name":"Bjoern Boss Henrichsen","email":"bjoernbossdev@gmail.com"},"license":"BSD-3-Clause","_id":"@bjoernboss/mws-quiz-game@1.1.0","maintainers":[{"name":"bjoernboss","email":"bjoernbossdev@gmail.com"}],"homepage":"https://github.com/BjoernBoss/mws-quiz-game#readme","bugs":{"url":"https://github.com/BjoernBoss/mws-quiz-game/issues"},"dist":{"shasum":"7011a1444df6569763a5ffc23b26fcb60f335e11","tarball":"https://registry.npmjs.org/@bjoernboss/mws-quiz-game/-/mws-quiz-game-1.1.0.tgz","fileCount":19,"integrity":"sha512-LNahKRuuEAiA+uehek7BOuxqDvRfHXvppTsgn1bdYfUJfkwNNMjACrPW3LqFj3p0HSWJxcVTNwSOCb5n1kwSNg==","signatures":[{"sig":"MEUCIGuxs4l50v6EPzhZjJxGkq22IQidDwfry0YOr7czi8opAiEAtjdaKghL+oRIauFRQysm+LxYOORHwXZWoz2gk28u+wM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":160265},"main":"dist/quiz-game.js","type":"module","types":"dist/quiz-game.d.ts","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./dist/quiz-game.d.ts","default":"./dist/quiz-game.js"},"./package.json":"./package.json"},"gitHead":"fcecf939324cd0653176c956555932c0ff6eb6ca","scripts":{"prepare":"tsc"},"_npmUser":{"name":"bjoernboss","email":"bjoernbossdev@gmail.com"},"repository":{"url":"git+https://github.com/BjoernBoss/mws-quiz-game.git","type":"git"},"_npmVersion":"11.13.0","description":"Module in TypeScript to create and play a quiz-game against each other - For Modular Web Server","directories":{},"_nodeVersion":"26.2.0","dependencies":{"@bjoernboss/mws":"^1.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^6.0.3","@types/node":"^25.6.0"},"_npmOperationalInternal":{"tmp":"tmp/mws-quiz-game_1.1.0_1781831325614_0.9826867816330764","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@bjoernboss/mws-quiz-game","version":"1.2.0","type":"module","description":"Module in TypeScript to create and play a quiz-game against each other - For Modular Web Server","license":"BSD-3-Clause","author":{"name":"Bjoern Boss Henrichsen","email":"bjoernbossdev@gmail.com"},"repository":{"type":"git","url":"git+https://github.com/BjoernBoss/mws-quiz-game.git"},"publishConfig":{"access":"public"},"main":"dist/quiz-game.js","types":"dist/quiz-game.d.ts","exports":{".":{"default":"./dist/quiz-game.js","types":"./dist/quiz-game.d.ts"},"./package.json":"./package.json"},"scripts":{"prepare":"tsc"},"dependencies":{"@bjoernboss/mws":"^1.4.0"},"devDependencies":{"@types/node":"^25.6.0","typescript":"^6.0.3"},"engines":{"node":">=22.0.0"},"gitHead":"b556683831caddcae3d7aedb125e4dd046793282","_id":"@bjoernboss/mws-quiz-game@1.2.0","bugs":{"url":"https://github.com/BjoernBoss/mws-quiz-game/issues"},"homepage":"https://github.com/BjoernBoss/mws-quiz-game#readme","_nodeVersion":"26.5.1","_npmVersion":"11.13.0","dist":{"integrity":"sha512-EKr5CcplDszMytFeNTfC0owFF7mnYMqN0XqGc4KOsEbvcuRqolnGkYOGtFcTi/az1sh5FK5Kd+W/7KKxjrhPHQ==","shasum":"db0b28ec9d81b7025f9c52b2d319b915265ac307","tarball":"https://registry.npmjs.org/@bjoernboss/mws-quiz-game/-/mws-quiz-game-1.2.0.tgz","fileCount":19,"unpackedSize":162916,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDb2RaxbhZmd2cguZ1aaSTNy/7G5IhDm8nQ0N/zKS8VZQIhALq1LMN2BWjgMzzovOhMIowKc0thAJgJk/8jZ3cWfbKC"}]},"_npmUser":{"name":"bjoernboss","email":"bjoernbossdev@gmail.com"},"directories":{},"maintainers":[{"name":"bjoernboss","email":"bjoernbossdev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mws-quiz-game_1.2.0_1785930669827_0.5858257085416203"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-18T02:33:29.005Z","modified":"2026-08-05T11:51:10.090Z","1.0.0":"2026-06-18T02:33:29.298Z","1.1.0":"2026-06-19T01:08:45.764Z","1.2.0":"2026-08-05T11:51:09.946Z"},"bugs":{"url":"https://github.com/BjoernBoss/mws-quiz-game/issues"},"author":{"name":"Bjoern Boss Henrichsen","email":"bjoernbossdev@gmail.com"},"license":"BSD-3-Clause","homepage":"https://github.com/BjoernBoss/mws-quiz-game#readme","repository":{"type":"git","url":"git+https://github.com/BjoernBoss/mws-quiz-game.git"},"description":"Module in TypeScript to create and play a quiz-game against each other - For Modular Web Server","maintainers":[{"name":"bjoernboss","email":"bjoernbossdev@gmail.com"}],"readme":"# \\[MWS\\] Module to Create and Play a Quiz Game Together\r\n![TypeScript](https://img.shields.io/badge/language-TypeScript-blue?style=flat-square)\r\n[![License](https://img.shields.io/badge/license-BSD--3--Clause-brightgreen?style=flat-square)](LICENSE.txt)\r\n\r\nA multiplayer quiz-game module for [`@bjoernboss/mws`](https://github.com/BjoernBoss/mws).\r\n\r\nPlayers create a session, join it by name, and compete through rounds of multiple-choice trivia. Each round consists of a category phase where players set their confidence and activate effects, followed by an answer phase where the question is revealed. Between rounds, players can use special effects to interfere with each other's scores, adding a strategic layer on top of the trivia.\r\n\r\nGame sessions live entirely in memory and are automatically cleaned up after inactivity. All active sessions are managed by the `QuizGame` module.\r\n\r\n## Installation\r\n\r\n\t$ npm install @bjoernboss/mws-quiz-game\r\n\r\nRequires Node.js 22 or later.\r\n\r\n## Setup\r\n\r\nThe `QuizGame` module takes an optional configuration object with a questions source and a `Params` object. Mount it under a path using `dispatch`:\r\n\r\n```typescript\r\nimport { Server, dispatch, addLogger, createConsoleLogger } from \"@bjoernboss/mws\";\r\nimport { QuizGame } from \"@bjoernboss/mws-quiz-game\";\r\n\r\naddLogger(createConsoleLogger());\r\n\r\nconst server = new Server();\r\nconst quiz = new QuizGame({\r\n    questions: './data/questions.json',\r\n    params: { create: true }\r\n});\r\n\r\nserver.listen(dispatch({ '/quiz': quiz }), { port: 8080 });\r\n```\r\n\r\nNavigate to `http://localhost:8080/quiz/` to create a new session.\r\n\r\n## Questions\r\n\r\nQuestions can be provided as a file path to a JSON file or as an array of `Question` objects passed directly to the constructor. If omitted, the module loads a built-in set of 241 trivia questions sourced from [Open Trivia Database](https://opentdb.com/).\r\n\r\nEach question requires a text, category, one correct answer, and at least one incorrect answer:\r\n\r\n```json\r\n[\r\n    {\r\n        \"text\": \"What is the largest planet in the Solar System?\",\r\n        \"category\": \"Science & Nature\",\r\n        \"correct\": \"Jupiter\",\r\n        \"incorrect\": [\"Saturn\", \"Earth\", \"Mars\"]\r\n    }\r\n]\r\n```\r\n\r\nAnswer options are shuffled on the client side each round.\r\n\r\n## Frontend\r\n\r\nThe module ships with a complete browser frontend across its three pages.\r\n\r\n### Lobby\r\n\r\n- One-click session creation, resulting in shareable links to the player client and the spectator scoreboard, each with a copy-to-clipboard button.\r\n- Displays the inactivity timeout after which the session will be deleted, and that sessions can be rejoined by name.\r\n\r\n### Client\r\n\r\n- Players log in by name - pre-filled from the cookie - and keep a persistent identity through a client-generated UUID cookie (or the name itself with `idByName`), so a session can be rejoined after closing the page.\r\n- A persistent header shows name, score, round, and confidence; in the resolved phase, the effective payout and the point delta are added.\r\n- The question text is masked as `???` during the category phase, unless the player has activated the Expose effect (see Effects below).\r\n- Confidence is set through a color-coded slider (-1 to 3); effects are activated through buttons showing their description and a live cooldown status, and opponent-targeting effects open a selection screen listing all other players sorted by score.\r\n- Answer options are shuffled individually per player; in the resolved phase the correct option is highlighted green and wrong choices red.\r\n- The ready button shows the live ready count and requires at least two players; once ready, the interface locks until the phase advances.\r\n- An in-game scoreboard view can be toggled at any time and includes a confirmed \"Remove Me!\" option to leave the game.\r\n- Player updates are stamped: after a reconnect the client re-fetches the state and re-uploads any local state that is newer than the server's, so brief connection losses do not lose input. Failed connections are retried with backoff before returning to the login screen with an error.\r\n- The layout adapts to small screens, and hover effects are limited to mouse devices.\r\n\r\n### Scoreboard\r\n\r\n- Read-only spectator view showing the round, phase, category, and live player standings sorted by score, updated on every state change.\r\n- In the resolved phase it reveals the correct answer and per-player results: the chosen answer, point delta, effective confidence, and all applied effects.\r\n\r\n## Parameters\r\n\r\nThe `Params` object controls module behavior and access. All fields are optional:\r\n\r\n| Field | Default | Description |\r\n|---|---|---|\r\n| `create` | `false` | Allow creating new game sessions |\r\n| `idByName` | `false` | Identify players by name instead of a client-generated UUID |\r\n| `lifetime` | `86400000` (24h) | Cookie lifetime in milliseconds |\r\n\r\nParameters can also be set per-request through `params` when dispatching to the module. Request parameters override the corresponding default, allowing parent modules to implement authentication or per-route access policies.\r\n\r\n## Endpoints\r\n\r\nThe `Endpoints` export provides the path constants used by the module. All paths are relative to the module's mount point.\r\n\r\n| Path | Method | Description |\r\n|---|---|---|\r\n| `/` | GET | Lobby page: create a session and access the player client and scoreboard (requires `create` param) |\r\n| `/new` | GET | Creates a new session and responds with the session id as JSON (requires `create` param) |\r\n| `/client` | GET | Player interface for joining and playing the game (query param: `id`) |\r\n| `/score` | GET | Spectator scoreboard showing live game state (query param: `id`) |\r\n| `/static/*` | GET | Static assets (CSS, JS) served with immutable cache headers |\r\n| `/ws` | WebSocket | Join a game session (query param: `id`) |\r\n\r\n## Game Flow\r\n\r\nA game progresses through rounds, each consisting of two phases. At least two players must be connected to start.\r\n\r\n### Phases\r\n\r\n| Phase | Description |\r\n|---|---|\r\n| `start` | Lobby. All players ready up to begin the first round. |\r\n| `category` | The question's category is shown. Players set their confidence (-1 to 3) and activate effects. The question text is hidden unless the player used the \"Expose\" effect. All players ready up to proceed. |\r\n| `answer` | The full question and answer options are revealed. Players pick an answer. All players ready up to resolve. |\r\n| `resolved` | Results are shown: correct answer, point deltas, and applied effects. All players ready up to advance to the next round. |\r\n| `done` | All questions exhausted. Final scores are displayed. |\r\n\r\n### Scoring\r\n\r\nEach correct answer earns points equal to the player's chosen confidence level; each wrong answer loses that amount. Scores cannot drop below zero.\r\n\r\n## Effects\r\n\r\nEffects are the strategic core of the game. During the `category` phase, players can activate one or more effects before seeing the question. Each effect has a cooldown measured in rounds.\r\n\r\n### Self-Targeting Effects\r\n\r\n| Effect | Cooldown | Description |\r\n|---|---|---|\r\n| Expose | 2 | Reveals the question text during the category phase |\r\n| Protect | 4 | Blocks all effects targeting this player for the round |\r\n| Double or Nothing | 15 | If correct, score doubles; if wrong, score drops to zero |\r\n\r\n### Opponent-Targeting Effects\r\n\r\nThese prompt the player to select an opponent:\r\n\r\n| Effect | Cooldown | Description |\r\n|---|---|---|\r\n| Wrong | 5 | Forces the opponent to fail regardless of their answer |\r\n| No Points | 3 | Prevents the opponent from earning or losing any points |\r\n| No Confidence | 4 | Overrides the opponent's confidence to -1 |\r\n| Absolute Confidence | 6 | Overrides the opponent's confidence to 3 |\r\n| Steal Points | 5 | Steals all points the opponent earns or loses this round |\r\n| Swap | 8 | Swaps total scores with the opponent (only triggers if the opponent answers correctly) |\r\n\r\n### Effect Resolution Order\r\n\r\nEffects are resolved in a fixed order after answers are submitted: protection is applied first (blocking all incoming effects), then fail, zero, min/max, double-or-nothing, steal, and finally swap. When multiple players apply the same effect to the same target, one is chosen randomly. Mutual steals and mutual swaps cancel each other out.\r\n\r\n## WebSocket Protocol\r\n\r\nThe game is built on trust. Each WebSocket connection publishes updates of its player state, which are then pushed to all other clients. The server validates the structure of incoming updates but does not verify game logic (e.g. whether a player's answer is actually correct).\r\n\r\n### Client Commands\r\n\r\n| Command | Fields | Description |\r\n|---|---|---|\r\n| `state` | `{ cmd: 'state' }` | Request the full current game state |\r\n| `update` | `{ cmd: 'update', id: string, value: PlayerState \\| null }` | Update the player's state, or remove the player if `value` is `null` |\r\n\r\n### Server Messages\r\n\r\n| Field | Description |\r\n|---|---|\r\n| `{ cmd: 'state', state: GameState }` | Full game state including phase, question, round, and all player states |\r\n| `{ cmd: 'malformed' }` | The client sent an invalid or unrecognized message |\r\n| `{ cmd: 'outdated' }` | The update's stamp is older than the server's current stamp for this player |\r\n| `{ cmd: 'inconsistent' }` | The player id does not match the player name (when `idByName` is enabled) |\r\n| `{ cmd: 'unknown-session' }` | The requested session does not exist (connection is closed) |\r\n\r\n### Player Identity\r\n\r\nBy default, players are identified by a client-generated UUID stored in the `quiz-game-player-id` cookie. This means a player can change their display name between sessions while keeping their identity, and two clients with the same name are treated as separate players.\r\n\r\nWhen `idByName` is enabled, players are identified by name instead. Logging in with the same name from multiple clients will result in both controlling the same player.\r\n\r\n## Session Lifecycle\r\n\r\nSessions are created via the `/new` endpoint and live entirely in memory. A session is automatically deleted after 20 minutes of inactivity. New WebSocket connections and valid player updates reset the inactivity timer. All open WebSocket connections are closed when a session is deleted.\r\n\r\n## Cookies\r\n\r\nThe client page uses two cookies (both with a configurable lifetime, default 24 hours):\r\n\r\n| Cookie | Description |\r\n|---|---|\r\n| `quiz-game-last-name` | Last used player name, pre-filled on the next visit |\r\n| `quiz-game-player-id` | Client-generated UUID used as the player identity (when `idByName` is `false`) |\r\n","readmeFilename":"README.md"}