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);
    });
  });
});