fuzzyworld

word-waltz / files / README.md

README.md

4.9 KB · history · blame

Word Waltz

A two-player, asynchronous Wordle duel. You challenge your opponent with a secret 5-letter word; they play Wordle against it and score based on how many guesses it takes. Then the tables turn — back and forth, indefinitely, keeping a running tally.

Play now!

How it works

  1. Challenge — Player A picks a valid 5-letter word to challenge Player B.
  2. Solve — Player B gets the classic Wordle experience: - Correct letter, correct position → green - Correct letter, wrong position → yellow - Not in the word → gray - An on-screen keyboard tracks letter states as you play.
  3. Score — Fewer guesses = higher score (6 tries possible; solving in 1 is perfect).
  4. Volley — Player B then picks a word to challenge Player A right back.
  5. Repeat — Games go on indefinitely; a running scoreboard tracks totals.

Tech stack

Concern Tool
Build Vite (static output)
Hosting GitLab Pages
Game storage Firebase Firestore
Encryption Web Crypto API (AES-GCM, 256-bit)
QR codes qrcode
Language TypeScript (strict mode, ES modules)

Security model — URL-keyed encryption

All game state stored in Firebase is encrypted at rest. The decryption key lives only in the URL fragment (the # portion), which browsers never transmit over the network.

https://your-app.gitlab.io/game/#gameId=xxxx&key=base64encodedkey

Flow — joining a game

  1. Player A creates a game → browser generates a game ID + encryption key.
  2. Player A receives a shareable URL and a QR code.
  3. Player A shares either with Player B (text, QR scan, etc.).
  4. Both players load the same URL; both can read/write the game via the shared key.

Scoring

Guesses taken Points
1 6
2 5
3 4
4 3
5 2
6 1
Failed (> 6) 0

Scores accumulate across rounds. There is no set game length — keep playing as long as you like.

Project structure

word-waltz/
├── .fw-ci.yml              # GitLab Pages deploy pipeline
├── package.json
├── tsconfig.json
├── vite.config.ts
├── AGENTS.md               # AI coding-agent instructions
└── src/
    ├── index.html          # App shell
    ├── main.ts             # Entry point — router/bootstrap
    ├── types.ts            # Shared TypeScript types
    ├── styles/
    │   └── main.css
    ├── components/
    │   ├── WordPicker.ts   # UI: pick a challenge word
    │   ├── WordleBoard.ts  # UI: 6×5 guess grid
    │   ├── Keyboard.ts     # UI: on-screen keyboard with letter states
    │   ├── Scoreboard.ts   # UI: running score display
    │   └── ShareDialog.ts  # UI: shareable link + QR code
    ├── firebase/
    │   ├── config.ts       # Firebase project configuration
    │   └── gameService.ts  # Firestore read/write for game state
    ├── crypto/
    │   └── encryption.ts   # AES-GCM encrypt/decrypt via Web Crypto API
    └── utils/
        ├── wordList.ts     # Valid 5-letter word list + validation
        ├── scoring.ts      # Score calculation logic
        └── shareLink.ts    # URL construction / parsing (fragment-based)

Local development

npm install
npm run dev        # Vite dev server at http://localhost:5173

You will need a Firebase project. Copy your config into src/firebase/config.js (see the placeholder there).

Deployment (GitLab Pages)

Every push to main triggers .gitlab-ci.yml, which runs npm run build and publishes dist/ to GitLab Pages automatically.

npm run build      # Outputs to dist/

Firebase setup

  1. Create a Firebase project at console.firebase.google.com.
  2. Enable Firestore in Native mode.
  3. Set Firestore security rules (see guidance in AGENTS.md).
  4. The Firebase project config is committed directly in src/firebase/config.ts.
Source
1# Word Waltz
2
3A two-player, asynchronous Wordle duel. You challenge your opponent with a secret 5-letter word; they play Wordle against it and score based on how many guesses it takes. Then the tables turn — back and forth, indefinitely, keeping a running tally.
4
5[Play now!](https://apps.fuzzyworld.net/word-waltz/)
6
7## How it works
8
91. **Challenge** — Player A picks a valid 5-letter word to challenge Player B.
102. **Solve** — Player B gets the classic Wordle experience:
11 - Correct letter, correct position → **green**
12 - Correct letter, wrong position → **yellow**
13 - Not in the word → **gray**
14 - An on-screen keyboard tracks letter states as you play.
153. **Score** — Fewer guesses = higher score (6 tries possible; solving in 1 is perfect).
164. **Volley** — Player B then picks a word to challenge Player A right back.
175. **Repeat** — Games go on indefinitely; a running scoreboard tracks totals.
18
19## Tech stack
20
21| Concern | Tool |
22| ------------ | ---------------------------------------------------------------- |
23| Build | [Vite](https://vitejs.dev/) (static output) |
24| Hosting | [GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/) |
25| Game storage | [Firebase Firestore](https://firebase.google.com/docs/firestore) |
26| Encryption | Web Crypto API (AES-GCM, 256-bit) |
27| QR codes | [qrcode](https://www.npmjs.com/package/qrcode) |
28| Language | TypeScript (strict mode, ES modules) |
29
30## Security model — URL-keyed encryption
31
32All game state stored in Firebase is encrypted at rest. The decryption key **lives only in the URL fragment** (the `#` portion), which browsers never transmit over the network.
33
34```text
35https://your-app.gitlab.io/game/#gameId=xxxx&key=base64encodedkey
36```
37
38- A fresh AES-GCM-256 key is generated in the browser at game creation time.
39- The key is encoded into the URL fragment and never sent to Firebase or any server.
40- Firebase stores only ciphertext. Even a Firebase admin cannot read game state without the URL.
41- Sharing the link = sharing access. Losing the link = losing access.
42
43## Flow — joining a game
44
451. Player A creates a game → browser generates a game ID + encryption key.
462. Player A receives a shareable URL and a QR code.
473. Player A shares either with Player B (text, QR scan, etc.).
484. Both players load the same URL; both can read/write the game via the shared key.
49
50## Scoring
51
52| Guesses taken | Points |
53| ------------- | ------ |
54| 1 | 6 |
55| 2 | 5 |
56| 3 | 4 |
57| 4 | 3 |
58| 5 | 2 |
59| 6 | 1 |
60| Failed (> 6) | 0 |
61
62Scores accumulate across rounds. There is no set game length — keep playing as long as you like.
63
64## Project structure
65
66```text
67word-waltz/
68├── .fw-ci.yml # GitLab Pages deploy pipeline
69├── package.json
70├── tsconfig.json
71├── vite.config.ts
72├── AGENTS.md # AI coding-agent instructions
73└── src/
74 ├── index.html # App shell
75 ├── main.ts # Entry point — router/bootstrap
76 ├── types.ts # Shared TypeScript types
77 ├── styles/
78 │ └── main.css
79 ├── components/
80 │ ├── WordPicker.ts # UI: pick a challenge word
81 │ ├── WordleBoard.ts # UI: 6×5 guess grid
82 │ ├── Keyboard.ts # UI: on-screen keyboard with letter states
83 │ ├── Scoreboard.ts # UI: running score display
84 │ └── ShareDialog.ts # UI: shareable link + QR code
85 ├── firebase/
86 │ ├── config.ts # Firebase project configuration
87 │ └── gameService.ts # Firestore read/write for game state
88 ├── crypto/
89 │ └── encryption.ts # AES-GCM encrypt/decrypt via Web Crypto API
90 └── utils/
91 ├── wordList.ts # Valid 5-letter word list + validation
92 ├── scoring.ts # Score calculation logic
93 └── shareLink.ts # URL construction / parsing (fragment-based)
94```
95
96## Local development
97
98```bash
99npm install
100npm run dev # Vite dev server at http://localhost:5173
101```
102
103You will need a Firebase project. Copy your config into `src/firebase/config.js` (see the placeholder there).
104
105## Deployment (GitLab Pages)
106
107Every push to `main` triggers `.gitlab-ci.yml`, which runs `npm run build` and publishes `dist/` to GitLab Pages automatically.
108
109```bash
110npm run build # Outputs to dist/
111```
112
113## Firebase setup
114
1151. Create a Firebase project at [console.firebase.google.com](https://console.firebase.google.com).
1162. Enable **Firestore** in Native mode.
1173. Set Firestore security rules (see guidance in `AGENTS.md`).
1184. The Firebase project config is committed directly in `src/firebase/config.ts`.