mirror of
https://github.com/tiennm99/loto.git
synced 2026-10-14 08:18:29 +00:00
feat(voice): bundled Vietnamese voice calls (master + player)
Speak the called number on master draw, "Chờ N" when a row is one
away, and "Kinh" on bingo. No runtime TTS API — clips are
pre-generated by `scripts/generate-audio.py` (free edge-tts) and
shipped as static MP3s under `static/audio/{voiceId}/`.
- src/lib/vietnamese-number.js + test (40 cases): tonal exceptions
mười lăm / hai mươi mốt / hai mươi lăm
- src/lib/voice.js: lazy <audio> cache, token-based cancellation,
cho+number sequencer, on-unmount cleanup
- src/lib/audio-manifest.js: re-exports static/audio/manifest.json
- scripts/generate-audio.py: discovers every vi-* edge-tts voice,
writes 92 clips per voice + manifest.json
- static/audio/manifest.json: placeholder until user runs the script
- src/lib/settings-store: +voiceEnabledMaster/voiceEnabledPlayer/voice
with per-key validators
- src/lib/SettingsButton: new "Âm thanh" fieldset (toggles + voice
picker rendered from manifest)
- MasterPanel.handleDrawNext: playNumber(next) + cancel on new game
- PlayerBoard $effect: playWaiting/playBingo beside toast/popup;
cancel on regenerate / clear
To materialize the MP3s on first install:
pip install edge-tts
python3 scripts/generate-audio.py
Tests: 98 pass (40 number + 31 settings + 27 game-logic).
This commit is contained in:
1 parent
35578d00fe
commit
69edd30529
23 files changed
+1578
-10
No files matched your search
@@ -12,7 +12,7 @@
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/lib/PlayerBoard.svelte` | Reusable player card (9×9 grid rendered as 3 stacked 3×9 mini-cards: Tân Tân / An khang thịnh vượng / Tân Tân tốt nhất). Tall (3:5 on mobile; wider on sm+) cells with condensed bold black numbers (`tan-tan-num` font stack), white number cells, purple empty cells by default. Handles crossed state, bingo popup, "Chờ X" toast. Two header actions: "Tạo bảng mới" (regenerate grid) and "Xoá đánh dấu" (clear all marks, keep grid — only shown when a grid exists). Accepts `storagePrefix` prop for multi-card isolation. Empty cells use `--empty-cell-bg` CSS var from settings store. |
|
||||
| `src/lib/SettingsButton.svelte` | Gear icon + modal. 4 fieldsets: Giao diện (theme auto/light/dark), Chế độ quản trò (master mode toggle), Tự động xổ (auto-call + speed 1–10s), Màu ô trống (10 Excel color swatches). Reset-to-default button. Mounted on `/`. |
|
||||
| `src/lib/SettingsButton.svelte` | Gear icon + modal. 5 fieldsets: Giao diện (theme auto/light/dark), Chế độ quản trò (master mode toggle), Tự động xổ (auto-call + speed 1–10s), Âm thanh (voice toggles for master + player + voice picker from manifest), Màu ô trống (10 Excel color swatches). Reset-to-default button. Mounted on `/`. |
|
||||
| `src/lib/MasterPanel.svelte` | Host controls. New game / draw, "Số vừa xổ" hero token, "Thứ tự đã xổ" history list, 11×9 last-digit-aligned tracking grid (with circular tokens + draw-order overlay). No host's own player card (the player already has one above). "Xổ số" / "Bắt đầu / Dừng" button bound to auto-call. Mounted conditionally on `/` when `settings.masterMode === true`. |
|
||||
| `src/lib/PageFooter.svelte` | Footer with tagline ("Made by miti99 with ❤️ SVG icon") + link. Mounted on `/`. |
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/lib/game-logic.js` | Stateless utilities: generateGrid (constraint-aware picker — exact 5 per row & per col, ascending-sorted columns, soft "no 3 consecutive filled cols per row" via rejection sampling), saveGrid, loadGrid, saveCrossedState, loadCrossedState, isRowComplete, getWaitingNumber. |
|
||||
| `src/lib/settings-store.svelte.js` | Reactive global UI settings via Svelte 5 runes. Stores 5 keys: `theme` (enum: "auto" / "light" / "dark"), `masterMode` (bool), `autoCallEnabled` (bool), `autoCallSpeed` (1–10), `emptyCellColor` (hex). Persisted to localStorage `loto_settings`. Pushes values to CSS vars and `<html class="dark">` on `:root`. Per-key validators preserve old data. |
|
||||
| `src/lib/vietnamese-number.js` | `numberToVietnamese(n)` — pure utility mapping 0..90 to spoken Vietnamese, with tonal exceptions (15 → "mười lăm", 21 → "hai mươi mốt", 25 → "hai mươi lăm"). Out-of-range falls back to `String(n)`. |
|
||||
| `src/lib/voice.js` | Bundled-MP3 playback. Exports `playNumber(n)`, `playWaiting(n)` (sequences cho + N), `playBingo()`, `cancelPlayback()`. Lazy `<audio>` cache, cancel-then-play, token-based cancel ensures stale promises can't resume after a new event. Reads active voice from `settings.voice`; URLs go through `import { base } from "$app/paths"` for basePath safety. |
|
||||
| `src/lib/audio-manifest.js` | Re-exports `static/audio/manifest.json` as `VOICES` (array) + `VOICE_IDS` (Set) + `DEFAULT_VOICE`. Manifest is generated by `scripts/generate-audio.py`. |
|
||||
| `src/lib/settings-store.svelte.js` | Reactive global UI settings via Svelte 5 runes. Stores 8 keys: `theme` (enum: "auto" / "light" / "dark"), `masterMode` (bool), `autoCallEnabled` (bool), `autoCallSpeed` (1–10), `emptyCellColor` (hex), `voiceEnabledMaster` (bool), `voiceEnabledPlayer` (bool), `voice` (string id matching audio manifest). Persisted to localStorage `loto_settings`. Pushes values to CSS vars and `<html class="dark">` on `:root`. Per-key validators preserve old data. |
|
||||
|
||||
### Styling
|
||||
| File | Purpose |
|
||||
@@ -30,8 +33,9 @@
|
||||
### Tests
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/lib/game-logic.test.js` | 26 unit tests: generateGrid shape (9×9, 5 per row/col, no duplicates), column ranges & ascending sort, row completion, waiting number detection, persistence (saveGrid/loadGrid/saveCrossedState/loadCrossedState with validators). |
|
||||
| `src/lib/settings-store.test.js` | 27 unit tests: defaults, loadSettings (restore 5 keys from localStorage, apply CSS vars, toggle dark class, handle empty/corrupt), saveSettings, resetSettings, theme toggle (auto → OS pref detection), master mode, auto-call + speed, color validation. |
|
||||
| `src/lib/game-logic.test.js` | 27 unit tests: generateGrid shape (9×9, 5 per row/col, no duplicates), column ranges & ascending sort, no-3-consecutive soft constraint, row completion, waiting number detection, persistence (saveGrid/loadGrid/saveCrossedState/loadCrossedState with validators). |
|
||||
| `src/lib/settings-store.test.js` | 31 unit tests: defaults (incl. voice keys), loadSettings (restore 8 keys, apply CSS vars, toggle dark class, handle empty/corrupt), saveSettings, resetSettings, theme toggle (auto → OS pref detection), master mode, auto-call + speed, color validation, voice round-trip + invalid-id fallback. |
|
||||
| `src/lib/vietnamese-number.test.js` | 40 unit tests: ones (0–9), teens (10–19 incl. mười lăm), 20–90 incl. mốt and lăm exceptions, out-of-range fall-through. |
|
||||
|
||||
### Configuration
|
||||
| File | Purpose |
|
||||
@@ -44,6 +48,9 @@
|
||||
| `.gitignore` | Excludes node_modules, build, .env.local, etc. |
|
||||
| `.env.example` | codeserver profile vars (CODESERVER_HOST, CODESERVER_PORT). |
|
||||
| `static/_redirects` | Cloudflare Pages: `/* / 301` — every unknown path 301-redirects to homepage. Static assets are served first so this only fires on misses. |
|
||||
| `static/audio/{voiceId}/*.mp3` | Pre-generated Vietnamese voice clips (1–90 + cho + kinh per voice). Generated once by `scripts/generate-audio.py` (edge-tts). Runtime never calls TTS. |
|
||||
| `static/audio/manifest.json` | Voice list `{ id, edgeName, label, gender }[]`. Written by the generator, imported by `audio-manifest.js`. |
|
||||
| `scripts/generate-audio.py` | One-shot Python script: discovers every `vi-*` edge-tts voice, generates 92 clips per voice, writes manifest. Run after voice/wording changes. |
|
||||
|
||||
## Key Data Structures
|
||||
|
||||
|
||||
Reference in new issue
Block a user