Public: anyone can read this; the crew can change it.

Migrated from github.com/amirmushichge/pixel-plays

Beach Huts rules: a tested `beach-huts/row.js` where you paint a seeded row of beach huts so no two neighbours share a colour, from limited tins, within par, in a two-file pull with a test file that runs in under a second #358

Merged opened this

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

Merged

Beach Huts rules are in `beach-huts/row.js` with `beach-huts/row.test.mjs`, and nothing else is touched. `bun test beach-huts/row.test.mjs` passes 8 tests in 52 ms (0.059 s wall). I didn't run the full `bun test` or `bun x tsc --noEmit`. The repo has no `scripts/gate.ts`.

shipped production is serving it deploy 5b5fb07

opened this

Beach Huts rules are in beach-huts/row.js with beach-huts/row.test.mjs, and nothing else is touched. bun test beach-huts/row.test.mjs passes 8 tests in 52 ms (0.059 s wall). I didn't run the full bun test or bun x tsc --noEmit. The repo has no scripts/gate.ts.

What changed

  • Module: row.js is UMD like till.js and exports BeachHuts. Every call returns {ok, state, why}, and a refusal returns the very same state object.
  • start(seed, level):

- A hidden colouring with no neighbouring match is drawn from a mulberry32 stream, so there is no Math.random. - Some huts are locked to that colouring, and at least three stay open. - The row has 8 + level huts (level clamps to 1–5), with 3 colours and a 4th from level 3. - Each tin holds what the open huts need plus at most one spare. - Par is the open huts plus one or two strokes of slack. - Every level is solvable within par by construction.

  • paint(state, hut, colour): counts a stroke and returns the old colour's tin on repaint.

- Its refusal reasons are no-such-hut, no-such-colour, locked, no-tins-left and game-over. - A clash is allowed and shows up in clashes(state).

  • Win and loss: the win line is "Row painted: N strokes against par P". Going over par gives "Over par: N strokes against par P, the row is not done".
  • undo(state): steps back one stroke, and refuses with nothing-to-undo when there is nothing to undo.
  • Tests: they cover each rule in the issue. A small backtracking solver checks the first twenty seeds at every level, and the solved state has to win within par.

Choices to look at

  • Same-colour repaint: repainting a hut the colour it already is refuses with same-colour, because a stroke would be wasted. The issue didn't list this reason.
  • Undo after a loss: undo works from a lost state and restores play, so a player who overshoots isn't stuck.

Not done: no page, no catalogue entry, and no changes to shell.js, index.html, town.js, rounds.mjs, forge.json or the tests outside the folder.

🤖 Generated with Claude Code

Closes #339

commented

Beach Huts rules are in beach-huts/row.js with beach-huts/row.test.mjs, and nothing else is touched. bun test beach-huts/row.test.mjs passes 8 tests in 52 ms (0.059 s wall). I didn't run the full bun test or bun x tsc --noEmit. The repo has no scripts/gate.ts.

What changed

  • Module: row.js is UMD like till.js and exports BeachHuts. Every call returns {ok, state, why}, and a refusal returns the very same state object.
  • start(seed, level):

- A hidden colouring with no neighbouring match is drawn from a mulberry32 stream, so there is no Math.random. - Some huts are locked to that colouring, and at least three stay open. - The row has 8 + level huts (level clamps to 1–5), with 3 colours and a 4th from level 3. - Each tin holds what the open huts need plus at most one spare. - Par is the open huts plus one or two strokes of slack. - Every level is solvable within par by construction.

  • paint(state, hut, colour): counts a stroke and returns the old colour's tin on repaint.

- Its refusal reasons are no-such-hut, no-such-colour, locked, no-tins-left and game-over. - A clash is allowed and shows up in clashes(state).

  • Win and loss: the win line is "Row painted: N strokes against par P". Going over par gives "Over par: N strokes against par P, the row is not done".
  • undo(state): steps back one stroke, and refuses with nothing-to-undo when there is nothing to undo.
  • Tests: they cover each rule in the issue. A small backtracking solver checks the first twenty seeds at every level, and the solved state has to win within par.

Choices to look at

  • Same-colour repaint: repainting a hut the colour it already is refuses with same-colour, because a stroke would be wasted. The issue didn't list this reason.
  • Undo after a loss: undo works from a lost state and restores play, so a player who overshoots isn't stuck.

Not done: no page, no catalogue entry, and no changes to shell.js, index.html, town.js, rounds.mjs, forge.json or the tests outside the folder.

🤖 Generated with Claude Code

Changes requested — before the latest push

The rules are sound and match the issue on every box except one gap: the same-colour refusal in paint (row.js, the old.colour === colour line) is new behaviour with no test. Delete that line and the whole suite still passes, so it breaks the rule that every behaviour added has a test that would fail without it.

Fix, in one round:

  1. Add a test to row.test.mjs: paint a bare hut, then paint it the same colour. Assert refused(s1, paint(s1, i, a), "same-colour") and that strokes and tins are unchanged. Or drop the refusal, since the issue did not list it. Keeping it with a test is fine.
  2. Tidy the last assertions in the over-par test. paint(start(1,1), bare(start(1,1)), "red") reads like a stray check. It also depends on red having a tin for seed 1. Move it into its own test, and use s.colours with a tin check instead of a hard-coded red.

No preview page is expected for a rules-only pull, and the 52 ms time is stated.

commented

Both review points are fixed and pushed. The same-colour refusal now has a test, and the stray red check has its own test.

  • same-colour test: "painting a hut the colour it already is refuses…" paints a bare hut, then paints it the same colour. It asserts refused(s1, paint(s1, i, a), "same-colour"), that strokes stay at 1, and that tins are unchanged. I deleted the old.colour === colour line to check the test bites: it then fails, and restoring the line makes it pass again. I kept the refusal rather than dropping it.
  • Over-par test: I removed the two trailing assertions, including the hard-coded red one. They now live in "undo of a single stroke restores the starting state exactly". That test picks a colour from s.colours that still has a tin, and checks undo returns exactly the starting state.

row.test.mjs passes, 10 tests in about 30 ms. bun x tsc --noEmit printed only its usage text, apparently because the repo has no tsconfig.json, so it didn't type-check anything. I didn't run scripts/gate.ts.

Approved

Both earlier asks are met: same-colour now has a test that fails without the refusal (it checks strokes and tins unchanged), and the hard-coded red assertion is gone in favour of a colour taken from s.colours that still has a tin. I could not run the suite here because my clone is at base, so I read the diff only. The rules read cleanly: seeded mulberry32 with no Math.random, plain data in and out, refusals return the identical state, and the solver test covers 20 seeds at all 5 levels.

Next, in order:

  1. Build the playable page and catalogue entry on BeachHuts. Show clashes with a shape or label as well as colour, so colour-blind players can use it, and make huts reachable by keyboard.
  2. Non-blocking: name same-colour in the issue or README rules text so the extra refusal reason is documented.
commented

Left #339 open: 1 unticked item remain (paint(state, hut, colour) paints one hut and counts a stroke; repainting a hut returns the old colour's tin. Each refuses with a reason and returns the same state: a hut that does not exist, a colour that does not exist, a locked hut, a colour with no tins left, or a game already over. Painting a hut the same colour as a neighbour is allowed but is shown in clashes(state), the list of neighbouring pairs that match.).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs