A Dwarf Fortress-inspired strategy sim with ASCII canvas rendering, procedural world generation, and a Supabase backend.
Play it live: https://psoren.github.io/pwarf/
Devlog: https://psoren.github.io/pwarf/blog/ — hourly digest of merged changes, auto-generated by GitHub Actions
pWarf is a browser-based colony sim where you manage a group of dwarves, dig into mountains, build structures, and survive. The game features:
- Procedural world generation — Simplex noise generates biomes (plains, mountains, forests, deserts, tundra, swamps, volcanoes) with smooth transitions
- Fortress building — Mine stone, build walls/floors/beds/wells/mushroom gardens, create stockpiles
- Dwarf AI — Dwarves autonomously eat, drink, sleep, and wander. They claim and execute player-designated tasks (mining, building, hauling)
- Needs and stress — Dwarves have hunger, thirst, energy, social, purpose, and beauty needs. Unmet needs cause stress
- Cave exploration — Discover cave entrances on the surface, explore underground caverns with ore and gem veins
- Combat — Monsters spawn and attack your fortress. Dwarves fight back with skill-based combat
- Sound engine — Procedural audio with ambient soundscapes and event-driven sound effects
- Node.js 18+
- npm 9+
- A Supabase project with the schema applied (see Database Setup)
# 1. Install dependencies
npm install
# 2. Build all workspaces
npm run build
# 3. Set up environment variables (see below)
# 4. Start the frontend
npm run dev:appOpen the URL printed by Vite (usually http://localhost:5173).
Click Generate World, wait for the tiles to populate, then pan around with WASD or click-drag. Select a non-ocean tile and click Embark to create your fortress.
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
Copy from app/.env.example and fill in your Supabase project values.
Set these as shell environment variables or in a .env file:
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_KEY=your-service-role-key
CIVILIZATION_ID=uuid-of-civilization
WORLD_ID=uuid-of-world
Then run:
npm run dev:simpwarf/
├── app/ React + Vite frontend (canvas ASCII renderer)
├── sim/ Node.js headless simulation engine
├── shared/ Shared TypeScript types and constants
├── supabase/ Database migrations
└── docs/ Documentation (design docs, brainstorming, system overviews)
| Key | Action |
|---|---|
| WASD / Arrows | Pan the map |
| Tab | Toggle fortress/world view |
| < | Go up one z-level |
| > | Go down one z-level |
| [ | Toggle left panel |
| ] | Toggle right panel |
| Click + drag | Pan the map |
| Key | Action |
|---|---|
| Space | Pause / unpause |
| 1 | Normal speed (1x) |
| 2 | Fast speed (2x) |
| 5 | Fastest speed (5x) |
| Key | Action |
|---|---|
| m | Mine / dig |
| b | Open build menu |
| S (shift+s) | Designate stockpile |
| D (shift+d) | Deconstruct |
| O (shift+o) | Smooth stone |
| E (shift+e) | Engrave |
| f | Farm (till soil) |
| p | Task priorities |
| Escape | Cancel current designation |
Click and drag on the map to designate an area after selecting a mode.
Apply migrations to your Supabase project using the Supabase CLI:
supabase db pushOr run the SQL files in supabase/migrations/ manually in the Supabase SQL editor, in order.
| Command | Description |
|---|---|
npm install |
Install all workspace dependencies |
npm run build |
Typecheck and build all workspaces |
npm run dev:app |
Start the frontend dev server |
npm run dev:sim |
Start the simulation engine |
npm test |
Run all tests |
npm test --workspace=sim |
Run sim tests only |
npm test --workspace=shared |
Run shared tests only |
The sim engine runs as a fixed-timestep loop, executing phases in deterministic order each tick:
- Need decay — Hunger, thirst, energy decrease over time
- Deprivation — Stress increases when needs are critically low
- Task claiming — Idle dwarves claim pending player-designated tasks
- Task execution — Dwarves move to task targets and perform work
- Task completion — Finished tasks modify the world (mine tiles, build structures)
- Monster spawning — Periodic hostile creature generation
- Combat — Melee resolution between dwarves and monsters
All game state syncs to Supabase in periodic batches. The frontend receives live updates via the sim snapshot system.
Tests use Vitest and live next to source files as *.test.ts. The sim is fully headless — no browser dependencies, seeded RNG for deterministic results.
npm test # all tests
npm test --workspace=sim # sim only
npm test --workspace=shared # shared only