Files
loto/web/README.md
T
tiennm99 e314ae01c8 feat(android)!: replace the Capacitor wrapper with a native Kotlin/Compose app
Fresh Kotlin 2.2 + Jetpack Compose app at feature parity with web/
(game logic, settings contract, voice semantics ported one-to-one with
twinned tests; 104 unit + 9 instrumentation tests). Fully offline:
VIBRATE is the only permission; Media3's ACCESS_NETWORK_STATE is
stripped. Voice clips mount from web/static/audio at build time.

Same applicationId, signing env contract, and Play alpha track;
versionCode 7 / 0.2.0. CI Android job is Gradle-only (lint, test,
debug + R8 release APK) and the release workflow fails fast on an
unbumped versionCode.

BREAKING CHANGE: WebView localStorage (settings, in-progress round)
is not migrated; wrapper users start clean.
2026-08-31 13:28:48 +07:00

134 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lô Tô
Web version of *Lô tô hội chợ* — Vietnamese fairground bingo. Built with SvelteKit
(static export). The caller draws numbers one at a time; a host panel manages the
game and called numbers are announced aloud in Vietnamese using bundled audio clips.
No runtime TTS or server required.
Live: [tiennm99.github.io/loto](https://tiennm99.github.io/loto)
Android port: [`android/`](../android) — a native Kotlin + Jetpack Compose
app at feature parity with this one (this app is the behavioral spec). It
mounts `static/audio/` straight into its APK, so the voice clips exist once.
---
## Features
- **Number board** — 1–90 grid; called numbers highlight as they are drawn
- **Host panel** — enable "Chế độ quản trò" (host mode) in settings to reveal the
master draw panel inline below your player card
- **Vietnamese voice callouts** — pre-generated MP3 clips for every number using
Microsoft Edge TTS (`edge-tts`); multiple voices selectable in settings; no API
key or internet connection needed at runtime
- **Auto-cross player card** — optional automatic crossing of numbers on the player's
bingo card as they are called
- **Auto-countdown** — configurable delay between number draws for unattended calling
- **PWA** — installable as a progressive web app; works offline after first load
- **Static export** — no server; deploys to any static host or GitHub Pages
---
## Requirements
[Node.js](https://nodejs.org) 18+ and npm.
---
## Quick Start
```sh
git clone https://github.com/tiennm99/loto
cd loto
npm install
npm run dev # dev server at http://localhost:5173
```
---
## Commands
| Command | Description |
|---|---|
| `npm run dev` | Start development server |
| `npm run build` | Production build (root base path) |
| `npm run build:gh` | Production build for GitHub Pages (`/loto` base path) |
| `npm run preview` | Preview production build locally |
| `npm test` | Run unit tests |
| `npm run lint` | ESLint check |
---
## Configuration
Copy `.env.example` to `.env.local` and set:
| Variable | Description |
|---|---|
| `CODESERVER_HOST` | Hostname for code-server reverse-proxy dev (optional) |
| `CODESERVER_PORT` | Port for code-server proxy (optional) |
When running inside code-server use `npm run dev:codeserver` and open
`https://<CODESERVER_HOST>/absproxy/<CODESERVER_PORT>/` (use `/absproxy/`, not
`/proxy/` — the latter strips the path prefix and breaks SvelteKit routing).
---
## Architecture
Single-page SvelteKit app, statically exported. No backend.
```
src/
├── routes/ # Single route: /
├── lib/
│ ├── game-logic.js # Draw state, number pool, called-number tracking
│ ├── master-store.svelte.js # Host panel state (Svelte 5 runes store)
│ ├── settings-store.svelte.js
│ ├── voice.js # Audio manifest loader + clip player
│ ├── player-auto-cross.js # Auto-cross logic
│ ├── MasterPanel.svelte # Host draw UI
│ ├── PlayerBoard.svelte # Bingo card + number grid
│ └── AutoCountdown.svelte # Countdown timer between draws
static/
└── audio/{voiceId}/ # Pre-generated MP3 clips (one per number per voice)
scripts/
└── generate-audio.py # Regenerates audio clips via edge-tts
```
Game state lives entirely in Svelte 5 rune stores; no server round-trips.
Audio manifest is generated once at build prep time — the app reads a JSON manifest
on load and plays the appropriate clip for each drawn number.
---
## Regenerating Audio
Voice clips in `static/audio/` were generated with
[edge-tts](https://github.com/rany2/edge-tts) (free, no API key). To regenerate
(e.g. to add a new Microsoft `vi-*` voice):
```sh
pip install edge-tts
python3 scripts/generate-audio.py
```
The script auto-discovers all available `vi-*` voices and writes an updated manifest.
---
## Deployment
Deployed to GitHub Pages by the `deploy-pages` job in
`.github/workflows/ci.yml` on push to `main`. Build command: `npm run build:gh`
(sets `/loto` base path). Static output in `build/`.
The same workflow also deploys the base-`""` build to Firebase Hosting and
bundles it into the debug APK, so `web/` is built once per base path per run.
---
## License
Apache-2.0 — see [`LICENSE`](../LICENSE) at the repository root.