Arcade-style Gauntlet clone with roguelike dungeon generation, up to 4-player local co-op, written in vanilla JavaScript on an HTML5 Canvas. No build tools, no dependencies — just open index.html.
# any static file server works; two easy options:
python3 -m http.server 8000
# or
npx --yes http-server -p 8000
Then open http://localhost:8000. Opening index.html directly via file:// works for most browsers too.
Each run drops you into a procedurally generated dungeon. Kill enemies, grab loot, find the exit tile — all living players must stand on it to descend. Difficulty scales with floor depth. Shop appears between floors; run ends when every player is dead.
| Player | Move | Attack | Special |
|---|---|---|---|
| P1 | WASD |
Space |
Q |
| P2 | Arrow keys | Enter |
Right Shift |
| P3 | IJKL |
U |
O |
| P4 | Numpad 8/4/5/6 |
Numpad 0 |
Numpad Enter |
| Class | HP | Speed | Damage | Armor | Attack | Special |
|---|---|---|---|---|---|---|
| Warrior (Thor) | 800 | 90 | 30 | 5 | Melee | Shield Bash |
| Valkyrie (Thyra) | 650 | 110 | 20 | 4 | Melee | Shield Block |
| Wizard (Merlin) | 400 | 100 | 35 | 1 | Ranged | Nova Blast |
| Elf (Questor) | 550 | 140 | 18 | 2 | Ranged | Rapid Shot |
- Grunt — basic melee fodder
- Ghost — phases through walls
- Demon — ranged projectiles, heavy damage
- Sorcerer — ranged + teleports
- Lobber — arcing projectiles
- Generators — spawn enemies until destroyed (capped at 6 concurrent children, 180 global)
- Death — invulnerable stalker that appears from floor 5 onward, drains HP on contact, despawns after ~25s
Items drop in four rarities (common / uncommon / rare / legendary) across four slots: weapon, armor, boots, ring. Also pickups for food (heal), potions, keys (unlock doors), and gold (currency in the between-floor shop).
Single HTML page, 18 JS modules loaded as classic <script> tags — no modules, no bundler. Files share globals defined in state.js and constants.js.
index.html entry point, canvas 800x600, loads scripts in order
js/
constants.js tile types, class defs, controls, rarity tables
state.js mutable globals (gameState, players, enemies, ...)
main.js requestAnimationFrame loop, dispatches by gameState
input.js keyboard handling, clearJustPressed
update.js per-frame update for 'playing' state
render.js all drawing (~49KB — largest file by far)
map.js procedural dungeon generation, rooms + corridors
pathfinding.js BFS distance maps for enemy AI
entities.js spawn items/enemies, createEnemy, createPlayer
enemies.js enemy/generator/death-entity AI
combat.js player attacks, damage, knockback
projectiles.js projectile physics
particles.js visual particles
juice.js screen shake, hit-stop, damage numbers
loot.js loot generation and stat rolls
audio.js WebAudio sound effects
gameflow.js floor exit, advance, game-over, camera
warnings.js on-screen warning banners (e.g. "DEATH HAS ENTERED")
main.js is a plain state machine:
title → charselect → playing ⇄ shop
⇄ lootscreen → (advance floor) → playing
→ gameover
Each state has its own update* and render* function.
- Add an entry to
CLASS_DEFSinconstants.js(stats, color, special name/cooldown). - Add the key to
CLASS_KEYS. - Wire the special into
combat.jsif it does anything beyond the generic attack. - Add any class-specific rendering in
render.js.
- Add a base stat block to the
baseobject increateEnemy(entities.js). - Add it to the spawn-roll in
spawnEnemies(same file). - Add rendering in
render.js.
Most numbers live in constants.js and the base / CLASS_DEFS objects in entities.js and constants.js:
TILE,MAP_W,MAP_H— tile size and dungeon dimensionsGEN_GLOBAL_CAP,GEN_CONCURRENT_CAP— enemy spawn capsdiffMultinspawnEnemies/createEnemy— per-floor scaling (+30% count, +25% stats per floor)- Death entity triggers at floor 5 after 30s idle
No license file. All rights reserved unless the author says otherwise.