2PlayerGame/detective-game/casegen/accusation.js

71 lines
4.1 KiB
JavaScript
Raw Normal View History

// 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 };