File size: 9,992 Bytes
1ecc382 a6cd8d1 1ecc382 a6cd8d1 1ecc382 ba9896a 1ecc382 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
/**
* Integration tests for complete battle scenarios
* Tests complex multi-turn battles following the design document
*/
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', () => {
// Space vs Bug - Space has advantage
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 // Flame Burst (Space type)
};
const opponentAction: BattleAction = {
type: 'move',
piclet: 'opponent',
moveIndex: 0 // Tackle
};
engine.executeActions(playerAction, opponentAction);
turns++;
}
expect(engine.isGameOver()).toBe(true);
expect(turns).toBeLessThan(maxTurns);
// Player should win due to type advantage
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);
// Turn 1: Toxic Crawler uses Toxic Sting to poison
engine.executeActions(
{ type: 'move', piclet: 'player', moveIndex: 1 }, // Toxic Sting
{ type: 'move', piclet: 'opponent', moveIndex: 0 } // Tackle
);
// Check that opponent is poisoned
expect(engine.getState().opponentPiclet.statusEffects).toContain('poison');
// Turn 2: Guardian tries to heal while poison damage occurs
const hpBeforeTurn = engine.getState().opponentPiclet.currentHp;
engine.executeActions(
{ type: 'move', piclet: 'player', moveIndex: 0 }, // Tackle
{ type: 'move', piclet: 'opponent', moveIndex: 1 } // Healing Light
);
// Poison should have done damage during turn end
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);
// Damage the berserker to trigger low HP condition
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;
// Use Berserker's End while at low HP
engine.executeActions(
{ type: 'move', piclet: 'player', moveIndex: 1 }, // Berserker's End
{ type: 'move', piclet: 'opponent', moveIndex: 0 } // Tackle
);
const finalDefense = engine.getState().playerPiclet.defense;
const finalOpponentHp = engine.getState().opponentPiclet.currentHp;
// Should deal damage (may miss due to 90% accuracy, so check if hit)
const damageDealt = initialOpponentHp - finalOpponentHp;
const log = engine.getLog();
const moveHit = !log.some(msg => msg.includes('attack missed'));
if (moveHit) {
expect(damageDealt).toBeGreaterThan(20); // Should be significant due to strong damage condition
} else {
expect(damageDealt).toBe(0); // No damage if missed
}
// The defense should decrease if HP is below 25% (0.25) due to ifLowHp condition
if (initialHpRatio < 0.25) {
expect(finalDefense).toBeLessThan(initialDefense);
} else {
// If not low HP, no defense change expected
expect(finalDefense).toBe(initialDefense);
}
});
it('should handle stat modifications and their effects on damage', () => {
const engine = new BattleEngine(STELLAR_WOLF, TOXIC_CRAWLER);
// Turn 1: Power Up to increase attack
engine.executeActions(
{ type: 'move', piclet: 'player', moveIndex: 3 }, // Power Up
{ type: 'move', piclet: 'opponent', moveIndex: 0 } // Tackle
);
const boostedAttack = engine.getState().playerPiclet.attack;
const opponentHpAfterBoost = engine.getState().opponentPiclet.currentHp;
// Turn 2: Attack with boosted stats
engine.executeActions(
{ type: 'move', piclet: 'player', moveIndex: 0 }, // Tackle
{ type: 'move', piclet: 'opponent', moveIndex: 0 } // Tackle
);
const finalOpponentHp = engine.getState().opponentPiclet.currentHp;
const damageWithBoost = opponentHpAfterBoost - finalOpponentHp;
// Damage should be higher due to attack boost
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);
// Execute several turns with different moves
const moves = [
[3, 0], // Power Up vs Tackle
[1, 1], // Flame Burst vs Berserker's End
[2, 2], // Healing Light vs Healing Light
[0, 0] // Tackle vs Tackle
];
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);
// Should contain move usage
expect(log.some(msg => msg.includes('used Power Up'))).toBe(true);
expect(log.some(msg => msg.includes('used Flame Burst'))).toBe(true);
// Should contain stat changes
expect(log.some(msg => msg.includes('attack rose'))).toBe(true);
// Should contain healing (check for either recovered HP or no actual healing if at full HP)
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);
// Drain all PP from one move
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;
// Try to use any move
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);
// Battle should continue (opponent can still act)
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) {
// Both use healing moves to prolong battle
engine.executeActions(
{ type: 'move', piclet: 'player', moveIndex: 1 }, // Healing Light
{ type: 'move', piclet: 'opponent', moveIndex: 1 } // Healing Light
);
turns++;
// Occasionally attack to prevent infinite loop
if (turns % 5 === 0) {
engine.executeActions(
{ type: 'move', piclet: 'player', moveIndex: 0 }, // Tackle
{ type: 'move', piclet: 'opponent', moveIndex: 0 } // Tackle
);
}
}
// Should either end naturally or reach turn limit
expect(turns).toBeLessThanOrEqual(maxTurns);
// Engine should remain stable
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);
// Perform many state-changing operations
for (let i = 0; i < 10 && !engine.isGameOver(); i++) {
const state = engine.getState();
// Verify state consistency before each turn
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 }
);
}
// Final state should still be consistent
const finalState = engine.getState();
expect(finalState.playerPiclet.currentHp).toBeGreaterThanOrEqual(0);
expect(finalState.opponentPiclet.currentHp).toBeGreaterThanOrEqual(0);
});
});
}); |