Real-time multiplayer quiz bowl in the browser, powered by the QBReader question database.
- Real-time multiplayer — any number of players share a room, zero server setup per game
- Live question reading — words revealed one at a time at configurable speed
- Buzz-in system — first buzz locks others out; 10-second answer window with decimal countdown
- End-of-question window — 10-second bonus buzz window after full reveal (no neg, no power)
- Bonuses — optional 3-part bonus questions awarded after a correct tossup
- Scoring — powers (+15), tens (+10), negs (−5); per-player stats tracked live (P/TU/N, PPB)
- Target score — optional win condition triggers a game-over leaderboard
- 3-column category filter — granular category / subcategory / alternate selection (QBReader-style)
- Live answer preview — spectators see the answering player's keystrokes in real time
- In-game chat — persistent chat panel per room
- Configurable settings — reading speed, year range, negs, rebuzz, powermark-only, standard sets
| Layer | Technology |
|---|---|
| Server | Node.js + Express |
| Real-time | Socket.io |
| Client | Vanilla JS / HTML / CSS — no framework, no build step |
| Questions | QBReader REST API |
- Node.js 18 or later
- npm
git clone https://github.com/EddyLL06/qb-reader-improved.git
cd qb-reader-improved
npm install
npm startOpen http://localhost:3000 in your browser. Open a second tab to test multiplayer.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP listening port |
NODE_ENV |
(unset) | Set to production for JSON log output |
LOG_LEVEL |
info (prod) / debug (dev) |
Logging verbosity: error · warn · info · debug |
- Push the repo to GitHub
- Go to railway.app → New Project → Deploy from GitHub repo
- Select the repository — Railway auto-detects Node.js and runs
npm start - Railway sets
NODE_ENV=productionautomatically, switching logs to structured JSON
No environment variables are required for basic operation.
- Enter a name and room name, then click Join Room (the room is created if it doesn't exist)
- Click the funnel icon (top-right) to open the filter — select categories and difficulties, then Save
- Click Next (or press
N) to fetch and start reading a question - Press Space (or click BUZZ) to interrupt reading and answer
- Type your answer and press Enter or click Submit — you have 10 seconds
- After the question is fully read, a 10-second end-of-question window opens:
- Any player who hasn't buzzed yet may attempt an answer
- No neg penalty; no power bonus
- If a target score is set, the first player to reach it triggers the game-over screen
| Setting | Description |
|---|---|
| Difficulties | 1 (Middle School) through 10 (National Tournament) |
| Categories | Literature, History, Science, Fine Arts, Religion, Mythology, Philosophy, Social Science, Current Events, Geography, Other Academic, Pop Culture |
| Subcategories | Fine-grained selection within each category |
| Alternate subcategories | Additional sub-groupings for Literature, Science, Fine Arts, Social Science |
At least one category must be selected before fetching a question.
| Setting | Default | Description |
|---|---|---|
| Reading Speed | 50 | 0 = slowest (~350 ms/word), 100 = fastest (~50 ms/word) |
| Year Range | 2005–2025 | Filter questions by tournament year |
| Target Score | 0 (off) | First player to reach this score wins the game |
| Bonuses | Off | Award a 3-part bonus after each correct tossup |
| Negs | On | Wrong buzz before end of question costs −5 points |
| Allow Rebuzz | Off | Allow the same player to buzz more than once per question |
| Powermark-only | Off | Only fetch questions that contain a powermark |
| Standard Sets Only | Off | Only fetch questions from standard tournament sets |
The server uses a structured logger (logger.js) with four levels:
| Level | When used |
|---|---|
error |
API failures, unexpected exceptions |
warn |
Empty fetch results, blocked actions (no categories selected) |
info |
Game events — tossup start, buzz, answer result, game over, join/leave |
debug |
Cache fills, individual socket events |
- Local dev — colourised, human-readable text
- Production (
NODE_ENV=production) — newline-delimited JSON, compatible with Railway's log viewer and external aggregators
Override the level at any time:
LOG_LEVEL=debug npm startDepending on the configured log level, server logs may include socket identifiers, room names, player-provided names, submitted answers, answer results, winners, and settings changes. Room names, player names, chat-adjacent gameplay data, and submitted answers are user-provided and should be treated as potentially identifying information.
Operators should restrict log access, avoid debug logging in production unless actively troubleshooting, configure an appropriate retention and deletion policy with their hosting provider, and never publish raw production logs without reviewing and redacting them. The application does not persist gameplay to a database by default, but hosting platforms and external log aggregators may retain their own copies according to their configured policies.
qb-reader-improved/
├── server.js # Express + Socket.io entry point; socket event routing
├── game.js # Authoritative room state machine (all game logic lives here)
├── api.js # Thin QBReader REST API wrapper
├── logger.js # Zero-dependency structured logger (colourised dev / JSON prod)
├── package.json
└── public/
├── index.html # Single-page app shell (two modals: filter + game settings)
├── client.js # Vanilla JS client — socket event handlers, UI, client-side timer
└── style.css # Dark-theme stylesheet; CSS Grid 3-column layout
Questions provided by QBReader — an open, community-maintained quiz bowl question database.