Skip to content

PJensen/JSHack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

353 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

JSHack ๐ŸŽฎโšก

A mobile-first roguelike built to be hacked.

Pure JavaScript. Zero dependencies. No build step. Serve the folder, open the page, and start playing. Edit a file, hit refresh, see your changes instantly. This is JavaScript the way it was meant to be: hackable, transparent, and fun.


What makes this different?

๐Ÿš€ Zero build step, zero dependencies

No npm. No webpack. No babel. No TypeScript. Just pure ES modules that run directly in your browser. Serve the project folder, open the page, and you're playing. Edit src/rules/systems/movementSystem.js, refresh, and your changes are live. No waiting, no compilation, no mysterious build errors.

# Any static HTTP server works. ES modules require it.
python3 -m http.server 8000
# Then open http://localhost:8000

๐Ÿ“ฑ Mobile-first, touch-first

This isn't "mobile-friendly" โ€” it's mobile-native. Touch controls are primary. Designed for phones. Keyboard works great too, but we built this for your thumb on a subway, not a mouse at a desk.

  • Tap sides to move (cardinal directions)
  • Double-tap to pick up items
  • Pinch to zoom
  • Swipe right for inventory, down for messages

All UI elements are finger-sized. No hover states. No tiny tap targets. Just intuitive touch controls that work.

๐ŸŽฒ Deterministic and replayable

Every run is seeded (default: 0xC0FFEE โ˜•). Same seed + same inputs = same outcome, every time. Debug by replaying. Share interesting seeds. Build regression tests that actually work.

const world = new World({ seed: 0xDEADBEEF });
world.tick(1); // Perfectly reproducible

๐Ÿ—๏ธ ECS architecture you can actually see

Built on a clean Entity-Component-System architecture. Not hidden behind abstractions โ€” you can see exactly how entities, components, and systems work. Want to understand ECS? Read the source. It's just JavaScript.

  • Entities are IDs
  • Components are plain objects
  • Systems are functions that query and modify components

No magic. No framework ceremony. Just composable logic.

๐Ÿงฉ One file = one idea

Every file has a single, clear purpose. Want to change how movement works? Open movementSystem.js. Want to add a new monster? Create an archetype in Creatures.js. The codebase is organized for humans, not bundlers.

๐ŸŽจ Church and State separation

Deterministic simulation (rules/) is completely separate from rendering (display/). The rules layer has zero DOM, zero rendering, zero async code. It's pure, testable logic. The display layer consumes a stable snapshot and renders however it wants. You can swap renderers, run headless tests, or replay demos without touching game logic.

See SEPARATION_MANIFEST.md for the philosophy.

๐Ÿ”ง Built to be hacked

We're not "shipping a product" โ€” we're exploiting JavaScript for fun. Clever tricks encouraged. Weird experiments welcome. Open your console, poke around, break things, fix them. This is a playground.

Every decision prioritizes hackability:

  • No transpilation (edit and refresh)
  • No bundling (import real files)
  • No frameworks (see the actual code)
  • No abstractions (everything is transparent)

If you can console.log it, you can understand it.


Getting Started

Play it in 30 seconds

git clone https://github.com/PJensen/JSHack.git
cd JSHack
python3 -m http.server 8000
# Open http://localhost:8000

Any static HTTP server works โ€” ES modules need to be served over HTTP, not opened as file:// URLs. No npm install. No npm run build. Just serve and play.

Controls

Touch / Mobile (primary):

  • Tap screen sides: Move in that direction
  • Double-tap: Pick up items at your feet
  • Pinch: Zoom in/out
  • Swipe right: Open inventory
  • Swipe down: Open message log

Keyboard (also works):

  • Arrow keys / WASD / HJKL: Move (pick your poison)
  • . (period): Wait a turn
  • , (comma): Pick up items
  • Q: Drink a potion
  • +/- (or numpad): Zoom in/out
  • 0: Reset zoom
  • X: Camera shake demo (because why not)

Performance tuning

On older phones or just want better framerates? Add URL params:

index.html?quality=low    # Fast mode: no glow, fewer particles
index.html?quality=high   # Full eye candy
index.html?dprCap=1       # Force 1x pixel density (speed boost)

These only affect visuals โ€” the deterministic simulation stays identical.


