diff --git a/check.mjs b/check.mjs index f00f33b..b2dc985 100644 --- a/check.mjs +++ b/check.mjs @@ -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; diff --git a/index.html b/index.html index 5365662..4b627ca 100644 --- a/index.html +++ b/index.html @@ -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; } @@ -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 = ''; + const matchesHtml = n => ``; + const MULT = { А: 1, Б: 2, В: 4 }; // the multiplier printed on the back of each chip + const chipHtml = c => c ? `${c}` : '?'; + + // 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 = `
${[0, 1, 2].map(i => `
+ Игрок № ${i + 1} +
${owner ? chipHtml(open && owner[i]) : ''}${took[i] ? `спичек: ${took[i]}` : ''}
+ ${matchesHtml(took[i])} +
`).join('')}
+
На столе${matchesHtml(table)}
+

${stage < 3 ? 'Оператор отворачивается, пока игроки берут спички и делят фишки.' : + !open ? 'Теперь оператор считает спички на столе и вводит их число в машину (в игре 6 — ответы игрока на вопросы игры 1 про это число).' : + [0, 1, 2].map(i => `игрок № ${i + 1} — ${owner[i]}`).join(', ') + '. Сверьте с лампами.'}

+
+ ${open ? '' : ``} + +
`; + }; + 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 = `
+
На столе: ${table}${matchesHtml(table)}
+
Общий котёл: ${pot}${matchesHtml(pot)}
+
+

${winner !== null ? `Седьмую спичку положил${winner ? 'и вы. Победа!' : ' противник.'}` : + `${half === 1 ? 'Первая половина: берут со стола в котёл.' : 'Вторая половина: возвращают из котла на стол.'} ${turn ? 'Ваш ход' : 'Ходит противник'}.` + + (turn ? ` ${half === 1 ? 'Взято со стола' : 'Положено на стол'}: ${count}, в машину вводится ${input}.` : '')} +
${log.join(' → ')}

+
+ ${winner !== null ? '' : [1, 2].map(n => ``).join('')} + +
`; + }; + 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; } } diff --git a/post.md b/post.md index 53c3700..2225a0f 100644 --- a/post.md +++ b/post.md @@ -43,7 +43,7 @@ ## Ошибки в оригинале -Все 11 программ я перенёс в эмулятор точно по рисункам и проверил на всех комбинациях входов, а игру 4 ещё и решил полным перебором партий. Проверка лежит в репозитории, её можно запустить самому: `node check.mjs`. Игры 7 и 10 так проверить нельзя: в тексте мало примеров и нет полной таблицы ответов. Остальные работают ровно так, как написано в тексте, включая все примеры. А в двух нашлось кое-что любопытное. +Все 11 программ я перенёс в эмулятор точно по рисункам и проверил на всех комбинациях входов, а игры 4 и 7 ещё и проверил перебором всех партий. Проверка лежит в репозитории, её можно запустить самому: `node check.mjs`. Игру 10 так проверить нельзя: в тексте нет полной таблицы ответов. Остальные работают ровно так, как написано в тексте, включая все примеры. А в двух нашлось кое-что любопытное. Рисунки при этом сверены со сканами гнездо за гнездом. Есть и независимая проверка: на фотографии настоящей ДЛМ из статьи на Хабре набран сумматор, все три переключателя стоят на «1», горят V и Z. Эмулятор на той же программе зажигает те же лампы.