/** * Tests for extreme risk-reward moves from the design document * These are the dramatic, high-stakes moves that define the battle system */ import { describe, it, expect, beforeEach } from 'vitest'; import { BattleEngine } from './BattleEngine'; import { PicletDefinition, Move, SpecialAbility } from './types'; import { PicletType, AttackType } from './types'; const STANDARD_STATS = { hp: 100, attack: 80, defense: 70, speed: 60 }; describe('Extreme Risk-Reward Moves - TDD Implementation', () => { describe('Self Destruct - Ultimate Sacrifice', () => { it('should handle Self Destruct move', () => { const selfDestruct: Move = { name: "Self Destruct", type: AttackType.NORMAL, power: 200, accuracy: 100, pp: 1, priority: 0, flags: ['explosive', 'contact'], effects: [ { type: 'damage', target: 'all', formula: 'standard', multiplier: 1.5 }, { type: 'damage', target: 'self', formula: 'fixed', value: 9999, condition: 'afterUse' } ] }; const bomberPiclet: PicletDefinition = { name: "Bomb Beast", description: "A creature that can self-destruct", tier: 'medium', primaryType: PicletType.MACHINA, baseStats: STANDARD_STATS, nature: "Brave", specialAbility: { name: "None", description: "No ability" }, movepool: [selfDestruct] }; const targetPiclet: PicletDefinition = { name: "Target", description: "Target dummy", tier: 'medium', primaryType: PicletType.BEAST, baseStats: STANDARD_STATS, nature: "Hardy", specialAbility: { name: "None", description: "No ability" }, movepool: [{ name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 35, priority: 0, flags: [], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] }] }; // Test that the move is properly defined expect(selfDestruct.effects).toHaveLength(2); expect(selfDestruct.effects[0].target).toBe('all'); expect(selfDestruct.effects[1].value).toBe(9999); }); }); describe('Berserker\'s End - Conditional Power', () => { it('should handle Berserker\'s End with conditional effects', () => { const berserkersEnd: Move = { name: "Berserker's End", type: AttackType.BEAST, power: 80, accuracy: 95, pp: 10, priority: 0, flags: ['contact', 'reckless'], effects: [ { type: 'damage', target: 'opponent', amount: 'normal' }, { type: 'damage', target: 'opponent', amount: 'strong', condition: 'ifLowHp' }, { type: 'mechanicOverride', target: 'self', mechanic: 'healingBlocked', value: true } ] }; const berserkerPiclet: PicletDefinition = { name: "Berserker", description: "Fights with reckless abandon", tier: 'high', primaryType: PicletType.BEAST, baseStats: { hp: 120, attack: 100, defense: 90, speed: 85 }, nature: "Reckless", specialAbility: { name: "None", description: "No ability" }, movepool: [berserkersEnd] }; // Test move structure expect(berserkersEnd.effects).toHaveLength(3); expect(berserkersEnd.effects[1].condition).toBe('ifLowHp'); expect(berserkersEnd.effects[2].mechanic).toBe('healingBlocked'); }); }); describe('Life Drain Overload - Massive Heal with Permanent Cost', () => { it('should handle Life Drain Overload move', () => { const lifeDrainOverload: Move = { name: "Life Drain Overload", type: AttackType.CULTURE, power: 0, accuracy: 100, pp: 3, priority: 0, flags: ['draining'], effects: [ { type: 'heal', target: 'self', formula: 'percentage', value: 75 }, { type: 'modifyStats', target: 'self', stats: { attack: 'greatly_decrease' }, condition: 'afterUse' } ] }; expect(lifeDrainOverload.effects[0].formula).toBe('percentage'); expect(lifeDrainOverload.effects[0].value).toBe(75); expect(lifeDrainOverload.effects[1].stats.attack).toBe('greatly_decrease'); }); }); describe('Cursed Gambit - Random Extreme Outcome', () => { it('should handle Cursed Gambit with random effects', () => { const cursedGambit: Move = { name: "Cursed Gambit", type: AttackType.CULTURE, power: 0, accuracy: 100, pp: 1, priority: 0, flags: ['gambling', 'cursed'], effects: [ { type: 'heal', target: 'self', formula: 'percentage', value: 100, condition: 'ifLucky50' }, { type: 'damage', target: 'self', formula: 'fixed', value: 9999, condition: 'ifUnlucky50' } ] }; expect(cursedGambit.effects).toHaveLength(2); expect(cursedGambit.effects[0].condition).toBe('ifLucky50'); expect(cursedGambit.effects[1].condition).toBe('ifUnlucky50'); expect(cursedGambit.flags).toContain('gambling'); }); }); describe('Blood Pact - Sacrifice HP for Permanent Power', () => { it('should handle Blood Pact move', () => { const bloodPact: Move = { name: "Blood Pact", type: AttackType.CULTURE, power: 0, accuracy: 100, pp: 3, priority: 0, flags: ['sacrifice'], effects: [ { type: 'damage', target: 'self', formula: 'percentage', value: 50 }, { type: 'mechanicOverride', target: 'self', mechanic: 'damageMultiplier', value: 2.0, condition: 'restOfBattle' } ] }; expect(bloodPact.effects[0].formula).toBe('percentage'); expect(bloodPact.effects[1].value).toBe(2.0); expect(bloodPact.flags).toContain('sacrifice'); }); }); describe('Soul Burn - PP Sacrifice for Power', () => { it('should handle Soul Burn move', () => { const soulBurn: Move = { name: "Soul Burn", type: AttackType.SPACE, power: 150, accuracy: 90, pp: 5, priority: 0, flags: ['burning'], effects: [ { type: 'damage', target: 'opponent', amount: 'extreme' }, { type: 'manipulatePP', target: 'self', action: 'drain', value: 3, targetMove: 'random', condition: 'afterUse' } ] }; expect(soulBurn.effects[0].amount).toBe('extreme'); expect(soulBurn.effects[1].value).toBe(3); expect(soulBurn.effects[1].targetMove).toBe('random'); }); }); describe('Mirror Shatter - Damage Reflection with Cost', () => { it('should handle Mirror Shatter move', () => { const mirrorShatter: Move = { name: "Mirror Shatter", type: AttackType.MINERAL, power: 0, accuracy: 100, pp: 5, priority: 4, flags: ['priority'], effects: [ { type: 'mechanicOverride', target: 'self', mechanic: 'damageReflection', value: 'double', condition: 'thisTurn' }, { type: 'modifyStats', target: 'self', stats: { defense: 'greatly_decrease' }, condition: 'afterUse' } ] }; expect(mirrorShatter.priority).toBe(4); expect(mirrorShatter.effects[0].value).toBe('double'); expect(mirrorShatter.effects[1].stats.defense).toBe('greatly_decrease'); }); }); describe('Apocalypse Strike - AoE Devastation with Vulnerability', () => { it('should handle Apocalypse Strike move', () => { const apocalypseStrike: Move = { name: "Apocalypse Strike", type: AttackType.SPACE, power: 120, accuracy: 85, pp: 1, priority: 0, flags: ['apocalyptic'], effects: [ { type: 'damage', target: 'all', formula: 'standard', multiplier: 1.3 }, { type: 'mechanicOverride', target: 'self', mechanic: 'criticalHits', value: 'alwaysReceive', condition: 'restOfBattle' }, { type: 'modifyStats', target: 'self', stats: { defense: 'greatly_decrease' } } ] }; expect(apocalypseStrike.effects).toHaveLength(3); expect(apocalypseStrike.effects[0].target).toBe('all'); expect(apocalypseStrike.effects[1].value).toBe('alwaysReceive'); expect(apocalypseStrike.pp).toBe(1); // Can only be used once }); }); describe('Temporal Overload - Extra Turn with Cost', () => { it('should handle Temporal Overload move', () => { const temporalOverload: Move = { name: "Temporal Overload", type: AttackType.SPACE, power: 0, accuracy: 100, pp: 2, priority: 0, flags: ['temporal'], effects: [ { type: 'mechanicOverride', target: 'self', mechanic: 'extraTurn', value: true, condition: 'nextTurn' }, { type: 'applyStatus', target: 'self', status: 'paralyze', chance: 100, condition: 'turnAfterNext' } ] }; expect(temporalOverload.effects[0].mechanic).toBe('extraTurn'); expect(temporalOverload.effects[1].condition).toBe('turnAfterNext'); expect(temporalOverload.flags).toContain('temporal'); }); }); describe('Multi-Stage Effects - Charging Blast', () => { it('should handle Charging Blast with multi-stage effects', () => { const chargingBlast: Move = { name: "Charging Blast", type: AttackType.SPACE, power: 120, accuracy: 90, pp: 5, priority: 0, flags: ['charging'], effects: [ { type: 'modifyStats', target: 'self', stats: { defense: 'increase' }, condition: 'onCharging' }, { type: 'damage', target: 'opponent', amount: 'extreme', condition: 'afterCharging' }, { type: 'applyStatus', target: 'self', status: 'paralyze', condition: 'afterCharging' } ] }; expect(chargingBlast.effects).toHaveLength(3); expect(chargingBlast.effects[0].condition).toBe('onCharging'); expect(chargingBlast.effects[1].condition).toBe('afterCharging'); expect(chargingBlast.flags).toContain('charging'); }); }); describe('Void Sacrifice - Field Effect with Self-Harm', () => { it('should handle Void Sacrifice from Tempest Wraith example', () => { const voidSacrifice: Move = { name: "Void Sacrifice", type: AttackType.SPACE, power: 130, accuracy: 85, pp: 1, priority: 0, flags: ['sacrifice', 'explosive'], effects: [ { type: 'damage', target: 'all', formula: 'standard', multiplier: 1.2 }, { type: 'damage', target: 'self', formula: 'percentage', value: 75 }, { type: 'fieldEffect', effect: 'voidStorm', target: 'field', stackable: false } ] }; expect(voidSacrifice.effects).toHaveLength(3); expect(voidSacrifice.effects[2].effect).toBe('voidStorm'); expect(voidSacrifice.effects[2].stackable).toBe(false); }); }); describe('Integration Test - Complex Battle with Extreme Moves', () => { it('should handle a battle with multiple extreme moves', () => { const extremePiclet: PicletDefinition = { name: "Chaos Incarnate", description: "Master of extreme techniques", tier: 'legendary', primaryType: PicletType.SPACE, secondaryType: PicletType.CULTURE, baseStats: { hp: 150, attack: 120, defense: 80, speed: 100 }, nature: "Reckless", specialAbility: { name: "Chaos Heart", description: "Gains power from desperation", triggers: [ { event: 'onLowHP', effects: [ { type: 'mechanicOverride', mechanic: 'damageMultiplier', value: 1.5 } ] } ] }, movepool: [ { name: "Cursed Gambit", type: AttackType.CULTURE, power: 0, accuracy: 100, pp: 1, priority: 0, flags: ['gambling'], effects: [ { type: 'heal', target: 'self', formula: 'percentage', value: 100, condition: 'ifLucky50' }, { type: 'damage', target: 'self', formula: 'fixed', value: 9999, condition: 'ifUnlucky50' } ] }, { name: "Blood Pact", type: AttackType.CULTURE, power: 0, accuracy: 100, pp: 3, priority: 0, flags: ['sacrifice'], effects: [ { type: 'damage', target: 'self', formula: 'percentage', value: 50 }, { type: 'mechanicOverride', target: 'self', mechanic: 'damageMultiplier', value: 2.0, condition: 'restOfBattle' } ] } ] }; const standardPiclet: PicletDefinition = { name: "Standard Fighter", description: "Uses normal moves", tier: 'medium', primaryType: PicletType.BEAST, baseStats: STANDARD_STATS, nature: "Hardy", specialAbility: { name: "None", description: "No ability" }, movepool: [{ name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 35, priority: 0, flags: [], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] }] }; const engine = new BattleEngine(extremePiclet, standardPiclet); // Test that the battle can be initialized with extreme moves expect(engine.getState().playerPiclet.definition.name).toBe("Chaos Incarnate"); expect(engine.getState().playerPiclet.moves).toHaveLength(2); expect(engine.getState().playerPiclet.moves[0].move.name).toBe("Cursed Gambit"); expect(engine.getState().playerPiclet.moves[1].move.name).toBe("Blood Pact"); // Test that the special ability is properly defined expect(extremePiclet.specialAbility.triggers).toHaveLength(1); expect(extremePiclet.specialAbility.triggers![0].event).toBe('onLowHP'); }); }); });