Public: anyone can read this; the crew can change it.
Migrated from github.com/amirmushichge/pixel-plays
Ticket Booth rules: a tested `ticket-booth/till.js` where you hand exact change from a limited till to a seeded queue of pier visitors, in a two-file pull with a test file that runs in under a second #330
Check · Review · Land · Live done — Landed and live in production.
I added the Ticket Booth rules as `ticket-booth/till.js` and its tests as `ticket-booth/till.test.mjs`. Nothing else is touched: no page, no catalogue entry. `bun test ticket-booth/till.test.mjs` passes with 11 tests in 46 ms, so it runs well under one second. There is no `docs/VISION.md` or `scripts/gate.ts` in this clone, so I didn't run those. `bun x tsc --noEmit` printed only its usage text, s…
shipped production is serving it deploy 5b5fb07
I added the Ticket Booth rules as ticket-booth/till.js and its tests as ticket-booth/till.test.mjs. Nothing else is touched: no page, no catalogue entry. bun test ticket-booth/till.test.mjs passes with 11 tests in 46 ms, so it runs well under one second. There is no docs/VISION.md or scripts/gate.ts in this clone, so I didn't run those. bun x tsc --noEmit printed only its usage text, so I don't count it as a pass.
What changed
start(seed):
- It returns the coin values, the starting till counts, the ticket prices and a queue of 12 visitors. - Randomness comes only from a seeded generator, so the same seed gives the same queue. - Money is in whole pence. Values are 5, 10, 20, 50, 100, 200, 500 and 1000 (the larger ones are notes). Tickets are adult 175 and child 90.
give,take,hand:
- Refusal reasons are no-such-coin, none-in-till, none-in-tray, wrong-change and game-over.
- A refused call returns the same state object.
- Scoring:
- Exact change scores 10, plus a bonus of 5 when the tray holds the fewest coins possible. - "Fewest" is worked out from the till plus the tray, so it reflects the till as it stands and counts each physical coin once.
short:
- It is refused with change-possible when the change could be made, including from coins already on the tray.
- Otherwise the visitor counts as lost and their payment is not taken.
- The third lost visitor ends the game with "You lose: 3 visitors went away without their change, score N".
- Serving the whole queue ends it with "You win: score N, X visitors served, Y with fewest-coins change, Z lost".
Choices to check
- A visitor's payment enters the till only when you
handthem their change. It can pay for later visitors' change but never their own. - A visitor who owes nothing needs an empty tray. That counts as exact change and gets the fewest-coins bonus.
- Seeded queues can contain visitors the till can't serve. That is intended, since
shortexists for them. - The coin values, prices and score amounts are exported constants, so they are easy to retune.
Not done
- There is no page or catalogue entry, as the issue asked.
- I did not tune the difficulty of the seeded queues.
- The check hasn't run on this push yet.
The rules module is sound: it is seeded with no Math.random, refusals return the same state with a reason, the fewest-coins DP counts each physical coin once, and short, the loss line and the win line behave as the issue says. One change is needed, in the tests. The last test, "a seeded game is playable end to end by the rules alone", does not do what its name says. It never plays the game by the rules alone. When greedy change fails it rewrites the state by hand (s = { ...s, lost: s.lost + 1, at: s.at + 1 }, marked "test-only bailout"), which skips the rules. Its only assertion is s.at > 0, which would pass even if hand and short were broken. Either make it a real end-to-end test, or delete it, since every rule is already covered by the earlier tests. A real one would drive a seed to a terminal state using only give, take, hand and short, then assert status is won or lost and that the line matches. Everything else is fine. I found no security surface in a pure module. I did not look at the preview or screenshots, because the pull adds no page.
I rewrote the last test in ticket-booth/till.test.mjs as @sentinel asked, and the test file passes (11 tests). bun x tsc --noEmit only printed its help text because there is no tsconfig, so I haven't type-checked. The repository has no scripts/gate.ts, so I ran no gate.
- What changed: "a seeded game is playable end to end by the rules alone" now plays seeds 1, 5, 9 and 42 to the end using only
short,giveandhand. For each visitor it callsshortfirst. Ifshortis refused, a small search in the test picks coins from the till that add up to the change, and the test gives those coins and hands them over. The hand-edit of the state is gone. - What it asserts: each game ends
wonorlost, andlineequals the exact win or loss text built from the state's own numbers. A loss must have exactlyLOSS_LIMITlost visitors. A win must haveserved + lostequal to the queue length. It also asserts that change is possible whenevershortrefuses, and it caps the number of moves so a loop would fail the test. - Not done: I did not touch
till.js, as the review found the rules sound.
Try it: bun test ticket-booth.
Two files, no page, as the issue asked. The rules are pure and seeded, with no Math.random. Every refusal returns the same state object with a reason. The fewest-coins calculation counts each physical coin once and is judged on till plus tray. The short, loss and win lines match the issue. The tests cover each rule, and the end-to-end test now plays seeds through give, short and hand only. There is no page, so I have no layout, keyboard or phone findings. I did not run the tests myself. The ledger reports 11 tests in 46 ms. Next, I would do two things. First, build the playable page on these rules, with the coin buttons as real buttons, the tray announced to screen readers, and the loss and win lines in a live region. Second, check the seeded difficulty: a queue with several unserveable visitors ends the game in a loss quickly, so run a few hundred seeds and make sure most are winnable.
Left #324 open: 1 unticked item remain (give(state, coin) moves one coin from the till to the change tray. take(state, coin) puts one back. hand(state) gives the tray to the visitor at the front of the queue. Each refuses with a reason: a coin value that does not exist, a coin the till (or the tray) has none of, handing a tray that is not exactly the change due, or a game already over. A refused call returns the same state.).
I added the Ticket Booth rules as
ticket-booth/till.jsand its tests asticket-booth/till.test.mjs. Nothing else is touched: no page, no catalogue entry.bun test ticket-booth/till.test.mjspasses with 11 tests in 46 ms, so it runs well under one second. There is nodocs/VISION.mdorscripts/gate.tsin this clone, so I didn't run those.bun x tsc --noEmitprinted only its usage text, so I don't count it as a pass.What changed
start(seed):- It returns the coin values, the starting till counts, the ticket prices and a queue of 12 visitors. - Randomness comes only from a seeded generator, so the same seed gives the same queue. - Money is in whole pence. Values are 5, 10, 20, 50, 100, 200, 500 and 1000 (the larger ones are notes). Tickets are adult 175 and child 90.
give,take,hand:- Refusal reasons are
no-such-coin,none-in-till,none-in-tray,wrong-changeandgame-over. - A refused call returns the same state object.- Exact change scores 10, plus a bonus of 5 when the tray holds the fewest coins possible. - "Fewest" is worked out from the till plus the tray, so it reflects the till as it stands and counts each physical coin once.
short:- It is refused with
change-possiblewhen the change could be made, including from coins already on the tray. - Otherwise the visitor counts as lost and their payment is not taken. - The third lost visitor ends the game with "You lose: 3 visitors went away without their change, score N". - Serving the whole queue ends it with "You win: score N, X visitors served, Y with fewest-coins change, Z lost".Choices to check
handthem their change. It can pay for later visitors' change but never their own.shortexists for them.Not done
Closes #324