55 lines
3.8 KiB
JavaScript
55 lines
3.8 KiB
JavaScript
|
|
// 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 дотримано.');
|