Initial commit
This commit is contained in:
908
server/cards.js
Normal file
908
server/cards.js
Normal file
@@ -0,0 +1,908 @@
|
||||
// ============================================================================
|
||||
// ARCANE DUELS — Complete Card Database
|
||||
// An original TCG with 5 colors: Radiance, Tide, Shadow, Flame, Growth
|
||||
// ============================================================================
|
||||
|
||||
const COLORS = {
|
||||
RADIANCE: 'radiance', // White — order, healing, protection
|
||||
TIDE: 'tide', // Blue — knowledge, control, illusion
|
||||
SHADOW: 'shadow', // Black — death, sacrifice, power
|
||||
FLAME: 'flame', // Red — chaos, destruction, speed
|
||||
GROWTH: 'growth', // Green — nature, strength, growth
|
||||
COLORLESS: 'colorless',
|
||||
};
|
||||
|
||||
const TYPES = {
|
||||
CREATURE: 'creature',
|
||||
SORCERY: 'sorcery',
|
||||
INSTANT: 'instant',
|
||||
ENCHANTMENT: 'enchantment',
|
||||
ARTIFACT: 'artifact',
|
||||
LAND: 'land',
|
||||
};
|
||||
|
||||
// Keywords mapping
|
||||
const KEYWORDS = {
|
||||
SWIFT: 'swift', // Can attack immediately (Haste)
|
||||
VIGILANT: 'vigilant', // Doesn't tap to attack (Vigilance)
|
||||
SOARING: 'soaring', // Can only be blocked by Soaring/Reaching (Flying)
|
||||
GUARDIAN: 'guardian', // Deals damage first in combat (First Strike)
|
||||
FORTIFIED: 'fortified', // Can't attack (Defender)
|
||||
DRAINING: 'draining', // Gain life equal to damage dealt (Lifelink)
|
||||
OVERWHELMING: 'overwhelming', // Excess combat damage hits player (Trample)
|
||||
VENOMOUS: 'venomous', // Any damage is lethal (Deathtouch)
|
||||
REACHING: 'reaching', // Can block Soaring creatures (Reach)
|
||||
};
|
||||
|
||||
// Mana cost encoding: { radiance: N, tide: N, shadow: N, flame: N, growth: N, colorless: N }
|
||||
// e.g. 2W = { colorless: 2, radiance: 1 }
|
||||
|
||||
let nextId = 1;
|
||||
function card(props) {
|
||||
return { id: nextId++, ...props };
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RADIANCE (White) — 12 cards
|
||||
// ============================================================================
|
||||
const radianceCards = [
|
||||
card({
|
||||
name: 'Sanctuary Guard',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 1, radiance: 1 },
|
||||
color: COLORS.RADIANCE,
|
||||
power: 2, toughness: 2,
|
||||
keywords: [],
|
||||
abilities: [],
|
||||
flavor: 'The temple never falls while its guardians stand.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Dawn Priest',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, radiance: 1 },
|
||||
color: COLORS.RADIANCE,
|
||||
power: 1, toughness: 4,
|
||||
keywords: [KEYWORDS.FORTIFIED],
|
||||
abilities: [{ trigger: 'upkeep', effect: 'gainLife', amount: 1 }],
|
||||
flavor: 'Each sunrise is a prayer answered.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Shieldbearer',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { radiance: 1 },
|
||||
color: COLORS.RADIANCE,
|
||||
power: 1, toughness: 1,
|
||||
keywords: [KEYWORDS.VIGILANT],
|
||||
abilities: [],
|
||||
flavor: 'Always watching, never resting.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Radiant Champion',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 3, radiance: 2 },
|
||||
color: COLORS.RADIANCE,
|
||||
power: 4, toughness: 4,
|
||||
keywords: [KEYWORDS.VIGILANT],
|
||||
abilities: [],
|
||||
flavor: 'She carries the light of a thousand dawns into battle.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Lightbringer',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, radiance: 1 },
|
||||
color: COLORS.RADIANCE,
|
||||
power: 2, toughness: 2,
|
||||
keywords: [],
|
||||
abilities: [{ trigger: 'enters', effect: 'gainLife', amount: 3 }],
|
||||
flavor: 'Where she walks, wounds close and despair lifts.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Angelic Sentinel',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 3, radiance: 2 },
|
||||
color: COLORS.RADIANCE,
|
||||
power: 3, toughness: 5,
|
||||
keywords: [KEYWORDS.SOARING, KEYWORDS.VIGILANT],
|
||||
abilities: [],
|
||||
flavor: 'Its wings span the horizon, shielding the faithful below.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Holy Wrath',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { colorless: 2, radiance: 2 },
|
||||
color: COLORS.RADIANCE,
|
||||
abilities: [{ effect: 'destroyCreatureIf', condition: 'powerGte4' }],
|
||||
flavor: 'The righteous need not fear judgment.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Mending Light',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { radiance: 1 },
|
||||
color: COLORS.RADIANCE,
|
||||
abilities: [{ effect: 'gainLife', amount: 4 }],
|
||||
flavor: 'A warm glow that knits flesh and spirit alike.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Celestial Shield',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { colorless: 1, radiance: 1 },
|
||||
color: COLORS.RADIANCE,
|
||||
abilities: [{ effect: 'pumpCreature', power: 0, toughness: 4, duration: 'endOfTurn' }],
|
||||
flavor: 'The blow struck true—but found only light.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Divine Decree',
|
||||
type: TYPES.ENCHANTMENT,
|
||||
cost: { colorless: 2, radiance: 2 },
|
||||
color: COLORS.RADIANCE,
|
||||
abilities: [{ effect: 'anthemBuff', power: 1, toughness: 1 }],
|
||||
flavor: 'By decree of the High Luminary, all shall stand firm.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Purifying Light',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { colorless: 3, radiance: 2 },
|
||||
color: COLORS.RADIANCE,
|
||||
abilities: [{ effect: 'destroyAllCreatures' }],
|
||||
flavor: 'When the light burns too bright, nothing remains.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Resolute Defender',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 1, radiance: 1 },
|
||||
color: COLORS.RADIANCE,
|
||||
power: 1, toughness: 3,
|
||||
keywords: [KEYWORDS.GUARDIAN],
|
||||
abilities: [],
|
||||
flavor: 'His shield has turned a thousand blades.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// TIDE (Blue) — 12 cards
|
||||
// ============================================================================
|
||||
const tideCards = [
|
||||
card({
|
||||
name: 'Reef Scholar',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 1, tide: 1 },
|
||||
color: COLORS.TIDE,
|
||||
power: 1, toughness: 2,
|
||||
keywords: [],
|
||||
abilities: [{ trigger: 'enters', effect: 'drawCards', amount: 1 }],
|
||||
flavor: 'The coral libraries hold secrets older than the continents.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Tidal Serpent',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 4, tide: 2 },
|
||||
color: COLORS.TIDE,
|
||||
power: 5, toughness: 5,
|
||||
keywords: [KEYWORDS.SOARING],
|
||||
abilities: [],
|
||||
flavor: 'It moves between sea and sky as if the boundary were a suggestion.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Mistwalker',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, tide: 1 },
|
||||
color: COLORS.TIDE,
|
||||
power: 2, toughness: 1,
|
||||
keywords: [KEYWORDS.SOARING],
|
||||
abilities: [],
|
||||
flavor: 'It drifts through the fog, never quite where you expect.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Arcane Student',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { tide: 1 },
|
||||
color: COLORS.TIDE,
|
||||
power: 1, toughness: 1,
|
||||
keywords: [],
|
||||
abilities: [{ trigger: 'tap', effect: 'scry', amount: 1 }],
|
||||
flavor: 'Every page reveals a new horizon.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Current Channeler',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, tide: 2 },
|
||||
color: COLORS.TIDE,
|
||||
power: 2, toughness: 4,
|
||||
keywords: [],
|
||||
abilities: [{ trigger: 'spellCast', effect: 'drawCards', amount: 1 }],
|
||||
flavor: 'Magic flows through her like water through a delta.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Mind Shatter',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { tide: 2 },
|
||||
color: COLORS.TIDE,
|
||||
abilities: [{ effect: 'counterSpell' }],
|
||||
flavor: 'Your incantation unravels before the last syllable.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Essence Drain',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { colorless: 1, tide: 1 },
|
||||
color: COLORS.TIDE,
|
||||
abilities: [{ effect: 'bounceCreature' }],
|
||||
flavor: 'Returned to the aether, as if it had never been summoned.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Foresight',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { tide: 1 },
|
||||
color: COLORS.TIDE,
|
||||
abilities: [{ effect: 'drawCards', amount: 2 }],
|
||||
flavor: 'To see what comes is the first step to mastering it.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Mental Fortress',
|
||||
type: TYPES.ENCHANTMENT,
|
||||
cost: { colorless: 2, tide: 1 },
|
||||
color: COLORS.TIDE,
|
||||
abilities: [{ effect: 'extraDraw', amount: 1 }],
|
||||
flavor: 'Within these walls, knowledge multiplies endlessly.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Time Warp',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { colorless: 3, tide: 2 },
|
||||
color: COLORS.TIDE,
|
||||
abilities: [{ effect: 'extraTurn' }],
|
||||
flavor: 'Yesterday and tomorrow are merely pages to be turned.',
|
||||
rarity: 'mythic',
|
||||
}),
|
||||
card({
|
||||
name: 'Frost Barrier',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { colorless: 1, tide: 1 },
|
||||
color: COLORS.TIDE,
|
||||
abilities: [{ effect: 'tapCreature' }],
|
||||
flavor: 'Frozen mid-stride, locked in a prison of ice.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Thought Thief',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 1, tide: 1 },
|
||||
color: COLORS.TIDE,
|
||||
power: 2, toughness: 1,
|
||||
keywords: [],
|
||||
abilities: [{ trigger: 'dealsDamage', effect: 'drawCards', amount: 1 }],
|
||||
flavor: 'What you know, he knows. What he knows, you never will.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// SHADOW (Black) — 12 cards
|
||||
// ============================================================================
|
||||
const shadowCards = [
|
||||
card({
|
||||
name: 'Graveborn Ghoul',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 1, shadow: 1 },
|
||||
color: COLORS.SHADOW,
|
||||
power: 2, toughness: 2,
|
||||
keywords: [],
|
||||
abilities: [],
|
||||
flavor: 'It remembers nothing of its former life, only hunger.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Soul Collector',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 3, shadow: 2 },
|
||||
color: COLORS.SHADOW,
|
||||
power: 3, toughness: 4,
|
||||
keywords: [KEYWORDS.DRAINING],
|
||||
abilities: [],
|
||||
flavor: 'Each soul taken makes it stronger, and its victims weaker.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Plague Rat',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 1, shadow: 1 },
|
||||
color: COLORS.SHADOW,
|
||||
power: 1, toughness: 1,
|
||||
keywords: [],
|
||||
abilities: [{ effect: 'plagueRatBonus' }],
|
||||
flavor: 'Where one scurries, a hundred follow.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Night Terror',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, shadow: 1 },
|
||||
color: COLORS.SHADOW,
|
||||
power: 2, toughness: 1,
|
||||
keywords: [KEYWORDS.SOARING],
|
||||
abilities: [],
|
||||
flavor: 'It feeds on screams the way others feed on bread.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Bone Revenant',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 4, shadow: 2 },
|
||||
color: COLORS.SHADOW,
|
||||
power: 6, toughness: 5,
|
||||
keywords: [KEYWORDS.VENOMOUS],
|
||||
abilities: [],
|
||||
flavor: 'Assembled from the bones of fallen champions.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Vampiric Noble',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, shadow: 1 },
|
||||
color: COLORS.SHADOW,
|
||||
power: 3, toughness: 2,
|
||||
keywords: [KEYWORDS.DRAINING],
|
||||
abilities: [],
|
||||
flavor: 'She attends the finest galas. Few guests leave alive.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Raise Dead',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { shadow: 1 },
|
||||
color: COLORS.SHADOW,
|
||||
abilities: [{ effect: 'returnFromGraveyard', type: 'creature' }],
|
||||
flavor: 'Death is merely a temporary inconvenience.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Dark Pact',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { shadow: 2 },
|
||||
color: COLORS.SHADOW,
|
||||
abilities: [{ effect: 'drawCards', amount: 2 }, { effect: 'loseLife', amount: 2 }],
|
||||
flavor: 'Power has a price. The wise pay it willingly.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Assassinate',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { colorless: 1, shadow: 2 },
|
||||
color: COLORS.SHADOW,
|
||||
abilities: [{ effect: 'destroyCreature' }],
|
||||
flavor: 'No spell, no blade—just silence, then nothing.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Drain Life',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { shadow: 2 },
|
||||
color: COLORS.SHADOW,
|
||||
abilities: [{ effect: 'drainLife', isX: true }],
|
||||
flavor: 'Your loss is my gain, in the most literal sense.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Cursed Ground',
|
||||
type: TYPES.ENCHANTMENT,
|
||||
cost: { colorless: 1, shadow: 1 },
|
||||
color: COLORS.SHADOW,
|
||||
abilities: [{ trigger: 'creatureDies', effect: 'opponentLosesLife', amount: 1 }],
|
||||
flavor: 'Nothing rests peacefully here.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Mind Rot',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { colorless: 1, shadow: 1 },
|
||||
color: COLORS.SHADOW,
|
||||
abilities: [{ effect: 'opponentDiscards', amount: 2 }],
|
||||
flavor: 'Thoughts dissolve like morning mist in the darkness.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// FLAME (Red) — 12 cards
|
||||
// ============================================================================
|
||||
const flameCards = [
|
||||
card({
|
||||
name: 'Goblin Striker',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { flame: 1 },
|
||||
color: COLORS.FLAME,
|
||||
power: 1, toughness: 1,
|
||||
keywords: [KEYWORDS.SWIFT],
|
||||
abilities: [],
|
||||
flavor: 'Speed is a virtue. Thinking is not.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Fire Elemental',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 3, flame: 2 },
|
||||
color: COLORS.FLAME,
|
||||
power: 5, toughness: 4,
|
||||
keywords: [],
|
||||
abilities: [],
|
||||
flavor: 'Born of the mountain\'s heart, it answers to no master.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Lightning Imp',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 1, flame: 1 },
|
||||
color: COLORS.FLAME,
|
||||
power: 2, toughness: 1,
|
||||
keywords: [KEYWORDS.SWIFT],
|
||||
abilities: [],
|
||||
flavor: 'It arrives with the thunder and leaves with the echo.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Lava Golem',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, flame: 1 },
|
||||
color: COLORS.FLAME,
|
||||
power: 3, toughness: 2,
|
||||
keywords: [],
|
||||
abilities: [],
|
||||
flavor: 'Molten stone given purpose, if not grace.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Dragon of the Peaks',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 4, flame: 2 },
|
||||
color: COLORS.FLAME,
|
||||
power: 5, toughness: 5,
|
||||
keywords: [KEYWORDS.SOARING, KEYWORDS.SWIFT],
|
||||
abilities: [],
|
||||
flavor: 'The mountain trembles when it wakes. The world trembles when it flies.',
|
||||
rarity: 'mythic',
|
||||
}),
|
||||
card({
|
||||
name: 'Berserker Ogre',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, flame: 2 },
|
||||
color: COLORS.FLAME,
|
||||
power: 4, toughness: 3,
|
||||
keywords: [KEYWORDS.OVERWHELMING],
|
||||
abilities: [],
|
||||
flavor: 'Subtlety is a foreign concept. Destruction is its mother tongue.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Lightning Bolt',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { flame: 1 },
|
||||
color: COLORS.FLAME,
|
||||
abilities: [{ effect: 'dealDamage', amount: 3, target: 'any' }],
|
||||
flavor: 'The sky cracks open and chooses its victim.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Fireball',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { flame: 1 },
|
||||
color: COLORS.FLAME,
|
||||
abilities: [{ effect: 'dealDamage', target: 'any', isX: true }],
|
||||
flavor: 'The equation is simple: more mana, more fire.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Inferno',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { colorless: 3, flame: 2 },
|
||||
color: COLORS.FLAME,
|
||||
abilities: [{ effect: 'dealDamageAll', amount: 3 }],
|
||||
flavor: 'Everything burns eventually. This just speeds up the schedule.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Battle Rage',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { flame: 1 },
|
||||
color: COLORS.FLAME,
|
||||
abilities: [{ effect: 'pumpCreature', power: 3, toughness: 0, duration: 'endOfTurn' }],
|
||||
flavor: 'Thought yields to instinct, instinct to fury.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Flame Barrage',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { colorless: 1, flame: 1 },
|
||||
color: COLORS.FLAME,
|
||||
abilities: [{ effect: 'dealDamage', amount: 2, target: 'any' }],
|
||||
flavor: 'One bolt for spite. Two for certainty.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Reckless Charge',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { flame: 1 },
|
||||
color: COLORS.FLAME,
|
||||
abilities: [{ effect: 'giveKeyword', keyword: KEYWORDS.SWIFT }, { effect: 'pumpCreature', power: 2, toughness: 0, duration: 'endOfTurn' }],
|
||||
flavor: 'Strategy is for cowards. Real warriors just run faster.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// GROWTH (Green) — 12 cards
|
||||
// ============================================================================
|
||||
const growthCards = [
|
||||
card({
|
||||
name: 'Woodland Elf',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { growth: 1 },
|
||||
color: COLORS.GROWTH,
|
||||
power: 1, toughness: 1,
|
||||
keywords: [],
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'any' }],
|
||||
flavor: 'The forest speaks through her, and she answers with magic.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Highland Bear',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 1, growth: 1 },
|
||||
color: COLORS.GROWTH,
|
||||
power: 2, toughness: 2,
|
||||
keywords: [],
|
||||
abilities: [],
|
||||
flavor: 'It needs no magic. Teeth and claws suffice.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Ironbark Treant',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 3, growth: 2 },
|
||||
color: COLORS.GROWTH,
|
||||
power: 5, toughness: 7,
|
||||
keywords: [],
|
||||
abilities: [],
|
||||
flavor: 'Centuries of growth have made it as unyielding as stone.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Wild Stallion',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, growth: 1 },
|
||||
color: COLORS.GROWTH,
|
||||
power: 3, toughness: 3,
|
||||
keywords: [],
|
||||
abilities: [],
|
||||
flavor: 'Untamed and untameable, it runs where it wills.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Ancient Wurm',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 4, growth: 2 },
|
||||
color: COLORS.GROWTH,
|
||||
power: 7, toughness: 7,
|
||||
keywords: [KEYWORDS.OVERWHELMING],
|
||||
abilities: [],
|
||||
flavor: 'Older than the forests it burrows beneath.',
|
||||
rarity: 'mythic',
|
||||
}),
|
||||
card({
|
||||
name: 'Thornweaver',
|
||||
type: TYPES.CREATURE,
|
||||
cost: { colorless: 2, growth: 1 },
|
||||
color: COLORS.GROWTH,
|
||||
power: 2, toughness: 3,
|
||||
keywords: [KEYWORDS.REACHING],
|
||||
abilities: [],
|
||||
flavor: 'Its thorny tendrils snatch birds from the sky.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Giant Growth',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { growth: 1 },
|
||||
color: COLORS.GROWTH,
|
||||
abilities: [{ effect: 'pumpCreature', power: 3, toughness: 3, duration: 'endOfTurn' }],
|
||||
flavor: 'Nature\'s fury compressed into a single heartbeat.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: "Nature's Gift",
|
||||
type: TYPES.SORCERY,
|
||||
cost: { colorless: 1, growth: 1 },
|
||||
color: COLORS.GROWTH,
|
||||
abilities: [{ effect: 'searchLand' }],
|
||||
flavor: 'The land provides for those who listen.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Regenerate',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { growth: 1 },
|
||||
color: COLORS.GROWTH,
|
||||
abilities: [{ effect: 'preventDamage', target: 'creature' }],
|
||||
flavor: 'What was broken, the forest mends.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Overrun',
|
||||
type: TYPES.SORCERY,
|
||||
cost: { colorless: 3, growth: 2 },
|
||||
color: COLORS.GROWTH,
|
||||
abilities: [{ effect: 'pumpAll', power: 3, toughness: 3, keyword: KEYWORDS.OVERWHELMING }],
|
||||
flavor: 'The forest does not march. It stampedes.',
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Vine Snare',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { colorless: 1, growth: 1 },
|
||||
color: COLORS.GROWTH,
|
||||
abilities: [{ effect: 'preventCombatDamage' }],
|
||||
flavor: 'Thorned vines erupt from the earth, halting all charge.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Feral Instinct',
|
||||
type: TYPES.INSTANT,
|
||||
cost: { growth: 1 },
|
||||
color: COLORS.GROWTH,
|
||||
abilities: [{ effect: 'pumpCreature', power: 1, toughness: 1, duration: 'endOfTurn' }, { effect: 'drawCards', amount: 1 }],
|
||||
flavor: 'Trust the animal within.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// COLORLESS — Artifacts (7 cards)
|
||||
// ============================================================================
|
||||
const artifactCards = [
|
||||
card({
|
||||
name: 'Crystal Amulet',
|
||||
type: TYPES.ARTIFACT,
|
||||
cost: { colorless: 2 },
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'any' }],
|
||||
flavor: 'It pulses with the light of every color, and none.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'War Golem',
|
||||
type: TYPES.CREATURE,
|
||||
subtype: 'artifact',
|
||||
cost: { colorless: 5 },
|
||||
color: COLORS.COLORLESS,
|
||||
power: 4, toughness: 4,
|
||||
keywords: [],
|
||||
abilities: [],
|
||||
flavor: 'Forged in the War of Five Suns, it still remembers how to fight.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Enchanted Blade',
|
||||
type: TYPES.ARTIFACT,
|
||||
subtype: 'equipment',
|
||||
cost: { colorless: 3 },
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ effect: 'equipBuff', power: 2, toughness: 1 }],
|
||||
flavor: 'Its edge never dulls, and its hunger never fades.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Healing Draught',
|
||||
type: TYPES.ARTIFACT,
|
||||
cost: { colorless: 1 },
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ trigger: 'tap_sacrifice', effect: 'gainLife', amount: 3 }],
|
||||
flavor: 'One sip restores what hours of rest cannot.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Mind Stone',
|
||||
type: TYPES.ARTIFACT,
|
||||
cost: { colorless: 2 },
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [
|
||||
{ trigger: 'tap', effect: 'addMana', color: 'colorless' },
|
||||
{ trigger: 'tap_sacrifice', effect: 'drawCards', amount: 1 },
|
||||
],
|
||||
flavor: 'It stores thoughts the way a gem stores light.',
|
||||
rarity: 'uncommon',
|
||||
}),
|
||||
card({
|
||||
name: 'Iron Shield',
|
||||
type: TYPES.ARTIFACT,
|
||||
subtype: 'equipment',
|
||||
cost: { colorless: 2 },
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ effect: 'equipBuff', power: 0, toughness: 3 }],
|
||||
flavor: 'Heavy, inelegant, and thoroughly reliable.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
card({
|
||||
name: 'Arcane Compass',
|
||||
type: TYPES.ARTIFACT,
|
||||
cost: { colorless: 1 },
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ trigger: 'tap', effect: 'scry', amount: 1 }],
|
||||
flavor: 'It always points toward what you need most.',
|
||||
rarity: 'common',
|
||||
}),
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// LANDS — 5 Basic + 5 Dual
|
||||
// ============================================================================
|
||||
const landCards = [
|
||||
// Basic Lands
|
||||
card({
|
||||
name: 'Sunlit Plains',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'basic',
|
||||
color: COLORS.RADIANCE,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'radiance' }],
|
||||
rarity: 'basic',
|
||||
}),
|
||||
card({
|
||||
name: 'Coral Reef',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'basic',
|
||||
color: COLORS.TIDE,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'tide' }],
|
||||
rarity: 'basic',
|
||||
}),
|
||||
card({
|
||||
name: 'Dark Swamp',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'basic',
|
||||
color: COLORS.SHADOW,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'shadow' }],
|
||||
rarity: 'basic',
|
||||
}),
|
||||
card({
|
||||
name: 'Volcanic Peak',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'basic',
|
||||
color: COLORS.FLAME,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'flame' }],
|
||||
rarity: 'basic',
|
||||
}),
|
||||
card({
|
||||
name: 'Verdant Forest',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'basic',
|
||||
color: COLORS.GROWTH,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'growth' }],
|
||||
rarity: 'basic',
|
||||
}),
|
||||
// Dual Lands
|
||||
card({
|
||||
name: 'Dual Springs',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'dual',
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'radiance|tide' }],
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Ashen Moor',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'dual',
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'shadow|flame' }],
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Twilight Glade',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'dual',
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'radiance|growth' }],
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Molten Cavern',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'dual',
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'flame|growth' }],
|
||||
rarity: 'rare',
|
||||
}),
|
||||
card({
|
||||
name: 'Frozen Depths',
|
||||
type: TYPES.LAND,
|
||||
subtype: 'dual',
|
||||
color: COLORS.COLORLESS,
|
||||
abilities: [{ trigger: 'tap', effect: 'addMana', color: 'tide|shadow' }],
|
||||
rarity: 'rare',
|
||||
}),
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// ALL CARDS
|
||||
// ============================================================================
|
||||
const ALL_CARDS = [
|
||||
...radianceCards,
|
||||
...tideCards,
|
||||
...shadowCards,
|
||||
...flameCards,
|
||||
...growthCards,
|
||||
...artifactCards,
|
||||
...landCards,
|
||||
];
|
||||
|
||||
// Card lookup by ID
|
||||
const CARD_DB = {};
|
||||
ALL_CARDS.forEach((c) => { CARD_DB[c.id] = c; });
|
||||
|
||||
// Get the converted mana cost (total mana needed)
|
||||
function getManaCost(cost) {
|
||||
if (!cost) return 0;
|
||||
return Object.values(cost).reduce((sum, v) => sum + v, 0);
|
||||
}
|
||||
|
||||
// Build a starter deck for a given color (24 lands + 36 spells)
|
||||
function buildStarterDeck(color) {
|
||||
const colorCards = ALL_CARDS.filter(
|
||||
(c) => c.color === color && c.type !== TYPES.LAND
|
||||
);
|
||||
const basicLand = ALL_CARDS.find(
|
||||
(c) => c.type === TYPES.LAND && c.subtype === 'basic' && c.color === color
|
||||
);
|
||||
|
||||
const deck = [];
|
||||
|
||||
// Add 4 copies of each color card (up to 9 unique cards = 36)
|
||||
const spellCards = colorCards.slice(0, 9);
|
||||
spellCards.forEach((c) => {
|
||||
for (let i = 0; i < 4; i++) deck.push(c.id);
|
||||
});
|
||||
|
||||
// Fill to 60 with basic lands
|
||||
while (deck.length < 60) {
|
||||
deck.push(basicLand.id);
|
||||
}
|
||||
|
||||
return deck;
|
||||
}
|
||||
|
||||
// Prebuilt starter decks
|
||||
const STARTER_DECKS = {
|
||||
radiance: { name: 'Dawn\'s Wrath', color: COLORS.RADIANCE },
|
||||
tide: { name: 'Depths of Knowledge', color: COLORS.TIDE },
|
||||
shadow: { name: 'Veil of Shadows', color: COLORS.SHADOW },
|
||||
flame: { name: 'Infernal Fury', color: COLORS.FLAME },
|
||||
growth: { name: 'Primal Might', color: COLORS.GROWTH },
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
COLORS,
|
||||
TYPES,
|
||||
KEYWORDS,
|
||||
ALL_CARDS,
|
||||
CARD_DB,
|
||||
getManaCost,
|
||||
buildStarterDeck,
|
||||
STARTER_DECKS,
|
||||
};
|
||||
Reference in New Issue
Block a user