AdaptiveTactics / tests /test_combat.js
EphAsad's picture
Upload 61 files
076c3cb verified
/**
* Adaptive Tactics - Combat Tests
* Tests for combat execution, healing, death handling
*/
import { Unit, Weapon, MapData } from '../js/models.js';
import { executeCombat, executeHeal, CombatResult, generateCombatLog } from '../js/combat.js';
// Test utilities
function assert(condition, message) {
if (!condition) {
throw new Error(`FAILED: ${message}`);
}
console.log(`PASSED: ${message}`);
}
function createTestWeapon(overrides = {}) {
return new Weapon({
id: 'test_sword',
name: 'Test Sword',
kind: 'physical',
might: 5,
hit: 100, // 100% hit for deterministic tests
minRange: 1,
maxRange: 1,
heal: 0,
...overrides
});
}
function createTestUnit(id, faction, weaponOverrides = {}) {
const weapon = createTestWeapon(weaponOverrides);
return new Unit({
id: id,
name: `Test ${id}`,
sprite: 'test.png',
faction: faction,
stats: {
hp: 30,
maxHp: 30,
power: 10,
skill: 15,
speed: 10,
def: 5,
res: 5,
mov: 5
},
weapons: [weapon.id],
equippedWeapon: weapon.id
}, { [weapon.id]: weapon });
}
function createTestMap() {
const mapData = new MapData({
id: 'test_map',
name: 'Test Map',
rows: 5,
cols: 5,
tiles: {
blocked: [],
bush: [],
throne: []
},
player_spawn: {},
enemy_layout: {},
flex_nodes: {},
reinforcement_entries: {}
});
mapData.initTiles();
return mapData;
}
const testGameRules = {
double_speed_gap: 4
};
// Test: Basic Combat Exchange
function testBasicCombat() {
const map = createTestMap();
const attacker = createTestUnit('attacker', 'player', { hit: 100 });
const defender = createTestUnit('defender', 'enemy', { hit: 100 });
attacker.power = 12;
attacker.setPosition(2, 2);
defender.def = 5;
defender.setPosition(2, 3);
map.getTile(2, 2).placeUnit(attacker);
map.getTile(2, 3).placeUnit(defender);
const result = executeCombat(attacker, defender, map, testGameRules);
// Damage: 12 + 5 - 5 = 12
assert(result.attacks.length >= 1, 'Should have at least 1 attack');
assert(result.attacks[0].damage === 12, `First attack should deal 12 damage, got ${result.attacks[0].damage}`);
assert(defender.hp === 18, `Defender should have 18 HP remaining, got ${defender.hp}`);
}
// Test: Counter Attack
function testCounterAttack() {
const map = createTestMap();
const attacker = createTestUnit('attacker', 'player', { hit: 100 });
const defender = createTestUnit('defender', 'enemy', { hit: 100 });
attacker.power = 10;
defender.power = 8;
attacker.setPosition(2, 2);
defender.setPosition(2, 3);
map.getTile(2, 2).placeUnit(attacker);
map.getTile(2, 3).placeUnit(defender);
const result = executeCombat(attacker, defender, map, testGameRules);
// Both should have acted
const attackerAttacks = result.attacks.filter(a => a.attackerId === 'attacker');
const defenderCounters = result.attacks.filter(a => a.attackerId === 'defender' && a.isCounter);
assert(attackerAttacks.length >= 1, 'Attacker should have attacked');
assert(defenderCounters.length >= 1, 'Defender should have countered');
}
// Test: No Counter at Range 2 with Sword
function testNoCounterAtRange2() {
const map = createTestMap();
const attacker = createTestUnit('attacker', 'player', {
id: 'spear',
minRange: 1,
maxRange: 2,
hit: 100
});
const defender = createTestUnit('defender', 'enemy', {
id: 'sword',
minRange: 1,
maxRange: 1,
hit: 100
});
attacker.setPosition(2, 2);
defender.setPosition(2, 4); // 2 tiles away
map.getTile(2, 2).placeUnit(attacker);
map.getTile(2, 4).placeUnit(defender);
const result = executeCombat(attacker, defender, map, testGameRules);
const defenderCounters = result.attacks.filter(a => a.attackerId === 'defender');
assert(defenderCounters.length === 0, 'Sword defender should not counter at range 2');
}
// Test: Doubling
function testDoubling() {
const map = createTestMap();
const attacker = createTestUnit('attacker', 'player', { hit: 100 });
const defender = createTestUnit('defender', 'enemy', { hit: 100 });
attacker.speed = 15;
defender.speed = 10; // 5 speed difference, should double
attacker.setPosition(2, 2);
defender.setPosition(2, 3);
map.getTile(2, 2).placeUnit(attacker);
map.getTile(2, 3).placeUnit(defender);
const result = executeCombat(attacker, defender, map, testGameRules);
const attackerAttacks = result.attacks.filter(a => a.attackerId === 'attacker');
assert(attackerAttacks.length === 2, `Attacker should attack twice (double), got ${attackerAttacks.length}`);
}
// Test: Death During Combat
function testDeathDuringCombat() {
const map = createTestMap();
const attacker = createTestUnit('attacker', 'player', { hit: 100, might: 30 });
const defender = createTestUnit('defender', 'enemy', { hit: 100 });
attacker.power = 30; // Will one-shot
defender.hp = 20;
defender.maxHp = 20;
attacker.setPosition(2, 2);
defender.setPosition(2, 3);
map.getTile(2, 2).placeUnit(attacker);
map.getTile(2, 3).placeUnit(defender);
const result = executeCombat(attacker, defender, map, testGameRules);
assert(result.defenderDied === true, 'Defender should be marked as died');
assert(defender.alive === false, 'Defender should be dead');
assert(defender.hp === 0, 'Defender HP should be 0');
}
// Test: Defender Dies Before Counter
function testDefenderDiesBeforeCounter() {
const map = createTestMap();
const attacker = createTestUnit('attacker', 'player', { hit: 100 });
const defender = createTestUnit('defender', 'enemy', { hit: 100 });
attacker.power = 30;
defender.hp = 10;
defender.maxHp = 10;
defender.power = 20; // Would deal big damage if alive
attacker.setPosition(2, 2);
defender.setPosition(2, 3);
map.getTile(2, 2).placeUnit(attacker);
map.getTile(2, 3).placeUnit(defender);
const result = executeCombat(attacker, defender, map, testGameRules);
const defenderCounters = result.attacks.filter(a => a.attackerId === 'defender');
assert(defenderCounters.length === 0, 'Dead defender should not counter');
assert(attacker.hp === 30, 'Attacker should be at full HP');
}
// Test: Healing
function testHealing() {
const map = createTestMap();
const healWeapon = new Weapon({
id: 'staff',
name: 'Heal Staff',
kind: 'heal',
might: 0,
hit: 100,
minRange: 1,
maxRange: 1,
heal: 6
});
const healer = new Unit({
id: 'healer',
name: 'Healer',
sprite: 'test.png',
faction: 'player',
stats: { hp: 20, maxHp: 20, power: 5, skill: 10, speed: 8, def: 3, res: 10, mov: 4 },
weapons: ['staff'],
equippedWeapon: 'staff'
}, { staff: healWeapon });
const target = createTestUnit('target', 'player');
target.hp = 15;
target.maxHp = 30;
healer.setPosition(2, 2);
target.setPosition(2, 3);
map.getTile(2, 2).placeUnit(healer);
map.getTile(2, 3).placeUnit(target);
const result = executeHeal(healer, target, map);
assert(result.success === true, 'Heal should succeed');
assert(result.amount === 6, `Should heal for 6, got ${result.amount}`);
assert(target.hp === 21, `Target should have 21 HP, got ${target.hp}`);
}
// Test: Overheal Capped at MaxHP
function testOverhealCapped() {
const map = createTestMap();
const healWeapon = new Weapon({
id: 'staff',
name: 'Heal Staff',
kind: 'heal',
might: 0,
hit: 100,
minRange: 1,
maxRange: 1,
heal: 10
});
const healer = new Unit({
id: 'healer',
name: 'Healer',
sprite: 'test.png',
faction: 'player',
stats: { hp: 20, maxHp: 20, power: 5, skill: 10, speed: 8, def: 3, res: 10, mov: 4 },
weapons: ['staff'],
equippedWeapon: 'staff'
}, { staff: healWeapon });
const target = createTestUnit('target', 'player');
target.hp = 28;
target.maxHp = 30;
healer.setPosition(2, 2);
target.setPosition(2, 3);
map.getTile(2, 2).placeUnit(healer);
map.getTile(2, 3).placeUnit(target);
const result = executeHeal(healer, target, map);
assert(result.success === true, 'Heal should succeed');
assert(result.amount === 2, `Should only heal 2 (to cap), got ${result.amount}`);
assert(target.hp === 30, `Target should be at max HP 30, got ${target.hp}`);
}
// Test: Combat Log Generation
function testCombatLogGeneration() {
const result = new CombatResult();
result.addAttack(
{ id: 'a', name: 'Attacker' },
{ id: 'd', name: 'Defender' },
true,
12,
false
);
result.addAttack(
{ id: 'd', name: 'Defender' },
{ id: 'a', name: 'Attacker' },
false,
0,
true
);
const logs = generateCombatLog(result);
assert(logs.length === 2, `Should have 2 log entries, got ${logs.length}`);
assert(logs[0].text.includes('hit') && logs[0].text.includes('12'), 'First log should mention hit and damage');
assert(logs[1].text.includes('missed'), 'Second log should mention miss');
}
// Run all tests
function runAllTests() {
console.log('=== Running Combat Tests ===\n');
try {
testBasicCombat();
testCounterAttack();
testNoCounterAtRange2();
testDoubling();
testDeathDuringCombat();
testDefenderDiesBeforeCounter();
testHealing();
testOverhealCapped();
testCombatLogGeneration();
console.log('\n=== All Combat Tests Passed ===');
} catch (e) {
console.error('\n=== Test Failed ===');
console.error(e.message);
throw e;
}
}
// Export for use in browser or Node
if (typeof window !== 'undefined') {
window.runCombatTests = runAllTests;
}
export { runAllTests };