commit af5b494cdc1d833dbd1b2a30cfec341a1b3db098 Author: Sage Vaillancourt Date: Fri Feb 28 14:49:19 2025 -0500 Init commit The burger - 'e bounce diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62de997 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pdx +.idea +lib/ diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..674d207 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,4 @@ +std = "lua54+playdate" +stds.project = { + globals = {"playdate", "tiny"}, +} \ No newline at end of file diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..1e771b6 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,7 @@ +{ + "Lua.runtime.version": "Lua 5.4", + "Lua.diagnostics.disable": ["undefined-global", "lowercase-global"], + "Lua.diagnostics.globals": ["playdate", "import", "tiny"], + "Lua.workspace.library": ["/home/sage/Downloads/PlaydateSDK-2.6.2/CoreLibs"], + "Lua.workspace.preloadFileSize": 1000 +} \ No newline at end of file diff --git a/.styluaignore b/.styluaignore new file mode 100644 index 0000000..c7a9bba --- /dev/null +++ b/.styluaignore @@ -0,0 +1 @@ +src/assets.lua diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e825dcf --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +all: + pdc --skip-unknown src Luncher.pdx + +assets: + # lua lib/preprocess-cl.lua src/assets.lua2p + +check: assets + stylua -c --indent-type Spaces src/ + luacheck -g -d --codes src/ --exclude-files src/test/ + +test: check + (cd src; find ./test -name '*lua' | xargs -L1 -I %% lua %% -v) + +lint: + stylua --indent-type Spaces src/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..23438e5 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Luncher + +Build the BIGGEST burger by **lunching** it through *delicious* ingredients. + diff --git a/src/booster.lua b/src/booster.lua new file mode 100644 index 0000000..4752dfc --- /dev/null +++ b/src/booster.lua @@ -0,0 +1,48 @@ +Booster = {} + +local gfx = playdate.graphics + +local function newBooster(x, y) + return setmetatable({ + position = { x = x, y = y }, + size = { x = 40, y = 10 }, + }, { __index = Booster }) +end + +function Booster:elasticity(velocity) + velocity.y = (math.abs(velocity.y) + 190) * -0.5 + velocity.x = velocity.x + 50 +end + +function Booster:draw() + gfx.fillRect(self.position.x, self.position.y, self.size.x, self.size.y) +end + +local boosterIndex = 1 +local boosterCache = {} +for i = 1, 200 do + boosterCache[i] = newBooster(-999, 999) +end + +function Booster.cacheSize() + return #boosterCache +end + +-- Boosters should be "initialized" almost always increasing in X value +function Booster.get(x, y) + local booster = boosterCache[boosterIndex] + booster.position.x = x + booster.position.y = y + + boosterIndex = boosterIndex + 1 + if boosterIndex > #boosterCache then + boosterIndex = 1 + end + + return booster +end + +-- Assumes that at least one Booster has already been created. +function Booster.newestBooster() + return boosterCache[boosterIndex] +end diff --git a/src/bouncer-coroutine.lua.bak b/src/bouncer-coroutine.lua.bak new file mode 100644 index 0000000..9fa9c93 --- /dev/null +++ b/src/bouncer-coroutine.lua.bak @@ -0,0 +1,42 @@ +function bouncer(v) + return function() + local h0 = 0.1 -- m/s + -- local v = 10 -- m/s, current velocity + local g = 10 -- m/s/s + local t = 0 -- starting time + local rho = 0.60 -- coefficient of restitution + local tau = 0.10 -- contact time for bounce + local hmax = h0 -- keep track of the maximum height + local h = h0 + local hstop = 0.01 -- stop when bounce is less than 1 cm + local freefall = true -- state: freefall or in contact + local t_last = -math.sqrt(2 * h0 / g) -- time we would have launched to get to h0 at t=0 + local vmax = math.sqrt(v * g) + + while hmax > hstop do + local dt = coroutine.yield(h, not freefall) + if freefall then + local hnew = h + v * dt - 0.5 * g * dt * dt + if hnew < 0 then + -- Bounced! + t = t_last + 2 * math.sqrt(2 * hmax / g) + freefall = false + t_last = t + tau + h = 0 + else + t = t + dt + v = v - g * dt + h = hnew + end + else + t = t + tau + vmax = vmax * rho + v = vmax + freefall = true + h = 0 + end + hmax = 0.5 * vmax * vmax / g + end + return 0 + end +end diff --git a/src/burger.lua b/src/burger.lua new file mode 100644 index 0000000..398730f --- /dev/null +++ b/src/burger.lua @@ -0,0 +1,28 @@ +Burger = {} + +local gfx = playdate.graphics + +function Burger.reset(o) + o.position = { + x = 20, + y = 50, + } + o.velocity = { + x = 100 * math.random(), + y = 150 * (math.random() - 1), + } + o.size = { + x = 10, + y = 10, + } + o.mass = T.marker + return o +end + +function Burger.new() + return setmetatable(Burger.reset({}), { __index = Burger }) +end + +function Burger:draw() + gfx.fillCircleAtPoint(burger.position.x, burger.position.y, 10) +end diff --git a/src/main.lua b/src/main.lua new file mode 100644 index 0000000..e886a67 --- /dev/null +++ b/src/main.lua @@ -0,0 +1,79 @@ +-- stylua: ignore start +import 'CoreLibs/animation.lua' +import 'CoreLibs/animator.lua' +import 'CoreLibs/easing.lua' +import 'CoreLibs/graphics.lua' +import 'CoreLibs/object.lua' +import 'CoreLibs/timer.lua' +import 'CoreLibs/ui.lua' +import 'CoreLibs/utilities/where.lua' +-- stylua: ignore end + +import("../lib/tiny.lua") +import("tiny-tools.lua") +import("systems/filter-types.lua") +import("systems/collision-detection.lua") +import("systems/draw.lua") +import("systems/gravity.lua") +import("systems/velocity.lua") +import("booster.lua") +import("burger.lua") + +local tiny = tiny +local gfx = playdate.graphics +playdate.display.setRefreshRate(50) +gfx.setBackgroundColor(gfx.kColorWhite) + +burger = Burger.new() +floor = { + position = { x = 0, y = 230 }, + size = { x = 10000, y = 10 }, + isSolid = true, +} +function floor:elasticity(velocity) + velocity.x = velocity.x * 0.9 + velocity.y = velocity.y * -0.7 +end +function floor:draw() + gfx.fillRect(floor.position.x, floor.position.y, floor.size.x, floor.size.y) +end + +Camera = { + pan = { + x = 0, + y = 0, + }, +} + +world = tiny.world(fallSystem, velocitySystem, collisionDetection, drawSystem, burger, floor) +for i = 1, Booster.cacheSize() do + world:addEntity(Booster.get(i * 60 + (math.random(0, 50)), math.random(50, 200))) +end + +function playdate.update() + local deltaSeconds = playdate.getElapsedTime() + playdate.resetElapsedTime() + gfx.clear(gfx.kColorWhite) + playdate.drawFPS(5, 5) + + Camera.pan.x = math.min(0, -burger.position.x + 200) + Camera.pan.y = burger.position.y + 120 + local newestX = Booster.newestBooster().position.x + local panX = -Camera.pan.x + printTable({ panX = panX, newestX = newestX }) + local offset = 600 + if newestX < panX then + Booster.get(panX + offset + (math.random(0, 50)), math.random(50, 200)) + end + floor.position.x = -Camera.pan.x - offset + gfx.setDrawOffset(Camera.pan.x, Camera.pan.y) + + world:update(deltaSeconds) + + if playdate.buttonJustPressed(playdate.kButtonA) then + Burger.reset(burger) + for i = 1, Booster.cacheSize() do + Booster.get(i * 60 + (math.random(0, 50)), math.random(50, 200)) + end + end +end diff --git a/src/systems/collision-detection.lua b/src/systems/collision-detection.lua new file mode 100644 index 0000000..545ae64 --- /dev/null +++ b/src/systems/collision-detection.lua @@ -0,0 +1,28 @@ +collisionDetection = tiny.filteredSystem( + { position = T.XyPair, size = T.XyPair, elasticity = T.Elasticity, isSolid = Maybe(T.bool) }, + -- Here, the entity, e, refers to some entity that the burger global(!) may be colliding with. + function(e) + if not burger.velocity then + return + end + local burgerTop = burger.position.y + local burgerBottom = burger.position.y + burger.size.y + local entityTop = e.position.y + local entityBottom = entityTop + e.size.y + + local withinY = (entityTop > burgerTop and entityTop < burgerBottom) + or (entityBottom > burgerTop and entityBottom < burgerBottom) + + if not withinY then + return + end + + if burger.position.x < e.position.x + e.size.x and burger.position.x + burger.size.x > e.position.x then + if e.isSolid then + -- Assumes impact from the top + burger.position.y = entityTop - burger.size.y + end + e:elasticity(burger.velocity) + end + end +) diff --git a/src/systems/draw.lua b/src/systems/draw.lua new file mode 100644 index 0000000..a1104fd --- /dev/null +++ b/src/systems/draw.lua @@ -0,0 +1,3 @@ +drawSystem = tiny.filteredSystem({ draw = T.SelfFunction }, function(e, dt) + e:draw() +end) diff --git a/src/systems/filter-types.lua b/src/systems/filter-types.lua new file mode 100644 index 0000000..4e497c8 --- /dev/null +++ b/src/systems/filter-types.lua @@ -0,0 +1,27 @@ +---@alias XyPair { x: number, y: number } + +---@alias Elasticity fun(self, velocity: XyPair) + +T = { + ---@type XyPair + XyPair = { x = 1, y = 1 }, + bool = true, + number = 0, + numberArray = { 1, 2, 3 }, + str = "", + marker = {}, + ---@type fun(self) + SelfFunction = function(self) end, + ---@type Elasticity + Elasticity = { + isRigid = true, + apply = function() end, + }, +} + +---@generic T +---@param t T +---@return nil | T +function Maybe(t) + return { maybe = t } +end diff --git a/src/systems/gravity.lua b/src/systems/gravity.lua new file mode 100644 index 0000000..2344cc2 --- /dev/null +++ b/src/systems/gravity.lua @@ -0,0 +1,4 @@ +local G = -300 +fallSystem = tiny.filteredSystem({ velocity = T.XyPair, mass = T.marker }, function(e, dt) + e.velocity.y = e.velocity.y - (G * dt) - (0.5 * dt * dt) +end) diff --git a/src/systems/velocity.lua b/src/systems/velocity.lua new file mode 100644 index 0000000..6576f0d --- /dev/null +++ b/src/systems/velocity.lua @@ -0,0 +1,11 @@ +local sqrt = math.sqrt + +velocitySystem = tiny.filteredSystem({ position = T.XyPair, velocity = T.XyPair }, function(e, dt) + if sqrt((e.velocity.x * e.velocity.x) + (e.velocity.y * e.velocity.y)) < 0.1 then + e.velocity = nil + world:addEntity(e) + else + e.position.x = e.position.x + (e.velocity.x * dt) + e.position.y = e.position.y + (e.velocity.y * dt) + end +end) diff --git a/src/tiny-tools.lua b/src/tiny-tools.lua new file mode 100644 index 0000000..48ff029 --- /dev/null +++ b/src/tiny-tools.lua @@ -0,0 +1,55 @@ +local isSimulator = playdate.isSimulator + +-- local function keysContain(key, allowedKeys) +-- for _, allowedKey in pairs(allowedKeys) do +-- if key == allowedKey then +-- return true +-- end +-- end +-- return false +-- end + +---@generic T +---@param shape T +---@param process fun(entity: T, dt: number, system: System) +---@return +function tiny.filteredSystem(shape, process) + local system = tiny.processingSystem() + local keys = {} + for key, value in pairs(shape) do + if type(value) ~= "table" or value.maybe == nil then + -- ^ Don't require any Maybe types + keys[#keys + 1] = key + end + end + system.filter = tiny.requireAll(table.unpack(keys)) + if isSimulator then + -- local acceptableKeys = "" + -- for _, key in ipairs(keys) do + -- acceptableKeys = acceptableKeys .. "'" .. key .. "', " + -- end + -- acceptableKeys = acceptableKeys .. "]" + function system:process(e, dt) + -- local _e = e + -- e = setmetatable({}, { + -- __newindex = function() + -- error("Do not attempt to mutate entity data!") + -- end, + -- __index = function(_, key) + -- -- TODO: also assert their types + -- assert( + -- keysContain(key, keys), + -- "Attempted to use key '" .. key .. "' - should be one of [ " .. acceptableKeys + -- ) + -- return _e[key] + -- end, + -- }) + process(e, dt, self) + end + else + function system:process(e, dt) + process(e, dt, self) + end + end + return system +end