// Спільні правила гри: коли доказ досяжний, кого пускають у квартиру, кого // лишили в колі підозри. Одні й ті самі правила ганяє fair-play тест (solve) // і живий сервер — розійтися вони не можуть. function newState() { return { found: [], identity: false, keys: [], warrants: [], filters: [], pointed: [], puzzlesSolved: [] }; } function candidates(cs, st) { return cs.citizens.filter(c => c.id !== cs.victim && st.filters.every(f => f.tol ? Math.abs(c[f.attr] - f.val) <= f.tol : c[f.attr] === f.val)); } // Чи можна увійти в приватне житло function unitAccess(cs, st, unit) { if (st.keys.includes(unit)) return true; const victim = cs.citizens.find(c => c.id === cs.victim); if (unit === victim.home && st.identity) return true; // житло жертви — після ідентифікації return cs.citizens.some(c => c.home === unit && st.warrants.includes(c.id)); } // Чи досяжний доказ за поточного стану. opts.ignorePuzzles — для симулятора. function available(cs, st, ev, opts = {}) { // «сплячі» докази (зламане алібі) відкриває лише сервер за фактом суперечності if (ev.special === 'alibi') return false; if (ev.requires.some(id => !st.found.includes(id))) return false; if (ev.needsIdentity && !st.identity) return false; if (ev.puzzle && !opts.ignorePuzzles && !st.puzzlesSolved.includes(ev.puzzle)) return false; if (ev.at.type === 'unit') return unitAccess(cs, st, ev.at.unit); return true; // start / spot / person / sys / lab — гейти вище } // Застосувати наслідки знайденого доказу function applyFound(cs, st, ev) { if (st.found.includes(ev.id)) return; st.found.push(ev.id); const g = ev.gives; if (g.identity) st.identity = true; if (g.key && !st.keys.includes(g.key)) st.keys.push(g.key); if (g.filter) st.filters.push(g.filter); if (g.pointsTo && !st.pointed.includes(g.pointsTo)) st.pointed.push(g.pointsTo); } module.exports = { newState, candidates, unitAccess, available, applyFound };