switch repo to just cleaning game focus

This commit is contained in:
Travis Shears 2026-04-26 20:23:48 +02:00
parent 276c7c562b
commit 6d5406c6c5
Signed by: travisshears
GPG key ID: CB9BF1910F3F7469
49 changed files with 0 additions and 150 deletions

9
game/src/assets.fnl Normal file
View file

@ -0,0 +1,9 @@
(local assets {})
(love.graphics.setDefaultFilter "nearest" "nearest")
(set assets.objects-sprite (love.graphics.newImage "assets/objects.png"))
(set assets.walls-sprite (love.graphics.newImage "assets/walls.png"))
(set assets.battery-bar-sprite (love.graphics.newImage "assets/battery_bar.png"))
(set assets.player-sprite (love.graphics.newImage "assets/player.png"))
assets

32
game/src/colors.fnl Normal file
View file

@ -0,0 +1,32 @@
(local utils (require "src/utils.fnl"))
;; colors
(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 colors {
:cream (color 255 238 204)
:dark-purple (color 70 66 94)
:dark-pink (color 255 105 115)
:dark-blue (color 21 120 140)
:light-blue (color 0 185 190)
:light-pink (color 255 176 163)
:black [0 0 0]
:black-half-tone [0 0 0 0.25]
})
(fn reset-color []
"reset color to white (no tinting)"
(love.graphics.setColor 1 1 1))
(lambda set-color [color-name]
"set color to the given color from the color pallet"
(love.graphics.setColor (unpack (. colors color-name))))
{
: reset-color
: set-color
: colors
}

View file

@ -0,0 +1,38 @@
(local utils (require "src/utils.fnl"))
(local beholder (require "libs/beholder"))
(local color (require "src/colors.fnl"))
(local levels (require "levels.fnl"))
(local assets (require "src/assets.fnl"))
(lambda info-pad [obj]
(let [
x obj.x
y obj.y
content obj.content
pad {: x : y : content :hitbox [x y 25 25]}
(w h) (assets.objects-sprite:getDimensions)]
(lambda pad.load [self]
(set self.quad (love.graphics.newQuad 75 0 25 25 w h)))
(lambda pad.on-hit [self]
(beholder.trigger "NOTIFICATION" self.content))
(fn pad.draw-debug [self]
(color.set-color :black)
(love.graphics.rectangle "line" self.x self.y 25 25))
(lambda pad.draw49 [self]
(color.reset-color)
(love.graphics.draw assets.objects-sprite self.quad self.x self.y))
pad
))
info-pad
; (lambda [{:bump-world bw :level-key key}]
; (set bump-world bw)
; (set level-key key)
; player)

View file

