Scaffold RogueHunter: roguelike deck-builder on Phaser 4 + React

Vite + TypeScript + React 19 shell hosting a Phaser 4 game canvas,
structured as a Slay the Spire-style single-player deck-builder.

Architecture - strict core/render split:
- core/ holds all game rules as pure TypeScript with zero Phaser
  imports, so the domain is deterministic and engine-agnostic:
  - cards/      data-driven card model, library, and starter deck
  - combat/     turn-based CombatEngine (energy, block, draw/discard/
                exhaust piles, enemy intents) + enemy definitions
  - run/        RunManager (deck/hp/gold) and seeded MapGenerator
  - shared/rng  seedable mulberry32 for reproducible runs
- game/ is the Phaser layer: it renders core state and forwards input
  - scenes/     Menu -> Map -> Combat -> Reward -> GameOver
  - components/ CardSprite, CombatantSprite, Button
  - events/     typed EventBus bridging combat/run state to a React HUD
  - config/     centralized scene keys, colors, fonts, layout constants

Playable loop: traverse a deterministic 10-node map, fight turn-based
card combat against enemies with telegraphed intents, pick a card
reward after each win, culminating in a boss.

Verified: npm run lint and npm run build pass.
This commit is contained in:
Nicolas Civade
2026-08-12 22:10:32 +02:00
parent 10e5e982dd
commit c55e0751e6
41 changed files with 5523 additions and 2 deletions

40
src/game/PhaserGame.tsx Normal file
View File

@@ -0,0 +1,40 @@
import { useEffect, useRef } from "react";
import * as Phaser from "phaser";
import { scenes } from "./scenes";
import { PhaserConfig } from "./config";
const PhaserGame = () => {
const gameRef = useRef<Phaser.Game | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (gameRef.current) return;
if (!containerRef.current) return;
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
parent: containerRef.current,
backgroundColor: PhaserConfig.backgroundColor,
scene: scenes,
scale: {
mode: Phaser.Scale.RESIZE,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: '100%',
height: '100%',
},
// No physics: this is a turn-based card game. Combat is resolved by
// the pure-TS CombatEngine, not by an Arcade/Matter world.
};
gameRef.current = new Phaser.Game(config);
return () => {
gameRef.current?.destroy(true);
gameRef.current = null;
};
}, []);
return <div ref={containerRef} style={{ width: '100%', height: '100%' }} />;
};
export default PhaserGame;