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:
73
src/game/scenes/gameover/GameOverScene.ts
Normal file
73
src/game/scenes/gameover/GameOverScene.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import * as Phaser from 'phaser';
|
||||
import { SceneKeys, Colors, Fonts } from '../../config';
|
||||
import { Button } from '../../components/button/Button';
|
||||
|
||||
interface GameOverData {
|
||||
floor?: number;
|
||||
victory?: boolean;
|
||||
}
|
||||
|
||||
export class GameOverScene extends Phaser.Scene {
|
||||
private floor = 1;
|
||||
private victory = false;
|
||||
|
||||
constructor() {
|
||||
super({ key: SceneKeys.GAME_OVER });
|
||||
}
|
||||
|
||||
init(data: GameOverData): void {
|
||||
this.floor = data.floor ?? 1;
|
||||
this.victory = data.victory ?? false;
|
||||
}
|
||||
|
||||
create(): void {
|
||||
this.layout();
|
||||
|
||||
const onResize = () => this.layout();
|
||||
this.scale.on('resize', onResize);
|
||||
this.events.once('shutdown', () => this.scale.off('resize', onResize));
|
||||
}
|
||||
|
||||
private layout(): void {
|
||||
this.children.removeAll(true);
|
||||
|
||||
const cx = this.scale.width / 2;
|
||||
const cy = this.scale.height / 2;
|
||||
|
||||
this.add.text(cx, cy - 140, this.victory ? 'RUN COMPLETE' : 'DEFEATED', {
|
||||
...Fonts.title,
|
||||
color: this.victory ? '#66cc88' : '#ff4444',
|
||||
stroke: Colors.stroke,
|
||||
strokeThickness: 6,
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.add.text(cx, cy - 60,
|
||||
this.victory
|
||||
? `You conquered all ${this.floor} floors.`
|
||||
: `You fell on floor ${this.floor}.`,
|
||||
{ ...Fonts.button, color: Colors.text, stroke: Colors.stroke, strokeThickness: 3 },
|
||||
).setOrigin(0.5);
|
||||
|
||||
new Button({
|
||||
scene: this,
|
||||
x: cx,
|
||||
y: cy + 40,
|
||||
label: 'NEW RUN',
|
||||
onClick: () => {
|
||||
this.registry.remove('run');
|
||||
this.scene.start(SceneKeys.MAP);
|
||||
},
|
||||
});
|
||||
|
||||
new Button({
|
||||
scene: this,
|
||||
x: cx,
|
||||
y: cy + 120,
|
||||
label: 'MAIN MENU',
|
||||
onClick: () => {
|
||||
this.registry.remove('run');
|
||||
this.scene.start(SceneKeys.MENU);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user