Files
Alex CubeandClaude Opus 5.5 6eacbd4890 Стол: спички и фишки для игр 5, 6 и 7; проверка игры 7 перебором
Игры 5 и 6: 24 спички, три игрока, фишки А, Б, В с множителями,
пошагово по правилам книги. Игра 7: стол и общий котёл, очерёдность
сохраняется во второй половине, подсказка ввода 111 и 110.
check.mjs: против любого противника машина в игре 7 выигрывает
(все 16 возможных партий).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:30:18 +03:00

164 lines
8.9 KiB
JavaScript
Raw Permalink 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.
// Checks every program in index.html against the booklet on all 8 inputs.
// The machine is simulated here independently of the page: a column fires when all three of its
// contacts are closed, a lamp lights when a jumper chain connects it to a firing column.
// Run: node check.mjs (exit code 1 if anything breaks, apart from the two known errata)
import { readFileSync } from 'node:fs';
const html = readFileSync(new URL('./index.html', import.meta.url), 'utf8');
const PROGRAMS = Function('return ' + html.match(/const PROGRAMS = (\[[\s\S]*?\n \]);/)[1])();
const P = Object.fromEntries(PROGRAMS.map(p => [p.id, p]));
// input n = А·4 + Б·2 + В·1; result = V·8 + X·4 + Y·2 + Z·1
function run(p, n) {
const inp = [n >> 2 & 1, n >> 1 & 1, n & 1];
const parent = {};
const find = a => (parent[a] ??= a) === a ? a : (parent[a] = find(parent[a]));
p.wires.forEach(([a, b]) => { parent[find(a)] = find(b); });
const fired = [];
for (let c = 0; c < 7; c++) {
const ok = p.rows.every((row, r) => { const ch = row[c]; return ch === 'B' || ch === (inp[r] ? 'L' : 'R'); });
if (ok) fired.push(find('C' + (c + 1)));
}
return [0, 1, 2, 3].reduce((s, k) => s + (fired.includes(find('L' + k)) ? 8 >> k : 0), 0);
}
const b3 = n => n.toString(2).padStart(3, '0'), b4 = n => n.toString(2).padStart(4, '0');
let failed = 0;
const errata = [];
function expect(id, n, want, why, erratum) {
const got = run(P[id], n);
if (got === want) return;
const line = `${id}: вход ${b3(n)} → ${b4(got)}, ожидалось ${b4(want)}. ${why}`;
if (erratum) errata.push(line); else { failed++; console.log('ОШИБКА ' + line); }
}
const all = f => { for (let n = 0; n < 8; n++) f(n); };
// Game 1: answers to the three questions → the number thought of. Game 9 uses the same figure.
const answers = x => (x % 2 === 0) * 4 + (x + x + 1 < 8) * 2 + [4, 5].includes((x + 10) % 6);
const numberOf = {};
all(x => { numberOf[answers(x)] = x; });
all(x => expect('game1', answers(x), x, `задумано ${x}`));
for (let x = 1; x <= 6; x++) expect('game3', answers(x), x + 7, `на кубике ${x}, плюс противоположные грани 7`);
// Game 2: everyone claims to be honest, so y is honest iff А = 1; z is honest iff В matches y.
all(n => {
const A = n >> 2 & 1, B = n >> 1 & 1, C = n & 1;
const y = A, z = +(C === y), x = z ? B : 1 - B;
expect('game2', n, x * 4 + y * 2 + z, `x=${x} y=${y} z=${z}`);
});
// Games 5 and 6: 24 matches; players 1, 2, 3 took 1, 2, 3 and then the owners of А, Б, В took 1×, 2×, 4× more.
const CODE = { А: 0, Б: 1, В: 2 };
const left = {};
for (const [a, b, c] of [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]) {
const owner = { [a]: 'А', [b]: 'Б', [c]: 'В' };
left[24 - 6 - (a + 2 * b + 4 * c)] = CODE[owner[1]] * 4 + CODE[owner[2]];
}
all(m => expect('game5', m, left[m] ?? 0, `осталось ${m} спичек`));
all(n => expect('game6', n, left[numberOf[n]] ?? 0, `осталось ${numberOf[n]} спичек`));
// Game 9: answer number → comment number, from the booklet's table of answers and comments.
const COMMENT = [0b101, 0b111, 0b011, 0b001, 0b100, 0b110, 0b010, 0b000];
all(n => expect('game9', n, COMMENT[n], 'таблица комментариев'));
// Game 11: Y — motor runs if there is any call, X — door unlocked when it does not,
// Z — up for an up call alone, keep direction А for both calls (no calls: Z does not matter).
all(n => {
const A = n >> 2 & 1, up = n >> 1 & 1, down = n & 1;
const y = up | down, z = up && down ? A : up;
const got = run(P.game11, n);
if (!up && !down) expect('game11', n, (got & 1) | 4, 'лифт стоит, дверь открыта');
else expect('game11', n, (1 - y) * 4 + y * 2 + z, 'лифт');
});
// Game 4: turn the die onto a side face and add it; the sum must not pass 12; whoever reaches
// exactly 12 wins; if nobody can, it is a draw. The machine gets the sum (minus 8 if over 7);
// if the advised face is not on a side, the player takes the nearest smaller side face.
// Solve the game outright (1 win, 0 draw, -1 loss for the player to move) and walk every game
// that can happen: the machine must never get less than the position allows.
const sides = t => [1, 2, 3, 4, 5, 6].filter(f => f !== t && f !== 7 - t);
const memo = {};
const after = (s, f) => s + f === 12 ? 1 : -value(s + f, f);
const value = (s, t) => memo[s + ',' + t] ??= Math.max(0, ...sides(t).filter(f => s + f <= 12).map(f => after(s, f)));
const machineMove = (s, t) => sides(t).filter(f => f <= run(P.game4, s % 8) && s + f <= 12).pop();
const RESULT = { 1: 'выигрыш', 0: 'ничья', '-1': 'проигрыш' };
const seen = new Set();
const machineTurn = (s, t) => {
if (seen.has(s + ',' + t)) return; seen.add(s + ',' + t);
const f = machineMove(s, t), got = f ? after(s, f) : 0;
if (got < value(s, t))
errata.push(`game4: сумма ${s}, сверху ${t}: машина советует ${run(P.game4, s % 8)}, игрок ставит ${f ?? '—'}, ` +
`и вместо «${RESULT[value(s, t)]}» выходит «${RESULT[got]}»`);
if (!f || s + f === 12) return;
for (const g of sides(f)) if (s + f + g < 12) machineTurn(s + f + g, g);
};
for (let t = 1; t <= 6; t++) machineTurn(t, t);
// Game 8: deal 4 birds, 4 beasts, 4 insects into three piles of four every possible way and play
// the booklet's patience. Questions: А — the bottom card at «Насекомые» is an insect,
// Б — the bottom card at «Звери» is a beast, В — one of these two cards is a bird.
// Lamps, as the booklet reads them: V X Y — full success; X — birds and insects; Y — birds and
// beasts; Z — birds, probably the rest too; none — birds only.
const outcomes = {};
(function deal(cards, left) {
if (!left.P && !left.Z && !left.N) {
const piles = { P: cards.slice(0, 4), Z: cards.slice(4, 8), N: cards.slice(8) };
const placed = { P: 0, Z: 0, N: 0 };
for (let cur = 'P'; piles[cur].length;) { const c = piles[cur].shift(); placed[c]++; cur = c; }
const done = 'PZN'.split('').filter(k => placed[k] === 4).join('');
const n = (cards[11] === 'N') * 4 + (cards[7] === 'Z') * 2 + (cards[11] === 'P' || cards[7] === 'P');
(outcomes[n] ??= new Set()).add(done);
return;
}
for (const k of 'PZN') if (left[k]) deal([...cards, k], { ...left, [k]: left[k] - 1 });
})([], { P: 4, Z: 4, N: 4 });
const LAMPS8 = { PZN: 0b1110, PN: 0b0100, PZ: 0b0010, P: 0 };
all(n => {
if (!outcomes[n]) return; // such answers cannot happen
const o = [...outcomes[n]], want = o.length === 1 ? LAMPS8[o[0]] : 0b0001;
expect('game8', n, want, `раскладка кончается так: ${o.join(' или ')}`, true);
});
// Game 7: seven matches into the pot one or two at a time, then back to the table; whoever puts down
// the seventh wins; the opponent starts and the turns alternate throughout. The machine gets the count
// taken (first half) or put back (second half, with 2 entered as 111 and 5 as 110) and shows the count
// to reach. Against every opponent the player following it must always win.
const bad7 = new Set();
const play7 = (half, table, myTurn, path) => {
if (!myTurn) {
for (const n of [1, 2]) {
const src = half === 1 ? table : 7 - table;
if (n > src) continue;
const t = half === 1 ? table - n : table + n;
if (half === 2 && t === 7) { bad7.add(path + ` противник ${n} и выигрывает`); continue; }
play7(half === 1 && t === 0 ? 2 : half, t, true, path + ` противник ${n};`);
}
return;
}
const count = half === 1 ? 7 - table : table;
const input = half === 2 && count === 2 ? 0b111 : half === 2 && count === 5 ? 0b110 : count;
const n = run(P.game7, input) - count;
const src = half === 1 ? table : 7 - table;
if (n < 1 || n > 2 || n > src) { bad7.add(path + ` машина советует ${run(P.game7, input)} при ${count}`); return; }
const t = half === 1 ? table - n : table + n;
if (half === 2 && t === 7) return; // the player puts down the seventh match
play7(half === 1 && t === 0 ? 2 : half, t, false, path + ` вы ${n};`);
};
play7(1, 7, false, '');
for (const s of bad7) { failed++; console.log('ОШИБКА game7:' + s); }
// Logic elements from the Habr article.
all(n => {
const A = n >> 2 & 1, B = n >> 1 & 1, C = n & 1;
expect('not', n, (1 - A) * 4 + (1 - B) * 2 + (1 - C), 'НЕ');
expect('nand', n, +!(A && B && C), 'И-НЕ');
expect('nor', n, +!(A || B || C), 'ИЛИ-НЕ');
expect('xor', n, A ^ B ^ C, 'XOR');
expect('adder', n, (A + B + C > 1) * 8 + ((A + B + C) & 1), 'сумматор');
expect('cipher', n, n ^ 0b100, 'шифр');
});
console.log(errata.length ? 'Ошибки в самом руководстве:\n ' + errata.join('\n ') : 'Ошибок в руководстве не найдено');
console.log(failed ? `Расхождений с руководством: ${failed}` : 'Все остальные проверки прошли');
process.exit(failed ? 1 : 0);