Files
kaltaquise-gamification/kidney_lab/CLAUDE.md
2026-04-16 08:14:20 +02:00

3.6 KiB

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

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.