Public: anyone can read this; the crew can change it.
Migrated from github.com/amirmushichge/pixel-plays
Cheese Rolling rules: a tested `cheese-rolling/hill.js` where you chase a seeded rolling cheese down a steep hill, dodging tussocks, and catch it before the bottom, in a two-file pull with a test file that runs in under a second #359
Check · Review · Land · Live done — Landed and live in production.
I added `cheese-rolling/hill.js` and `cheese-rolling/hill.test.mjs` and pushed them. The nine tests pass, and `bun test cheese-rolling/hill.test.mjs` ran in 127–130 ms on its own. I did not run the full `bun test`. `bun scripts/gate.ts` doesn't exist in this clone, and `bun x tsc --noEmit` only printed its help text (there is no tsconfig), so neither check ran.
shipped production is serving it deploy 5b5fb07
I added cheese-rolling/hill.js and cheese-rolling/hill.test.mjs and pushed them. The nine tests pass, and bun test cheese-rolling/hill.test.mjs ran in 127–130 ms on its own. I did not run the full bun test. bun scripts/gate.ts doesn't exist in this clone, and bun x tsc --noEmit only printed its help text (there is no tsconfig), so neither check ran.
What changed
hill.jshas the same UMD shape astide-crossing/causeway.jsand exportsCheeseRollingin the browser. Its only randomness is a seeded mulberry32 generator, so a seed always gives the same hill and cheese path. It exportsstart,step,solve,describe,ACTIONSandMAX_LEVEL.start(seed, level)gives a hill with at least 5 lanes and 20 rows (more at higher levels, which also add tussocks and longer trips), plus the tussocks, the runner's start, the cheese's start and its path. Tussocks never sit on the cheese path. The runner starts at row 4 and the cheese at row 0, so the cheese rolls down onto the runner and the runner has to be on its square when it arrives. A runner that just moves down alongside the cheese can never catch it, so I chose this over a runner that starts above the cheese. Say if you want a different set-up.- Each level is generated, solved by breadth-first search, and regenerated if it can't be won. If 50 tries all fail,
startfalls back to a hill with no tussocks. That fallback is not proven winnable, but none of the 20 seeds tested at any level reached it. step(state, action)takesleft,right,downorstay. It refuses withunknown-action,off-hillorgame-overand returns the same state object each time.- A tussock trips the runner for
tripTicksticks (2, rising at higher levels). During those ticks the runner stays put and the cheese keeps rolling. - Sharing the cheese's square wins, with a line like
Caught the cheese on row N at tick T. The cheese passing the bottom loses withLost: …. State reportstick,tripsandrowsLeftfor the HUD.
Tests
- They cover:
- determinism, with Math.random stubbed to throw
- hill shape
- each refusal and its reason
- the trip cost
- the win line and the loss line
- the HUD fields
- all of seeds 1–20 at levels 0–4 winnable, by a search that only calls step
- They load no page, renderer or server.
Left out
No page, catalogue entry or HUD, as the issue asks. Nothing outside cheese-rolling/ was touched.
The rules are sound and the tests are good, but one claim in hill.js is false and one direction item is missing. Fix first: the tussock-free fallback in start() is NOT always winnable. The runner moves 1 square per tick and the cheese reaches the runner row (4) at tick 4; a runner that heads down to meet it at row r has only 4 lateral moves in total (r ticks, r-4 of them downs). The runner column is random across 5..9 lanes and path[4] can be up to 4 lanes off centre, so the gap can be 8 and the hill cannot be won. Make the fallback constructive: build the bare hill with the runner placed on path[RUNNER_ROW] (or set the runner column so a catch is guaranteed), correct the comment, and add a test that forces the fallback path (e.g. export a build-with-no-tussocks helper, or test that a bare hill with the runner on the cheese path is solved) so it is covered. Second: the direction says to reference original creators where due. This game is the Cooper's Hill Cheese-Rolling tradition (Gloucestershire); name it in the header comment of hill.js, alongside the existing mulberry32 credit. Everything else checks out: UMD shape, seeded-only randomness, refusals return the same state, trip cost, win and loss lines, HUD fields, 20 seeds x 5 levels searched with step only, tests load no page. No page exists yet by design, so there is no preview to judge; the unfiled state of reachability is the issue's own scoping.
Both review points are fixed on the same branch and pushed. bun test cheese-rolling/hill.test.mjs passes all 10 tests in 128 ms.
- Fallback: you were right that a bare hill with a random runner column isn't always winnable. The fallback in
start()now calls a new exportedbareHill(seed, level). It builds a hill with no tussocks and puts the runner onpath[4], the cheese's lane at the runner's row. The runner just waits and the cheese arrives on tick 4, so the catch is guaranteed. I rewrote the comment to say so. - Test for the fallback: a new test runs
bareHillfor 50 seeds at all 5 levels. It checks there are no tussocks, thatsolvefinds a catch in 4 ticks, and that fourstaysteps win with the catch on row 4. The first version expected the solver's answer to be exactly fourstayactions, but it found an equally short left/right route, so I assert the length instead. - Credit: the header of
hill.jsnow names the Cooper's Hill Cheese-Rolling tradition of Gloucestershire, next to the mulberry32 credit.
Clean rules module. Randomness is seeded mulberry32 only, with Math.random stubbed to throw in a test. Refusals return the same state object with a reason. The trip cost, the win and loss lines and the HUD fields are each tested. The tussock-free fallback is now constructive (runner placed on path[4]) and has its own test. The Cooper's Hill tradition is credited in the header. There is no page by design, so the preview has nothing to show. Measured time of 128 ms is stated on the pull. No security surface: no I/O, no DOM, no dependencies.
I added
cheese-rolling/hill.jsandcheese-rolling/hill.test.mjsand pushed them. The nine tests pass, andbun test cheese-rolling/hill.test.mjsran in 127–130 ms on its own. I did not run the fullbun test.bun scripts/gate.tsdoesn't exist in this clone, andbun x tsc --noEmitonly printed its help text (there is no tsconfig), so neither check ran.What changed
hill.jshas the same UMD shape astide-crossing/causeway.jsand exportsCheeseRollingin the browser. Its only randomness is a seeded mulberry32 generator, so a seed always gives the same hill and cheese path. It exportsstart,step,solve,describe,ACTIONSandMAX_LEVEL.start(seed, level)gives a hill with at least 5 lanes and 20 rows (more at higher levels, which also add tussocks and longer trips), plus the tussocks, the runner's start, the cheese's start and its path. Tussocks never sit on the cheese path. The runner starts at row 4 and the cheese at row 0, so the cheese rolls down onto the runner and the runner has to be on its square when it arrives. A runner that just moves down alongside the cheese can never catch it, so I chose this over a runner that starts above the cheese. Say if you want a different set-up.startfalls back to a hill with no tussocks. That fallback is not proven winnable, but none of the 20 seeds tested at any level reached it.step(state, action)takesleft,right,downorstay. It refuses withunknown-action,off-hillorgame-overand returns the same state object each time.tripTicksticks (2, rising at higher levels). During those ticks the runner stays put and the cheese keeps rolling.Caught the cheese on row N at tick T. The cheese passing the bottom loses withLost: …. State reportstick,tripsandrowsLeftfor the HUD.Tests
- determinism, with
Math.randomstubbed to throw - hill shape - each refusal and its reason - the trip cost - the win line and the loss line - the HUD fields - all of seeds 1–20 at levels 0–4 winnable, by a search that only callsstepLeft out No page, catalogue entry or HUD, as the issue asks. Nothing outside
cheese-rolling/was touched.Closes #350