2PlayerGame/detective-game/casegen/accusation.js
2026-08-15 14:07:39 +03:00

70 lines
4.1 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.

// casegen/accusation.js — симулятор досяжності та перевірка звинувачення
const RT = require('./runtime');
function solve(cs) {
const E = cs.evidence;
const st = RT.newState();
let changed = true;
while (changed) {
changed = false;
for (const cid of st.pointed) if (!st.warrants.includes(cid)) { st.warrants.push(cid); changed = true; }
for (const ev of Object.values(E)) {
if (st.found.includes(ev.id) || !RT.available(cs, st, ev, { ignorePuzzles: true })) continue;
RT.applyFound(cs, st, ev); changed = true;
}
}
const has = pred => Object.values(E).some(ev => st.found.includes(ev.id) && pred(ev));
return {
reached: new Set(st.found), identity: st.identity,
killerConfirmed: has(ev => ev.gives.confirms),
methodKnown: has(ev => ev.gives.method),
motiveProven: has(ev => ev.gives.motive),
herringDisproved: has(ev => ev.gives.disproves === cs.herring),
herringLeadFound: st.found.includes('her_lead'),
lairProven: has(ev => ev.gives.lair),
finalCandidates: RT.candidates(cs, st)
};
}
function checkAccusation(cs, ans, pinned) {
const E = cs.evidence, killer = cs.citizens.find(c => c.id === cs.finale.killer);
const has = pred => pinned.some(id => E[id] && pred(E[id]));
const hasConfirmFor = (name) => pinned.some(id => {
const ev = E[id];
if (!ev || !ev.gives || !ev.gives.confirms) return false;
if (ev.gives.pointsTo) {
const target = cs.citizens.find(c => c.id === ev.gives.pointsTo);
return target && target.name.toLowerCase() === name;
}
return true;
});
const accused = (ans.who || '').trim().toLowerCase();
if (accused !== killer.name.toLowerCase())
return { ok: false, hint: 'Ім\'я не сходиться з доказами: сліди з місця злочину (взуття, тканина, зріст, ДНК) звужують коло — і хтось із перевірених має підтверджене алібі.' };
if (cs.caseType === 'kidnap') {
if (ans.how !== cs.finale.where)
return { ok: false, hint: 'Місце не сходиться. ДЕ саме вказують знахідки: бирка ключа, план, чек за замок?' };
if (ans.why !== cs.finale.motive)
return { ok: false, hint: 'Мотив не той. ЩО пов\'язувало викрадача зі зниклою особою?' };
if (!hasConfirmFor(accused))
return { ok: false, hint: 'Особу викрадача не підтверджено: потрібен збіг ДНК або спростоване алібі на дошці.' };
if (!has(ev => ev.gives.motive))
return { ok: false, hint: 'Мотив не підкріплено жодним закріпленим доказом.' };
if (!has(ev => ev.gives.lair))
return { ok: false, hint: 'Місце утримання не підкріплено закріпленим доказом — штурм наосліп ніхто не санкціонує.' };
return { ok: true };
}
if (ans.how !== cs.finale.method)
return { ok: false, hint: 'Спосіб суперечить висновку розтину. Перечитайте, що саме сказав патологоанатом.' };
if (ans.why !== cs.finale.motive)
return { ok: false, hint: 'Мотив не той. ЩО пов\'язувало цих двох людей за тиждень до смерті?' };
if (!hasConfirmFor(accused))
return { ok: false, hint: 'Немає прямого підтвердження особи: потрібен збіг ДНК, повний відбиток зі знаряддя або спростоване алібі на дошці.' };
if (!has(ev => ev.gives.motive))
return { ok: false, hint: 'Мотив не підкріплено жодним закріпленим доказом.' };
if (!has(ev => ev.gives.method))
return { ok: false, hint: 'Спосіб смерті не підкріплено: закріпіть висновок розтину.' };
return { ok: true };
}
module.exports = { solve, checkAccusation };