Files
2026-04-16 08:14:20 +02:00

68 lines
3.6 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Running the game
Open `index.html` directly in a browser — there is no build step, no bundler, and no package manager. All JS files are loaded as plain `<script>` tags in dependency order.
For local development use any static file server, e.g.:
```bash
python3 -m http.server 8080
# then open http://localhost:8080
```
## Architecture
The game is split into seven files. Load order in `index.html` reflects the dependency chain:
```
settings.js → player.js → conveyor.js → lab.js → highscore.js → game.js → intro.js
```
### File responsibilities
| File | Exports | Role |
|---|---|---|
| `settings.js` | `SETTINGS` (frozen object) | All numeric constants and colours. No side-effects. Tweak values here only. |
| `player.js` | `Player` class | Grid position (`col` 0-2, `row` 0-2), inventory, movement cooldown, walk animation. No rendering. |
| `conveyor.js` | `LeftConveyorSystem`, `RightConveyorSystem`, `Good`, `Medication` classes + state enums | Belt physics and item lifecycle. No rendering. |
| `lab.js` | `Lab` class, `LabState` enum | Accepts deposited goods, detects specials, runs analysis timer, fires `onMedicationReady` callback. No rendering. |
| `game.js` | `Game.init(name1, name2)` | Game loop, all canvas rendering, input handling, 2P split-screen logic. Calls `HighScoreScreen.show()` on time-out. |
| `intro.js` | `IntroScreen.show()` | Two-step intro: player count selection → name entry → `Game.init()`. |
| `highscore.js` | `HighScoreScreen.show(names, scores, playerCount)` | Persists top-10 to `localStorage` (`kidneylab_scores`), renders results. Both 1P (scalar) and 2P (array) calls are supported. |
### 2-player split-screen (game.js)
All mutable state is held in per-player arrays (`gPlayers[i]`, `gScores[i]`, etc.). The `RS` proxy object exposes `_pIdx`-indexed getters so `renderOneSide()` can be called twice without duplication.
P2's half is drawn with a mirror transform (`ctx.translate(W, 0); ctx.scale(-1, 1)`). Text inside that transform must go through the `ft(text, x, y)` helper, which applies a local `scale(-1, 1)` to cancel the outer flip.
P2 keyboard input (WASD) has left/right swapped to match the mirrored visual: pressing visual-left (A) calls `move('right')`. The virtual joystick applies the same swap via the `swapLR` flag in `makeJoystick()`.
### Interaction model
Interactions are purely position-based — no action button. `handleInteractionsFor(pIdx)` runs every frame:
- `col === 0` → attempt pickup from left belt at current row
- `col === PLAYER_COLS - 1, row === 2` → deposit all carried goods to lab
- `col === PLAYER_COLS - 1, row === 1` → pick up medication from right belt
- `col === PLAYER_COLS - 1, row === 0` → deliver medication to patient
### Layout constants (all in `settings.js`)
```
x: 0 350 600 900
|←LEFT→ |←MIDDLE → |←RIGHT →|
belts player grid lab/belt/patient
```
Rows are shared vertically: `BELT_ROWS: [155, 300, 445]` (top→bottom, y-centre).
Player pixel position is computed in `Player.pixelX()` — evenly distributed across the middle zone with a 30 px margin from each edge.
### Mobile joystick
`makeJoystick()` in `game.js` builds a virtual joystick per player and appends it to the game screen div. The joystick uses `position: fixed` anchored via `.joy-left` / `.joy-right` CSS classes. It is hidden on non-touch devices via `@media (pointer: coarse)`. Repeated movement while held is driven by `setInterval` at 160 ms; the player's own `moveCooldown` (180 ms) naturally gates actual step rate.