118 lines
3.2 KiB
JavaScript
118 lines
3.2 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import {
|
|
DEFAULT_PIRATE_SHOT_DAMAGE,
|
|
PLAYER_MAX_HULL,
|
|
normalizePlayerDamage,
|
|
reducePlayerProjectileHits,
|
|
resolveTaggedProjectile,
|
|
} from '../src/game-logic.js';
|
|
|
|
test('first pirate projectile removes exactly the default damage', () => {
|
|
const result = reducePlayerProjectileHits({
|
|
hull: PLAYER_MAX_HULL,
|
|
hits: [{ damage: DEFAULT_PIRATE_SHOT_DAMAGE }],
|
|
});
|
|
|
|
assert.equal(result.hull, 94);
|
|
assert.equal(result.appliedHits, 1);
|
|
assert.equal(result.totalDamage, 6);
|
|
assert.equal(result.gameOver, false);
|
|
});
|
|
|
|
test('damage normalization handles invalid and extreme values safely', () => {
|
|
assert.equal(normalizePlayerDamage(undefined), 6);
|
|
assert.equal(normalizePlayerDamage('not-a-number'), 6);
|
|
assert.equal(normalizePlayerDamage(999), 25);
|
|
assert.equal(normalizePlayerDamage(-12), 1);
|
|
assert.equal(normalizePlayerDamage('8'), 8);
|
|
});
|
|
|
|
test('invalid hull state recovers from max hull before applying projectile damage', () => {
|
|
const result = reducePlayerProjectileHits({
|
|
hull: Number.NaN,
|
|
hits: [{ damage: 6 }],
|
|
});
|
|
|
|
assert.equal(result.hull, 94);
|
|
assert.equal(result.gameOver, false);
|
|
});
|
|
|
|
test('lethal projectile damage clamps hull at zero and reports game over', () => {
|
|
const result = reducePlayerProjectileHits({
|
|
hull: 5,
|
|
hits: [{ damage: 6 }],
|
|
});
|
|
|
|
assert.equal(result.hull, 0);
|
|
assert.equal(result.totalDamage, 6);
|
|
assert.equal(result.gameOver, true);
|
|
});
|
|
|
|
test('empty projectile queue is a no-op', () => {
|
|
const result = reducePlayerProjectileHits({
|
|
hull: 72,
|
|
hits: [],
|
|
});
|
|
|
|
assert.equal(result.hull, 72);
|
|
assert.equal(result.totalDamage, 0);
|
|
assert.equal(result.appliedHits, 0);
|
|
assert.equal(result.gameOver, false);
|
|
});
|
|
|
|
test('every distinct pirate projectile can damage once with no cross-shot suppression', () => {
|
|
const first = reducePlayerProjectileHits({
|
|
hull: 100,
|
|
hits: [{ damage: 6 }],
|
|
});
|
|
const second = reducePlayerProjectileHits({
|
|
hull: first.hull,
|
|
hits: [{ damage: 6 }],
|
|
});
|
|
|
|
assert.equal(first.hull, 94);
|
|
assert.equal(second.hull, 88);
|
|
});
|
|
|
|
test('multiple distinct pirate projectiles in the same frame all apply damage', () => {
|
|
const result = reducePlayerProjectileHits({
|
|
hull: 100,
|
|
hits: [
|
|
{ damage: 6 },
|
|
{ damage: 6 },
|
|
{ damage: 6 },
|
|
],
|
|
});
|
|
|
|
assert.equal(result.hull, 82);
|
|
assert.equal(result.totalDamage, 18);
|
|
assert.equal(result.appliedHits, 3);
|
|
});
|
|
|
|
test('projectile resolver is independent of Phaser overlap argument order', () => {
|
|
const player = {
|
|
getData() { return undefined; },
|
|
};
|
|
const pirateShot = {
|
|
getData(key) {
|
|
return key === 'projectileOwner' ? 'pirate' : undefined;
|
|
},
|
|
};
|
|
|
|
assert.equal(resolveTaggedProjectile(pirateShot, player, 'pirate'), pirateShot);
|
|
assert.equal(resolveTaggedProjectile(player, pirateShot, 'pirate'), pirateShot);
|
|
});
|
|
|
|
test('projectile resolver never mistakes the player for an untagged shot', () => {
|
|
const player = {
|
|
getData() { return undefined; },
|
|
};
|
|
const untagged = {
|
|
getData() { return undefined; },
|
|
};
|
|
|
|
assert.equal(resolveTaggedProjectile(player, untagged, 'pirate'), null);
|
|
});
|