Real-time multiplayer word-guessing party game for mobile. Players split into teams, explain words from a virtual hat, and compete across three rounds. Built for a private group of friends (~5–16 players).
- How to Play
- Features
- Tech Stack
- Project Structure
- Getting Started
- Admin Management
- Development
- CI/CD
- Documentation
- Contributing
- License
The Hat (Шляпа) is a classic party word-guessing game played in three rounds with the same set of words.
- Word Entry — Every player submits words (e.g., 5 each). All words go into a shared "hat."
- Round 1 — Explain Freely — Use any words to describe the target word. No gestures, no translations.
- Round 2 — Pantomime — No words at all. Act it out.
- Round 3 — One Word — Explain using only ONE word. Teammates guess from that single hint.
Each turn, one player explains while their team guesses. Timer counts down. Correct guesses score points. Hat empties → round ends. Three rounds → scoreboard shows the winner.
Roles:
| Role | Capabilities |
|---|---|
| Admin | Creates rooms, pauses timer, reassigns explainer. Google sign-in required. |
| Player | Joins via link. No auth needed. Enters name, picks team, plays. |
- Real-time multiplayer — Firebase RTDB syncs game state across all clients
- Mobile-first — Touch targets ≥ 44px, works on any screen size
- Atomic word drawing — Transactions prevent two players from drawing the same word
- Server-synced timer — Firebase server timestamps eliminate clock drift
- Admin controls — Pause, change explainer, restart game
- Team rotation — Round-robin turn order preserved across rounds
- Skip penalty — Optional -1 point for skipped words (skipping is disabled in round 3)
- Undo — Revert last action during a turn
- Scoreboard — Per-team scores + per-player words-explained stats
- QR code invites — Generate shareable invite links
| Layer | Technology |
|---|---|
| Framework | SvelteKit + Svelte 5 (runes) |
| Language | TypeScript 5.7 |
| Database | Firebase Realtime Database |
| Auth | Firebase Auth (Google) |
| Styling | Tailwind CSS 3.4 |
| Testing | Vitest + Firebase Emulators |
| Hosting | Firebase Hosting (static) |
| CI/CD | GitHub Actions |
Why RTDB over Firestore? RTDB bills by data transferred, not operations — cheaper for high-frequency game state updates. Firestore would bill per read/write (10–20 ops/min per client).
Why no backend? All game logic runs client-side. RTDB transactions provide atomicity. Only server-side code is one scheduled Cloud Function for room cleanup.
src/
├── lib/
│ ├── firebase.ts # Firebase app init, db/auth exports
│ ├── db-types.ts # TypeScript interfaces for all RTDB nodes
│ ├── auth.ts # Admin auth helpers
│ ├── colors.ts # Player color assignment (collision-free)
│ ├── team-colors.ts # Team identity colors
│ ├── context.ts # Svelte context keys
│ ├── game/ # Game logic (no UI)
│ │ ├── hat.ts # Word drawing/returning transactions
│ │ ├── timer.ts # Timer math (server-timestamp based)
│ │ ├── turn.ts # Turn advancement, rotations
│ │ ├── turn-start.ts # Turn initialization
│ │ ├── turn-advance.ts # Post-turn transitions
│ │ ├── turn-expiry.ts # Timer expiry handling
│ │ ├── turn-round.ts # Round end/refill logic
│ │ ├── scoring.ts # Score computation
│ │ ├── explainer-actions.ts # Guessed/Skip/Undo actions
│ │ ├── word-display.ts # Word visibility timing
│ │ ├── words.ts # Word submission helpers
│ │ ├── room.ts # Room creation
│ │ ├── room-route.ts # Room join/route helpers
│ │ ├── join-room.ts # Player join logic
│ │ └── lobby.ts # Lobby/ready logic
│ ├── stores/ # Svelte 5 reactive stores (RTDB subscriptions)
│ │ ├── auth.svelte.ts # Auth state
│ │ ├── gameState.svelte.ts # gameState node subscription
│ │ ├── players.svelte.ts # players map subscription
│ │ ├── room.svelte.ts # room meta/config subscription
│ │ └── teams.svelte.ts # teams subscription
│ └── components/
│ ├── phases/ # Game phase screens
│ │ ├── NameEntry.svelte
│ │ ├── WordEntry.svelte
│ │ ├── Lobby.svelte
│ │ ├── GameMain.svelte
│ │ ├── ExplainerView.svelte
│ │ ├── PostTurn.svelte
│ │ ├── RoundEnd.svelte
│ │ ├── Scoreboard.svelte
│ │ ├── RoomCreation.svelte
│ │ └── RoomCreated.svelte
│ └── shared/ # Reusable components
│ ├── Timer.svelte
│ └── TeamScore.svelte
├── routes/
│ ├── +page.svelte # Landing page
│ └── room/[roomId]/
│ ├── +page.svelte # Phase switcher (main game route)
│ └── +page.ts # URL param parsing
└── app.css # Global styles, Tailwind imports
- Node.js == 25
- Firebase project with RTDB and Google Auth enabled
- Service account key for admin scripts
git clone git@github.com:AlexShein/TheHat.git
cd TheHat
npm installCopy .env.example to .env and configure:
VITE_FIREBASE_API_KEY=your-api-key
VITE_FIREBASE_PROJECT_ID=your-project-id
VITE_FIREBASE_DATABASE_URL=https://your-project-default-rtdb.region.firebasedatabase.app
VITE_USE_EMULATOR=true # true for local dev, false for productionTo deploy the app to production, configure .env.production:
VITE_FIREBASE_API_KEY=your-api-key
VITE_FIREBASE_PROJECT_ID=your-project-id
VITE_FIREBASE_DATABASE_URL=https://your-project-default-rtdb.region.firebasedatabase.app
VITE_USE_EMULATOR=false # always false for production# Start Firebase emulators + dev server with solo mode
npm run dev:solo:full
# Or start them separately
npm run emulators # Firebase emulators (RTDB + Auth)
npm run dev:solo # Dev server with min-player bypassApp runs at http://localhost:5173.
# Build the app and deploy everything to Firebase
firebase deployAdmins are stored in RTDB at /admins/{uid}: true. Three scripts in scripts/ manage admins without the Firebase Console.
- Service account key: Download from Firebase Console → Project Settings → Service Accounts → Generate new private key. Save as
service-account.jsonin the project root (git-ignored). - Database URL: From Firebase Console → Realtime Database → Data tab (e.g.,
https://the-word-guessing-game-default-rtdb.europe-west1.firebasedatabase.app).
export GOOGLE_APPLICATION_CREDENTIALS="$(pwd)/service-account.json"
export FIREBASE_DATABASE_URL="https://YOUR-PROJECT-default-rtdb.REGION.firebasedatabase.app"npx tsx scripts/list-admins.ts # List current admins
npx tsx scripts/add-admin.ts friend@gmail.com # Add admin by email
npx tsx scripts/remove-admin.ts friend@gmail.com # Remove admin by emailBefore running admin scripts, add your own UID manually once:
- Sign in to the app locally with Google.
- Open DevTools → Application → Session Storage → copy
firebase:authUserUID. - In Firebase Console → Realtime Database → add
/admins/{YOUR_UID}with valuetrue. - Deploy rules:
firebase deploy --only database.
After this, use scripts/add-admin.ts for everyone else.
| Command | Description |
|---|---|
npm run dev |
Start dev server |
npm run dev:solo |
Dev server with min-player bypass |
npm run emulators |
Firebase emulators (RTDB + Auth) |
npm run dev:solo:full |
Emulators + dev server + bypass (one command) |
npm run dev:bootstrap |
Seed emulator data (test users) |
npm test |
Run all tests with Firebase emulators |
npm run test:watch |
Vitest in watch mode |
npm run test:rules |
RTDB security rules tests only |
npm run lint |
ESLint + svelte-check + TypeScript |
npm run typecheck |
svelte-check + tsc --noEmit |
npm run build |
Production build |
Set VITE_DEV_BYPASS_MIN_PLAYERS=true (or use npm run dev:solo) to bypass:
- Minimum 2 players per team requirement
- All-players-ready gate
This flag only affects Lobby.svelte and game initialization. Never true in production.
All tests run against Firebase emulators — never against production:
npm testTests use Vitest with @firebase/rules-unit-testing. Coverage reports generated automatically.
GitHub Actions workflow on PR and push to main:
- lint-and-typecheck — ESLint, svelte-check, TypeScript. Must pass with zero warnings.
- unit-tests — Vitest with Firebase emulators.
- firebase-rules — RTDB security rules validation.
- build — Production build + bundle-size gate (non-blocking, ≤ 1024 KB).
All checks complete under 4 minutes.
| Document | Description |
|---|---|
| PRD | Product requirements and game flow |
| Architecture | Technical architecture and key decisions |
| Design Brief | Visual design direction |
| Data Schema | RTDB node shapes and relationships |
| Constraints | AI development rules (Svelte 5 runes, Firebase patterns) |
| Cross-Cutting Constraints | Invariants and phase-to-phase dependencies |
| Progress | Implementation status tracker |
| Implementation Plan | Phase-by-phase development plan |
| Readiness Checklist | Pre-launch verification items |
| Local Dev Setup | Detailed local environment configuration |
| Design System | Brand colors, typography, components |
This is a personal project for a private group of friends. No public contribution process at this time.
If you find a bug or have a suggestion, open an issue on GitHub.
MIT — see LICENSE file.