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:
93
src/core/cards/card-library.ts
Normal file
93
src/core/cards/card-library.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import type { CardData } from './card.types';
|
||||
|
||||
/**
|
||||
* Central card library. All card definitions live here, keyed by id.
|
||||
* Content is data — add cards by adding entries, not code paths.
|
||||
*/
|
||||
export const CARD_LIBRARY: Record<string, CardData> = {
|
||||
strike: {
|
||||
id: 'strike',
|
||||
name: 'Strike',
|
||||
type: 'attack',
|
||||
rarity: 'starter',
|
||||
cost: 1,
|
||||
target: 'enemy',
|
||||
description: 'Deal 6 damage.',
|
||||
effect: { damage: 6 },
|
||||
},
|
||||
defend: {
|
||||
id: 'defend',
|
||||
name: 'Defend',
|
||||
type: 'skill',
|
||||
rarity: 'starter',
|
||||
cost: 1,
|
||||
target: 'self',
|
||||
description: 'Gain 5 block.',
|
||||
effect: { block: 5 },
|
||||
},
|
||||
bash: {
|
||||
id: 'bash',
|
||||
name: 'Bash',
|
||||
type: 'attack',
|
||||
rarity: 'starter',
|
||||
cost: 2,
|
||||
target: 'enemy',
|
||||
description: 'Deal 10 damage.',
|
||||
effect: { damage: 10 },
|
||||
},
|
||||
quick_slash: {
|
||||
id: 'quick_slash',
|
||||
name: 'Quick Slash',
|
||||
type: 'attack',
|
||||
rarity: 'common',
|
||||
cost: 1,
|
||||
target: 'enemy',
|
||||
description: 'Deal 4 damage. Draw 1 card.',
|
||||
effect: { damage: 4, draw: 1 },
|
||||
},
|
||||
cleave: {
|
||||
id: 'cleave',
|
||||
name: 'Cleave',
|
||||
type: 'attack',
|
||||
rarity: 'common',
|
||||
cost: 1,
|
||||
target: 'all-enemies',
|
||||
description: 'Deal 8 damage to ALL enemies.',
|
||||
effect: { damage: 8 },
|
||||
},
|
||||
flurry: {
|
||||
id: 'flurry',
|
||||
name: 'Flurry',
|
||||
type: 'attack',
|
||||
rarity: 'uncommon',
|
||||
cost: 1,
|
||||
target: 'enemy',
|
||||
description: 'Deal 3 damage 3 times.',
|
||||
effect: { damage: 3, hits: 3 },
|
||||
},
|
||||
adrenaline: {
|
||||
id: 'adrenaline',
|
||||
name: 'Adrenaline',
|
||||
type: 'skill',
|
||||
rarity: 'uncommon',
|
||||
cost: 0,
|
||||
target: 'none',
|
||||
description: 'Gain 1 energy. Draw 2 cards. Exhaust.',
|
||||
effect: { energy: 1, draw: 2 },
|
||||
exhaust: true,
|
||||
},
|
||||
};
|
||||
|
||||
/** Look up a card definition by id; throws early on typos rather than failing silently. */
|
||||
export function getCard(id: string): CardData {
|
||||
const card = CARD_LIBRARY[id];
|
||||
if (!card) throw new Error(`Unknown card id: ${id}`);
|
||||
return card;
|
||||
}
|
||||
|
||||
/** The deck every run begins with (Slay the Spire-style: 5 Strike, 4 Defend, 1 Bash). */
|
||||
export const STARTER_DECK: readonly string[] = [
|
||||
'strike', 'strike', 'strike', 'strike', 'strike',
|
||||
'defend', 'defend', 'defend', 'defend',
|
||||
'bash',
|
||||
];
|
||||
Reference in New Issue
Block a user