diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..9b09888 Binary files /dev/null and b/.DS_Store differ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..074c301 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,169 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a LÖVE2D game development playground using Fennel (a Lisp dialect that compiles to Lua). The repository contains multiple game/project experiments: + +- **hello_world**: Basic LÖVE2D + Fennel template with REPL support +- **hello_world_lua**: Basic LÖVE2D in pure Lua (reference implementation) +- **one_line**: A pixel drawing application demonstrating canvas manipulation +- **two_player_cleaning_game**: Two-player game (primary focus for development) + +## Key Architecture + +### Fennel + LÖVE2D Setup + +Fennel is a Lisp-like language that compiles to Lua at runtime. Each project directory shares common bootstrap files via symlinks: + +- **fennel-1.5.3.lua** (root): The Fennel compiler itself +- **fennel_bootstrap.lua** (root): Sets up Fennel module loading and compiles `.fnl` files on-the-fly + +Each project directory contains: +- **main.fnl**: The Fennel source code (user-editable) +- **main.lua** → symlink to `../fennel_bootstrap.lua`: Entry point that Lua/LÖVE2D loads +- **fennel.lua** → symlink to `../fennel-1.5.3.lua`: Fennel compiler reference + +### How It Works + +1. LÖVE2D loads `main.lua` (the bootstrap file) +2. Bootstrap loads the Fennel compiler and sets up a custom module loader +3. Module loader intercepts `require("main.fnl")` and compiles it via Fennel at runtime +4. Compiled Fennel code executes with full LÖVE2D API access + +### LÖVE2D Lifecycle + +Games define lifecycle callbacks (all optional): +- **love.load()**: Called once at startup +- **love.update()**: Called every frame (~60fps by default) +- **love.draw()**: Called every frame after update, render graphics here +- **love.keypressed(key)**: Keyboard input handler +- **love.handlers.stdin(line)**: REPL input for live evaluation (used in hello_world template) + +## Development Workflow + +### Running a Game + +```bash +love ./two_player_cleaning_game +``` + +LÖVE2D must be installed and in your PATH. See [Getting Started](https://love2d.org/wiki/Getting_Started) for installation. + +### Live Coding / REPL + +The hello_world template includes stdin REPL support. While a game is running: +- Type Fennel code at the terminal +- Code is evaluated via `fennel.eval()` and results printed + +To enable in other projects, add this to `love.load()`: + +```fennel +(: (love.thread.newThread "require('love.event') +while 1 do love.event.push('stdin', io.read('*line')) end") :start) + +(fn love.handlers.stdin [line] + (let [(ok val) (pcall fennel.eval line)] + (print (if ok (fennel.view val) val)))) +``` + +## Fennel Language Basics + +### Syntax Patterns + +```fennel +; Variables +(local name "value") +(var counter 0) + +; Functions +(fn my-func [arg1 arg2] + (+ arg1 arg2)) + +; Tables/Objects +(local person { + :name "Alice" + :age 30 + :greet (fn [self] (print (.. self.name " says hello"))) +}) + +; Common control flow +(when condition + (do-something)) + +(if condition + (then-branch) + (else-branch)) + +(match value + "a" (handle-a) + "b" (handle-b)) + +; Iteration +(each [k v (pairs table)] + (print k v)) + +(for [i 1 10] + (print i)) +``` + +### Interop with Lua + +Call Lua functions naturally: + +```fennel +(love.graphics.setColor 1 0 0) ; red +(love.window.getMode) ; returns width, height, flags table +``` + +Access nested Lua objects with colon `:` syntax: + +```fennel +(: canvas-obj :setFilter "nearest" "nearest") ; equivalent to canvas_obj:setFilter(...) +``` + +## Project-Specific Notes + +### two_player_cleaning_game + +Currently uses the hello_world template structure. Development focus: +- Extend main.fnl with game logic (player movement, cleanup mechanics, etc.) +- Reference one_line/main.fnl for canvas/graphics patterns if needed +- Use LÖVE2D physics (love.physics) or collision detection as needed + +### Symlink Pattern + +Do **not** modify symlinked files (main.lua, fennel.lua) — modify main.fnl instead. If adding new modules, create them as separate .fnl files in the project directory. + +## Common Development Tasks + +### Adding a new module/file + +1. Create `my_module.fnl` in the project directory +2. Require it: `(local my-module (require "my_module"))` +3. The bootstrap module loader will auto-compile it + +### Debugging + +- Use `(print (fennel.view value))` to inspect tables and complex values +- stdin REPL (hello_world template) allows live evaluation for quick tests +- LÖVE2D console output is visible in the terminal where you ran `love` + +### Working with Graphics + +LÖVE2D graphics API examples: + +```fennel +(love.graphics.setColor 1 0 0) ; set color to red +(love.graphics.rectangle "fill" x y w h) ; draw filled rectangle +(love.graphics.circle "line" x y radius) ; draw circle outline +(love.graphics.print text x y) ; draw text +(love.graphics.draw drawable x y) ; draw canvas/image +``` + +## References + +- **LÖVE2D API**: https://love2d.org/wiki/Main_Page +- **Fennel Language**: https://fennel-lang.org/ +- **Fennel Plugin**: The compiler is shipped in this repo; no external install needed beyond LÖVE2D diff --git a/two_player_cleaning_game/.DS_Store b/two_player_cleaning_game/.DS_Store new file mode 100644 index 0000000..ee089d4 Binary files /dev/null and b/two_player_cleaning_game/.DS_Store differ diff --git a/two_player_cleaning_game/assets/level_001.aseprite b/two_player_cleaning_game/assets/level_001.aseprite new file mode 100644 index 0000000..14f5fb1 Binary files /dev/null and b/two_player_cleaning_game/assets/level_001.aseprite differ diff --git a/two_player_cleaning_game/assets/level_001.png b/two_player_cleaning_game/assets/level_001.png new file mode 100644 index 0000000..6e5278a Binary files /dev/null and b/two_player_cleaning_game/assets/level_001.png differ diff --git a/two_player_cleaning_game/assets/level_002.aseprite b/two_player_cleaning_game/assets/level_002.aseprite new file mode 100644 index 0000000..96f2170 Binary files /dev/null and b/two_player_cleaning_game/assets/level_002.aseprite differ diff --git a/two_player_cleaning_game/assets/level_002.png b/two_player_cleaning_game/assets/level_002.png new file mode 100644 index 0000000..c5ae829 Binary files /dev/null and b/two_player_cleaning_game/assets/level_002.png differ diff --git a/two_player_cleaning_game/assets/player_001.aseprite b/two_player_cleaning_game/assets/player_001.aseprite new file mode 100644 index 0000000..80604a7 Binary files /dev/null and b/two_player_cleaning_game/assets/player_001.aseprite differ diff --git a/two_player_cleaning_game/assets/player_001.png b/two_player_cleaning_game/assets/player_001.png new file mode 100644 index 0000000..fc97a25 Binary files /dev/null and b/two_player_cleaning_game/assets/player_001.png differ diff --git a/two_player_cleaning_game/main.fnl b/two_player_cleaning_game/main.fnl index 4172050..1b7e742 100644 --- a/two_player_cleaning_game/main.fnl +++ b/two_player_cleaning_game/main.fnl @@ -1,4 +1,12 @@ +(var player-sprite nil) ; 20x20 pixels +(local game-state { + :player-pos [0 0] +}) + (fn love.load [] + (love.window.setMode 600 640) + + (set player-sprite (love.graphics.newImage "assets/player_001.png")) ;; start a thread listening on stdin (: (love.thread.newThread "require('love.event') while 1 do love.event.push('stdin', io.read('*line')) end") :start)) @@ -8,8 +16,34 @@ while 1 do love.event.push('stdin', io.read('*line')) end") :start)) (let [(ok val) (pcall fennel.eval line)] (print (if ok (fennel.view val) val)))) + +;; drawing +(lambda color [full-r full-g full-b] + (let [(r g b) (love.math.colorFromBytes full-r full-g full-b)] + [r g b] +)) + +(local off-white (color 237 230 200)) +(local black [0 0 0]) + +(fn draw-game-outline [] + (love.graphics.setColor (unpack black)) + (love.graphics.rectangle "line" 50 50 500 500)) + +(fn draw-player [] + (love.graphics.setColor 1 1 1) ; reset color to white (no tinting) + (love.graphics.draw player-sprite 50 50 0 1.25 1.25)) + (fn love.draw [] - (love.graphics.print "Hello from Fennel!\nPress any key to quit" 10 10)) + ;; clear the screen and set bg to off white + (love.graphics.clear) + (love.graphics.setColor (unpack off-white)) + (love.graphics.rectangle "fill" 0 0 600 640) + + (draw-player) + (draw-game-outline) +) + ; (love.graphics.print "Hello from Fennel!\nPress any key to quit" 10 10)) (fn love.keypressed [key] (love.event.quit))