How It Works

The 30-second architecture tour

src/
  rules/     โ€” Pure deterministic simulation (the roguelike logic)
  bridge/    โ€” Stable contract between rules and display
  display/   โ€” Rendering, particles, camera, input handling
  main/      โ€” Wires everything together

Rules never import Display. Display never imports Rules. They talk through a clean Bridge contract. This keeps the simulation pure and deterministic while letting the visuals do whatever they want.

Turn-based simulation

Strict turn order: player acts โ†’ all monsters act โ†’ effects trigger โ†’ cleanup runs โ†’ back to player. One action per entity per turn. No realtime chaos.

Actions that consume a turn:

  • Moving one tile
  • Attacking something
  • Using an item
  • Waiting (yes, waiting is an action)

Combat rules (D&D-style)

Attacker rolls: d20 + attackBonus
Target has: armorClass

Hit if roll โ‰ฅ AC
Natural 1: always miss
Natural 20: always crit (damage ร— critMult)

Damage: roll(minDamage, maxDamage) - defense

Equipment modifies your stats. Affixes add special effects. Crits feel good.

Systems run in phases

Systems are organized into three phases:

  1. intents: AI, player input, movement, combat, interactions
  2. effects: Status effects, equipment bonuses, hunger, mana regen, spawners
  3. cleanup: Remove dead entities, update spatial index

See scheduler.js for the full registration order. Add your own systems by registering them to a phase. Systems never call other systems โ€” they emit events instead.

Action transactions vs intents

JSHack also has a rules-layer action transaction utility (src/rules/interaction/mutations.js) used by action contexts (item use/apply/eat) for commit-or-cancel behavior.

  • Use intents + phases for system ordering and turn flow.
  • Use action transactions only for local all-or-nothing mutation batches inside a single action resolver.
  • Do not treat action transactions as a second scheduler or engine queue. ECS-js remains the only engine-level deferred command system.

Components are just frozen objects

export const Position = Object.freeze({
  x: 0,
  y: 0,
});

export const Vitality = Object.freeze({
  hp: 10,
  maxHp: 10,
});

That's it. No classes. No inheritance. Just plain data.

Archetypes spawn entities

import { Goblin } from './rules/archetypes/Creatures.js';
import { createFrom } from './lib/ecs-js/archetype.js';

const goblinId = createFrom(world, Goblin, { x: 10, y: 10 });

Archetypes are templates. Spawn as many as you want. Modify their components. They're just entities.


Hacking on JSHack

Make a new monster in 2 minutes

  1. Open src/rules/archetypes/Creatures.js
  2. Copy an existing monster definition
  3. Change the stats (hp, damage, XP, name)
  4. Refresh your browser
  5. Your monster spawns

No compilation. No bundling. Just edit and refresh.

Add a new system

  1. Create src/rules/systems/mySystem.js:

    export function mySystem(world, dt) {
      for (const [id, pos, thing] of world.query(Position, Thing)) {
        // Your logic here
      }
    }
  2. Register it in src/main/scheduler.js:

    import { mySystem } from '../rules/systems/mySystem.js';
    registerSystem(mySystem, 'intents'); // or 'effects' or 'cleanup'
  3. Refresh browser

  4. Your system runs every tick

Debug with determinism

// In your browser console
const world = new World({ seed: 0xC0FFEE });
// ...set up your scenario...
world.tick(1); // Run one turn
world.tick(1); // Run another

// Same seed, same setup โ†’ same results, always

Replay bugs. Share seeds. Build regression tests. Determinism is your superpower.

Emit events, not calls

Systems communicate via events, never direct calls:

// โœ… GOOD: Emit an event
world.emit('combat:hit', { attackerId, targetId, damage });

// โŒ BAD: Call another system
combatSystem(world, dt); // Never do this!

Events keep the scheduler in control and execution order predictable.


What's Inside?

