89 lines
4.0 KiB
JavaScript
89 lines
4.0 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const test = require('node:test');
|
|
const { createTargetActivator, detectFeedback, createReactionPicker } = require('../../data/target_interaction.js');
|
|
|
|
test('a single tap selects a target without firing', () => {
|
|
const selected = [];
|
|
let shots = 0;
|
|
const activator = createTargetActivator({ canFire: () => true, select: target => selected.push(target), fire: async () => { shots += 1; } });
|
|
assert.equal(activator.tap({ x: 1, y: 6 }).action, 'select');
|
|
assert.deepEqual(selected, [{ x: 1, y: 6 }]);
|
|
assert.equal(shots, 0);
|
|
});
|
|
|
|
test('a second tap on the same cell fires once while a different cell selects', async () => {
|
|
let timeMs = 0;
|
|
const selected = [];
|
|
let shots = 0;
|
|
const activator = createTargetActivator({ canFire: () => true, select: target => selected.push(target), fire: async () => { shots += 1; }, now: () => timeMs });
|
|
activator.tap({ x: 1, y: 6 });
|
|
timeMs = 200;
|
|
const sameCell = activator.tap({ x: 1, y: 6 });
|
|
await sameCell.promise;
|
|
timeMs = 300;
|
|
assert.equal(activator.tap({ x: 2, y: 6 }).action, 'select');
|
|
assert.equal(shots, 1);
|
|
assert.deepEqual(selected, [{ x: 1, y: 6 }, { x: 2, y: 6 }]);
|
|
});
|
|
|
|
test('double activation and explicit fire are deduplicated while a shot is pending', async () => {
|
|
let resolveShot;
|
|
let shots = 0;
|
|
const activator = createTargetActivator({ canFire: () => true, select: () => {}, fire: () => new Promise(resolve => { shots += 1; resolveShot = resolve; }) });
|
|
const first = activator.doubleActivate({ x: 1, y: 6 });
|
|
const duplicate = activator.doubleActivate({ x: 1, y: 6 });
|
|
assert.equal(await duplicate.promise, false);
|
|
assert.equal(shots, 1);
|
|
resolveShot();
|
|
await first.promise;
|
|
});
|
|
|
|
test('out-of-turn or already-targeted cells cannot select or fire', () => {
|
|
let selected = 0;
|
|
let shots = 0;
|
|
const activator = createTargetActivator({ canFire: () => false, select: () => { selected += 1; }, fire: async () => { shots += 1; } });
|
|
assert.equal(activator.tap({ x: 1, y: 6 }).action, 'ignored');
|
|
assert.equal(activator.keyboard({ x: 1, y: 6 }).action, 'ignored');
|
|
assert.equal(selected, 0);
|
|
assert.equal(shots, 0);
|
|
});
|
|
|
|
function gameState(version, boards, overrides = {}) {
|
|
return { version, gameId: 7, phase: 'in_progress', turn: 'player1', winner: null, boards, ...overrides };
|
|
}
|
|
|
|
test('feedback distinguishes a hit on the opponent from damage to the player', () => {
|
|
const water = '0'.repeat(100);
|
|
const opponentHit = `${water.slice(0, 12)}3${water.slice(13)}`;
|
|
assert.equal(detectFeedback(gameState(1, [water, water]), gameState(2, [water, opponentHit]), 'player1').type, 'hit');
|
|
assert.equal(detectFeedback(gameState(1, [water, water]), gameState(2, [opponentHit, water]), 'player1').type, 'damage');
|
|
});
|
|
|
|
test('sinking and victory feedback take priority over ordinary cell changes', () => {
|
|
const water = '0'.repeat(100);
|
|
const sunk = `44${water.slice(2)}`;
|
|
assert.equal(detectFeedback(gameState(1, [water, water]), gameState(2, [water, sunk]), 'player1').type, 'sunk');
|
|
const finished = gameState(3, [water, sunk], { phase: 'finished', winner: 0 });
|
|
assert.equal(detectFeedback(gameState(2, [water, water]), finished, 'player1').type, 'victory');
|
|
assert.equal(detectFeedback(gameState(2, [water, water]), finished, 'player2').type, 'defeat');
|
|
});
|
|
|
|
test('duplicate snapshots do not repeat rewards', () => {
|
|
const water = '0'.repeat(100);
|
|
assert.equal(detectFeedback(gameState(2, [water, water]), gameState(2, [water, water]), 'player1'), undefined);
|
|
});
|
|
|
|
test('reaction picker avoids an immediate repeat for the same event', () => {
|
|
const picker = createReactionPicker({ hit: ['one', 'two', 'three'] }, () => 0);
|
|
assert.equal(picker('hit'), 'one');
|
|
assert.equal(picker('hit'), 'two');
|
|
assert.equal(picker('hit'), 'one');
|
|
});
|
|
|
|
test('reaction picker handles single and missing catalogs', () => {
|
|
const picker = createReactionPicker({ miss: ['splash'] }, () => 0.9);
|
|
assert.equal(picker('miss'), 'splash');
|
|
assert.equal(picker('miss'), 'splash');
|
|
assert.equal(picker('unknown'), undefined);
|
|
});
|