Compare commits

..

No commits in common. "bd12a4d50469119cc5fbb1f99309979d050ac9be" and "31fb931c4ae71c4a463cbe189ac9a00619393cd7" have entirely different histories.

6 changed files with 41 additions and 1957 deletions

File diff suppressed because it is too large Load diff

View file

@ -6,8 +6,6 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
This is a two-player cooperative cleaning game built with LÖVE2D using Fennel (a Lisp dialect that compiles to Lua). This is a two-player cooperative cleaning game built with LÖVE2D using Fennel (a Lisp dialect that compiles to Lua).
Fennel docs are available locally at `.claude/API_DOCs/fennel.md`
## Key Architecture ## Key Architecture
### Fennel + LÖVE2D Setup ### Fennel + LÖVE2D Setup
@ -43,7 +41,7 @@ Games define lifecycle callbacks (all optional):
### Running the Game ### Running the Game
```bash ```bash
love ./game 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. LÖVE2D must be installed and in your PATH. See [Getting Started](https://love2d.org/wiki/Getting_Started) for installation.

View file

@ -30,10 +30,10 @@
(local width 19) (local width 19)
(local height 15) (local height 15)
(local player { (local player {
:x 50 :y 50 :speed 80 :battery 100 :rot 0 :x 50 :y 50 :w 25 :h 25 :speed 80 :battery 100 :rot 0
:vision-pts [] :vision-pts []
:hitbox [50 50 width height] :hitbox [50 50 width height]
:ever-moved false ; useful for inital update :init-move false ; nudge the player at start of game to trigger starting collisions
}) })
@ -42,13 +42,13 @@
(when col.other.on-hit (col.other:on-hit)) (when col.other.on-hit (col.other:on-hit))
(beholder.trigger "PLAYER.HIT" col.other))) (beholder.trigger "PLAYER.HIT" col.other)))
; TODO: make this internal (lambda origin-pt [x y]
(lambda player.origin-pt [self] (values (+ x (/ width 2)) (+ y (/ height 2))))
(values (+ self.x (/ width 2)) (+ self.y (/ height 2))))
(lambda player.cal-vision-points [self] (lambda player.cal-vision-points [self]
(let [ (let [
(ox oy) (self:origin-pt) (ox oy) (origin-pt self.x self.y)
pts []] pts []]
(lambda add-pt [x y] (table.insert pts [x y])) (lambda add-pt [x y] (table.insert pts [x y]))
(for [i 1 5] (for [i 1 5]
@ -72,7 +72,7 @@
(lambda player.cal-vision-poly [self] (lambda player.cal-vision-poly [self]
(let [pts (self:cal-vision-points) (let [pts (self:cal-vision-points)
(ox oy) (self:origin-pt) (ox oy) (origin-pt self.x self.y)
poly-pts [ox oy] poly-pts [ox oy]
] ]
(each [_ pt (ipairs pts)] (each [_ pt (ipairs pts)]
@ -91,6 +91,10 @@
(fn player.update [self dt] (fn player.update [self dt]
(when (= self.init-move false)
(set self.init-move true)
(let [(x y cols len) (bump-world:move self self.x self.y bump-filter)]
(handle-collisions cols)))
(let [ (let [
d-key (love.keyboard.isDown :d) d-key (love.keyboard.isDown :d)
a-key (love.keyboard.isDown :a) a-key (love.keyboard.isDown :a)
@ -102,10 +106,7 @@
{:d-key false :a-key false :e-key true :q-key false} (set self.rot (- self.rot (* dt 2))) {:d-key false :a-key false :e-key true :q-key false} (set self.rot (- self.rot (* dt 2)))
{:d-key false :a-key false :e-key false :q-key true} (set self.rot (+ self.rot (* dt 2))) {:d-key false :a-key false :e-key false :q-key true} (set self.rot (+ self.rot (* dt 2)))
) )
(when (or (when (and (> self.battery 0) (or d-key a-key e-key q-key))
(= self.ever-moved false)
(and (> self.battery 0) (or d-key a-key e-key q-key)))
(set self.ever-moved true)
(let [ (let [
dir-fn (if (or d-key a-key) #(+ $1 $2) #(- $1 $2)) dir-fn (if (or d-key a-key) #(+ $1 $2) #(- $1 $2))
new-x (dir-fn self.x (* self.speed dt (math.cos self.rot))) new-x (dir-fn self.x (* self.speed dt (math.cos self.rot)))
@ -115,10 +116,10 @@
(set self.x x) (set self.x x)
(set self.y y) (set self.y y)
(self:cal-vision-poly)) (self:cal-vision-poly))
(when (> self.battery 0) (set self.battery (- self.battery (* dt 2)))) (if (> self.battery 0)
(beholder.trigger "PLAYER.MOVED" self))) (set self.battery (- self.battery (* dt 2))))))
; (beholder.trigger "PLAYER.POS" self.x self.y) (beholder.trigger "PLAYER.POS" self.x self.y)
; (beholder.trigger "PLAYER.VISION" self.vision-pts) (beholder.trigger "PLAYER.VISION" self.vision-pts)
(beholder.trigger "PLAYER.BATTERY" self.battery)) (beholder.trigger "PLAYER.BATTERY" self.battery))
(fn player.load [self] (fn player.load [self]

View file

@ -11,11 +11,10 @@
(fn camera.load [self] (fn camera.load [self]
(let [screen self.pool.data.screen] (let [screen self.pool.data.screen]
(beholder.observe "PLAYER.MOVED" (lambda [player] (beholder.observe "PLAYER.POS" (lambda [x y]
(let [(ox oy) (player:origin-pt)]
;; Update camera to follow player (keep player centered on screen) ;; Update camera to follow player (keep player centered on screen)
(set self.x (- ox (/ screen.canvas-w 2))) (set self.x (- x (/ screen.canvas-w 2)))
(set self.y (- oy (/ screen.canvas-h 2)))))))) (set self.y (- y (/ screen.canvas-h 2)))))))
(fn camera.draw89 [self] (fn camera.draw89 [self]
(love.graphics.origin)) ; reset camera translation (love.graphics.origin)) ; reset camera translation

View file

@ -8,15 +8,19 @@
{ {
:player-vision-pts [] :player-vision-pts []
:player-pos [0 0] :player-pos [0 0]
:player-quad nil
:font nil :font nil
:canvas nil }) :canvas nil })
(lambda fow.load [self] (lambda fow.load [self]
(let [(w h) (assets.player-sprite:getDimensions)]
(set self.player-quad (love.graphics.newQuad 0 0 25 25 w h)))
(set self.font (love.graphics.newFont 10))
(set self.canvas (love.graphics.newCanvas 1000 1000)) (set self.canvas (love.graphics.newCanvas 1000 1000))
(beholder.observe "PLAYER.MOVED" (lambda [player] (beholder.observe "PLAYER.VISION" (lambda [pts]
(set self.player-pos [player.x player.y]) (set self.player-vision-pts pts)))
(set self.player-vision-pts player.vision-pts))) (beholder.observe "PLAYER.POS" (lambda [x y]
(set self.player-pos [x y])))
(let [previousCanvas (love.graphics.getCanvas)] (let [previousCanvas (love.graphics.getCanvas)]
(love.graphics.setCanvas self.canvas) (love.graphics.setCanvas self.canvas)
(color.set-color :dark-purple) (color.set-color :dark-purple)
@ -35,6 +39,7 @@
(love.graphics.rectangle "fill" 0 0 1000 1000) (love.graphics.rectangle "fill" 0 0 1000 1000)
(love.graphics.setBlendMode "replace") (love.graphics.setBlendMode "replace")
(love.graphics.setColor 0 0 0 0) ; transparent (love.graphics.setColor 0 0 0 0) ; transparent
; draw player quad to uncover the player
(love.graphics.push) (love.graphics.push)
(love.graphics.translate (- x 3) (- y 7)) (love.graphics.translate (- x 3) (- y 7))
(love.graphics.polygon "fill" 3 11 3 17 7 21 17 21 21 17 21 11 17 7 7 7) (love.graphics.polygon "fill" 3 11 3 17 7 21 17 21 21 17 21 11 17 7 7 7)

View file

@ -5,29 +5,32 @@
(local assets (require "src/assets.fnl")) (local assets (require "src/assets.fnl"))
(local radar (local radar
{ :x 0 { :player-pos [0 0]
:y 0
:rot 0 :rot 0
:speed 4 }) :speed 4 })
(lambda radar.load [self] (lambda radar.load [self]
(beholder.observe "PLAYER.MOVED" (lambda [player] (beholder.observe "PLAYER.POS" (lambda [x y]
(let [(x y) (player:origin-pt)] (set self.player-pos [x y]))))
(set self.x x)
(set self.y y)))))
(lambda radar.update [self dt] (lambda radar.update [self dt]
(set self.rot (+ self.rot (* (/ dt self.speed))))) (set self.rot (+ self.rot (* (/ dt self.speed)))))
(lambda radar.draw81 [self] (lambda radar.draw81 [self]
(let [
w 19 h 15
[x y] self.player-pos
ox (+ x (/ w 2))
oy (+ y (/ h 2))
]
(color.set-color :dark-pink) (color.set-color :dark-pink)
(love.graphics.push) (love.graphics.push)
(love.graphics.translate self.x self.y) (love.graphics.translate ox oy)
(love.graphics.rotate self.rot) (love.graphics.rotate self.rot)
(love.graphics.setPointSize 2) (love.graphics.setPointSize 2)
(love.graphics.points 0 0) (love.graphics.points 0 0)
(love.graphics.circle "line" 0 0 150) (love.graphics.circle "line" 0 0 150)
(love.graphics.line 0 0 0 150) (love.graphics.line 0 0 0 150)
(love.graphics.pop)) (love.graphics.pop)))
radar radar