;; deps (local levels (. (require "levels.fnl") :levels)) (local bump (require "libs/bump")) (local tiny (require "libs/tiny")) (local assets (require "src/assets.fnl")) (local utils (require "src/utils.fnl")) (local colors (require "src/colors.fnl")) (local tutorial (require "src/levels/tutorial.fnl")) ; (utils.debug-print assets) ; (local network (require "network.fnl")) (var pool nil) ;; global vars ; (var level-key "tutorial") ; (var player-art nil) ; 25x50 pixels each player is 25x25 ; (var dust-sprite nil) ; 35x35 pixels ; (var battery-bar-sprite nil) ; (local walls { ; :sprite nil ; :quads [] ; :batch nil ; }) ; (local objects { ; :sprite nil ; :quads {} ; :list [] ; }) (local screen (let [scale 2 canvas-w 800 canvas-h 450] { :screen-w (* canvas-w scale) :screen-h (* canvas-h scale) :canvas-w canvas-w :canvas-h canvas-h :scale scale})) (local debug true) ; (local collider-debug-boxes []) ; (lambda add-collider-debug-box [x y w h] ; (table.insert collider-debug-boxes {: x : y :width w :height h})) ; (var bump-world nil) ; (local player { :x 50 :y 50 :w 25 :h 25 :speed 80 :battery 100 :rot 0 }) ; (lambda mirror-collider [collider] ; "Mirror a collider box horizontally within a 25-unit tile (center is at 12.5) ; Transforms collider positions so they're reflected across the vertical center line." ; { ; :x (- 25 collider.x collider.width) ; :y collider.y ; :width collider.width ; :height collider.height ; }) ; (fn load-walls [] ; (set walls.batch (love.graphics.newSpriteBatch walls.sprite 2500)) ; ;; load quads ; (let [(w h) (walls.sprite:getDimensions)] ; (for [i 0 19 1] ; (table.insert walls.quads (love.graphics.newQuad (* i 25) 0 25 25 w h)))) ; ;; fill batch ; (each [_ row (pairs (. levels :levels level-key :tiles))] ; (each [_ tile (pairs row)] ; (let [ ; x tile.x ; y tile.y ; id tile.tile-id ; colliders tile.colliders] ; (if (and (> id 0) (< id 21)) ;; 1-20 are wall tiles ; (do ; ; (print (fennel.view {:quad (. walls.quads id) : x : y : id})) ; (walls.batch:add (. walls.quads id) (if tile.h-flip (+ x 25) x) y 0 (if tile.h-flip -1 1) 1) ; (each [_ collider (pairs colliders)] ; (let [ ; mirrored-collider (if tile.h-flip (mirror-collider collider) collider) ; collider-x (+ x mirrored-collider.x) ; collider-y (+ y mirrored-collider.y)] ; (bump-world:add {: x : y :name :wall :behavior :block} collider-x collider-y mirrored-collider.width mirrored-collider.height) ; (table.insert ; collider-debug-boxes ; {:x collider-x :y collider-y :width mirrored-collider.width :height mirrored-collider.height}))))))))) ; (lambda create-charging-station [x y] ; (let [ ; station {: x : y :type :charging-station :active-pads {1 false 2 false 3 false}} ; pads { ; 1 {:x (- x 25) :y y :width 25 :height 25 :light-x (+ x 5)} ; 3 {:x (+ x 25) :y y :width 25 :height 25 :light-x (+ x 5 6 6)} ; 2 {:x x :y (+ y 25) :width 25 :height 25 :light-x (+ x 5 6)} ; }] ; (bump-world:add {: x : y :name :charging-station :behavior :block} x y 25 25) ; (add-collider-debug-box x y 25 25) ; (fn station.draw [self] ; (reset-color) ; (love.graphics.draw objects.sprite objects.quads.charging-station self.x self.y) ; (for [i 1 3 1] ; (let [pad (. pads i)] ; (if (. self.active-pads i) ; (do (love.graphics.draw objects.sprite objects.quads.charging-pad-active pad.x pad.y) ; (set-color :light-pink) ; (love.graphics.rectangle "fill" pad.light-x (+ y 18) 2 2) ; (reset-color)) ; (love.graphics.draw objects.sprite objects.quads.charging-pad pad.x pad.y)))) ; ) ; (lambda pad-hover-cb [self dt] ; (tset station.active-pads self.id true) ; (when (< player.battery 100) (set player.battery (+ player.battery (* dt 6))))) ; (lambda station.update [self _dt] ; (set self.active-pads {1 false 2 false 3 false})) ; (for [i 1 3 1] ; (let [pad (. pads i)] ; (bump-world:add {:name :charging-pad :behavior :hover :hover-cb pad-hover-cb :id i} pad.x pad.y 25 25) ; (add-collider-debug-box pad.x pad.y 25 25))) ; (table.insert objects.list station))) ; (lambda load-h-door [obj] ; (debug-print obj)) ; (fn load-objects [] ; (let [(w h) (objects.sprite:getDimensions)] ; (set objects.quads.charging-pad (love.graphics.newQuad 0 0 25 25 w h)) ; (set objects.quads.charging-pad-active (love.graphics.newQuad 50 0 25 25 w h)) ; (set objects.quads.charging-station (love.graphics.newQuad 25 0 25 25 w h)) ; (set objects.quads.door (love.graphics.newQuad 25 25 25 25 w h))) ; (each [_ object (pairs (. levels :levels level-key :objects))] ; ; (debug-print object) ; (case object.type ; "h_door" (load-h-door object)) ; )) ; (fn load-player [] ; (set player.battery 100) ; (set player.x (. levels :levels level-key :spawns :player-1 :x)) ; (set player.y (. levels :levels level-key :spawns :player-1 :y)) ; (bump-world:add player player.x player.y player.w player.h)) ; (lambda load-level [lvl-name] ; (set bump-world (bump.newWorld 25)) ; (set level-key :tutorial) ; ;; set player 1 location ; (load-player) ; (load-walls) ; (load-objects)) ; (fn load-assets [] ; (set objects.sprite (love.graphics.newImage "assets/objects.png")) ; (set walls.sprite (love.graphics.newImage "assets/walls.png")) ; (set battery-bar-sprite (love.graphics.newImage "assets/battery_bar.png")) ; (set player-art ; (let [ ; player-sprite (love.graphics.newImage "assets/player.png") ; (w h) (player-sprite:getDimensions)] ; { ; :player-sprite player-sprite ; :player1 { ; :quads { ; :n (love.graphics.newQuad 0 0 25 25 w h) ; :s (love.graphics.newQuad 25 0 25 25 w h) ; :ne (love.graphics.newQuad 50 0 25 25 w h) ; :e (love.graphics.newQuad 75 0 25 25 w h) ; :se (love.graphics.newQuad 100 0 25 25 w h) ; :sw (love.graphics.newQuad 125 0 25 25 w h) ; :w (love.graphics.newQuad 150 0 25 25 w h) ; :nw (love.graphics.newQuad 175 0 25 25 w h) ; } ; } ; } ; )) ; ) (fn love.load [] (love.window.setMode screen.screen-w screen.screen-h) ; (tset screen :canvas (love.graphics.newCanvas screen.canvas-w screen.canvas-h)) (set pool (tutorial.load screen)) (pool:flush) (pool:emit :load :tutorial) ; (utils.debug-print pool) ; (utils.debug-print world) ; (load-assets) ; (load-level :tutorial) ;; Initialize network ; (network.init) ; (network.send-msg "REGISTER") ;; start a thread listening on stdin (: (love.thread.newThread "require('love.event') while 1 do love.event.push('stdin', io.read('*line')) end") :start)) (fn love.handlers.stdin [line] ;; evaluate lines read from stdin as fennel code (let [(ok val) (pcall fennel.eval line)] (print (if ok (fennel.view val) val)))) ;; drawing ; (fn draw-world [] ; (reset-color) ; (love.graphics.draw walls.batch) ; ;; draw collider debug boxes ; (when debug ; (set-color :black) ; (each [_ collider (pairs collider-debug-boxes)] ; (love.graphics.rectangle "line" collider.x collider.y collider.width collider.height)))) (fn love.update [dt] (pool:flush) (pool:emit :update dt)) ; (fn love.update [dt] ; (each [_ obj (pairs objects.list)] ; (obj:update dt)) ; ; move / rotate player ; (let [ ; d-key (love.keyboard.isDown :d) ; a-key (love.keyboard.isDown :a) ; e-key (love.keyboard.isDown :e) ; q-key (love.keyboard.isDown :q)] ; ; (print (fennel.view {: d-key : a-key : e-key : q-key})) ; (match {:d-key d-key :a-key a-key :e-key e-key :q-key q-key} ; {:d-key true :a-key false :e-key false :q-key false} (tset player :rot (+ player.rot (* dt 2))) ; {:d-key false :a-key true :e-key false :q-key false} (tset player :rot (- player.rot (* dt 2))) ; {:d-key false :a-key false :e-key true :q-key false} (tset player :rot (- player.rot (* dt 2))) ; {:d-key false :a-key false :e-key false :q-key true} (tset player :rot (+ player.rot (* dt 2))) ; ) ; (when (and (> player.battery 0) (or d-key a-key e-key q-key)) ; (let [ ; dir-fn (if (or d-key a-key) #(+ $1 $2) #(- $1 $2)) ; new-x (dir-fn player.x (* player.speed dt (math.cos player.rot))) ; new-y (dir-fn player.y (* player.speed dt (math.sin player.rot))) ; col-filter-fn (lambda [item other] ; (if (= other.behavior "block") :slide :cross)) ; (x y cols len) (bump-world:move player new-x new-y col-filter-fn)] ; (tset player :x x) ; (tset player :y y)) ; (if (> player.battery 0) ; (set player.battery (- player.battery (* dt 2)))))) ; ;; Update camera to follow player (keep player centered on screen) ; (tset camera :x (- player.x (/ screen.canvas-w 2))) ; (tset camera :y (- player.y (/ screen.canvas-h 2))) ; (let ; [(items len) (bump-world:queryRect player.x player.y 25 25 #(= $1.behavior "hover"))] ; (each [_ item (pairs items)] ; (item:hover-cb dt))) ; ;; Network updates ; (network.update dt)) ; (set net-state.net-update-timer (+ net-state.net-update-timer dt)) ; (when (>= net-state.net-update-timer net-state.net-update-interval) ; (when net-state.connected ; (network.send-update player.x player.y player.rot player.battery)) ; (set net-state.net-update-timer 0))) ; (fn draw-ui [] ; (love.graphics.push) ; (love.graphics.translate 0 400) ; (let [font (love.graphics.newFont 10) font-small (love.graphics.newFont 6)] ; (set-color :cream) ; (love.graphics.rectangle "fill" 0 0 screen.canvas-w 100) ; (reset-color) ; (love.graphics.draw battery-bar-sprite 78 8) ; (set-color :dark-purple) ; (love.graphics.setFont font) ; (love.graphics.print (.. "Battery: " (string.format "%d" player.battery) "%") 6 7) ; (set-color :light-blue) ; (love.graphics.rectangle "fill" 80 12 (* 152 (/ player.battery 100)) 4) ; (love.graphics.setFont font-small) ; (set-color :dark-purple) ; (love.graphics.print "100%" 229 2) ; (set-color :light-pink) ; (love.graphics.print "125%" 267 2) ; (love.graphics.print "125%" 306 2) ; (love.graphics.print "150%" 345 2) ; (love.graphics.print "200%" 382 2) ; (set-color :dark-purple) ; (love.graphics.line 0 0 screen.canvas-w 0) ; ) ; (love.graphics.pop) ; ) ; (lambda angle-to-direction [angle] ; "Convert angle (radians) to compass direction keyword" ; (local tau (* 2 math.pi)) ; ; Normalize angle to 0-2π range ; (local normalized (% (+ angle tau) tau)) ; ; Offset by 22.5° (π/8) so section boundaries align with directions ; (local offset (+ normalized (/ math.pi 8))) ; ; Find which 45° section (π/4) it falls into ; (local section (math.floor (/ offset (/ math.pi 4)))) ; ; Map to compass directions ; (local directions [:e :se :s :sw :w :nw :n :ne]) ; (. directions (+ (% section 8) 1))) ; (fn draw-objects [] ; (each [_ obj (pairs objects.list)] ; ; (print (fennel.view obj)) ; (obj:draw))) (fn love.draw [] (for [i 1 99 1] (let [draw-trigger (.. "draw" i)] (pool:emit draw-trigger)))) ; (fn love.draw [] ; (love.graphics.setCanvas screen.canvas) ; (love.graphics.clear) ; (set-color :cream) ; (love.graphics.rectangle "fill" 0 0 screen.canvas-w screen.canvas-h) ; (love.graphics.push) ; stores the default coordinate system ; (love.graphics.translate (* -1 camera.x) (* -1 camera.y)) ; (draw-world) ; (draw-objects) ; (draw-player) ; (love.graphics.pop) ; (draw-ui) ; (love.graphics.setCanvas) ; (reset-color) ; (love.graphics.draw screen.canvas 0 0 0 screen.scale screen.scale)) ; (love.graphics.print "Hello from Fennel!\nPress any key to quit" 10 10)) (fn love.keypressed [key] nil ) (fn love.quit [] "Clean up before game closes" ; (network.close) false)