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.
41 lines
1.2 KiB
TypeScript
41 lines
1.2 KiB
TypeScript
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;
|