Стол: спички и фишки для игр 5, 6 и 7; проверка игры 7 перебором

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alex Cube
2026-10-27 20:30:18 +03:00
co-authored by Claude Opus 5.5
parent 3fcf120fc6
commit 6eacbd4890
3 changed files with 119 additions and 4 deletions
+28
View File
@@ -119,6 +119,34 @@ all(n => {
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;
+90 -3
View File
@@ -160,6 +160,15 @@ table.tt { border-collapse: collapse; font-family: var(--font-mono); font-size:
.card { width: 64px; height: 56px; background: #fff; border: 1px solid #b9c3d6; border-radius: 4px; display: grid; place-items: center; align-content: center; color: #23409a; font: 700 10px var(--font-body); box-shadow: 0 1px 2px rgba(0, 0, 0, .18); flex: none; }
.card svg { width: 42px; height: 30px; fill: none; stroke: #23409a; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.card.label { width: auto; height: 30px; padding: 0 8px; font-size: 11px; letter-spacing: .05em; }
.matches { display: flex; flex-wrap: wrap; gap: 3px; min-height: 40px; align-items: end; }
.match { width: 8px; height: 40px; }
.match rect { fill: #e8c48a; stroke: #b08a4e; stroke-width: .6; }
.match ellipse { fill: #b3261e; }
.players, .piles2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; width: 100%; }
.player { display: grid; gap: 6px; align-content: start; }
.player-top { display: flex; gap: 8px; align-items: center; min-height: 34px; }
.tok { width: 32px; height: 32px; border-radius: 50%; display: inline-grid; place-items: center; color: #fff; font: 700 15px var(--font-body); box-shadow: inset 0 -3px 0 rgba(0, 0, 0, .25); }
.tok-0 { background: #c8222c; } .tok-1 { background: #23409a; } .tok-2 { background: #e0a800; } .tok-x { background: #8a8f99; }
.card.back { background: repeating-linear-gradient(45deg, #23409a 0 4px, #3552ad 4px 8px); border-color: #1b2f73; }
</style>
@@ -812,8 +821,8 @@ table.tt { border-collapse: collapse; font-family: var(--font-mono); font-size:
let order, piles, placed, cur, shown, done;
const deal = () => {
const deck = Object.keys(KINDS).flatMap(k => KINDS[k].names.map(name => ({ k, name })));
for (let i = deck.length - 1; i > 0; i--) { const j = rnd(i + 1); [deck[i], deck[j]] = [deck[j], deck[i]]; }
order = Object.keys(KINDS).sort(() => Math.random() - .5);
shuffle(deck);
order = shuffle(Object.keys(KINDS));
piles = { P: deck.slice(0, 4), Z: deck.slice(4, 8), N: deck.slice(8) }; // index 0 is the top card
placed = { P: [], Z: [], N: [] }; cur = 'P'; shown = false; done = false;
};
@@ -856,10 +865,88 @@ table.tt { border-collapse: collapse; font-family: var(--font-mono); font-size:
deal(); draw();
}
const shuffle = a => { for (let i = a.length - 1; i > 0; i--) { const j = rnd(i + 1); [a[i], a[j]] = [a[j], a[i]]; } return a; };
const MATCH = '<svg class="match" viewBox="0 0 8 40" aria-hidden="true"><rect x="2.6" y="8" width="2.8" height="31" rx="1"/><ellipse cx="4" cy="7" rx="3.3" ry="5"/></svg>';
const matchesHtml = n => `<div class="matches" role="img" aria-label="Спичек: ${n}">${MATCH.repeat(n)}</div>`;
const MULT = { А: 1, Б: 2, В: 4 }; // the multiplier printed on the back of each chip
const chipHtml = c => c ? `<span class="tok tok-${'АБВ'.indexOf(c)}" title="На обороте: ×${MULT[c]}">${c}</span>` : '<span class="tok tok-x" title="Фишка спрятана">?</span>';
// Games 5 and 6: 24 matches, three players and three chips, step by step as in the booklet.
function chipsDesk() {
let stage, owner, took, table; // owner[i]: the chip of player i + 1
const STEPS = ['Игроки берут 1, 2 и 3 спички', 'Игроки тайком делят фишки', 'Игроки добирают спички по фишкам', 'Открыть фишки'];
const reset = () => { stage = 0; owner = null; took = [0, 0, 0]; table = 24; };
const draw = () => {
const open = stage === 4;
deskBody.innerHTML = `<div class="players">${[0, 1, 2].map(i => `<div class="player">
<span class="cap">Игрок № ${i + 1}</span>
<div class="player-top">${owner ? chipHtml(open && owner[i]) : ''}<span class="cap">${took[i] ? `спичек: ${took[i]}` : ''}</span></div>
${matchesHtml(took[i])}
</div>`).join('')}</div>
<div><span class="cap">На столе</span>${matchesHtml(table)}</div>
<p class="desk-note">${stage < 3 ? 'Оператор отворачивается, пока игроки берут спички и делят фишки.' :
!open ? 'Теперь оператор считает спички на столе и вводит их число в машину (в игре 6 — ответы игрока на вопросы игры 1 про это число).' :
[0, 1, 2].map(i => `игрок № ${i + 1} — ${owner[i]}`).join(', ') + '. Сверьте с лампами.'}</p>
<div class="desk-btns">
${open ? '' : `<button type="button" class="ghost" data-act="next">${STEPS[stage]}</button>`}
<button type="button" class="ghost" data-act="new">Новая игра</button>
</div>`;
};
deskBody.onclick = e => {
const act = e.target.closest('button')?.dataset.act;
if (act === 'new') reset();
else if (act === 'next') {
if (stage === 0) { took = [1, 2, 3]; table -= 6; }
else if (stage === 1) owner = shuffle(['А', 'Б', 'В']);
else if (stage === 2) owner.forEach((c, i) => { const n = (i + 1) * MULT[c]; took[i] += n; table -= n; });
stage++;
} else return;
draw();
};
reset(); draw();
}
// Game 7: seven matches go into the common pot one or two at a time, then back in the same order;
// whoever puts down the seventh wins. The opponent starts; the turns keep alternating into the second half.
function sevenDesk() {
let half, table, pot, turn, log, winner; // turn 0: the opponent, 1: you
const reset = () => { half = 1; table = 7; pot = 0; turn = 0; log = []; winner = null; };
const draw = () => {
const from = half === 1 ? table : pot, count = half === 1 ? pot : table;
const input = half === 2 && count === 2 ? '111' : half === 2 && count === 5 ? '110' : count.toString(2).padStart(3, '0');
deskBody.innerHTML = `<div class="piles2">
<div><span class="cap">На столе: ${table}</span>${matchesHtml(table)}</div>
<div><span class="cap">Общий котёл: ${pot}</span>${matchesHtml(pot)}</div>
</div>
<p class="desk-note">${winner !== null ? `Седьмую спичку положил${winner ? 'и вы. Победа!' : ' противник.'}` :
`${half === 1 ? 'Первая половина: берут со стола в котёл.' : 'Вторая половина: возвращают из котла на стол.'} ${turn ? 'Ваш ход' : 'Ходит противник'}.` +
(turn ? ` ${half === 1 ? 'Взято со стола' : 'Положено на стол'}: ${count}, в машину вводится ${input}.` : '')}
<br><span class="cap">${log.join(' → ')}</span></p>
<div class="desk-btns">
${winner !== null ? '' : [1, 2].map(n => `<button type="button" class="ghost" data-n="${n}"${n > from ? ' disabled' : ''}>${turn ? 'Вы' : 'Противник'}: ${half === 1 ? 'взять' : 'положить'} ${n}</button>`).join('')}
<button type="button" class="ghost" data-act="new">Новая партия</button>
</div>`;
};
deskBody.onclick = e => {
const b = e.target.closest('button');
if (!b) return;
if (b.dataset.act === 'new') reset();
else if (b.dataset.n) {
const n = +b.dataset.n;
if (half === 1) { table -= n; pot += n; } else { pot -= n; table += n; }
log.push(`${turn ? 'вы' : 'противник'} ${n}`);
if (half === 2 && table === 7) winner = turn;
else { if (half === 1 && table === 0) { half = 2; log.push('|'); } turn = 1 - turn; }
} else return;
draw();
};
reset(); draw();
}
function renderDesk() {
if (deskFor === S.prog) return;
deskFor = S.prog;
const make = { game3: () => dieDesk(false), game4: () => dieDesk(true), game8: cardsDesk }[S.prog];
const make = { game3: () => dieDesk(false), game4: () => dieDesk(true), game5: chipsDesk, game6: chipsDesk, game7: sevenDesk, game8: cardsDesk }[S.prog];
desk.hidden = !make;
if (make) make(); else { deskBody.innerHTML = ''; deskBody.onclick = null; }
}
+1 -1
View File
@@ -43,7 +43,7 @@
## Ошибки в оригинале
Все 11 программ я перенёс в эмулятор точно по рисункам и проверил на всех комбинациях входов, а игру 4 ещё и решил полным перебором партий. Проверка лежит в репозитории, её можно запустить самому: `node check.mjs`. Игры 7 и 10 так проверить нельзя: в тексте мало примеров и нет полной таблицы ответов. Остальные работают ровно так, как написано в тексте, включая все примеры. А в двух нашлось кое-что любопытное.
Все 11 программ я перенёс в эмулятор точно по рисункам и проверил на всех комбинациях входов, а игры 4 и 7 ещё и проверил перебором всех партий. Проверка лежит в репозитории, её можно запустить самому: `node check.mjs`. Игру 10 так проверить нельзя: в тексте нет полной таблицы ответов. Остальные работают ровно так, как написано в тексте, включая все примеры. А в двух нашлось кое-что любопытное.
Рисунки при этом сверены со сканами гнездо за гнездом. Есть и независимая проверка: на фотографии настоящей ДЛМ из статьи на Хабре набран сумматор, все три переключателя стоят на «1», горят V и Z. Эмулятор на той же программе зажигает те же лампы.