@ -0,0 +1,117 @@
(local utils (require "src/utils.fnl"))
(local beholder (require "libs/beholder"))
(local color (require "src/colors.fnl"))
(local levels (require "levels.fnl"))
(local assets (require "src/assets.fnl"))
(var bump-world nil)
(var level-key nil)
(lambda bump-filter [item other]
(if (= other.behavior "block") :slide :cross))
(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)))
(local width 19)
(local height 15)
(local player {
:x 50 :y 50 :w 25 :h 25 :speed 80 :battery 100 :rot 0
:hitbox [50 50 width height]
:init-move false ; nudge the player at start of game to trigger starting collisions
})
(lambda handle-collisions [cols]
(each [_ col (pairs cols)]
(when col.other.on-hit (col.other:on-hit))
(beholder.trigger "PLAYER.HIT" col.other)))
(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 [
d-key (love.keyboard.isDown :d)
a-key (love.keyboard.isDown :a)
e-key (love.keyboard.isDown :e)
q-key (love.keyboard.isDown :q)]
(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} (set self.rot (+ self.rot (* dt 2)))
{:d-key false :a-key true :e-key false :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)))
)
(when (and (> self.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 self.x (* self.speed dt (math.cos self.rot)))
new-y (dir-fn self.y (* self.speed dt (math.sin self.rot)))
(x y cols len) (bump-world:move self new-x new-y bump-filter)]
(handle-collisions cols)
(set self.x x)
(set self.y y))
(if (> self.battery 0)
(set self.battery (- self.battery (* dt 2))))))
(beholder.trigger "PLAYER.POS" self.x self.y)
(beholder.trigger "PLAYER.BATTERY" self.battery))
(fn player.load [self]
(set self.battery 100)
(let [
spawn-x (-> (. levels :levels level-key :spawns :player-1 :x) (- (/ width 2)))
spawn-y (-> (. levels :levels level-key :spawns :player-1 :y) (- (/ height 2)))
(x y cols len) (bump-world:move self spawn-x spawn-y bump-filter)]
(set self.x x)
(set self.y y))
(set self.quads
(let [
(w h) (assets.player-sprite:getDimensions)]
{
: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 player.draw50 [self]
"draw player sprite and hitbox"
(color:reset-color)
(love.graphics.draw
assets.player-sprite
(. self.quads (angle-to-direction player.rot))
(- self.x 3) (- self.y 7)))
(fn player.draw-debug [self]
"draw player hitbox and direction line"
(color.set-color :black)
(love.graphics.rectangle "line" self.x self.y width height)
(love.graphics.rectangle "line" self.x self.y 1 1)
(love.graphics.push)
(let [ox (+ self.x (/ width 2)) oy (+ self.y (/ height 2))]
(love.graphics.translate ox oy)
(love.graphics.rotate self.rot)
(love.graphics.line 0 0 35 0))
(love.graphics.pop))
(lambda [{:bump-world bw :level-key key}]
(set bump-world bw)
(set level-key key)
player)

72
game/src/levels/dev.fnl Normal file
View file

@ -0,0 +1,72 @@
(local bump (require "libs/bump"))
(local color (require "src/colors.fnl"))
(local nata (require "libs/nata"))
(local utils (require "src/utils.fnl"))
(local camera (require "src/systems/camera.fnl"))
(local beholder (require "libs/beholder"))
(local player (require "src/entities/player.fnl"))
(local walls (require "src/systems/walls.fnl"))
(local hud (require "src/systems/hud.fnl"))
(local levels (require "levels.fnl"))
(local info-pad (require "src/entities/info-pad.fnl"))
(lambda load-objects [level-key pool]
(each [_ object (pairs (. levels :levels level-key :objects))]
; (debug-print object)
(case object.type
"info_pad" (pool:queue (info-pad object))
))
)
(lambda collider-manager [bump-world]
(local collider-manager {})
(lambda collider-manager.addToGroup [self group-name entity]
(when (= group-name :hitbox)
; (utils.debug-print {:adding entity})
(bump-world:add entity (unpack entity.hitbox))))
collider-manager)
; (lambda register-notifications []
; (beholder.observe "PLAYER.HIT" (lambda [other]
; (when (= other.object-type :info-pad)
; (beholder.trigger "NOTIFICATION" "[a] is left wheel forward [d] is right wheel forward"))
; (utils.debug-print {: other}))))
; (beholder.observe "PLAYER.HIT" :info-pad (lambda [info-pad-name]
; (case info-pad-name
; :controls (beholder.trigger "NOTIFICATION" "[a] is left wheel forward [d] is right wheel forward")
; ))))
(fn load [screen]
; (register-notifications)
(let [
canvas (love.graphics.newCanvas screen.canvas-w screen.canvas-h)
level-key :dev
bump-world (bump.newWorld 25)
pool (nata.new {
:data {
: screen
: canvas
: bump-world
: level-key
}
:groups {
; gravity = {filter = {'gravity'}},
:hitbox {:filter [:hitbox]}
; :post-draw {:filter [:post-draw]}
}
:systems [
(nata:oop)
(collider-manager bump-world)
camera
walls
(hud {: screen})
]
})]
(pool:queue (player {: bump-world : level-key}))
(load-objects level-key pool)
pool))
{
: load
}

View file

@ -0,0 +1,71 @@
(local bump (require "libs/bump"))
(local color (require "src/colors.fnl"))
(local nata (require "libs/nata"))
(local utils (require "src/utils.fnl"))
(local camera (require "src/systems/camera.fnl"))
(local beholder (require "libs/beholder"))
(local player (require "src/entities/player.fnl"))
(local walls (require "src/systems/walls.fnl"))
(local hud (require "src/systems/hud.fnl"))
(local levels (require "levels.fnl"))
(local info-pad (require "src/entities/info-pad.fnl"))
(lambda load-objects [level-key pool]
(each [_ object (pairs (. levels :levels level-key :objects))]
; (debug-print object)
(case object.type
"info_pad" (pool:queue (info-pad object.x object.y object.name))
))
)
(lambda collider-manager [bump-world]
(local collider-manager {})
(lambda collider-manager.addToGroup [self group-name entity]
(when (= group-name :hitbox)
; (utils.debug-print {:adding entity})
(bump-world:add entity (unpack entity.hitbox))))
collider-manager)
(lambda register-notifications []
(beholder.observe "PLAYER.HIT" (lambda [other]
(when (= other.object-type :info-pad)
(beholder.trigger "NOTIFICATION" "[a] is left wheel forward [d] is right wheel forward"))
(utils.debug-print {: other}))))
; (beholder.observe "PLAYER.HIT" :info-pad (lambda [info-pad-name]
; (case info-pad-name
; :controls (beholder.trigger "NOTIFICATION" "[a] is left wheel forward [d] is right wheel forward")
; ))))
(fn load [screen]
(register-notifications)
(let [
canvas (love.graphics.newCanvas screen.canvas-w screen.canvas-h)
bump-world (bump.newWorld 25)
pool (nata.new {
:data {
: screen
: canvas
: bump-world
:level-key :tutorial
}
:groups {
; gravity = {filter = {'gravity'}},
:hitbox {:filter [:hitbox]}
; :post-draw {:filter [:post-draw]}
}
:systems [
(nata:oop)
(collider-manager bump-world)
camera
walls
(hud {: screen})
]
})]
(pool:queue (player {:bump-world bump-world :level-key :tutorial}))
(load-objects :tutorial pool)
pool))
{
: load
}

133
game/src/network.fnl Normal file
View file

@ -0,0 +1,133 @@
(local socket (require "socket"))
;; Guide: https://www.love2d.org/wiki/Tutorial:Networking_with_UDP
;; Configuration
(local config {
:host "localhost"
:port 9999
})
;; Network state
(local state {
:socket nil
:connected false
; :player-id nil
; :last-error nil
})
;; Callback functions for network events
; (local callbacks {
; :on-ready (fn [])
; :on-wait (fn [])
; :on-opponent-update (fn [data])
; :on-error (fn [error])
; })
(fn init []
"Initialize UDP connection to server
Returns true on success, false on error"
(let [udp (socket.udp)]
(udp:setpeername config.host config.port)
(udp:settimeout 0) ; non-blocking
(set state.socket udp)
(set state.connected true)))
(fn send-msg [dg]
(state.socket:send dg))
(fn update [dt]
(let [(data msg) (state.socket:receive)]
(when data
(print (fennel.view {: data : msg})))))
; (fn register []
; "Send REGISTER message to server"
; (when state.socket
; (try
; (state.socket:send "REGISTER")
; (catch err
; (set state.last-error err)
; (when callbacks.on-error (callbacks.on-error err))))))
; (fn split-string [str delimiter]
; "Split string by delimiter into table"
; (local result [])
; (local current "")
; (each [i 1 (length str) 1]
; (let [char (string.sub str i i)]
; (if (= char delimiter)
; (do
; (table.insert result current)
; (set current ""))
; (set current (.. current char)))))
; (table.insert result current)
; result)
; (fn parse-message [msg]
; "Parse message in format: COMMAND#arg1#arg2#..."
; (split-string msg "#"))
; (fn send-update [x y rotation battery]
; "Send player state to server in format: UPDATE#x#y#rotation#battery"
; (when state.socket
; (try
; (let [msg (.. "UPDATE#" x "#" y "#" rotation "#" battery)]
; (state.socket:send msg))
; (catch err
; (set state.last-error err)))))
; (fn update [dt]
; "Call from love.update to process incoming messages"
; (when state.socket
; (let [(msg _src-ip _src-port) (state.socket:receivefrom)]
; (when msg
; (let [parts (parse-message msg)
; command (. parts 1)]
; (match command
; "READY_TO_PLAY" (callbacks.on-ready)
; "WAIT" (callbacks.on-wait)
; "UPDATE" (callbacks.on-opponent-update {
; :x (tonumber (. parts 2))
; :y (tonumber (. parts 3))
; :rotation (tonumber (. parts 4))
; :battery (tonumber (. parts 5))
; })
; _ (print (.. "← Message: " msg))))))))
; (fn set-callback [event cb]
; "Register callback for network events
; Available events: :on-ready :on-wait :on-opponent-update :on-error"
; (tset callbacks event cb))
(fn close []
"Close network connection"
(when state.socket
(state.socket:close)
(set state.socket nil)
(set state.connected false)))
; (fn is-connected []
; "Check if currently connected to server"
; state.connected)
; (fn get-last-error []
; "Get the last error that occurred"
; state.last-error)
;; Export public API
{
: init
: update
: state
: send-msg
: close
; :register register
; :send-update send-update
; :update update
; :set-callback set-callback
; :close close
; :is-connected is-connected
; :get-last-error get-last-error
}

View file

@ -0,0 +1,37 @@
(local beholder (require "libs/beholder"))
(local color (require "src/colors.fnl"))
;; Camera System
(local camera {:x 0 :y 0})
(fn camera.draw2 [self]
(love.graphics.push)
(love.graphics.translate (* -1 self.x) (* -1 self.y)))
(fn camera.load [self]
(let [screen self.pool.data.screen]
(beholder.observe "PLAYER.POS" (lambda [x y]
;; Update camera to follow player (keep player centered on screen)
(set self.x (- x (/ screen.canvas-w 2)))
(set self.y (- y (/ screen.canvas-h 2)))))))
(fn camera.draw89 [self]
(love.graphics.pop)) ; undo camera translation
(fn camera.draw99 [self]
(let [screen self.pool.data.screen canvas self.pool.data.canvas]
(love.graphics.setCanvas) ; reset to root canvas
(color:reset-color)
(love.graphics.draw canvas 0 0 0 screen.scale screen.scale)))
(fn camera.draw1 [self]
(let [screen self.pool.data.screen canvas self.pool.data.canvas]
; use canvas
(love.graphics.setCanvas canvas)
; clear the screen
(love.graphics.clear)
(color.set-color :cream)
(love.graphics.rectangle "fill" 0 0 screen.canvas-w screen.canvas-h)))
camera

88
game/src/systems/hud.fnl Normal file
View file

@ -0,0 +1,88 @@
(local beholder (require "libs/beholder"))
(local assets (require "src/assets.fnl"))
(local color (require "src/colors.fnl"))
(var screen nil)
(local hud {
:player-battery 0
:notification nil
:notification-life 0
})
(lambda draw-notification [self]
(when (and self.notification (> self.notification-life 0))
(love.graphics.push)
(love.graphics.translate 0 (- 400 20))
(color.set-color :cream)
(love.graphics.rectangle "fill" 0 0 screen.canvas-w 20)
(color.set-color :dark-purple)
(color.set-color :dark-purple)
(love.graphics.line 0 0 screen.canvas-w 0)
(love.graphics.setFont self.font)
(love.graphics.print self.notification 3 3)
(love.graphics.pop)))
(fn hud.load [self]
(set self.font (love.graphics.newFont 10))
(set self.font-small (love.graphics.newFont 6))
(beholder.observe "PLAYER.BATTERY" (lambda [bat]
(set self.player-battery bat)))
(beholder.observe "NOTIFICATION" (lambda [text]
(set self.notification text)
; set life of notification to 5 seconds
(set self.notification-life 5))))
(lambda hud.update [self dt]
(when (> self.notification-life 0)
(set self.notification-life (- self.notification-life dt))))
(lambda draw-header [self]
(color.set-color :cream)
(love.graphics.rectangle "fill" 0 0 screen.canvas-w 20)
(color.set-color :dark-purple)
(love.graphics.line 0 20 screen.canvas-w 20))
(lambda draw-side-walls [self]
(color.set-color :cream)
(love.graphics.rectangle "fill" 0 0 20 screen.canvas-h)
(love.graphics.rectangle "fill" (- screen.canvas-w 20) 0 20 screen.canvas-h)
(color.set-color :dark-purple)
(love.graphics.line 20 0 20 screen.canvas-w)
(love.graphics.line (- screen.canvas-w 20) 0 (- screen.canvas-w 20) screen.canvas-w))
(lambda draw-footer [self]
(love.graphics.push)
(love.graphics.translate 0 400)
(color.set-color :cream)
(love.graphics.rectangle "fill" 0 0 screen.canvas-w 100)
(color.reset-color)
(love.graphics.draw assets.battery-bar-sprite 78 8)
(color.set-color :dark-purple)
(love.graphics.setFont self.font)
(love.graphics.print (.. "Battery: " (string.format "%d" self.player-battery) "%") 6 7)
(color.set-color :light-blue)
(love.graphics.rectangle "fill" 80 12 (* 152 (/ self.player-battery 100)) 4)
(love.graphics.setFont self.font-small)
(color.set-color :dark-purple)
(love.graphics.print "100%" 229 2)
(color.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)
(color.set-color :dark-purple)
(love.graphics.line 0 0 screen.canvas-w 0)
(love.graphics.pop))
(fn hud.draw90 [self]
(draw-side-walls self)
(draw-footer self)
(draw-header self)
(draw-notification self))
(lambda [{:screen s}]
(set screen s)
hud)

View file

@ -0,0 +1,58 @@
(local assets (require "src/assets.fnl"))
(local levels (require "levels.fnl"))
(local color (require "src/colors.fnl"))
(local walls {
:quads []
:batch nil
:collider-debug-boxes []
})
(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 walls.load [self]
(set self.batch (love.graphics.newSpriteBatch assets.walls-sprite 2500))
;; load quads
(let [(w h) (assets.walls-sprite:getDimensions)]
(for [i 0 19 1]
(table.insert self.quads (love.graphics.newQuad (* i 25) 0 25 25 w h))))
;; fill batch
(each [_ row (pairs (. levels :levels self.pool.data.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
(self.batch:add (. self.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)]
(self.pool.data.bump-world:add {: x : y :name :wall :behavior :block} collider-x collider-y mirrored-collider.width mirrored-collider.height)
(table.insert
self.collider-debug-boxes
{:x collider-x :y collider-y :width mirrored-collider.width :height mirrored-collider.height})))))))))
(fn walls.draw49 [self]
(color.reset-color)
(love.graphics.draw self.batch))
(fn walls.draw-debug [self]
"draw collider debug boxes"
(color.set-color :black)
(each [_ collider (pairs self.collider-debug-boxes)]
(love.graphics.rectangle "line" collider.x collider.y collider.width collider.height)))
walls

6
game/src/utils.fnl Normal file
View file

@ -0,0 +1,6 @@
(lambda debug-print [obj]
(print (fennel.view obj)))
{
: debug-print
}