A strategic hexagonal board game played with dice as soldiers. HexDice blends tactical movement, resource management (dice), and unique class abilities into a fast-paced strategy experience.
- Play:
- Rules: Rules | v2.0 Manual
- Devlog: Development Blog
- Hexagonal Grid: Tactical positioning on a procedurally generated or hand-crafted hex grid.
- Dice as Units: Each face (1-6) represents a unique class with distinct stats (Attack, Armor, Range) and movement patterns.
- Merge & Reroll: Tactical depth through merging units for strength or rerolling to adapt to the battlefield.
- Campaign Mode: A single-player progression system with unique maps, persistent state, and collectible runes.
- Romance Mode: Spectate AI vs AI battles (Romance of the Dice Kingdoms).
- Terrain Effects: Forests, Lakes, Towers, and Mountains influence movement and combat.
game.js: The heart of the game. An Alpine.js component managing the game state, grid, and combat logic.index.html: Main entry point. PWA-ready reactive UI.service-worker.js: Handles offline caching for thousands of assets using chunked loading.
A modular AI system with multiple strategies:
ai-heuristic.js: Primary AI using weight-based evaluation and state simulation.heuristic-profiles.js: Personality profiles (Aggressive, Balanced, Defensive, Chaos).
campaign-manager.js: Handles level progression, RMI (Resource Management Interface) map loading, and player runes.generator.js: Procedural map generation for infinite replayability.
server.ts: Lightweight Deno server for local development.simulate.ts/tournament.ts: Headless Deno scripts for AI benchmarking and game balance analysis.replay.ts: Tool for replaying recorded matches.
/assets: Contains sprites, icons, andassets-manifest.jsonfor PWA caching.generate-assets-manifest.js: Utility to keep the PWA manifest up to date.
HexDice is designed to run with Deno.
-
Start the server:
deno run --allow-net --allow-read server.ts
Alternatively, use
npm startwhich includes necessary unstable flags for KV storage. -
Access the game: Open
http://localhost:1166in your browser.
The project uses npm as a task runner for asset management and automated testing:
- Asset Management:
npm run manifest:gen: Updatesassets/assets-manifest.jsonby scanning all files in/assets. Essential for PWA offline support.npm run sprites:gen: Scans/assets/sprites/sets/and updatesassets/sets.jsonwith the list of available sprite themes.
- Simulations & Benchmarking:
npm run simulate: Runs a standard balance simulation between AI profiles.npm run tournament:full: Runs an exhaustive 20-game tournament between all loaded heuristic profiles.npm run tournament:mutate: Runs a genetic-algorithm style simulation with property mutation.
- Campaign Tools:
npm run campaign:ro_rmi: Generates campaign map levels based on the RMI (Resource Management Interface) logic.
Headless Deno scripts (simulate.ts, tournament.ts) test the game engine, but they stub the browser β they cannot catch Alpine.js reactivity bugs, broken buttons, load-order issues, or anything DOM-related. For those, drive the real UI in Chromium via Playwright.
- Node.js 18+ is required (
node --versionto check). - Install Playwright (already listed in
devDependencies):npm install
- Install the Chromium browser binary (~100 MB, downloaded once into
~/.cache/ms-playwright):On a fresh Linux machine also install OS-level dependencies (needs sudo):npx playwright install chromium
npx playwright install chromium --with-deps
- Verify:
npx playwright --version
- Start a server on port 1166. Either the full dev server:
or, when you only need the static game (no auth/online β auth 404s are expected and ignored by the test):
npm start
python3 -m http.server 1166
- Run:
Any URL query can be passed directly:
npm run test:browser # normal mode (?players=2) npm run test:browser:v2 # Destiny dice (?players=2&version=2) npm run test:browser:epic # Epic 21 preset (?players=2&preset=E_21)
node tests/browser-smoke.mjs "?players=3". A different server address:BASE_URL=http://localhost:8080 npm run test:browser.
The smoke test (tests/browser-smoke.mjs) clicks [Practice] then [Random] and asserts every player rolled and deployed all dice and the phase reached PLAYER_TURN. It fails on any uncaught page error from game code. Exit code 0 = pass.
Guidelines:
- Read game state through Alpine, not the DOM:
window.Alpine.$data(document.querySelector('[x-data]'))returns the live game component (phase,players,hexes, ...). Assert on that β it is far more stable than CSS selectors. - Wait for lazy components.
index.htmlloadshtml/*.htmlpartials vialoadComponentafter Alpine init β wait ~3s (or poll for the button) before clicking anything. - Capture errors: attach
page.on('pageerror')andpage.on('console')listeners beforepage.goto. On a static server, ignoreFailed to load resourcenoise (auth/online endpoints). - Prefer text locators for buttons (
page.locator('button', { hasText: 'Practice' })) β the UI has no test ids and classes change often. - Keep tests headless and assertive β print the game state JSON, exit non-zero on failure, so tests can run in CI or be driven by agents.
- Put new tests in
tests/as plain.mjsscripts (no test framework β matches the project's no-dependency philosophy; Playwright is the only dev dependency).
The /assets directory is organized to support a highly customizable and themeable experience:
/sprites: The core visual library./sets: Contains hundreds of thematic unit sets (e.g.,ro_job,aw_static_blue,tos_mix). Each folder typically contains sprites for dice values 1-6./terrain: Custom textures for Forests, Lakes, and Mountains.
/ro_maps: A collection of static map backgrounds used in Campaign and Romance modes./infographics: High-quality splash screens, rule summaries, and recap images./images: Essential PWA assets (favicons, manifest icons, logo).assets-manifest.json: Auto-generated index used by the Service Worker to ensure every image and script is available offline.
The game engine is encapsulated in the alpineHexDiceTacticGame function within game.js. It manages state reactively via Alpine.js.
setupTerrain(force): Generates the hex grid using either a "Roulette" (sum-of-6) algorithm or by loading an RMI map.rollInitialDice(playerId): Generates the starting pool of units (dice faces) for a player.deployUnit(hexId): Places a selected die from the pool onto the board, initializing its base stats.
The primary entry point for all player interaction. It contextually dispatches actions:
- Phase: SETUP_DEPLOY: Calls
deployUnit()on clicked hexes. - Phase: PLAYER_TURN:
- If no unit is selected: Selects a unit and calls
calcValidMoves(). - If a unit is selected:
- If clicking an empty valid hex: Initiates
MOVE. - If clicking an enemy: Initiates
ATTACK. - If clicking self: Deselects.
- If clicking an empty valid hex: Initiates
- If no unit is selected: Selects a unit and calls
calcValidMoves(unitHexId, isForMerging): Calculates reachable hexes based on unit class (Fencer: Any, Hussar: L-shape, Knight: X-shape).handleCombat(attackerHexId, defenderHexId, type): The core resolution engine. It comparesattackvsarmor, applies terrain modifiers, and handles unit destruction or "Scars" (fatigue).performAction(type, targetHexId): Dispatches specific logic forGUARD,REROLL,SKIRMISH, orSPELLCAST.
performMerge(mergingUnitHexId, targetUnitHexId): Combines two units, summing their values and potentially increasing stats.selectOracleSpell(spell): Handles the Oracle's (Die 6) unique abilities likeSHIELDorSWAP.
performAI(): Orchestrates the AI turn. It evaluates the board state usingai-heuristic.jsand executes the best move series.
HexDice has undergone hundreds of iterations to balance its unique "Dice Soldiers" mechanic.
- Design Backlogs: Check the
/backlogsdirectory for balance analysis and feature plans. - Rule Evolution: View the progression of the game in the
/rulesfolder.
To maintain the project's long-term health and readability, please adhere to the following standards:
- Surgical Changes: Modify only what is necessary. Avoid unrelated refactoring or "cleanup" in PRs.
- Plan First: For complex features, draft a plan in
.chat/before implementation. - Preserve History: When removing code, comment it out with a
// DEPRECATEDnotice instead of deleting it immediately. - Minimalism: Favor simple, readable logic over complex abstractions. "Less code is less debt."
- Vanilla JS First: Avoid adding new frameworks or large libraries. The core game logic should remain dependency-free.
- Global Scope Awareness: Since scripts are loaded via tags, be mindful of naming collisions and the global namespace.
- Functional Patterns: Prefer functional, immutable, and stateless approaches where they improve clarity.
- Indentation: Use Tabs.
- Semicolons: Always use semicolons.
- Braces: Use One True Brace Style (1TBS).
- Naming:
camelCase: Variables and functions (e.g.,handleHexClick).UPPER_SNAKE_CASE: Constants and global configuration (e.g.,UNIT_STATS)._prefix: Internal or private utility functions.
HexDice is designed with a "Physical-First" philosophy. The digital version serves as a testbed for the core mechanics, which are being refined for a premium physical board game release.
- Mechanics Testing: The AI and simulation tools ensure the "Dice Soldiers" classes are balanced for table play.
- Reserved Rights: While the source code is licensed under Apache 2.0, the commercial rights to the "Hex Dice" brand, thematic lore, and physical game production are reserved by the original author.
- Community Contributions: Rules adjustments or balance changes submitted via PRs may be incorporated into the official physical rulebook.
Contributions, balance suggestions, and bug reports are welcome! Note: This project follows a "surgical change" philosophy. Keep PRs focused and idiomatic.
Licensed under the Apache License, Version 2.0. See the LICENSE file for the full text. The Apache 2.0 license provides an explicit patent grant and protects the "Hex Dice" trade name, ensuring a secure foundation for both open-source contributors and future commercial production.