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.
54 lines
1.4 KiB
TypeScript
54 lines
1.4 KiB
TypeScript
import type { EnemyDefinition } from './combat.types';
|
|
|
|
/**
|
|
* Enemy definitions — data-driven. Move patterns cycle by turn number, so a
|
|
* player can learn and read intents. Add enemies by adding entries.
|
|
*/
|
|
export const ENEMY_DEFINITIONS: Record<string, EnemyDefinition> = {
|
|
slime: {
|
|
id: 'slime',
|
|
name: 'Slime',
|
|
maxHp: 22,
|
|
moves: [
|
|
{ intent: { kind: 'attack', value: 6 } },
|
|
{ intent: { kind: 'block' } },
|
|
],
|
|
},
|
|
cultist: {
|
|
id: 'cultist',
|
|
name: 'Cultist',
|
|
maxHp: 30,
|
|
moves: [
|
|
{ intent: { kind: 'buff' } },
|
|
{ intent: { kind: 'attack', value: 8 } },
|
|
{ intent: { kind: 'attack', value: 8 } },
|
|
],
|
|
},
|
|
jaw_worm: {
|
|
id: 'jaw_worm',
|
|
name: 'Jaw Worm',
|
|
maxHp: 42,
|
|
moves: [
|
|
{ intent: { kind: 'attack', value: 11 } },
|
|
{ intent: { kind: 'block' } },
|
|
{ intent: { kind: 'attack', value: 7 } },
|
|
],
|
|
},
|
|
brute: {
|
|
id: 'brute',
|
|
name: 'Brute',
|
|
maxHp: 54,
|
|
moves: [
|
|
{ intent: { kind: 'attack', value: 6, hits: 2 } },
|
|
{ intent: { kind: 'attack', value: 14 } },
|
|
{ intent: { kind: 'block' } },
|
|
],
|
|
},
|
|
};
|
|
|
|
export function getEnemyDef(id: string): EnemyDefinition {
|
|
const def = ENEMY_DEFINITIONS[id];
|
|
if (!def) throw new Error(`Unknown enemy id: ${id}`);
|
|
return def;
|
|
}
|