A hub for turn-based, two-player board games. Two players in different browsers play against each other, with game state synced through Firebase Realtime Database. Built with Vite + TypeScript, served as static pages.
First game: Connect Four. The architecture is built so adding more games is cheap — see Adding a game.
index.html— lobby: pick a game, enter a room name and your name.game.html?game=<id>&room=<room>&player=<name>— the game board.
No authentication, no room listing (yet) — just share a room name with a friend.
npm install
npm run dev # http://localhost:5173
npm run build # static output in dist/ (deployable to GitHub Pages)- In the repository settings, set Pages -> Build and deployment -> Source to GitHub Actions.
- Push to
main; the workflow in .github/workflows/deploy-pages.yml buildsdist/and deploys it as the Pages site. - Because the Vite config uses the repository base path, the published site is
https://xtofs.github.io/BoardGameHub/.
Open the dev URL in two browsers (or one normal + one incognito), pick the same game and room name, and play.
The web config in src/firebase/config.ts is already filled in for this project.
The values are not secret — they ship in every static client; security comes
from your Database rules, not from hiding the config. To point at your own
project:
-
Create a project at https://console.firebase.google.com.
-
Build → Realtime Database → Create database (pick a region; start in test mode).
-
Project settings (gear) → General → Your apps → add a Web app, then copy the generated
firebaseConfigvalues intosrc/firebase/config.ts. -
Set Database rules. Permissive dev rules (open — fine for a hobby hub, tighten before any real deployment):
{ "rules": { "rooms": { "$room": { ".read": true, ".write": true } } } }
rooms/{room}in the database holds{ game, state, status, seats }.- Seats are claimed by join order via a transaction; re-joining with the same name reclaims your seat (survives a refresh).
- Moves go through a Firebase transaction (
submitMove) that re-reads the state, so out-of-turn and racing moves are rejected. Both browsers re-render on every update via anonValuesubscription.
- Create
src/games/<your-game>/implementing theGame<S, M>interface fromsrc/games/types.ts(createInitialState,render,hitTest,applyMove,getStatus). Keep state JSON-serializable, and keepapplyMove/getStatuspure (they run on both clients and inside the move transaction). - Register it with one line in
src/games/registry.ts.
That's it — the lobby and game page pick it up automatically.
index.html / game.html Vite entry points (MPA)
src/firebase/ config, app singletons, generic room sync
src/games/types.ts Game interface
src/games/registry.ts game id -> Game (add games here)
src/games/connect-four/ first game: logic, render, assembly
src/lobby/ src/game/ page controllers