Alpha.Demo.mov
A fully browser-native chess game with handcrafted rules, programmatic visuals, and a custom game engine — built from scratch with no chess libraries.
Features a complete single-player opponent, a modular rules architecture, and six original game-changing piece mechanics that can be toggled independently mid-game.
XChess is a 2D browser chess game that extends the standard ruleset with six independently toggleable Xtra Rules — one per piece type. Each rule fundamentally changes how that piece interacts with the board, enabling thousands of distinct game configurations from a single codebase.
The design goal was to preserve the strategic DNA of chess while injecting mechanical chaos through opt-in rule modifiers. Toggling rules mid-session recomputes all legal moves in real time without restarting the game.
The project is built around a clean separation of concerns across five core modules:
| Module | Responsibility |
|---|---|
ChessEngine.js |
Board state, move legality (including check filtering), move execution |
GameScene.js |
Phaser scene: rendering, input, camera FX, check visuals, UI binding |
PieceFactory.js |
Programmatic piece construction (no sprites), hover animations, capture animations |
SoundFX.js |
Procedural Web Audio API synthesis — no audio files required |
XtraRuleRegistry.js |
Strategy + Registry pattern for pluggable rule modules |
Bot.js |
Minimax engine with alpha-beta pruning and Elo-scaled difficulty |
Xtra rules are zero-coupling. Adding a new rule means creating one file and registering it — the engine, scene, and UI auto-discover it. No engine changes needed.
Each rule is an independent strategy class with three optional hooks:
getExtraMoves()— injects new destination squares beyond standard movementtransformMoves()— augments existing moves with metadata (e.g. elbow kill tags)applyXtraEffect()— executes board side-effects after a move commits
All six rules are enabled by default and can be individually toggled. The legal move filter (getValidMoves) re-runs on every selection, so rule changes take effect immediately.
"The shortest path is always forward."
Standard: Pawns capture only diagonally.
Xtra: A Pawn can capture the piece directly in front of it — same square it would normally step onto. The forward square functions as both a movement and capture target simultaneously.
Implementation: PawnStraightShooterRule.getExtraMoves() appends a forward move with isCapture: true if an enemy occupies that square.
"It doesn't leap over you. It crushes through you."
Standard: Knights jump in an L-shape and capture only at their destination.
Xtra: If an enemy piece sits at the "elbow" of the Knight's L-path (the intermediate corner square), that piece is also destroyed — yielding a double capture in a single turn.
Implementation: KnightTrampleRule.transformMoves() tags each jump move with hasElbowKill, elbowRow, and elbowCol when an enemy occupies that intermediate square. applyXtraEffect() removes the elbow piece from the board post-commit.
"Angles are just directions you haven't considered yet."
Standard: Bishops move diagonally until blocked or off-board.
Xtra: When a Bishop's diagonal path hits the board edge, it reflects 90° and continues. This allows it to reach squares normally outside its color complex, including pieces around corners.
Implementation: BishopRicochetRule.getExtraMoves() traces each diagonal ray, and when it strikes an edge, calculates the reflection vector and continues the ray from that point.
"When it arrives, everything nearby pays the price."
Standard: Rooks capture only the piece on their destination square.
Xtra: When a Rook captures, it detonates an AOE explosion that destroys all pieces — friendly and enemy — in the 8 squares immediately surrounding the landing tile.
Implementation: RookExplosiveLandingRule.transformMoves() marks eligible capture moves as xtraType: 'explosive'. applyXtraEffect() scans the 3×3 grid around the landing square and removes all occupants, returning the kill list for visual FX dispatch.
"She doesn't move. She merely decides."
Standard: Queens move to a square to capture the piece on it.
Xtra: The Queen can destroy any enemy in her line of sight without physically moving. The target is removed from its square; the Queen stays. This counts as her turn.
Implementation: QueenSirensCallRule.getExtraMoves() generates target squares along all eight rays with queenStays: true. In movePiece(), the engine detects this flag and removes the target without moving the Queen or updating her position.
"A king who repositions is a king who survives."
Standard: Kings move one square in any direction.
Xtra: The King can swap positions with any friendly piece anywhere on the board as a full turn. Both pieces teleport simultaneously.
Implementation: KingSwapperRule.getExtraMoves() scans all friendly pieces and emits swap targets tagged xtraType: 'swap'. The engine handles the exchange atomically; the scene plays a lightning arc + dual-slide animation.
Check detection runs after every move. isCheck(color) scans all enemy pseudo-legal moves (including Xtra types — Siren's Call, Trample elbow paths, Explosive radius) and tests whether any threatens the king.
getValidMoves() filters every candidate move through a simulation: the move is applied to a cloned engine instance, and the result is discarded if the moving side's king remains in check. This ensures all displayed moves are fully legal.
On check:
- A faint red threat line is drawn from the attacker to the king
- The king's tile gets a red fill + border highlight
- The king sprite plays a pulse flash animation (×3)
- A tense metallic sound fires (880Hz → 440Hz descending ping)
On checkmate:
- All moves return empty (the filter eliminates everything)
- The game-over overlay appears with the correct winner
The opponent uses iterative minimax with alpha-beta pruning. Difficulty scales via a simulated Elo rating (100–3500), controlled by a live slider in the UI.
| Elo Range | Search Depth | Blunder Probability |
|---|---|---|
| < 800 | 1 ply | 60% |
| 800–1599 | 2 ply | 20% |
| 1600–2499 | 3 ply | 5% |
| 2500+ | 3 ply | 0% |
The bot evaluates boards using material score with a center-control bonus. Since it calls getValidMoves() internally, it is fully aware of active Xtra rules and will exploit them — or defend against them — based on the current configuration.
All piece graphics are programmatically drawn using Phaser 3 Graphics primitives. There are zero external image assets for pieces.
Each piece has:
- A unique geometric body built from polygon paths and circles
- A dedicated idle hover animation on mouse-over (per-part, not just float)
- A custom capture animation dispatched from
CaptureAnimations.js
| Piece | Hover Animation |
|---|---|
| Pawn | Sword windmill spin (360° continuous) |
| Knight | Rears back (−18° tilt yoyo) + mane shimmer |
| Rook | Battlements rise + cannon barrel sweeps |
| Bishop | Magic orb orbits the tip in an ellipse |
| Queen | Crown floats and tilts majestically |
| King | Cross pulses in scale + halo expands and fades |
Xtra effects use a four-emitter particle system (purple burst, red burst, cyan spark, gold flare) all generated from a single procedural Graphics texture baked at scene start.
All audio is synthesized in real time using the Web Audio API. No .mp3 or .ogg files are required (one optional ambient background track is loaded separately).
| Event | Synthesis |
|---|---|
| Move | Low sine + lowpass noise thud |
| Capture | Dual detuned triangle oscillators + bandpass snap |
| Check | Descending 880→440Hz sine ping + 1200→600Hz triangle overtone |
| Checkmate / Game Over | Cinematic sawtooth + square wave boom |
Both players have a configurable countdown clock (default 10 minutes). The active clock ticks down every frame using Phaser's delta time in update(). On timeout, the game ends and the opponent is declared the winner.
| Layer | Technology |
|---|---|
| Rendering / Input | Phaser 3 (WebGL) |
| Build | Vite + ES Modules |
| Styling | Vanilla CSS with CSS custom properties |
| Audio | Web Audio API (procedural synthesis) |
| Language | JavaScript (ES2022, class-based modules) |
| No dependencies | Zero external chess libraries — engine written from scratch |
# Install
npm install
# Dev server (localhost:5173)
npm run dev
# Production build
npm run build