# Fertig Classic Games A Phaser 3.90 framework for classic tabletop games (Backgammon, Parchisi, ...) and casino games (Blackjack, Texas Hold 'Em, ...), with accounts, profiles, and match history. Games are single-player against AI opponents. The frontend uses **native browser ES modules** — no bundler, no build step. The backend is Node.js + Express with SQLite for persistence. --- ## Table of contents - [Features](#features) - [Prerequisites](#prerequisites) - [Quick start](#quick-start) - [Configuration (`.env`)](#configuration-env) - [Running the server](#running-the-server) - [Project layout](#project-layout) - [Database schema](#database-schema) - [REST API](#rest-api) - [Frontend architecture](#frontend-architecture) - [Adding a new game](#adding-a-new-game) - [Email verification](#email-verification) - [Profile pictures](#profile-pictures) - [Troubleshooting](#troubleshooting) - [Roadmap](#roadmap) --- ## Features - Account creation with email + username + password (bcrypt hashed) - Email verification with configurable SMTP — falls back to logging dev links to the console when SMTP is not set up - Session cookies backed by SQLite (httpOnly, SameSite=Lax) - Profile management: display name, bio, avatar upload (PNG / JPEG / WebP) - Match history (wins / losses / draws) recorded for single-player games - Pluggable game registry — register tabletop or casino games server-side - Single-player games against configurable AI opponents - 1920×1080 canvas that scales to any viewport via `Phaser.Scale.FIT` - Vector-only placeholder graphics — drop sprites in later without refactoring scenes - Mouse + keyboard controls --- ## Prerequisites - **Node.js 20 or newer** (uses `node --watch`, native fetch, ES modules) - **npm 9 or newer** - A C/C++ toolchain for `better-sqlite3` and `bcrypt` to build native bindings: - **Linux**: `build-essential`, `python3` - **macOS**: Xcode command line tools (`xcode-select --install`) - **Windows**: install **Visual Studio Build Tools 2022** with the "Desktop development with C++" workload and **Python 3.x** (add to PATH). Do **not** use the deprecated `windows-build-tools` npm package — it is broken on modern Node.js. See [Troubleshooting](#troubleshooting) for step-by-step instructions. No bundler, no Docker, no external database required to get started. --- ## Quick start ```bash git clone cd fertig-classic-games cp example.env .env # Edit .env — at minimum, set SESSION_SECRET to a long random string. # Generate one with: # node -e "console.log(require('crypto').randomBytes(48).toString('hex'))" npm install npm run migrate npm run dev ``` Open http://localhost:3000 in your browser. Register an account; if SMTP is not configured (the default), the verification link is printed to the server console — click it to verify. --- ## Configuration (`.env`) All configuration lives in `.env` at the project root. Use `example.env` as the template. Fields: ### Server | Variable | Default | Description | |-------------|--------------------------|------------------------------------------| | `NODE_ENV` | `development` | `development` or `production`. | | `HOST` | `0.0.0.0` | Bind address. | | `PORT` | `3000` | HTTP port. | | `BASE_URL` | `http://localhost:3000` | Public URL used in verification emails. | | `LOG_LEVEL` | `info` | `error`, `warn`, `info`, `debug`. | ### Database | Variable | Default | Description | |-----------|-------------------------|-----------------------------------| | `DB_PATH` | `./data/fertig.sqlite` | SQLite file path. Auto-created. | ### Auth | Variable | Default | Description | |------------------------|---------------|--------------------------------------------------------------------------------------------------------------| | `SESSION_SECRET` | *(required)* | Long random string. **Required in production.** A dev fallback is used if empty in development with a warning. | | `SESSION_COOKIE_NAME` | `fcg_sid` | Cookie name. | | `SESSION_TTL_DAYS` | `30` | Session lifetime in days. | | `BCRYPT_ROUNDS` | `12` | bcrypt cost factor. | ### Uploads (profile pictures) | Variable | Default | Description | |------------------------|--------------------------------------|--------------------------------------| | `UPLOAD_DIR` | `./public/uploads` | Directory for avatar files. | | `MAX_UPLOAD_SIZE_MB` | `5` | Max image size in MB. | | `ALLOWED_UPLOAD_MIME` | `image/png,image/jpeg,image/webp` | Comma-separated MIME allowlist. | ### Email | Variable | Default | Description | |---------------------------------|---------|------------------------------------------------------------------------| | `SMTP_HOST` | *(empty)* | If empty, verification links log to the console instead of sending. | | `SMTP_PORT` | `587` | SMTP port. | | `SMTP_SECURE` | `false` | `true` for SMTPS (usually port 465). | | `SMTP_USER` | *(empty)* | SMTP username, if your provider requires auth. | | `SMTP_PASS` | *(empty)* | SMTP password. | | `SMTP_FROM` | *(see example.env)* | `From:` header for outgoing mail. | | `VERIFICATION_TOKEN_TTL_HOURS` | `24` | How long verification links remain valid. | --- ## Running the server ```bash npm run dev # node --watch, auto-restart on file changes npm start # plain node, production-style npm run migrate # apply any pending DB migrations ``` The server serves both the API (`/api/*`) and the static frontend (`/`, `/src/...`, `/uploads/...`) on the same port. After it starts you should see: ``` [server] listening on http://0.0.0.0:3000 ``` --- ## Project layout ``` fertig-classic-games/ ├── example.env Configuration template (commit this) ├── .env Your local configuration (gitignored) ├── package.json ├── README.md │ ├── server/ Backend (Node.js, ES modules) │ ├── index.js Express bootstrap │ ├── config.js Loads & validates .env │ ├── db/ │ │ ├── index.js better-sqlite3 connection singleton │ │ ├── migrate.js SQL migration runner │ │ └── migrations/ │ │ └── 001_init.sql Initial schema │ ├── auth/ │ │ ├── routes.js /api/auth/* endpoints │ │ ├── service.js bcrypt, sessions, verification tokens │ │ └── middleware.js loadUser, requireAuth │ ├── profile/ │ │ ├── routes.js /api/profile/*, multer upload │ │ └── service.js │ ├── history/ │ │ └── routes.js /api/history │ ├── email/ │ │ └── mailer.js Nodemailer wrapper with console fallback │ └── games/ │ └── registry.js Game definitions │ ├── public/ Frontend, served as static files │ ├── index.html Loads Phaser via importmap │ ├── styles.css │ ├── uploads/ Avatars (gitignored) │ └── src/ │ ├── main.js Phaser.Game + scale config │ ├── config.js UI colors, dimensions, API base │ ├── services/ │ │ ├── api.js fetch wrapper │ │ └── auth.js Client-side auth store │ ├── ui/ │ │ ├── Button.js │ │ ├── TextInput.js DOM-overlay input that follows canvas scale │ │ └── Modal.js │ ├── scenes/ │ │ ├── BootScene.js │ │ ├── PreloadScene.js │ │ ├── LandingScene.js │ │ ├── LoginScene.js │ │ ├── RegisterScene.js │ │ ├── VerifyScene.js │ │ ├── ProfileScene.js │ │ ├── GameMenuScene.js │ │ ├── OpponentSelectScene.js │ │ └── GameRoomScene.js │ └── games/ One subdirectory per game (uno/, blackjack/, ...) │ └── data/ SQLite database (gitignored) └── fertig.sqlite ``` --- ## Database schema Created by `server/db/migrations/001_init.sql`. Run `npm run migrate` to apply any pending migrations. - **`users`** — `id, email, username, password_hash, email_verified, verification_token, verification_expires_at, created_at` - **`sessions`** — `id, user_id, expires_at, created_at` - **`profiles`** — `user_id (PK/FK), display_name, avatar_path, bio, updated_at` - **`games`** — `id, slug, name, category ('tabletop'|'casino'), max_players, supports_multiplayer` - **`matches`** — `id, game_id, started_at, ended_at, status` - **`match_players`** — `match_id, user_id, seat, result ('win'|'loss'|'draw'|'abandoned'), score` Add new migrations as `server/db/migrations/00N_description.sql` — they are applied in lexicographic order and tracked in `schema_migrations`. --- ## REST API All endpoints are JSON. Session is carried by the `fcg_sid` cookie automatically; the client uses `credentials: 'same-origin'` in fetch calls. ### Auth — `/api/auth` | Method | Path | Auth | Description | |--------|-------------|------|----------------------------------------------------------------------------------------------| | POST | `/register` | — | Body: `{ email, username, password }`. Creates user, sends verification, sets session. | | POST | `/login` | — | Body: `{ identifier, password }` where `identifier` is email or username. | | POST | `/logout` | — | Destroys session. | | GET | `/me` | — | Returns `{ user }` or `{ user: null }`. | | GET | `/verify` | — | `?token=...`. Marks user as verified. Returns HTML so a user clicking the email link sees text. | ### Profile — `/api/profile` | Method | Path | Auth | Description | |--------|------------|-----------|-----------------------------------------------------------------------------------| | GET | `/` | Required | Returns the current user's profile. | | PATCH | `/` | Required | Body: `{ displayName?, bio? }`. Returns the updated profile. | | POST | `/avatar` | Required | Multipart with field `avatar`. Stores the file and updates `avatar_path`. | ### History — `/api/history` | Method | Path | Auth | Description | |--------|-------|----------|------------------------------------------------------------| | GET | `/` | Required | Last 100 matches for the user, plus `{ wins, losses, draws }`. | ### Misc | Method | Path | Auth | Description | |--------|----------------|------|------------------------------------------------------| | GET | `/api/health` | — | `{ ok: true }`. | | GET | `/api/games` | — | Lists registered games from `games/registry.js`. | --- ## Frontend architecture - **No bundler.** `public/index.html` uses an `