2PlayerGame/chapter1/test.js

55 lines
3.8 KiB
JavaScript
Raw Permalink Normal View History

2026-07-23 17:03:26 +03:00
// Fair-play перевірка кейса: node test.js
const assert = require('assert');
const { CASE, checkAccusation } = require('./case.js');
// 1. Кожен доказ досяжний рівно одним джерелом (spot / query / topic.gives)
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, `evidence "${id}" має ${sources[id]?.length || 0} джерел`);
for (const id of Object.keys(sources))
assert(CASE.evidence[id], `джерело посилається на неіснуючий доказ "${id}"`);
// 2. Роль джерела збігається з роллю доказу
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');
// 3. Усі requires існують і досяжні без циклів (симуляція повного проходження)
const allReq = [];
for (const ev of Object.values(CASE.evidence)) if (ev.requires) allReq.push(...ev.requires);
for (const p of Object.values(CASE.people)) for (const t of p.topics) if (t.requires) allReq.push(...t.requires);
for (const r of allReq) assert(CASE.evidence[r], `requires посилається на неіснуючий доказ "${r}"`);
const pinned = new Set();
let progress = true;
while (progress) {
progress = false;
const ok = req => !req || req.every(r => pinned.has(r));
for (const l of CASE.locations) for (const sp of l.spots)
if (sp.ev && !pinned.has(sp.ev)) { pinned.add(sp.ev); progress = true; }
for (const s of CASE.systems) for (const q of s.queries)
if (q.ev && !pinned.has(q.ev) && ok(q.requires ?? CASE.evidence[q.ev].requires)) { pinned.add(q.ev); progress = true; }
for (const p of Object.values(CASE.people)) for (const t of p.topics)
if (t.gives && !pinned.has(t.gives) && ok(t.requires)) { pinned.add(t.gives); progress = true; }
}
for (const id of Object.keys(CASE.evidence)) assert(pinned.has(id), `доказ "${id}" недосяжний (deadlock у requires)`);
// 4. Звинувачення: правильна комбінація виграє
assert(checkAccusation('elena', 'push', ['override_log', 'guard', 'entry_log', 'fabric']).ok);
assert(checkAccusation('elena', 'push', ['print_match', 'elena_lie', 'struggle']).ok);
// 5. Кожна зламана ланка дає осмислену підказку, а не перемогу
assert(!checkAccusation('rook', 'push', ['override_log', 'guard', 'entry_log', 'fabric']).ok);
assert(!checkAccusation('elena', 'poison', ['override_log', 'guard', 'entry_log', 'fabric']).ok);
assert(!checkAccusation('elena', 'push', ['guard', 'entry_log', 'fabric']).ok); // без способу
assert(!checkAccusation('elena', 'push', ['override_log', 'guard', 'fabric']).ok); // суперечність неповна
assert(!checkAccusation('elena', 'push', ['override_log', 'guard', 'entry_log']).ok); // без контакту
for (const bad of [['rook','push'],['elena','poison']].map(([s,m]) => checkAccusation(s, m, [])))
assert(bad.hint && bad.hint.length > 10);
// 6. Ключове протиріччя вимагає обох ролей
assert(CASE.evidence.guard.role === 'field' && CASE.evidence.entry_log.role === 'analyst');
console.log('OK — кейс розв\'язний, fair-play дотримано.');