# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

`pygamejr` is an educational PyGame extension library. It contains two installable packages under `src/`:
- **`pygamejr`** — core game engine (sprites, scenes, game loop)
- **`codomir`** — a quest/map game layer built on top of `pygamejr`

Dependencies: `pygame-ce`, `pytmx`. Build backend: `hatchling`.

## Commands

```bash
# Install in editable mode (run from repo root)
pip install -e .

# Run a demo
python demo/1simple.py
python demo/scene.py
python demo/codomir/map1_game_over.py

# Run a codomir quest demo
python demo/codomir/loop/map1.py
```

There are no tests or linting configured yet.

## Architecture

### `pygamejr` package

**`base.py`** — Module-level side effect: calls `pygame.init()` and creates the global `screen` on import. Window size is configurable via `PYGAMEJR_WINDOW_WIDTH` / `PYGAMEJR_WINDOW_HEIGHT` env vars.

Key functions:
- `every_frame(frame_count=0)` — generator that yields `dt` each frame at 60 fps; drives the game loop
- `wait_quit()` — blocks until window is closed
- `next_frame()` — renders one frame manually (used inside `codomir`)
- `set_scene(scene)` / `get_current_scene()` / `get_global_scene()` — manage the two-scene system

**Two-scene system**: There is always a *current scene* (set with `set_scene()`) and a *global scene* (always rendered on top). Sprites created after a `set_scene()` call auto-register to the current scene; sprites created before any `set_scene()` call go to the global scene. This is how HUD elements persist across level transitions.

**`scene.py`** — `Scene` holds a dict of named `LayeredUpdates` sprite groups. The `"default"` layer is where auto-added sprites land. `Scene.from_tilemap()` and `QuestScene` (in `codomir`) build scenes from `.tmx` map files.

**`sprite/`** — All sprites extend `BaseSprite(pygame.sprite.DirtySprite)`. On `__init__`, each sprite calls `(scene or get_current_scene() or get_global_scene()).add(self)` to auto-register. Sprite types:
- `ImageSprite` — loads from file path or `pygame.Surface`; auto-crops alpha, maintains mask for collision
- `CircleSprite`, `RectSprite` — shape sprites
- `TextSprite` — renders text; has a writable `.text` property that re-renders on change
- `SubtitlesSprite` — scrolling subtitle overlay

**`tilemap.py`** — `TileMap` wraps `pytmx.load_pygame`. `get_layer_sprites(layer_name)` returns a list of `ImageSprite` objects positioned on a tile grid. There is a pytmx bug workaround in `load_image()` for loading tiles from a tileset spritesheet directly.

**`resources/`** — `resolve_path.py` provides path resolution for bundled assets. `image.py` exposes named constants for all built-in images (e.g., `pygamejr.resources.image.bee`).

### `codomir` package

**`quest.py`** — Has heavy import-time side effects: calls `pygame.init()`, reads display info, sets window position via `SDL_VIDEO_WINDOW_POS`, loads a `.tmx` map, builds a `QuestScene`, and creates the `player` singleton. Importing `codomir` starts the game.

Public API (re-exported from `codomir/__init__.py`):
- `player` — the `Player` instance; call `player.move_forward()`, `player.turn_left()`, `player.turn_right()`
- `set_map(map_path)` — loads a new map and repositions player at spawn
- `init(map_path)` — alias for `set_map`
- `wait_quit` — re-exported from `pygamejr`

**`maps/`** — Subpackages expose path constants to `.tmx` map files: `maps.linear`, `maps.loop`, `maps.nested_loops`, `maps.pirates.linear`.

**Player movement** is tile-based: each `move_forward()` call animates the player one tile (64px) in the current direction, running its own `every_frame()` loop internally for the animation frames. Walls and win tiles are detected via pytmx layer data (`layernames['walls']`, `layernames['objects']`).

### Typical usage patterns

**Simple sprite demo** (`pygamejr`):
```python
import pygamejr
bee = pygamejr.ImageSprite(pygamejr.resources.image.bee)
pygamejr.wait_quit()
```

**Multi-scene game** (`pygamejr`):
```python
import pygamejr
# Sprites created here go to global scene (always visible)
hud = pygamejr.TextSprite("Score: 0")

level1 = pygamejr.Scene()
pygamejr.set_scene(level1)
# Sprites created here belong to level1

for dt in pygamejr.every_frame():
    # game logic here
    pass
```

**Quest/map game** (`codomir`):
```python
from codomir import player, wait_quit
player.move_forward()
player.turn_right()
player.move_forward()
wait_quit()
```
