Paul's Programming Notes PostsRSSGithub

JavaScript roguelike development in 2026

I’ve been building Brute Slicer, a turn-based tile dungeon crawler in TypeScript. Sharing a browser game is just sending a link, and it opens on a phone without an app store. Godot and Unity export to the web too, but that ships the engine itself as WebAssembly, and Godot’s export requires WebGL 2.0 in the browser. The game state only advances when the player takes a turn, and the browser handles the animations, so there’s no per-frame loop for an engine to run.

rot.js is the standard roguelike toolkit and it covers the algorithm layer. I use it mostly for pathfinding, and it also handles dungeon generation, field of view and turn scheduling, with no dependencies of its own. Its README calls the project feature-complete, and the last release was a maintenance one in November 2024.

import { Path } from 'rot-js'

export function findPath(from: Pos, to: Pos, passable: (x: number, y: number) => boolean): Pos[] {
  const astar = new Path.AStar(to.x, to.y, passable, { topology: 4 })
  const path: Pos[] = []
  astar.compute(from.x, from.y, (x, y) => path.push({ x, y }))
  return path
}

Phaser and Excalibur are both actively developed, and either one hands you a renderer, an input system and a camera. None of them has the RPG systems layer, so turn-based combat resolution, the inventory and equipment panels, save and load, and telegraphed enemy intent are all mine to write. I render SVG rather than canvas, so the camera that follows the player is mine too. rot.js never had one.

RPG-JS comes closest, with inventory, skills, save and load, and prebuilt GUI screens. It’s built for RPG Maker-style games with maps you draw in the Tiled editor, and this game generates its floors from a seed. The rest of what turns up is abandoned. The one npm package named for the combat half, turn-based-combat-framework, last published in November 2018. Malwoden, the newer take on rot.js, last released in January 2022. The rotjs topic on GitHub is mostly finished games rather than pieces you can pull out of one.

So everyone seems to write it again, which surprised me given how many browser roguelikes are out there. My guess is that these games get finished as monoliths and nobody goes back to extract the reusable half.

The route I’ve settled on keeps the game logic in a pure sim/ module with no React and no DOM in it, and a React interface reads from it and renders SVG. There are four runtime dependencies: react, rot-js, pure-rand for the seeded RNG, and zod to validate an imported save string. rot.js has its own RNG, but it’s a global singleton, and the sim keeps all of its state explicit, so a run replays identically from its seed and the whole thing unit-tests without a browser.