๐ŸŽฎ Core Features

  • Turn-based roguelike gameplay โ€” classic dungeon crawling
  • Monster AI โ€” chase the player, respect obstacles
  • Item system โ€” potions, scrolls, weapons, armor, wands
  • Magic system โ€” fireball, lightning bolt, heal, and more
  • Equipment โ€” weapons, armor, with affix modifiers
  • Status effects โ€” poison, burn, regen, stun
  • Hunger system โ€” eat food or suffer penalties
  • Deity favor โ€” worship gods, gain boons or invoke wrath
  • Pet companions โ€” they follow and fight for you
  • Traps โ€” pressure plates, arrow traps, spike pits
  • Shops โ€” buy and sell items
  • Dungeon generation โ€” procedural levels with rooms and corridors
  • FOV & exploration โ€” shadowcasting visibility, fog of war

๐Ÿ”ง Developer Tools

  • Deterministic replay โ€” seeded RNG for perfect reproducibility
  • Rules profiler โ€” per-system timing (?rulesProfile=1)
  • Event system โ€” inter-system communication without coupling
  • Spatial indexing โ€” fast radius queries for AI and effects
  • Script system โ€” attach behavior to entities without hardcoding
  • Hot reload โ€” edit JS, refresh browser, see changes instantly

๐Ÿ“š Content

  • 26 systems โ€” movement, combat, AI, items, effects, spawning, cleanup
  • 30+ components โ€” Position, Vitality, Inventory, Brain, Equipment, etc.
  • 12 archetype files โ€” Player, Creatures, Items, Tiles, Doors, Stairs, Traps, etc.
  • Spell library โ€” offensive, defensive, utility spells
  • Monster roster โ€” kobolds, goblins, orcs, trolls, and more
  • Item database โ€” consumables, equipment, treasures
  • Deity pantheon โ€” multiple gods with unique mechanics

All data-driven. All modifiable. All in plain JavaScript files.


Project Structure

JSHack/
โ”œโ”€โ”€ index.html              # Entry point (serve over HTTP)
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ rules/              # Pure deterministic simulation
โ”‚   โ”‚   โ”œโ”€โ”€ systems/        # 26 game logic systems
โ”‚   โ”‚   โ”œโ”€โ”€ components/     # 30+ data containers
โ”‚   โ”‚   โ”œโ”€โ”€ archetypes/     # Entity templates
โ”‚   โ”‚   โ”œโ”€โ”€ scripts/        # Behavior hooks (spells, items, traps)
โ”‚   โ”‚   โ”œโ”€โ”€ data/           # Spells, monsters, items, loot tables
โ”‚   โ”‚   โ””โ”€โ”€ environment/    # Dungeon generation, FOV, tiles
โ”‚   โ”œโ”€โ”€ bridge/             # Rules โ†” Display contract
โ”‚   โ”‚   โ””โ”€โ”€ schema/         # WorldView, MapView DTOs
โ”‚   โ”œโ”€โ”€ display/            # Rendering & presentation
โ”‚   โ”‚   โ”œโ”€โ”€ passes/         # Render pipeline (glyphs, VFX, particles)
โ”‚   โ”‚   โ”œโ”€โ”€ camera/         # Camera controller, follow, shake, zoom
โ”‚   โ”‚   โ”œโ”€โ”€ input/          # Touch & keyboard input routing
โ”‚   โ”‚   โ”œโ”€โ”€ ui/             # HUD, inventory, messages, overlays
โ”‚   โ”‚   โ””โ”€โ”€ palette/        # Visual mappings (glyphs, colors)
โ”‚   โ”œโ”€โ”€ main/               # Application wiring
โ”‚   โ”‚   โ”œโ”€โ”€ scheduler.js    # System registration & phases
โ”‚   โ”‚   โ””โ”€โ”€ input/          # Input โ†’ Intent conversion
โ”‚   โ”œโ”€โ”€ shared/             # Pure utilities (math, grid algorithms)
โ”‚   โ””โ”€โ”€ lib/                # Vendored libraries (ecs-js, deity-js)
โ”œโ”€โ”€ tests/                  # Test suite (Deno)
โ”œโ”€โ”€ reference/              # Demos and examples
โ””โ”€โ”€ AGENTS.md               # Guide for AI/autonomous agents

Design principle: Import boundaries enforce separation. Rules can't import Display. Display can't import Rules. Bridge is the contract. See SEPARATION_MANIFEST.md for details.


Philosophy

