import { describe, it, expect, beforeEach } from 'vitest'; import { BattleEngine } from './BattleEngine'; import type { PicletDefinition } from './types'; import { PicletType, AttackType } from './types'; describe('Extreme Risk-Reward Moves', () => { describe('Self-Destruct Moves', () => { it('should deal massive damage to all but KO the user', () => { const bomber: PicletDefinition = { name: "Suicide Bomber", description: "Sacrifices itself for massive damage", tier: 'medium', primaryType: PicletType.MACHINA, baseStats: { hp: 60, attack: 40, defense: 60, speed: 50 }, nature: "Brave", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Self Destruct", type: AttackType.MACHINA, 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 opponent: PicletDefinition = { name: "Sturdy Opponent", description: "Tanky opponent", tier: 'high', primaryType: PicletType.MINERAL, baseStats: { hp: 100, attack: 60, defense: 100, speed: 40 }, nature: "Impish", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 10, priority: 0, flags: ['contact'], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] } ] }; const engine = new BattleEngine(bomber, opponent); const initialOpponentHp = engine.getState().opponentPiclet.currentHp; engine.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, // Self Destruct { type: 'move', piclet: 'opponent', moveIndex: 0 } ); const finalOpponentHp = engine.getState().opponentPiclet.currentHp; const playerHp = engine.getState().playerPiclet.currentHp; // User should be KO'd expect(playerHp).toBe(0); // Opponent should take massive damage expect(finalOpponentHp).toBeLessThan(initialOpponentHp); const damage = initialOpponentHp - finalOpponentHp; expect(damage).toBeGreaterThan(45); // Should be very high damage for a self-destruct move const log = engine.getLog(); expect(log.some(msg => msg.includes('Self Destruct') || msg.includes('exploded'))).toBe(true); expect(engine.isGameOver()).toBe(true); }); }); describe('Gambling Moves', () => { it('should have random success/failure outcomes', () => { const gambler: PicletDefinition = { name: "Lucky Gambler", description: "Relies on luck for power", tier: 'medium', primaryType: PicletType.CULTURE, baseStats: { hp: 70, attack: 60, defense: 60, speed: 80 }, nature: "Hasty", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Cursed Gambit", type: AttackType.CULTURE, power: 0, accuracy: 100, pp: 1, priority: 0, flags: ['gambling', 'cursed'], effects: [ { type: 'heal', target: 'self', amount: 'percentage', value: 100, condition: 'ifLucky50' }, { type: 'damage', target: 'self', formula: 'fixed', value: 9999, condition: 'ifUnlucky50' } ] } ] }; const opponent: PicletDefinition = { name: "Opponent", description: "Standard opponent", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Hardy", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Do Nothing", type: AttackType.NORMAL, power: 0, accuracy: 100, pp: 10, priority: 0, flags: [], effects: [] // No effects - just waste a turn } ] }; // Test multiple times to check for randomness let healedCount = 0; let faintedCount = 0; for (let i = 0; i < 20; i++) { const engine = new BattleEngine(gambler, opponent); // Damage the gambler first engine['state'].playerPiclet.currentHp = Math.floor(engine['state'].playerPiclet.maxHp * 0.5); const preGambitHp = engine.getState().playerPiclet.currentHp; engine.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, { type: 'move', piclet: 'opponent', moveIndex: 0 } ); const postGambitHp = engine.getState().playerPiclet.currentHp; const maxHp = engine.getState().playerPiclet.maxHp; if (postGambitHp === 0) { faintedCount++; } else if (postGambitHp > preGambitHp) { healedCount++; } } // Should have some of each outcome (allowing for randomness) expect(healedCount + faintedCount).toBeGreaterThan(0); expect(healedCount).toBeGreaterThan(0); expect(faintedCount).toBeGreaterThan(0); }); }); describe('Sacrifice Moves', () => { it('should provide powerful effects at great personal cost', () => { const sacrificer: PicletDefinition = { name: "Blood Warrior", description: "Sacrifices HP for power", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 100, attack: 70, defense: 60, speed: 60 }, nature: "Brave", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Blood Pact", type: AttackType.BEAST, 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' } ] }, { name: "Strike", type: AttackType.BEAST, power: 60, accuracy: 100, pp: 10, priority: 0, flags: ['contact'], effects: [ { type: 'damage', target: 'opponent', amount: 'normal' } ] } ] }; const opponent: PicletDefinition = { name: "Opponent", description: "Standard opponent", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Hardy", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 10, priority: 0, flags: ['contact'], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] } ] }; const engine = new BattleEngine(sacrificer, opponent); const initialHp = engine.getState().playerPiclet.currentHp; // Use Blood Pact engine.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, // Blood Pact { type: 'move', piclet: 'opponent', moveIndex: 0 } ); const hpAfterSacrifice = engine.getState().playerPiclet.currentHp; expect(hpAfterSacrifice).toBeLessThan(initialHp); // Now attack should do double damage const initialOpponentHp = engine.getState().opponentPiclet.currentHp; engine.executeActions( { type: 'move', piclet: 'player', moveIndex: 1 }, // Strike (should be doubled) { type: 'move', piclet: 'opponent', moveIndex: 0 } ); const finalOpponentHp = engine.getState().opponentPiclet.currentHp; const damage = initialOpponentHp - finalOpponentHp; // Should do significantly more damage than normal (doubled) expect(damage).toBeGreaterThan(60); // Normal would be ~30-40 const log = engine.getLog(); expect(log.some(msg => msg.includes('Blood Pact') || msg.includes('sacrifice'))).toBe(true); }); }); describe('Conditional Power Scaling', () => { it('should scale damage based on conditions', () => { const revengeUser: PicletDefinition = { name: "Revenge Fighter", description: "Gets stronger when damaged", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 90, attack: 70, defense: 80, speed: 50 }, nature: "Brave", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Revenge Strike", type: AttackType.BEAST, power: 60, accuracy: 100, pp: 10, priority: 0, flags: ['contact'], effects: [ { type: 'damage', target: 'opponent', amount: 'normal' }, { type: 'damage', target: 'opponent', amount: 'strong', condition: 'ifDamagedThisTurn' } ] } ] }; const attacker: PicletDefinition = { name: "Fast Attacker", description: "Quick attacker", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 200, attack: 80, defense: 60, speed: 100 }, nature: "Hasty", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Quick Strike", type: AttackType.BEAST, power: 50, accuracy: 100, pp: 10, priority: 1, flags: ['contact', 'priority'], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] } ] }; // First test: revenge without being damaged first (revenge user at full HP) const engine1 = new BattleEngine(revengeUser, attacker); const initialOpponentHp = engine1.getState().opponentPiclet.currentHp; engine1.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, { type: 'move', piclet: 'opponent', moveIndex: 0 } ); const hpAfterNormalRevenge = engine1.getState().opponentPiclet.currentHp; const normalRevengeDamage = initialOpponentHp - hpAfterNormalRevenge; // Second test: revenge when damaged (revenge user starts damaged) const engine2 = new BattleEngine(revengeUser, attacker); // Damage the revenge user to trigger the condition engine2['state'].playerPiclet.currentHp = Math.floor(engine2['state'].playerPiclet.maxHp * 0.5); const initialOpponentHp2 = engine2.getState().opponentPiclet.currentHp; engine2.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, { type: 'move', piclet: 'opponent', moveIndex: 0 } ); const hpAfterPoweredRevenge = engine2.getState().opponentPiclet.currentHp; const poweredRevengeDamage = initialOpponentHp2 - hpAfterPoweredRevenge; // Verify that the conditional effect triggered by checking for multiple damage instances const damageMessages = engine2.getLog().filter(msg => msg.includes('took') && msg.includes('damage')); expect(damageMessages.length).toBeGreaterThanOrEqual(3); // Attacker hits revenge user, then revenge user hits back twice // Verify the powered revenge did more damage overall expect(poweredRevengeDamage).toBeGreaterThan(100); // Should be significant damage from both effects }); }); });