No turn advantage, no peeking at opponent moves — both players plan simultaneously, then every order resolves in a single explosive tick
BattleGrid is a real-time multiplayer hex strategy game where every decision happens simultaneously. Two players issue orders to all their units during a timed planning phase. When the timer expires, movement, abilities, and combat resolve in parallel. The entire game engine is Rust compiled to WASM — deterministic simulation, instant pathfinding previews, and a shared core between server and browser.
- Simultaneous resolution — both players plan in secret; all orders execute at the same instant with no turn-order advantage
- 6 unit classes — Scout (fast, reveals fog), Soldier (fortress specialist), Archer (3-range, no counter), Knight (charge bonus), Healer (pre-combat heal), Siege (destroys terrain)
- Procedural maps — noise-based hex terrain with rotational symmetry; four presets or custom seeds
- WASM game core — pathfinding, line-of-sight raycasting, and combat preview run in the browser via WASM for instant feedback without server round-trips
- Terrain-aware visibility — mountains and outside forests block sight, while fortress and plains lanes stay readable
- Deterministic replay —
BTreeMapthroughout, zeroHashMapiteration in game logic; same inputs always produce identical output - Reconnect support — drop and rejoin mid-game; the Axum server replays the full state to reconnecting clients
- Rust stable toolchain (via rustup) with
wasm-packcargo install wasm-pack
- Node.js 18+ with pnpm
- Docker + Docker Compose (optional, for zero-install server)
git clone https://github.com/saagpatel/BattleGrid.git
cd BattleGrid
./setup.sh# Start server + client together
make dev
# Server: http://localhost:3001
# Client: http://localhost:5173
# Run all tests (Rust workspace + client)
make test
# Browser smoke test (Playwright)
make smoke
# Docker (zero local Rust install)
docker-compose upOpen two browser tabs. Create a room in one, join with the room code in the other. Ready up and play.
| Layer | Technology |
|---|---|
| Game engine | Rust (battleground-core, battleground-wasm) |
| Server | Axum (async Rust, battleground-server) |
| Client | React 19, TypeScript, Tailwind CSS 4 |
| Rendering | HTML5 Canvas 2D |
| Wire protocol | Bincode (binary, versioned) over WebSocket |
| State | Zustand |
| Build | Vite + Makefile |
| Tests | cargo test (Rust) + Vitest (TS) + Playwright |
The Rust monorepo has three crates: battleground-core (pure game logic, no I/O), battleground-wasm (thin WASM bindings over core), and battleground-server (Axum WebSocket server). The browser client loads the WASM module at startup and calls it synchronously for pathfinding previews and combat previews — no server round-trip needed for local feedback. When a player submits orders, the server collects both players' orders, runs the authoritative core simulation, and broadcasts the resolved state. Bincode over WebSocket keeps payloads small and deserialization fast.
Feature-complete and in maintenance. The core game — 6 unit classes, procedural hex
maps, simultaneous resolution, WASM-side pathfinding/combat previews, deterministic
replay, and mid-game reconnect — is implemented across the battleground-core,
battleground-wasm, and battleground-server crates plus the React client, with
cargo test + Vitest + Playwright coverage, Docker packaging, an OpenAPI spec, and full
CI. Recent work has been dependency and CI hygiene (a rand API migration, a
tokio-tungstenite bump, Dependabot group updates) rather than new gameplay.
- Shared-core version skew — the browser runs
battleground-wasmfor pathfinding and combat previews while the server resolves orders with the samebattleground-coreauthoritatively. If the WASM build drifts from the server's core version, client previews diverge from the resolved state. - Determinism is load-bearing — replay and reconnect depend on
BTreeMapeverywhere and zeroHashMapiteration in game logic. AnyHashMapintroduced into the simulation path silently breaks deterministic replay. - Versioned binary wire protocol — Bincode over WebSocket is compact but schema-sensitive; a server/client protocol-version mismatch breaks the session rather than degrading gracefully.
- Server-side room lifetime — rooms persist for mid-game reconnect; there is no documented room-cleanup or reconnection-storm bound.
Confirm the recent rand and tokio-tungstenite migrations did not perturb the
deterministic simulation: run make test (Rust workspace + client) and make smoke
(Playwright), and spot-check a replay. Then settle disposition — if the game is considered
shipped, cut a tagged release from main; if development resumes, the next gameplay
milestone (e.g. matchmaking or spectating) is the natural pickup.
MIT