We believe in:

  • Zero build steps โ€” pure ES modules, instant feedback
  • Determinism โ€” seeded RNG, reproducible runs, testable logic
  • Transparency โ€” no frameworks, no magic, just readable code
  • Hackability โ€” one file = one idea, easy to modify
  • Mobile-first โ€” touch is primary, phones are the platform
  • Fun โ€” we're hacking and exploring JavaScript, not shipping enterprise software

We avoid:

  • โŒ Build tools (webpack, babel, rollup)
  • โŒ Frameworks (React, Vue, Angular)
  • โŒ Dependencies (zero npm packages)
  • โŒ TypeScript (just JavaScript)
  • โŒ Node (we use Deno for tests)
  • โŒ Backwards compatibility hacks (just rework it)

If you can't console.log it and understand it immediately, we're doing it wrong.


Advanced Topics

Status Effects

Apply effects to entities:

const effects = world.get(entityId, ActiveEffects) || { effects: [] };
effects.effects.push({
  key: 'poison',      // Effect type
  turnsLeft: 5,       // Duration
  potency: 2,         // Damage per turn
});
world.set(entityId, ActiveEffects, effects);

Effects tick automatically. Poison deals damage. Regen heals. Stun... stuns. Current statuses are mirrored to the Status component each tick for easy querying.

Scripting System

Attach behavior to entities without hardcoding systems:

// In src/rules/scripts/myScript.js
import { registerScript, ScriptVerb } from '../scripting.js';

registerScript('lightning_wand', {
  [ScriptVerb.ItemUse]: (world, ctx) => {
    const { userId, targetX, targetY } = ctx;
    // Zap logic here
    world.emit('damage', { id: targetId, amount: 10 });
  }
});

// Attach to entity
world.set(wandId, ScriptRef, { ref: 'lightning_wand' });

Scripts respond to verbs: spell:cast, item:use, trap:trigger, affix:onHit, etc.

Event System

Systems communicate via events to avoid coupling:

// System A emits
world.emit('combat:hit', { attackerId, targetId, damage });

// System B listens (installed once at startup)
const INSTALLED = Symbol.for('jshack:combatLogger:installed');
if (!world[INSTALLED]) {
  world[INSTALLED] = true;
  world.on('combat:hit', ({ attackerId, targetId, damage }) => {
    console.log(`Entity ${attackerId} hit ${targetId} for ${damage} damage`);
  });
}

Events flow through the world. Systems stay decoupled. Order is predictable.

Spatial Queries

Fast radius queries for AI, explosions, AOE:

import { forEachInRadius } from './rules/utils/spatialIndex.js';

forEachInRadius(world, x, y, radius, (entityId) => {
  // Apply damage, effects, etc.
});

Maintained automatically by spatialIndexSystem in the cleanup phase.


Testing

We use Deno (not Node) for testing:

deno test --allow-read tests/
deno run tests/movementSystem.test.js

Tests are simple:

import { World } from '../src/lib/ecs-js/index.js';
import { movementSystem } from '../src/rules/systems/movementSystem.js';

const world = new World({ seed: 42 });
// ...setup...
movementSystem(world, 1);
// ...assert...

Deterministic seeds mean tests are reproducible. No flaky tests. No "works on my machine."


Contributing

Contributions that align with the project's vision are welcome. Read CONTRIBUTING.md for setup, guidelines, and expectations. The short version: keep it simple, test your changes, don't break the constraints in TEN_COMMANDMENTS.md.


For AI Agents & Copilots

If you're an autonomous agent or LLM-based copilot reading this:

๐Ÿ‘‰ Read AGENTS.md first. It has everything you need to work with this codebase effectively.

Key rules:

  • ECS-js is external; only fix genuine bugs
  • No system-to-system calls; use events with Symbol tracking
  • Mobile-first always (touch is primary)
  • Church (display) and State (rules) are separated
  • Deno, not Node

Resources

Documentation

Inspiration


License

Human-Scale Source License (HSSL) v1.2

See LICENSE for terms.


Why JSHack?

Because JavaScript doesn't need frameworks and build tools to be powerful. Because mobile deserves great roguelikes. Because deterministic simulations are beautiful. Because one file should equal one idea. Because hacking should be fun.

Serve the folder. Edit a file. Refresh. Hack.

That's it. That's the whole pitch.

Now go build something weird. โšก


Built with โ˜• (0xC0FFEE) and pure JavaScript.

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •