2PlayerGame/chapter2/test.js
2026-07-23 17:03:26 +03:00

107 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Перевірки розділу 2: fair-play кейса, логіка актів, логіка міні-ігор. node test.js
const assert = require('assert');
const { CASE, checkAccusation } = require('./case2.js');
const { MG_SYMBOLS, seqGen, seqPrefixOk, triPos, inZone } = require('./minigames.js');
// ---------- 1. Кожен доказ має рівно одне джерело, ролі збігаються ----------
const sources = {};
for (const l of CASE.locations) for (const sp of l.spots) if (sp.ev) (sources[sp.ev] ??= []).push('spot:' + sp.id);
for (const s of CASE.systems) for (const q of s.queries) if (q.ev) (sources[q.ev] ??= []).push('query:' + q.id);
for (const p of Object.values(CASE.people)) for (const t of p.topics) if (t.gives) (sources[t.gives] ??= []).push('topic:' + t.id);
for (const id of Object.keys(CASE.evidence))
assert(sources[id]?.length === 1, `доказ "${id}" має ${sources[id]?.length || 0} джерел`);
for (const id of Object.keys(sources)) assert(CASE.evidence[id], `джерело посилається на неіснуючий доказ "${id}"`);
for (const l of CASE.locations) for (const sp of l.spots) if (sp.ev) assert(CASE.evidence[sp.ev].role === 'field');
for (const s of CASE.systems) for (const q of s.queries) if (q.ev) assert(CASE.evidence[q.ev].role === 'analyst');
// ---------- 2. requires існують ----------
for (const ev of Object.values(CASE.evidence)) for (const r of ev.requires || []) assert(CASE.evidence[r], `bad requires ${r}`);
for (const p of Object.values(CASE.people)) for (const t of p.topics) for (const r of t.requires || []) assert(CASE.evidence[r], `bad requires ${r}`);
for (const id of CASE.actGate) assert(CASE.evidence[id], `actGate: неіснуючий доказ ${id}`);
// ---------- 3. Симуляція проходження з актами ----------
// Людей можна допитати лише в їхній локації — акт локації гейтить і допит.
const personAct = {};
for (const l of CASE.locations) for (const pid of l.people)
personAct[pid] = Math.min(personAct[pid] || 9, l.act || 1);
for (const pid of Object.keys(CASE.people)) assert(personAct[pid], `персонаж "${pid}" не стоїть у жодній локації`);
function simulate(skipIds = new Set()) {
const pinned = new Set();
let act = 1, progress = true;
while (progress) {
progress = false;
if (act === 1 && CASE.actGate.every(id => pinned.has(id))) { act = 2; progress = true; }
const ok = req => !req || req.every(r => pinned.has(r));
const grab = id => { if (!skipIds.has(id) && !pinned.has(id)) { pinned.add(id); return true; } return false; };
for (const l of CASE.locations) if ((l.act || 1) <= act)
for (const sp of l.spots) if (sp.ev && grab(sp.ev)) progress = true;
for (const s of CASE.systems) for (const q of s.queries)
if (q.ev && (q.act || 1) <= act && ok(CASE.evidence[q.ev].requires) && grab(q.ev)) progress = true;
for (const [pid, p] of Object.entries(CASE.people)) if (personAct[pid] <= act)
for (const t of p.topics)
if (t.gives && ok(t.requires) && grab(t.gives)) progress = true;
}
return { pinned, act };
}
// повне проходження: все досяжно, акт 2 відкрився
const full = simulate();
assert(full.act === 2, 'акт 2 не відкрився');
for (const id of Object.keys(CASE.evidence)) assert(full.pinned.has(id), `доказ "${id}" недосяжний`);
// без доказів вбивства акт 2 не відкривається, і докази акту 2 недоступні
const stuck = simulate(new Set(['decanter']));
assert(stuck.act === 1, 'акт 2 відкрився без доказу з actGate');
for (const [id, ev] of Object.entries(CASE.evidence))
if ((ev.act || 1) === 2) assert(!stuck.pinned.has(id), `доказ акту 2 "${id}" досяжний в акті 1`);
// ---------- 4. Звинувачення ----------
assert(checkAccusation('stryzh', 'digoxin', ['blister', 'coatcheck', 'parking', 'dna_match']).ok);
assert(checkAccusation('stryzh', 'digoxin', ['pharma_log', 'stryzh_lie', 'dna_match']).ok);
assert(!checkAccusation('iryna', 'digoxin', ['blister', 'coatcheck', 'parking', 'dna_match']).ok);
assert(!checkAccusation('stryzh', 'placebo', ['blister', 'coatcheck', 'parking', 'dna_match']).ok);
assert(!checkAccusation('stryzh', 'digoxin', ['coatcheck', 'parking', 'dna_match']).ok); // без засобу
assert(!checkAccusation('stryzh', 'digoxin', ['blister', 'parking', 'dna_match']).ok); // суперечність неповна
assert(!checkAccusation('stryzh', 'digoxin', ['blister', 'coatcheck', 'parking']).ok); // без доступу
for (const r of [checkAccusation('mazur', 'digoxin', []), checkAccusation('stryzh', 'natural', [])])
assert(!r.ok && r.hint.length > 10);
// ключова суперечність вимагає обох ролей
assert(CASE.evidence.coatcheck.role === 'field' && CASE.evidence.parking.role === 'analyst');
// перехід між актами вимагає обох ролей
assert(new Set(CASE.actGate.map(id => CASE.evidence[id].role)).size === 2, 'actGate має вимагати доказів від обох ролей');
// ---------- 5. Міні-гра аналітика: код доступу ----------
assert.deepStrictEqual(seqGen(42, 5), seqGen(42, 5), 'seqGen недетермінований');
assert.notDeepStrictEqual(seqGen(42, 5), seqGen(43, 5), 'різні seed дають однакові коди');
for (const len of [4, 5, 6]) {
const s = seqGen(123, len);
assert(s.length === len);
for (const v of s) assert(Number.isInteger(v) && v >= 0 && v < MG_SYMBOLS.length, 'символ поза межами');
}
const seq = [0, 3, 1, 5];
assert(seqPrefixOk(seq, []));
assert(seqPrefixOk(seq, [0]));
assert(seqPrefixOk(seq, [0, 3, 1]));
assert(seqPrefixOk(seq, [0, 3, 1, 5]));
assert(!seqPrefixOk(seq, [1]));
assert(!seqPrefixOk(seq, [0, 3, 2]));
// ---------- 6. Міні-гра польового: сканер ----------
const P = 1600;
assert.strictEqual(triPos(0, P), 0);
assert.strictEqual(triPos(P / 2, P), 100);
assert.strictEqual(triPos(P, P), 0);
assert.strictEqual(triPos(P / 4, P), 50);
assert.strictEqual(triPos(3 * P / 4, P), 50);
assert.strictEqual(triPos(P + P / 2, P), 100, 'періодичність зламана');
for (let t = -5000; t <= 5000; t += 37) {
const p = triPos(t, P);
assert(p >= 0 && p <= 100, `triPos(${t}) = ${p} поза [0,100]`);
}
assert(inZone(50, 40, 20));
assert(inZone(40, 40, 20) && inZone(60, 40, 20), 'межі зони мають зараховуватись');
assert(!inZone(39.99, 40, 20) && !inZone(60.01, 40, 20));
console.log('OK — кейс розв\'язний, акти працюють, міні-ігри перевірено.');