The web server exists but is minimal:
- Server-rendered Go template (
landing.tmpl) with dark theme - Pulls live data from a hardcoded Google Sheets spreadsheet
- In-memory only — scores reset on restart
- No auth, no game submission UI beyond the raw
assets/index.htmldev tool - No player profiles, game history detail, or stats beyond raw Elo score
- Duplicate scoring logic across three files
- Two-headed giant games are silently skipped
Turn Guildmaster into a proper, self-contained web app:
- Real persistence (survive restarts, support history queries)
- Clean public-facing UI with player profiles and game history
- Admin-protected game submission
- Keep the Sheets integration as an optional import source
- Consolidate and harden the scoring engine
Problem: ScoreGame is duplicated in main.go, internal/analyzer/analyzer.go, and internal/scoring/store.go.
Action:
- Make
internal/scoringthe single source of truth for all Elo logic - Remove duplicates from
main.goandinternal/analyzer/analyzer.go - Update
internal/analyzerto import frominternal/scoring
Problem: All scores live in memory. Server restart = data loss.
Action:
- Add SQLite via
modernc.org/sqlite(pure Go, no CGo) - Schema:
CREATE TABLE players (id INTEGER PRIMARY KEY, name TEXT UNIQUE, elo INTEGER DEFAULT 1500); CREATE TABLE games (id INTEGER PRIMARY KEY, played_at DATETIME, source TEXT); CREATE TABLE game_players (game_id INTEGER, player_id INTEGER, placement INTEGER);
- Wrap in a
db.Storethat implements the same interface as the currentscoring.Store - On startup: load current Elo from DB into memory; write through to DB on each game submission
Problem: Sheets is the existing data source; we can't discard it.
Action:
- Add a CLI command
make import/go run ./cmd/importthat:- Fetches all game rows from Sheets
- Replays them chronologically through the scoring engine
- Writes the resulting game records + ratings into SQLite
- This becomes a one-time migration and an optional ongoing sync
Expand the API surface:
| Method | Route | Description |
|---|---|---|
GET |
/api/scores |
All player scores, sorted desc |
GET |
/api/players/:name |
Player profile + game history |
GET |
/api/games |
Paginated game history |
GET |
/api/games/:id |
Single game detail with placements |
POST |
/api/games |
Submit a new game (auth required) |
POST |
/api/refresh |
Re-sync from Sheets (auth required) |
Action:
- Simple API key auth for write endpoints (
POST /api/games,POST /api/refresh) - Key loaded from env var
GUILDMASTER_ADMIN_KEY - Middleware checks
Authorization: Bearer <key>header - No need for full user auth — this is a small group tool
The current landing.tmpl is a good start but needs more pages and interactivity.
| Page | Route | Description |
|---|---|---|
| Leaderboard | / |
Full ranked scoreboard, filterable |
| Player Profile | /players/:name |
Elo history chart, win/loss record, recent games |
| Game History | /games |
Paginated list of all games |
| Game Detail | /games/:id |
Who played, placements, Elo changes per player |
| Submit Game | /submit |
Admin-gated form to enter results |
Current board shows rank + name + score. Add:
- Δ column: Elo change from last game
- Games played count
- Win rate (1st place finishes / total games)
- Sortable columns (client-side)
- Elo trend chart over time (use a lightweight lib like Chart.js or just SVG sparklines)
- Recent game history table (placement, opponents, Elo change)
- Head-to-head record vs other players
- Ordered list with drag-to-reorder (or simple numbered dropdowns)
- Preview of projected Elo changes before submitting
- Confirmation dialog
Keep it server-rendered Go templates + vanilla JS (no build step, minimal deps). Use HTMX for partial page updates if interactivity grows. Avoid a full JS framework unless the complexity demands it.
- Unit tests for scoring engine (already has
store_test.go— expand coverage) - Integration tests for HTTP handlers with an in-memory SQLite DB
- Test the Sheets parser against fixture data
Move all hardcoded values to env vars / a config struct:
SPREADSHEET_ID(currently hardcoded insheets.go)SCOREBOARD_API_KEYGUILDMASTER_ADMIN_KEYDATABASE_PATH(default:./guildmaster.db)PORT(default:8080)
Update Dockerfile to:
- Use multi-stage build
- Mount a volume for the SQLite DB file
- Accept all config via env vars
- Remove
main.goroot-level binary or keep it as a thin wrapper that usesinternal/scoring - Consolidate
cmd/analyzeand the root CLI — they do the same thing - Keep
cmd/serveras the canonical server entry point
Phase 1.1 → 1.2 → 1.3 (foundation, unblocks everything)
Phase 2.1 → 2.2 (API, needed by UI)
Phase 3.1 → 3.2 → 3.3 (UI, iterative)
Phase 4 throughout (tests/config as you go)
- User accounts / login
- Multiplayer game types beyond standard Elo (Commander variants, etc.)
- Mobile app
- Two-headed giant support (currently skipped — revisit later)
- Real-time updates (WebSocket leaderboard)