Browser game inspired by Paper.io: claim territory by enclosing areas with your trail, avoid getting your trail cut by others, and compete against AI bots on a large grid. Single-player / local only — no server or multiplayer.
Stack: React 18, TypeScript, Vite 5, Canvas 2D.
- Prerequisites: Node.js 18+ (LTS recommended), npm.
- Install:
npm install - Dev (HMR):
npm run dev— open the URL Vite prints (usuallyhttp://localhost:5173). - Production build:
npm run build— outputs todist/(tsc -bthenvite build). - Preview build locally:
npm run preview— servesdist/(defaulthttp://localhost:4173).
- Menu: name + skin, then start.
- Controls: arrow keys (or WASD) to turn; you move continuously.
- Goal: expand your colored territory; kills when you cut another player’s trail; you die if someone cuts yours or you hit a border (red edge).
- HUD: map coverage %, kills, survival time; leaderboard lists alive players by territory %.
| Path | Role |
|---|---|
src/main.tsx, src/App.tsx |
App bootstrap; root renders GameCanvas. |
src/components/ |
UI overlays: GameCanvas, StartScreen, HUD, Leaderboard, DeathScreen, SkinSelector. |
src/hooks/useGameState.ts |
Creates Game, subscribes to state for React, exposes startGame / restartMenu. |
src/game/ |
Core simulation: grid, players, bots, territory fill, collisions, camera, renderer, input. |
src/styles/ |
Global / game layout CSS. |
Gameowns the loop: fixed 20 Hz simulation (fixedDt), requestAnimationFrame for draw; React state updates are throttled ~10/s to avoid re-rendering every frame.Grid— cell ownership (territory, trails, empty); map is 200×200 cells, 20px per cell (constants.ts).Player/BotController— movement and bot AI.TerritorySystem— flood-fill / closure when a trail completes a loop back to own territory.CollisionSystem— trail cuts, border deaths.Renderer— all Canvas drawing;Camerafollows the human player with smoothing.
flowchart LR
subgraph react [React]
GameCanvas --> useGameState
useGameState -->|subscribe| Game
end
subgraph loop [Game loop]
Game --> Input
Game --> Grid
Game --> TerritorySystem
Game --> CollisionSystem
Game --> Camera
Game --> Renderer
Game --> BotController
end
Renderer --> Canvas[(canvas)]
GameState (src/game/types.ts) is what subscribe pushes to React:
phase:menu|playing|deadhumanPlayer: position, direction, trail, skin, kills, territory %,aliveleaderboard: sorted alive players by territory %mapCoverage: % of map that is claimed (any player)timeSurvived: seconds since run started
Most knobs live in src/game/constants.ts: grid size, cell size, player speed, BOT_COUNT, bot respawn delay, camera lerp, colors/skins, tick rate.
| Command | What it does |
|---|---|
npm run dev |
Vite dev server + HMR |
npm run build |
Typecheck + production bundle → dist/ |
npm run preview |
Static serve of dist/ |
npm run lint |
ESLint on the repo |
- Black / wrong canvas size:
Gamelistens towindow.resizeand resizes the renderer; check the canvas element in DevTools if layout CSS changed. - “React not updating”: intentional — game state to React is throttled; use
gameRefinuseGameStateif you need to inspectGamefrom a temporaryuseEffectwhile developing. - Logic bugs: follow
Game.tstick order andTerritorySystem/CollisionSystem; grid semantics useEMPTY_CELLandTRAIL_CELL_OFFSETinconstants.ts. - Type errors:
npm run buildrunstsc -b; fix there before relying on the editor only. - Lint:
npm run lint— config iseslint.config.js(flat config).