airship-phaser/tests/pirate-ai.test.mjs

104 lines
2.9 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
PIRATE_CLASSES,
PIRATE_CLASS_COSTS,
pirateDesiredHeading,
pirateRosterForWave,
pirateSpeedForRange,
separationVector,
waveDifficultyBudget,
} from '../src/pirate-ai.js';
const rosterCost = (roster) => roster.reduce(
(sum, pirateClass) => sum + PIRATE_CLASS_COSTS[pirateClass.key],
0,
);
test('pirate classes expose distinct combat roles and projectile damage', () => {
assert.ok(PIRATE_CLASSES.raider.speed > PIRATE_CLASSES.frigate.speed);
assert.ok(PIRATE_CLASSES.raider.hull < PIRATE_CLASSES.frigate.hull);
assert.ok(PIRATE_CLASSES.captain.hull > PIRATE_CLASSES.frigate.hull);
assert.ok(PIRATE_CLASSES.captain.projectileDamage > PIRATE_CLASSES.frigate.projectileDamage);
});
test('wave roster uses a monotonic difficulty budget with a six-ship cap', () => {
assert.deepEqual(
pirateRosterForWave(1).map(({ key }) => key),
['frigate'],
);
assert.deepEqual(
pirateRosterForWave(2).map(({ key }) => key),
['frigate', 'raider'],
);
assert.deepEqual(
pirateRosterForWave(4).map(({ key }) => key),
['captain', 'raider', 'raider', 'raider'],
);
let previousCost = 0;
for (let wave = 1; wave <= 12; wave += 1) {
const roster = pirateRosterForWave(wave);
const cost = rosterCost(roster);
assert.ok(roster.length <= 6);
assert.ok(cost <= waveDifficultyBudget(wave));
assert.ok(cost >= previousCost, `wave ${wave} should not get cheaper`);
previousCost = cost;
}
});
test('pirates retreat away from the player when inside retreat range', () => {
const heading = pirateDesiredHeading({
bearing: 20,
currentHeading: 90,
distance: 150,
preferredRange: 335,
retreatRange: 210,
});
assert.equal(heading, 200);
});
test('pirates choose the closest broadside heading inside preferred range', () => {
const heading = pirateDesiredHeading({
bearing: 0,
currentHeading: 80,
distance: 300,
preferredRange: 335,
retreatRange: 210,
});
assert.equal(heading, 90);
});
test('retreat speed keeps pirates moving out of close range', () => {
const retreat = pirateSpeedForRange({
distance: 150,
preferredRange: 335,
retreatRange: 210,
baseSpeed: 118,
});
const broadside = pirateSpeedForRange({
distance: 300,
preferredRange: 335,
retreatRange: 210,
baseSpeed: 118,
});
assert.ok(retreat > broadside);
});
test('separation steering pushes nearby ships apart and ignores distant ships', () => {
const ship = { x: 100, y: 100, active: true };
const close = { x: 150, y: 100, active: true };
const far = { x: 500, y: 100, active: true };
const vector = separationVector(ship, [ship, close, far], 125);
assert.ok(vector.x < 0, 'ship should steer away from a close ship on its right');
assert.equal(vector.y, 0);
assert.ok(Math.hypot(vector.x, vector.y) <= 1);
});