The table was clearable behind a confirm. It should not be: a stand earns its place and loses it only to a better summer, so the control and clearScores() are gone and the screen says so.
97 lines
4.0 KiB
Markdown
97 lines
4.0 KiB
Markdown
# Lemonade Stand
|
||
|
||
A browser recreation of the Atari 8-bit BASIC classic — the one you typed in,
|
||
ran on a TV, and lost two dollars to on a cloudy day.
|
||
|
||
React + TypeScript + Vite. No art assets and no audio files: the weather is
|
||
drawn as pixel rectangles in SVG, and the music is generated by a small
|
||
chiptune engine built on the Web Audio API.
|
||
|
||
## Playing
|
||
|
||
```
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
One to four players share the keyboard, hot-seat style. Each morning you see
|
||
the weather, then decide three things: how many glasses to make, how many
|
||
15-cent signs to put up, and what to charge. Then the town votes with its
|
||
pocket money.
|
||
|
||
- Lemonade costs 2 cents a glass on days 1–2, 4 cents through day 7, and
|
||
5 cents after that.
|
||
- Everything you make is made *today*. Unsold glasses are poured away.
|
||
- A hot, dry day brings a bigger crowd that will pay more. A heat wave is
|
||
better still.
|
||
- A cloudy day can turn into a thunderstorm, and a thunderstorm ruins every
|
||
glass you made.
|
||
- Street crews close the road now and then, and they tend to stay for a
|
||
second day.
|
||
- Signs work, but with sharp diminishing returns — the fourth one barely
|
||
earns its 15 cents.
|
||
|
||
You are broke when you cannot afford a single glass. Otherwise the summer runs
|
||
as long as you like; **RETIRE** closes the books and shows the standings.
|
||
|
||
## High scores
|
||
|
||
Retiring writes every player's closing balance to a top-ten table kept in
|
||
`localStorage`, reachable from the title screen and from the standings. Runs
|
||
from the summer you just finished are picked out in yellow. Ties break on the
|
||
shorter season, then on the earlier date.
|
||
|
||
There is no way to clear the table. A stand leaves the list only by being
|
||
pushed off the bottom by a better one, so a good summer stands until somebody
|
||
beats it.
|
||
|
||
The table is per-browser, not per-device, and it is the one piece of state the
|
||
game keeps between visits. Anything already in storage can be edited by hand,
|
||
so every field is validated on the way back in and malformed rows are dropped
|
||
rather than trusted. If storage is unavailable — a private window, or a browser
|
||
set to block site data — the game plays normally and the table simply stays
|
||
empty.
|
||
|
||
## How it is put together
|
||
|
||
```
|
||
src/
|
||
game/ pure simulation - no React, no DOM
|
||
constants.ts costs, weather profiles, the demand curve
|
||
engine.ts rolls each day and settles the takings
|
||
reducer.ts the day/turn state machine
|
||
rng.ts seeded mulberry32, so a run can be replayed
|
||
highscores.ts the persisted table, with validation on load
|
||
audio/
|
||
synth.ts pulse-wave voices, noise percussion, look-ahead sequencer
|
||
tunes.ts the title, trading and closing themes
|
||
components/ the screens, and the CRT they are drawn on
|
||
```
|
||
|
||
The simulation is deliberately kept out of React: `engine.ts` takes a player, a
|
||
decision and a day and hands back a `DayResult`. That is what makes it possible
|
||
to run a few hundred seasons in a script and check that a careful player grows
|
||
their two dollars while a reckless one goes broke about seven times in ten.
|
||
|
||
### The demand curve
|
||
|
||
The original BASIC listing is not reproduced line for line. `constants.ts`
|
||
holds a reconstruction tuned to behave the way the game plays: cheap lemonade
|
||
sells out, sales fall to nothing once the price stops being a bargain, heat
|
||
lifts both the crowd and the price people will tolerate, and signs help a lot
|
||
at first and hardly at all later.
|
||
|
||
### The television
|
||
|
||
An Atari 800 fed a 4:3 television, and a television has no scrollbar. The tube
|
||
is locked to 4:3 and everything inside it is sized in container units, so the
|
||
picture stays in proportion at any size. If a page ever did come out taller
|
||
than the tube, `Fit` scales it down rather than clipping or scrolling it.
|
||
|
||
## Sound
|
||
|
||
Audio starts on the first click or key press, as browsers require. Two pulse
|
||
voices, a triangle bass and filtered white noise for percussion, driven by a
|
||
scheduler that queues notes 200 ms ahead so the music does not stutter when
|
||
React re-renders. `MUSIC` and `SOUND` toggle independently.
|