feat: add label random and animations to web interface

This commit is contained in:
2026-08-30 23:10:23 +03:00
parent 5cb4d8e9f6
commit c0c3c73c7a
8 changed files with 598 additions and 21 deletions
+40 -1
View File
@@ -1,6 +1,6 @@
const assert = require('node:assert/strict');
const test = require('node:test');
const { createTargetActivator } = require('../../data/target_interaction.js');
const { createTargetActivator, detectFeedback, createReactionPicker } = require('../../data/target_interaction.js');
test('a single tap selects a target without firing', () => {
const selected = [];
@@ -47,3 +47,42 @@ test('out-of-turn or already-targeted cells cannot select or fire', () => {
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);
});