|
|
|
|
|
|
|
|
|
|
|
import { describe, it, expect } from 'vitest'; |
|
import { BattleEngine } from './BattleEngine'; |
|
import { |
|
STELLAR_WOLF, |
|
TOXIC_CRAWLER, |
|
BERSERKER_BEAST, |
|
AQUA_GUARDIAN |
|
} from './test-data'; |
|
import { BattleAction } from './types'; |
|
|
|
describe('Battle Engine Integration', () => { |
|
describe('Complete Battle Scenarios', () => { |
|
it('should handle a complete battle with type effectiveness', () => { |
|
|
|
const engine = new BattleEngine(STELLAR_WOLF, TOXIC_CRAWLER); |
|
let turns = 0; |
|
const maxTurns = 20; |
|
|
|
while (!engine.isGameOver() && turns < maxTurns) { |
|
const playerAction: BattleAction = { |
|
type: 'move', |
|
piclet: 'player', |
|
moveIndex: 1 |
|
}; |
|
const opponentAction: BattleAction = { |
|
type: 'move', |
|
piclet: 'opponent', |
|
moveIndex: 0 |
|
}; |
|
|
|
engine.executeActions(playerAction, opponentAction); |
|
turns++; |
|
} |
|
|
|
expect(engine.isGameOver()).toBe(true); |
|
expect(turns).toBeLessThan(maxTurns); |
|
|
|
|
|
expect(engine.getWinner()).toBe('player'); |
|
|
|
const log = engine.getLog(); |
|
expect(log.some(msg => msg.includes("It's super effective!"))).toBe(true); |
|
}); |
|
|
|
it('should handle a battle with status effects and healing', () => { |
|
const engine = new BattleEngine(TOXIC_CRAWLER, AQUA_GUARDIAN); |
|
|
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 1 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
|
|
expect(engine.getState().opponentPiclet.statusEffects).toContain('poison'); |
|
|
|
|
|
const hpBeforeTurn = engine.getState().opponentPiclet.currentHp; |
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 1 } |
|
); |
|
|
|
|
|
const log = engine.getLog(); |
|
expect(log.some(msg => msg.includes('hurt by poison'))).toBe(true); |
|
}); |
|
|
|
it('should handle conditional move effects correctly', () => { |
|
const engine = new BattleEngine(BERSERKER_BEAST, AQUA_GUARDIAN); |
|
|
|
|
|
engine['state'].playerPiclet.currentHp = Math.floor(engine['state'].playerPiclet.maxHp * 0.2); |
|
|
|
const initialDefense = engine.getState().playerPiclet.defense; |
|
const initialOpponentHp = engine.getState().opponentPiclet.currentHp; |
|
const initialHpRatio = engine.getState().playerPiclet.currentHp / engine.getState().playerPiclet.maxHp; |
|
|
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 1 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const finalDefense = engine.getState().playerPiclet.defense; |
|
const finalOpponentHp = engine.getState().opponentPiclet.currentHp; |
|
|
|
|
|
const damageDealt = initialOpponentHp - finalOpponentHp; |
|
const log = engine.getLog(); |
|
const moveHit = !log.some(msg => msg.includes('attack missed')); |
|
|
|
if (moveHit) { |
|
expect(damageDealt).toBeGreaterThan(20); |
|
} else { |
|
expect(damageDealt).toBe(0); |
|
} |
|
|
|
|
|
if (initialHpRatio < 0.25) { |
|
expect(finalDefense).toBeLessThan(initialDefense); |
|
} else { |
|
|
|
expect(finalDefense).toBe(initialDefense); |
|
} |
|
}); |
|
|
|
it('should handle stat modifications and their effects on damage', () => { |
|
const engine = new BattleEngine(STELLAR_WOLF, TOXIC_CRAWLER); |
|
|
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 3 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const boostedAttack = engine.getState().playerPiclet.attack; |
|
const opponentHpAfterBoost = engine.getState().opponentPiclet.currentHp; |
|
|
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const finalOpponentHp = engine.getState().opponentPiclet.currentHp; |
|
const damageWithBoost = opponentHpAfterBoost - finalOpponentHp; |
|
|
|
|
|
expect(damageWithBoost).toBeGreaterThan(20); |
|
expect(boostedAttack).toBeGreaterThan(STELLAR_WOLF.baseStats.attack); |
|
}); |
|
|
|
it('should maintain battle log integrity throughout complex battle', () => { |
|
const engine = new BattleEngine(STELLAR_WOLF, BERSERKER_BEAST); |
|
|
|
|
|
const moves = [ |
|
[3, 0], |
|
[1, 1], |
|
[2, 2], |
|
[0, 0] |
|
]; |
|
|
|
for (const [playerMove, opponentMove] of moves) { |
|
if (engine.isGameOver()) break; |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: playerMove }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: opponentMove } |
|
); |
|
} |
|
|
|
const log = engine.getLog(); |
|
expect(log.length).toBeGreaterThan(8); |
|
|
|
|
|
expect(log.some(msg => msg.includes('used Power Up'))).toBe(true); |
|
expect(log.some(msg => msg.includes('used Flame Burst'))).toBe(true); |
|
|
|
|
|
expect(log.some(msg => msg.includes('attack rose'))).toBe(true); |
|
|
|
|
|
const hasHealing = log.some(msg => msg.includes('recovered') && msg.includes('HP')); |
|
const hasHealingAttempt = log.some(msg => msg.includes('used Healing Light')); |
|
expect(hasHealing || hasHealingAttempt).toBe(true); |
|
}); |
|
|
|
it('should handle edge case: all moves run out of PP', () => { |
|
const engine = new BattleEngine(STELLAR_WOLF, TOXIC_CRAWLER); |
|
|
|
|
|
engine['state'].playerPiclet.moves[0].currentPP = 0; |
|
engine['state'].playerPiclet.moves[1].currentPP = 0; |
|
engine['state'].playerPiclet.moves[2].currentPP = 0; |
|
engine['state'].playerPiclet.moves[3].currentPP = 0; |
|
|
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const log = engine.getLog(); |
|
expect(log.some(msg => msg.includes('no PP left'))).toBe(true); |
|
|
|
|
|
expect(engine.isGameOver()).toBe(false); |
|
}); |
|
}); |
|
|
|
describe('Performance and Stability', () => { |
|
it('should handle very long battles without issues', () => { |
|
const engine = new BattleEngine(AQUA_GUARDIAN, AQUA_GUARDIAN); |
|
let turns = 0; |
|
const maxTurns = 100; |
|
|
|
while (!engine.isGameOver() && turns < maxTurns) { |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 1 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 1 } |
|
); |
|
turns++; |
|
|
|
|
|
if (turns % 5 === 0) { |
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
} |
|
} |
|
|
|
|
|
expect(turns).toBeLessThanOrEqual(maxTurns); |
|
|
|
|
|
const state = engine.getState(); |
|
expect(state.turn).toBeGreaterThan(1); |
|
expect(state.log.length).toBeGreaterThan(0); |
|
}); |
|
|
|
it('should maintain state consistency after many operations', () => { |
|
const engine = new BattleEngine(STELLAR_WOLF, TOXIC_CRAWLER); |
|
|
|
|
|
for (let i = 0; i < 10 && !engine.isGameOver(); i++) { |
|
const state = engine.getState(); |
|
|
|
|
|
expect(state.playerPiclet.currentHp).toBeGreaterThanOrEqual(0); |
|
expect(state.opponentPiclet.currentHp).toBeGreaterThanOrEqual(0); |
|
expect(state.playerPiclet.currentHp).toBeLessThanOrEqual(state.playerPiclet.maxHp); |
|
expect(state.opponentPiclet.currentHp).toBeLessThanOrEqual(state.opponentPiclet.maxHp); |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: i % 4 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: i % 3 } |
|
); |
|
} |
|
|
|
|
|
const finalState = engine.getState(); |
|
expect(finalState.playerPiclet.currentHp).toBeGreaterThanOrEqual(0); |
|
expect(finalState.opponentPiclet.currentHp).toBeGreaterThanOrEqual(0); |
|
}); |
|
}); |
|
}); |