148 lines
3.8 KiB
JavaScript
148 lines
3.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import {
|
|
DEFAULT_PIRATE_SHOT_DAMAGE,
|
|
PLAYER_HIT_COOLDOWN_MS,
|
|
PLAYER_MAX_HULL,
|
|
normalizePlayerDamage,
|
|
processQueuedPlayerHits,
|
|
reducePlayerHitQueue,
|
|
} from '../src/game-logic.js';
|
|
|
|
test('first pirate hit removes exactly the default damage and does not end the game', () => {
|
|
const result = reducePlayerHitQueue({
|
|
hull: PLAYER_MAX_HULL,
|
|
lastHitAt: -Infinity,
|
|
now: 1000,
|
|
damages: [DEFAULT_PIRATE_SHOT_DAMAGE],
|
|
});
|
|
|
|
assert.equal(result.hull, 94);
|
|
assert.equal(result.appliedHits, 1);
|
|
assert.equal(result.ignoredHits, 0);
|
|
assert.equal(result.totalDamage, 6);
|
|
assert.equal(result.gameOver, false);
|
|
});
|
|
|
|
|
|
test('actual queued first player hit applies 6 damage without mutating projectile lifecycle', () => {
|
|
const shot = {
|
|
active: true,
|
|
destroyed: false,
|
|
destroy() {
|
|
this.destroyed = true;
|
|
this.active = false;
|
|
},
|
|
};
|
|
|
|
const firstFrame = processQueuedPlayerHits({
|
|
hull: PLAYER_MAX_HULL,
|
|
lastHitAt: -Infinity,
|
|
now: 1000,
|
|
hits: [{ shot, damage: DEFAULT_PIRATE_SHOT_DAMAGE }],
|
|
});
|
|
|
|
assert.equal(shot.destroyed, false);
|
|
assert.equal(shot.active, true);
|
|
assert.equal(firstFrame.hull, 94);
|
|
assert.equal(firstFrame.appliedHits, 1);
|
|
assert.equal(firstFrame.ignoredHits, 0);
|
|
assert.equal(firstFrame.gameOver, false);
|
|
|
|
const followingFrame = processQueuedPlayerHits({
|
|
hull: firstFrame.hull,
|
|
lastHitAt: firstFrame.lastHitAt,
|
|
now: 1016,
|
|
hits: [],
|
|
});
|
|
|
|
assert.equal(followingFrame.hull, 94);
|
|
assert.equal(followingFrame.gameOver, false);
|
|
assert.equal(followingFrame.appliedHits, 0);
|
|
});
|
|
|
|
test('three cannonballs arriving in one broadside only apply one hull hit', () => {
|
|
const result = reducePlayerHitQueue({
|
|
hull: 100,
|
|
lastHitAt: -Infinity,
|
|
now: 2000,
|
|
damages: [6, 6, 6],
|
|
});
|
|
|
|
assert.equal(result.hull, 94);
|
|
assert.equal(result.appliedHits, 1);
|
|
assert.equal(result.ignoredHits, 2);
|
|
});
|
|
|
|
test('hit inside cooldown is ignored and exact cooldown boundary is accepted', () => {
|
|
const blocked = reducePlayerHitQueue({
|
|
hull: 94,
|
|
lastHitAt: 1000,
|
|
now: 1000 + PLAYER_HIT_COOLDOWN_MS - 1,
|
|
damages: [6],
|
|
});
|
|
|
|
assert.equal(blocked.hull, 94);
|
|
assert.equal(blocked.appliedHits, 0);
|
|
assert.equal(blocked.ignoredHits, 1);
|
|
|
|
const accepted = reducePlayerHitQueue({
|
|
hull: 94,
|
|
lastHitAt: 1000,
|
|
now: 1000 + PLAYER_HIT_COOLDOWN_MS,
|
|
damages: [6],
|
|
});
|
|
|
|
assert.equal(accepted.hull, 88);
|
|
assert.equal(accepted.appliedHits, 1);
|
|
});
|
|
|
|
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 damage', () => {
|
|
const result = reducePlayerHitQueue({
|
|
hull: Number.NaN,
|
|
lastHitAt: -Infinity,
|
|
now: 3000,
|
|
damages: [6],
|
|
});
|
|
|
|
assert.equal(result.hull, 94);
|
|
assert.equal(result.gameOver, false);
|
|
});
|
|
|
|
test('lethal hit clamps hull at zero and reports game over', () => {
|
|
const result = reducePlayerHitQueue({
|
|
hull: 5,
|
|
lastHitAt: -Infinity,
|
|
now: 4000,
|
|
damages: [6],
|
|
});
|
|
|
|
assert.equal(result.hull, 0);
|
|
assert.equal(result.totalDamage, 6);
|
|
assert.equal(result.gameOver, true);
|
|
});
|
|
|
|
test('empty hit queue is a no-op', () => {
|
|
const result = reducePlayerHitQueue({
|
|
hull: 72,
|
|
lastHitAt: 1234,
|
|
now: 5000,
|
|
damages: [],
|
|
});
|
|
|
|
assert.equal(result.hull, 72);
|
|
assert.equal(result.lastHitAt, 1234);
|
|
assert.equal(result.appliedHits, 0);
|
|
assert.equal(result.ignoredHits, 0);
|
|
assert.equal(result.gameOver, false);
|
|
});
|