piclets / src /lib /battle-engine /extreme-moves.test.ts
Fraser's picture
add battle engine
1ecc382
/**
* 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');
});
});
});