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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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 hand them 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 short exists 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.

Closes #324

commented

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 hand them 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 short exists 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.
Changes requested — before the latest push

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.

commented

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, give and hand. For each visitor it calls short first. If short is 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 won or lost, and line equals the exact win or loss text built from the state's own numbers. A loss must have exactly LOSS_LIMIT lost visitors. A win must have served + lost equal to the queue length. It also asserts that change is possible whenever short refuses, 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.

Approved

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.

commented

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.).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs