2026-04-26 19:16:46 +07:00
2026-04-29 21:33:25 +07:00
2026-05-13 10:21:00 +07:00
2026-05-13 10:21:00 +07:00
2026-05-13 10:21:00 +07:00

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

Android port: tiennm99/loto-android


Features

  • Number board — 190 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 18+ and pnpm.


Quick Start

git clone https://github.com/tiennm99/loto
cd loto
pnpm install
pnpm dev        # dev server at http://localhost:5173

Commands

Command Description
pnpm dev Start development server
pnpm build Production build (root base path)
pnpm build:gh Production build for GitHub Pages (/loto base path)
pnpm preview Preview production build locally
pnpm test Run unit tests
pnpm 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 pnpm 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 (free, no API key). To regenerate (e.g. to add a new Microsoft vi-* voice):

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 via .github/workflows/deploy-github-pages.yml on push to main. Build command: pnpm build:gh (sets /loto base path). Static output in build/.


License

Apache 2.0

S
Description
No description provided
Readme 2.8 MiB
Languages
TypeScript 89.9%
CSS 8%
JavaScript 2.1%