{"id":51863970,"url":"https://github.com/kshru9/tic-tac-toe-lila","last_synced_at":"2026-07-24T13:01:51.696Z","repository":{"id":349172878,"uuid":"1199802426","full_name":"kshru9/tic-tac-toe-lila","owner":"kshru9","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-04T15:22:30.000Z","size":112,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-04T17:02:50.484Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kshru9.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-02T18:14:21.000Z","updated_at":"2026-04-04T15:22:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kshru9/tic-tac-toe-lila","commit_stats":null,"previous_names":["kshru9/tic-tac-toe-lila"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kshru9/tic-tac-toe-lila","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kshru9%2Ftic-tac-toe-lila","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kshru9%2Ftic-tac-toe-lila/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kshru9%2Ftic-tac-toe-lila/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kshru9%2Ftic-tac-toe-lila/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kshru9","download_url":"https://codeload.github.com/kshru9/tic-tac-toe-lila/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kshru9%2Ftic-tac-toe-lila/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35842301,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-24T02:00:07.870Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-24T13:01:51.606Z","updated_at":"2026-07-24T13:01:51.681Z","avatar_url":"https://github.com/kshru9.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LILA Multiplayer Tic-Tac-Toe (Server-Authoritative Nakama)\n\nA production-minded, server-authoritative multiplayer Tic-Tac-Toe built with **React + TypeScript + Vite** on the frontend and **Nakama** on the backend.\n\nThis submission is designed to show more than a working board. It demonstrates authoritative multiplayer state management, rooming/matchmaking, reconnect handling, timed mode, and reviewer-friendly deployment/documentation.\n\n---\n\n## Live Deployment\n\nReplace these before submission:\n\n- **Live Frontend (GitHub Pages):** https://kshru9.github.io/tic-tac-toe-lila/\n- **Deployed Nakama Endpoint (Railway):** https://handsome-courage-production-d68e.up.railway.app/\n- **Demo Video (Loom):** https://www.loom.com/share/742fc09035ff42299802a0f32351d3ba\n\n---\n\n## Assignment Coverage\n\n### Core requirements\n- ✅ Responsive UI optimized for mobile devices\n- ✅ Real-time multiplayer game state updates\n- ✅ Player information and match status visibility\n- ✅ Server-authoritative game logic on Nakama\n- ✅ Server-side move validation and cheat prevention\n- ✅ Room creation\n- ✅ Automatic matchmaking / Quick Play\n- ✅ Room discovery and joining\n- ✅ Graceful disconnect / reconnect handling\n- ✅ Public frontend deployment + backend deployment documentation\n\n### Bonus / optional features implemented\n- ✅ Concurrent room isolation through one Nakama match instance per room\n- ✅ Timed mode with **30-second server-authoritative turns**\n- ✅ Automatic timeout forfeit\n- ✅ Mode-aware matchmaking (`classic` vs `timed`)\n- ✅ Persistent leaderboard with wins / losses / draws / streaks\n- ✅ QR-based room sharing\n- ✅ Hidden debug overlay via `?debug=1`\n- ✅ Rematch handshake flow\n- ✅ Refresh-to-resume / session persistence\n\n---\n\n## Screenshots\n\n### Lobby Interface\n![Lobby Interface](web/public/screenshots/lobby.png)\n*Players can enter nicknames and choose between Quick Play, Create Room, Join by Code, or Room Discovery.*\n\n### Classic Match Gameplay\n![Classic Match Gameplay](web/public/screenshots/match-classic.png)\n*Real-time Tic-Tac-Toe gameplay with turn indicators and board state in classic mode.*\n\n### Winning interface \n![Timed Match Gameplay](web/public/screenshots/match-timed.png)\n*Visual interface showing winning screen*\n\n### Timed Match Gameplay  \n![Timed Match Gameplay](web/public/screenshots/timed.png)\n*Timed mode with server-authoritative 30-second turn deadlines and countdown display.*\n\n### Leaderboard\n![Reconnect Grace Period](web/public/screenshots/reconnect-grace.png)\n*Leaderboard showing the ranked players and their streak*\n\n---\n\n## Tech Stack\n\n### Frontend\n- React\n- TypeScript\n- Vite\n- CSS-based responsive/mobile-first UI\n\n### Backend\n- Nakama TypeScript runtime\n- Server-authoritative match handler\n- RPC endpoints for rooming, matchmaking, and leaderboard access\n\n### Infrastructure\n- Docker Compose for local development\n- PostgreSQL for Nakama persistence\n- Railway for backend deployment\n- GitHub Pages for frontend deployment\n\n---\n\n## Repository Structure\n\n```text\ntic-tac-toe/\n├── README.md\n├── .env.example\n├── docker-compose.yml\n├── railway.json\n├── DEPLOYMENT-STEPS.md\n├── FLAG-FIX-SUMMARY.md\n├── assets/\n│   ├── screenshot1-lobby.png\n│   ├── screenshot2-gameplay.png\n│   ├── screenshot3-win.png\n│   └── screenshot4-reconnect.png\n├── scripts/\n│   ├── dev.sh\n│   └── deploy.sh\n├── nakama/\n│   ├── Dockerfile\n│   ├── package.json\n│   ├── tsconfig.json\n│   ├── start.sh\n│   ├── start-railway.sh\n│   └── src/\n│       ├── index.ts\n│       ├── rpc.ts\n│       ├── gameRules.ts\n│       ├── ticTacToeMatch.ts\n│       └── leaderboard.ts\n└── web/\n    ├── package.json\n    ├── vite.config.ts\n    ├── index.html\n    ├── public/\n    │   └── screenshots/\n    │       ├── lobby.png\n    │       ├── create-room-qr.png\n    │       ├── match-classic.png\n    │       ├── match-timed.png\n    │       ├── reconnect-grace.png\n    │       ├── leaderboard.png\n    │       └── debug-overlay.png\n    └── src/\n        ├── main.tsx\n        ├── App.tsx\n        ├── Lobby.tsx\n        ├── MatchView.tsx\n        ├── LeaderboardView.tsx\n        ├── Board.tsx\n        ├── nakamaClient.ts\n        ├── types.ts\n        └── theme.css\n```\n\n---\n\n## Architecture Overview\n\n### High-level model\n\n* The **client renders state and sends intents**.\n* The **Nakama match handler owns gameplay truth**.\n* The server validates moves, applies legal state transitions, and broadcasts canonical match state.\n* The client never authoritatively decides wins, turn ownership, timeout outcomes, or reconnect outcomes.\n\n### Why this architecture\n\nThis design keeps the trust boundary clear:\n\n* **Frontend responsibilities**\n\n  * nickname entry\n  * authentication/session restore\n  * room/join UX\n  * render authoritative state\n  * send move/rematch intents\n  * show pending / rejected / reconnect / timeout messaging\n\n* **Backend responsibilities**\n\n  * room lifecycle\n  * seat ownership\n  * turn ownership\n  * move validation\n  * win/draw detection\n  * reconnect grace\n  * timed-mode deadlines\n  * timeout and disconnect forfeits\n  * leaderboard persistence\n\n---\n\n## Gameplay and Rooming Flow\n\n### 1. Quick Play\n\nQuick Play attempts to find a compatible public waiting room for the selected mode. If none exists, it creates one.\n\n### 2. Create Room\n\nA player can create a room and receive:\n\n* a 6-character room code\n* a shareable link\n* a QR code for quick join\n\n### 3. Join by Code\n\nAnother player can join directly using the room code.\n\n### 4. Room Discovery\n\nPublic waiting rooms can be listed and joined from the lobby.\n\n### 5. Deep Link Join\n\nRoom links use query parameters like:\n\n```text\n?room=ABC123\u0026mode=classic\n?room=ABC123\u0026mode=timed\n```\n\n### 6. Match Lifecycle\n\nThe gameplay model includes these states:\n\n* `waiting_for_opponent`\n* `ready`\n* `in_progress`\n* `reconnect_grace`\n* `completed`\n* `rematch_pending`\n\n---\n\n## Server-Authoritative Design\n\n### Move validation\n\nAll moves are validated on the server before they are applied.\n\nExamples of rejection reasons include:\n\n* not your turn\n* cell already occupied\n* game not in progress\n* invalid payload\n* stale state / duplicate action\n* reconnect in progress\n\nThe client sends **move intent**, then waits for:\n\n* authoritative state sync, or\n* an action rejection payload\n\n### Protocol hardening\n\nThe implementation includes:\n\n* monotonic state versioning\n* action IDs\n* expected version / expected turn checks\n* duplicate/stale action rejection\n\nThis makes the multiplayer protocol more reliable under reconnects, retries, and delayed messages.\n\n---\n\n## Timed Mode\n\nTimed mode is fully server-authoritative.\n\n* Turn budget: **30 seconds**\n* The server owns the deadline via an absolute turn timestamp\n* The client renders countdown based on server state\n* If a player does not act before deadline, the server applies **timeout forfeit**\n\nThis avoids client-side timer authority and keeps timing fair for both players.\n\n---\n\n## Reconnect, Refresh, and Failure Handling\n\n### Reconnect grace\n\nIf a player disconnects during a live match:\n\n* their seat remains reserved temporarily\n* the match enters reconnect grace\n* the opponent sees reconnect state/countdown\n* if the player returns in time, the match resumes\n* if not, the server resolves disconnect forfeit\n\n### Refresh-to-resume\n\nThe client persists:\n\n* identity\n* session\n* active match context\n* pending join / room intent\n\nRefreshing the page attempts to resume the active match safely rather than dropping the player back to square one.\n\n### Intentional failure states\n\nThe UI is designed to make failure visible rather than silent:\n\n* room not found\n* room full\n* invalid room code\n* stale join link\n* move rejected\n* reconnect in progress\n* timeout / disconnect outcomes\n\n---\n\n## Leaderboard\n\nThe leaderboard is an additive bonus feature and is updated only from authoritative match completion.\n\nTracked statistics include:\n\n* wins\n* losses\n* draws\n* current streak\n* best streak\n\nThe leaderboard UI is read-only from the client side. Writes happen on the backend after eligible match completion.\n\n---\n\n## Rematch\n\nAfter a completed match, players can rematch through a **two-party handshake**:\n\n* one player requests rematch\n* the other player accepts\n* the backend resets board state and starts a fresh match in the same room context\n\nThis keeps rematch behavior deterministic and avoids accidental resets.\n\n---\n\n## Debug Overlay\n\nA hidden reviewer/debug feature is available through:\n\n```text\n?debug=1\n```\n\nWhen enabled during a match, it can expose useful authoritative fields such as:\n\n* match ID\n* room code\n* mode\n* phase\n* version\n* reconnect deadline\n* turn deadline\n* pending move\n* stats commit state\n\nThis is useful for demos and debugging without requiring a separate debug RPC surface.\n\n---\n\n## Flow Diagram\n\n```mermaid\nsequenceDiagram\n  participant A as Player A (Web)\n  participant B as Player B (Web)\n  participant NK as Nakama (RPC + Match)\n  participant DB as Postgres\n\n  A-\u003e\u003eNK: quick_play / create_room / join_room\n  NK--\u003e\u003eA: matchId, roomCode, mode\n  A-\u003e\u003eNK: joinMatch(matchId, nickname)\n  B-\u003e\u003eNK: joinMatch(matchId, nickname)\n  NK--\u003e\u003eA: STATE_SYNC\n  NK--\u003e\u003eB: STATE_SYNC\n\n  A-\u003e\u003eNK: MOVE_INTENT(actionId, expectedVersion, expectedTurn, cellIndex)\n  NK-\u003e\u003eNK: validate + apply\n  alt Accepted\n    NK--\u003e\u003eA: STATE_SYNC\n    NK--\u003e\u003eB: STATE_SYNC\n  else Rejected\n    NK--\u003e\u003eA: ACTION_REJECTED\n  end\n\n  opt Completion\n    NK-\u003e\u003eDB: leaderboard persistence\n  end\n```\n\n---\n\n## RPC Endpoints\n\nThe backend registers these RPCs:\n\n* `health`\n* `create_room`\n* `join_room`\n* `list_rooms`\n* `quick_play`\n* `get_leaderboard`\n\n---\n\n## Match Opcodes\n\nThe gameplay protocol uses these opcodes:\n\n* `1` — `MOVE_INTENT`\n* `2` — `STATE_SYNC`\n* `3` — `ACTION_REJECTED`\n* `4` — `REMATCH_REQUEST`\n* `5` — `REMATCH_ACCEPT`\n\n---\n\n## Key Code References\n\n### Backend\n\n* `nakama/src/index.ts` — runtime initialization, RPC registration, match registration\n* `nakama/src/rpc.ts` — room creation, room join, room listing, quick play, leaderboard read\n* `nakama/src/gameRules.ts` — pure board/game outcome logic\n* `nakama/src/ticTacToeMatch.ts` — authoritative match loop, reconnect grace, timer handling, rematch flow\n* `nakama/src/leaderboard.ts` — leaderboard persistence and ranking\n\n### Frontend\n\n* `web/src/App.tsx` — app bootstrap, deep-link handling, top-level state/view transitions\n* `web/src/Lobby.tsx` — lobby actions, mode selection, QR flow, room discovery\n* `web/src/MatchView.tsx` — match UI, timer rendering, reconnect status, rejection messages, debug overlay\n* `web/src/LeaderboardView.tsx` — leaderboard screen\n* `web/src/nakamaClient.ts` — client auth/session restore, RPC calls, socket lifecycle, resume logic\n* `web/src/types.ts` — shared protocol/types\n\n---\n\n## Local Setup\n\n### Prerequisites\n\n* Docker\n* Docker Compose\n* Node.js 18+\n* Git\n\n### 1. Clone and prepare environment\n\n```bash\ngit clone https://github.com/kshru9/tic-tac-toe-lila.git\ncd tic-tac-toe-lila\ncp .env.example .env\n```\n\n### 2. Build the Nakama runtime\n\n```bash\ncd nakama\nnpm install\nnpm run build\ncd ..\n```\n\n### 3. Start local backend services\n\n```bash\ndocker-compose up\n```\n\nThis starts:\n\n* PostgreSQL\n* Nakama\n\n### 4. Start the frontend\n\nIn another terminal:\n\n```bash\ncd web\nnpm install\nnpm run dev\n```\n\n### Local URLs\n\n* **Frontend:** `[private-url-redacted]`\n* **Nakama HTTP:** `[private-url-redacted]`\n* **Nakama WebSocket:** `ws://[private-host-redacted]:7350`\n* **PostgreSQL:** `[private-host-redacted]:5432`\n\n---\n\n## Environment Variables\n\n### Frontend\n\n| Variable                     |        Required | Purpose                                            |\n| ---------------------------- | --------------: | -------------------------------------------------- |\n| `VITE_NAKAMA_HOST`           |             Yes | Nakama host for HTTP / socket connection           |\n| `VITE_NAKAMA_PORT`           |              No | Nakama port, defaults to `7350`                    |\n| `VITE_NAKAMA_SERVER_KEY`     |             Yes | Must match backend server key                      |\n| `VITE_NAKAMA_USE_SSL`        |             Yes | `false` locally, `true` in production              |\n| `VITE_NAKAMA_WEBSOCKET_PORT` |              No | Optional; usually same as HTTP port                |\n| `VITE_BASE_PATH`             | Production only | Needed for GitHub Pages project path               |\n| `VITE_APP_TITLE`             |              No | Present in env template, not currently used by app |\n\n### Backend / deployment\n\n| Variable              |                       Required | Purpose                                            |\n| --------------------- | -----------------------------: | -------------------------------------------------- |\n| `DATABASE_URL`        |                           Yes* | Primary Railway/Postgres connection input          |\n| `NAKAMA_RUNTIME_PATH` |                            Yes | Path to compiled runtime modules                   |\n| `NAKAMA_SERVER_KEY`   |                            Yes | Must match frontend key                            |\n| `NAKAMA_CORS_ORIGIN`  | Recommended in deployment docs | Configure CORS appropriately for deployed frontend |\n\n* Railway may provide either `DATABASE_URL` or equivalent `PG*` variables depending on setup.\n\n---\n\n## How to Test Multiplayer\n\nUse two browser contexts:\n\n* one normal window\n* one incognito window\n\nRecommended test checklist:\n\n1. **Quick Play**\n\n   * Player A joins Quick Play\n   * Player B joins Quick Play\n   * Verify both enter the same match\n\n2. **Create private room**\n\n   * Create room\n   * copy room code / share link\n   * verify room code is visible\n\n3. **Join by code**\n\n   * Enter valid code in second window\n   * verify match starts\n\n4. **Room discovery**\n\n   * Create a public room\n   * refresh room list\n   * join from second client\n\n5. **Authoritative rejection**\n\n   * attempt move out of turn\n   * click an occupied cell\n   * verify rejection messaging\n\n6. **Reconnect grace**\n\n   * disconnect one player mid-match\n   * verify reconnect state/countdown\n   * reconnect within grace window\n\n7. **Refresh-to-resume**\n\n   * refresh during active match\n   * verify resume flow attempts to restore state\n\n8. **Timed mode**\n\n   * select `timed`\n   * verify countdown appears\n   * let timer expire\n   * verify timeout forfeit result\n\n9. **Rematch**\n\n   * finish a match\n   * request rematch from one side\n   * accept from the other\n   * verify board resets\n\n10. **Leaderboard**\n\n* finish match\n* open leaderboard\n* verify updated stats\n\n11. **Debug overlay**\n\n* add `?debug=1`\n* verify debug panel appears during match\n\n---\n\n## Deployment\n\n### Backend: Railway\n\nThe repository is configured to deploy Nakama to Railway using:\n\n* `railway.json`\n* `nakama/Dockerfile`\n* `nakama/start-railway.sh`\n\nExpected deployment shape:\n\n* Railway service root directory: `/nakama`\n* PostgreSQL attached to Railway project\n* environment variables configured for database + server key + runtime path\n* HTTPS/WSS endpoint exposed publicly\n\n### Frontend: GitHub Pages\n\nThe repository includes a GitHub Actions workflow for Pages deployment:\n\n* `.github/workflows/deploy-pages.yml`\n\nImportant notes:\n\n* the production base path is derived from repo name via `VITE_BASE_PATH`\n* the primary intended Pages workflow is `deploy-pages.yml`\n* `.github/workflows/static.yml` exists but appears to be a conflicting/duplicate static workflow and should not be the primary deployment path\n\n### Health check\n\nThe repo includes:\n\n* a custom `health` RPC in the Nakama runtime\n* a Railway healthcheck path configured at `/health`\n\nFor submission wording, it is safest to describe `/health` as the configured deployment healthcheck path and verify the exact deployed response contract in the live environment.\n\n---\n\n## Submission Checklist\n\nBefore submitting:\n\n* add live frontend URL\n* add live Nakama endpoint\n* add Loom demo link\n* add screenshots\n* confirm production frontend connects successfully to deployed Nakama\n* confirm CORS is configured correctly for the deployed frontend domain\n* verify health check in deployed environment\n* verify HTTPS/WSS connectivity\n* re-test timed mode, reconnect grace, leaderboard update, and rematch in the live environment\n\n---\n\n## Known Notes / Careful Wording\n\n* The code/config inspection indicates timed mode, leaderboard, rematch, QR join, and debug overlay are implemented, even though the older README underreported them.\n* `VITE_NAKAMA_HOST` should be set explicitly; the client should not rely on guessing production backend location.\n* `NAKAMA_SERVER_KEY` should be changed from insecure defaults in real deployment.\n* `VITE_NAKAMA_WEBSOCKET_PORT` exists in config but may not materially differ from the main Nakama port in practice.\n* CORS setup should be verified against the actual deployed frontend origin.\n* Production deployment status, exact public URLs, and exact `/health` response shape should be manually confirmed before final submission/demo.\n\n---\n\n## Why this submission is structured this way\n\nThis project was intentionally built as a compact but production-minded multiplayer system:\n\n* lean repo shape\n* clear trust boundary\n* explicit match lifecycle\n* reconnect-aware UX\n* mode-aware matchmaking\n* additive bonus features without destabilizing the core\n\nThe goal is not to over-engineer Tic-Tac-Toe. The goal is to show solid multiplayer/backend thinking, good product judgment, and end-to-end ownership.\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkshru9%2Ftic-tac-toe-lila","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkshru9%2Ftic-tac-toe-lila","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkshru9%2Ftic-tac-toe-lila/lists"}