A small rogue-like built in a custom game engine on C#/Blazor. Features a tileset renderer using the great Ultimate Fantasy Tileset from Oryx, and an ASCII renderer, switchable at any time.
- Features
- Screenshots
- Getting started
- How to play
- Tileset
- Project structure
- Architecture
- Game data / configuration
- Contributing
- License
- Procedural dungeon generation, including animated liquid pools - water, mud, acid and lava. Liquid is walkable, but mud/water slow you, acid burns, and lava is insta-death.
- Procedurally-placed fence enclosures - blocks movement and combat, but are see-through.
- A variety of monsters, animated using CSS animations.
- Sounds and music, plus a screen-shake effect on hits.
- Useable environment objects (doors, chests) and classical field-of-view/vision.
- Pick-up items with a lettered inventory: consumable potions and equippable gear (e.g. a ring of protection).
- Basic combat, driven by a Warhammer-inspired ruleset.
- A tick-based turn scheduler - monsters can be faster or slower than the player (e.g. a goblin may get two actions to your one, while an ogre sometimes misses a turn).
- A tileset renderer (using the Ultimate Fantasy Tileset) and an ASCII renderer (old-school format, with colors), switchable client-side - and auto-selected on load based on whether tileset graphics are present.
- Almost everything (monster/hero stats, floor/wall sets, decorations, map generation weights) is data-driven via JSON, rather than hardcoded - see Game data / configuration.
A partially explored sandy dungeon with a number of monsters chasing:
Fighting an Ogre in a muddy chamber, next to a room with murky pool and a coffin:
Finishing off a skeleton in a bloody torch-lit vault, with the stairs to the next level:
The same scene rendered in the ASCII renderer:
- .NET 10 SDK or later.
git clone https://github.com/dontrolle/BlazorRogue.git
cd BlazorRogue
dotnet build
dotnet run
By default the app listens on https://localhost:5001 (see Properties/launchSettings.json) - open either URL in a browser to play.
BlazorRogue.Tests is an xUnit test project covering core, UI-independent game logic (dice/combat math, Configuration JSON parsing, Map geometry helpers, end-to-end dungeon generation smoke tests, and a headless play driver for scripted or randomized play-testing with no browser involved). Run it with:
dotnet test
Code style is enforced via .editorconfig conventions and the compiler's nullable-reference-type warnings (the build is currently warning-free; please keep it that way), plus two dedicated formatting/lint steps. CI runs dotnet build, dotnet csharpier check . (whitespace/layout), dotnet format BlazorRogue.sln style --verify-no-changes (code-style rules, e.g. IDE0001 "simplify name" - .editorconfig promotes every analyzer diagnostic to error, so these fail the build too, not just show as editor hints), then dotnet test on every push/PR to master via GitHub Actions (.github/workflows/CI.yml). Run dotnet format BlazorRogue.sln style followed by dotnet csharpier format . locally before committing to match it - that order matters, since a style fix can touch whitespace and csharpier should get the final say on layout.
Build and run via docker:
docker build -f docker/Dockerfile -t blazorrogue .
docker run -p 8080:8080 blazorrogue
Open http://localhost:8080 in a browser. The image is a Linux container built via multi-stage dotnet publish (see docker/Dockerfile) and never contains the proprietary tileset assets at all - see Tileset - so the containerized game runs in ASCII-renderer mode unless BLAZORROGUE_ART_PATH is pointed at a separately-deployed tileset atlas.
| Action | Keys |
|---|---|
| Move / attack (8-directional) | Numpad, or qweasdzxc |
| Use (open door, chest, etc.) | Shift + move towards the object |
| Pick up item(s) on your tile | g |
| Open inventory | i - then u use/equip, d drop, Esc close |
| Quick use / equip an item | u - opens the inventory ready to use/equip |
| Start a new game | "New game" button (left panel) |
| Toggle tileset/ASCII rendering | CTRL-A |
| Toggle debug mode | CTRL-D |
| Help overlay | ? |
This project employs the excellent Ultimate Fantasy Tileset.
If you own the UF Tileset, see tools/AtlasPacker/README.md for how to build an atlas bundle from it and point the game at it via BLAZORROGUE_ART_PATH. Without one, the game automatically falls back to the built-in ASCII renderer - no setup needed to get playing.
BlazorRogue.csproj / Program.cs Minimal-hosting entry point, unified Blazor Components hosting
BlazorRogue.Tests/ xUnit test project for core game-logic classes (see below)
App.razor / Routes.razor Root HTML shell + router
Pages/ Blazor pages (GamePage.razor is the main game view)
Shared/ Shared Razor components
GameObjects/ GameObject and its subclasses (Moveable, Door, Chest, ...)
Components/ Component base class, InventoryComponent, UseableComponent
Combat/ Combat system, incl. the Warhammer-inspired ruleset (Combat/Warhammer/)
AI/ Monster AI components
Effects/ EffectsSystem (screen shake) and SoundManager (audio cues)
Vision/ Field-of-view implementation
World/ Map, Tile, Decoration and related types; World/Generation/ holds
the map generators (IMapGenerator and implementors)
Rendering/ SpriteAtlas (licensed-tileset atlas -> CSS), AnimationCssGenerator
and HandAuthoredSpriteAnimations (generate @keyframes CSS from
monster/hero, liquid-pool, and torch/hit-flash animation data)
Entities/ Type definitions parsed from configuration (MoveableType,
LevelConfiguration, SettingsMap, etc.), plus Configuration.cs
which parses Data/*.json into them
Sessions/ Per-browser session state that survives page reloads
Utility/ Small standalone helpers (e.g. string extension methods)
Data/ JSON game data: monsters, heroes, floorsets, wallsets,
liquidsets, decorations, items, levels
Game.cs / References.cs Core game state (see Architecture below)
wwwroot/ Static assets: CSS, JS interop, sounds
docker/ Dockerfile (see Docker below)
tools/AtlasPacker/ Dev-machine-only tool that packs the licensed tileset into an
obfuscated atlas bundle for BLAZORROGUE_ART_PATH
One playthrough is rooted in a Game, which owns the map generator, Map, combat system, and Configuration. A Game is created and held by a GameSession for it to survive page reloads (each reload is a fresh Blazor circuit). GameSessionStore keeps sessions in memory, keyed by an id the browser holds in localStorage. Cross-cutting services (Map, Configuration, SoundManager, EffectsSystem) are reached through static holders in References.cs. GameSession.Activate() re-points them at the active game before any handler runs.
Everything placed on the map is a GameObject (Moveable, Door, Chest, …). Inspired by ECS GameObject's have optional Component's added at construction. The Map holds the Tile grid a map generator builds. Vision/ does field-of-view. GameObject's are rendered to Decoration's. Rendering has a tileset path and an ASCII path, chosen client-side.
See ARCHITECTURE.md for the full picture - engine internals, the map-generation and rendering pipelines, and the gotchas worth knowing before a structural change.
Most game content is data, not code - new monsters, heroes, floor/wall sets, and decorations can usually be added without touching C#:
Data/monsters.json,Data/heroes.json- combat stats, AI behavior, sprites/animations.Data/floorsets.json,Data/wallsets.json- tileset mappings and map-generation weights.Data/liquidsets.json- animated liquid pools (water/mud/acid/lava): frames, ASCII colour, and hazard effect.Data/decorations.json- static decorative objects (torches, carpets, etc.).Data/items.json- pickup-able items: name, kind (use_once/equipable), sprite + ASCII glyph, and effect (heal/armour_bonus) with a magnitude.Data/levels.json- one entry per level: dimensions, which map generator to use (by string id), and that generator's own tuning parameters.Data/game-config.json- optional local knobs:starting_level(the levelnoa new game starts on, default0; set to-1000to start on the test level),debug_mode(initial value of the in-game Ctrl-D debug toggle, defaultfalse),seed(an integer; the same seed generates the same dungeon - omitted by default, in which case each game picks a random seed, shown when toggling debug mode with Ctrl-D), anddebug_level(the levelnoCtrl-G jumps to/from once debug mode is on - e.g.-1002for the fence gallery; omitted by default, in which case Ctrl-G does nothing). The file, or any key, may be omitted.
These are parsed in Entities/Configuration.cs via a Parse*Type method per entity kind - follow the existing pattern (and the GetRequiredString/RequireNonNullString helpers for required fields) when adding a new data-driven concept.
A level's map_generator.parameters in levels.json is different from the rest: It's parsed into a SettingsMap (Entities/SettingsMap.cs) - a small recursive value tree of int/double/string/nested-map, read back via typed getters (GetInt, GetDouble, GetString, GetMap), each with a required form and a (key, defaultValue) form. This keeps the JSON-parsing library out of the map generators entirely - see the Map generation and Map-generator parameters entries in CLAUDE.md for the full design.
masteris protected: every change needs to go through a pull request; CI (dotnet build+dotnet test) must pass before merging.- Please keep the build warning-free - nullable reference types are enabled project-wide.
- Add or update tests in
BlazorRogue.Testsfor changes to game logic (combat, configuration parsing, map/dungeon generation, etc.); for changes that are hard to unit test (rendering, Blazor components, JS interop), please describe how you manually verified the change (e.g. a screenshot or a description of in-browser testing) in your PR description. - Small, focused PRs are preferred over large ones, especially for anything touching rendering or the hosting model - those are the areas most likely to have subtle runtime-only breakage that
dotnet buildwon't catch.
This project's code is licensed under the MIT License. The Ultimate Fantasy Tileset assets referenced in Tileset are © Oryx Design Lab and are not covered by this license.


