From 842a9899ca213af74f97954fe4c4251232ce77f7 Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Wed, 22 Apr 2026 23:14:07 +0200 Subject: [PATCH] comment everything out before trying out tiny-ecs --- two_player_cleaning_game/assets.fnl | 12 + .../assets/tiled/untitled.tiled-session | 8 +- two_player_cleaning_game/{ => libs}/bump.lua | 0 two_player_cleaning_game/libs/tiny.lua | 863 ++++++++++++++++++ two_player_cleaning_game/main.fnl | 615 +++++++------ 5 files changed, 1194 insertions(+), 304 deletions(-) create mode 100644 two_player_cleaning_game/assets.fnl rename two_player_cleaning_game/{ => libs}/bump.lua (100%) create mode 100644 two_player_cleaning_game/libs/tiny.lua diff --git a/two_player_cleaning_game/assets.fnl b/two_player_cleaning_game/assets.fnl new file mode 100644 index 0000000..8202a9d --- /dev/null +++ b/two_player_cleaning_game/assets.fnl @@ -0,0 +1,12 @@ +(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 +} diff --git a/two_player_cleaning_game/assets/tiled/untitled.tiled-session b/two_player_cleaning_game/assets/tiled/untitled.tiled-session index e2adf3b..7156a2f 100644 --- a/two_player_cleaning_game/assets/tiled/untitled.tiled-session +++ b/two_player_cleaning_game/assets/tiled/untitled.tiled-session @@ -1,9 +1,9 @@ { - "activeFile": "tutorial.tmx", + "activeFile": "objects.tsx", "expandedProjectPaths": [ - "/Users/she0001t/personal_projects/fennel_love2d_experiments/two_player_cleaning_game", + "/Users/she0001t/personal_projects/fennel_love2d_experiments/two_player_cleaning_game/assets", ".", - "/Users/she0001t/personal_projects/fennel_love2d_experiments/two_player_cleaning_game/assets" + "/Users/she0001t/personal_projects/fennel_love2d_experiments/two_player_cleaning_game" ], "fileStates": { "level_001.tmx": { @@ -60,8 +60,8 @@ "recentFiles": [ "level_001.tmx", "walls.tsx", - "objects.tsx", "tutorial.tmx", + "objects.tsx", "map_tileset.tsx" ], "tileset.lastUsedFormat": "tsx", diff --git a/two_player_cleaning_game/bump.lua b/two_player_cleaning_game/libs/bump.lua similarity index 100% rename from two_player_cleaning_game/bump.lua rename to two_player_cleaning_game/libs/bump.lua diff --git a/two_player_cleaning_game/libs/tiny.lua b/two_player_cleaning_game/libs/tiny.lua new file mode 100644 index 0000000..e2cd869 --- /dev/null +++ b/two_player_cleaning_game/libs/tiny.lua @@ -0,0 +1,863 @@ +--[[ +Copyright (c) 2016 Calvin Rose + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] + +--- @module tiny-ecs +-- @author Calvin Rose +-- @license MIT +-- @copyright 2016 +local tiny = {} + +-- Local versions of standard lua functions +local tinsert = table.insert +local tremove = table.remove +local tsort = table.sort +local setmetatable = setmetatable +local type = type +local select = select + +-- Local versions of the library functions +local tiny_manageEntities +local tiny_manageSystems +local tiny_addEntity +local tiny_addSystem +local tiny_add +local tiny_removeEntity +local tiny_removeSystem + +--- Filter functions. +-- A Filter is a function that selects which Entities apply to a System. +-- Filters take two parameters, the System and the Entity, and return a boolean +-- value indicating if the Entity should be processed by the System. A truthy +-- value includes the entity, while a falsey (nil or false) value excludes the +-- entity. +-- +-- Filters must be added to Systems by setting the `filter` field of the System. +-- Filter's returned by tiny-ecs's Filter functions are immutable and can be +-- used by multiple Systems. +-- +-- local f1 = tiny.requireAll("position", "velocity", "size") +-- local f2 = tiny.requireAny("position", "velocity", "size") +-- +-- local e1 = { +-- position = {2, 3}, +-- velocity = {3, 3}, +-- size = {4, 4} +-- } +-- +-- local entity2 = { +-- position = {4, 5}, +-- size = {4, 4} +-- } +-- +-- local e3 = { +-- position = {2, 3}, +-- velocity = {3, 3} +-- } +-- +-- print(f1(nil, e1), f1(nil, e2), f1(nil, e3)) -- prints true, false, false +-- print(f2(nil, e1), f2(nil, e2), f2(nil, e3)) -- prints true, true, true +-- +-- Filters can also be passed as arguments to other Filter constructors. This is +-- a powerful way to create complex, custom Filters that select a very specific +-- set of Entities. +-- +-- -- Selects Entities with an "image" Component, but not Entities with a +-- -- "Player" or "Enemy" Component. +-- filter = tiny.requireAll("image", tiny.rejectAny("Player", "Enemy")) +-- +-- @section Filter + +-- A helper function to compile filters. +local filterJoin + +-- A helper function to filters from string +local filterBuildString + +do + local loadstring = loadstring or load + local function getchr(c) + return "\\" .. c:byte() + end + local function make_safe(text) + return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr) + end + + local function filterJoinRaw(prefix, seperator, ...) + local accum = {} + local build = {} + for i = 1, select('#', ...) do + local item = select(i, ...) + if type(item) == 'string' then + accum[#accum + 1] = ("(e[%s] ~= nil)"):format(make_safe(item)) + elseif type(item) == 'function' then + build[#build + 1] = ('local subfilter_%d_ = select(%d, ...)') + :format(i, i) + accum[#accum + 1] = ('(subfilter_%d_(system, e))'):format(i) + else + error 'Filter token must be a string or a filter function.' + end + end + local source = ('%s\nreturn function(system, e) return %s(%s) end') + :format( + table.concat(build, '\n'), + prefix, + table.concat(accum, seperator)) + local loader, err = loadstring(source) + if err then error(err) end + return loader(...) + end + + function filterJoin(...) + local state, value = pcall(filterJoinRaw, ...) + if state then return value else return nil, value end + end + + local function buildPart(str) + local accum = {} + local subParts = {} + str = str:gsub('%b()', function(p) + subParts[#subParts + 1] = buildPart(p:sub(2, -2)) + return ('\255%d'):format(#subParts) + end) + for invert, part, sep in str:gmatch('(%!?)([^%|%&%!]+)([%|%&]?)') do + if part:match('^\255%d+$') then + local partIndex = tonumber(part:match(part:sub(2))) + accum[#accum + 1] = ('%s(%s)') + :format(invert == '' and '' or 'not', subParts[partIndex]) + else + accum[#accum + 1] = ("(e[%s] %s nil)") + :format(make_safe(part), invert == '' and '~=' or '==') + end + if sep ~= '' then + accum[#accum + 1] = (sep == '|' and ' or ' or ' and ') + end + end + return table.concat(accum) + end + + function filterBuildString(str) + local source = ("return function(_, e) return %s end") + :format(buildPart(str)) + local loader, err = loadstring(source) + if err then + error(err) + end + return loader() + end +end + +--- Makes a Filter that selects Entities with all specified Components and +-- Filters. +function tiny.requireAll(...) + return filterJoin('', ' and ', ...) +end + +--- Makes a Filter that selects Entities with at least one of the specified +-- Components and Filters. +function tiny.requireAny(...) + return filterJoin('', ' or ', ...) +end + +--- Makes a Filter that rejects Entities with all specified Components and +-- Filters, and selects all other Entities. +function tiny.rejectAll(...) + return filterJoin('not', ' and ', ...) +end + +--- Makes a Filter that rejects Entities with at least one of the specified +-- Components and Filters, and selects all other Entities. +function tiny.rejectAny(...) + return filterJoin('not', ' or ', ...) +end + +--- Makes a Filter from a string. Syntax of `pattern` is as follows. +-- +-- * Tokens are alphanumeric strings including underscores. +-- * Tokens can be separated by |, &, or surrounded by parentheses. +-- * Tokens can be prefixed with !, and are then inverted. +-- +-- Examples are best: +-- 'a|b|c' - Matches entities with an 'a' OR 'b' OR 'c'. +-- 'a&!b&c' - Matches entities with an 'a' AND NOT 'b' AND 'c'. +-- 'a|(b&c&d)|e - Matches 'a' OR ('b' AND 'c' AND 'd') OR 'e' +-- @param pattern +function tiny.filter(pattern) + local state, value = pcall(filterBuildString, pattern) + if state then return value else return nil, value end +end + +--- System functions. +-- A System is a wrapper around function callbacks for manipulating Entities. +-- Systems are implemented as tables that contain at least one method; +-- an update function that takes parameters like so: +-- +-- * `function system:update(dt)`. +-- +-- There are also a few other optional callbacks: +-- +-- * `function system:filter(entity)` - Returns true if this System should +-- include this Entity, otherwise should return false. If this isn't specified, +-- no Entities are included in the System. +-- * `function system:onAdd(entity)` - Called when an Entity is added to the +-- System. +-- * `function system:onRemove(entity)` - Called when an Entity is removed +-- from the System. +-- * `function system:onModify(dt)` - Called when the System is modified by +-- adding or removing Entities from the System. +-- * `function system:onAddToWorld(world)` - Called when the System is added +-- to the World, before any entities are added to the system. +-- * `function system:onRemoveFromWorld(world)` - Called when the System is +-- removed from the world, after all Entities are removed from the System. +-- * `function system:preWrap(dt)` - Called on each system before update is +-- called on any system. +-- * `function system:postWrap(dt)` - Called on each system in reverse order +-- after update is called on each system. The idea behind `preWrap` and +-- `postWrap` is to allow for systems that modify the behavior of other systems. +-- Say there is a DrawingSystem, which draws sprites to the screen, and a +-- PostProcessingSystem, that adds some blur and bloom effects. In the preWrap +-- method of the PostProcessingSystem, the System could set the drawing target +-- for the DrawingSystem to a special buffer instead the screen. In the postWrap +-- method, the PostProcessingSystem could then modify the buffer and render it +-- to the screen. In this setup, the PostProcessingSystem would be added to the +-- World after the drawingSystem (A similar but less flexible behavior could +-- be accomplished with a single custom update function in the DrawingSystem). +-- +-- For Filters, it is convenient to use `tiny.requireAll` or `tiny.requireAny`, +-- but one can write their own filters as well. Set the Filter of a System like +-- so: +-- system.filter = tiny.requireAll("a", "b", "c") +-- or +-- function system:filter(entity) +-- return entity.myRequiredComponentName ~= nil +-- end +-- +-- All Systems also have a few important fields that are initialized when the +-- system is added to the World. A few are important, and few should be less +-- commonly used. +-- +-- * The `world` field points to the World that the System belongs to. Useful +-- for adding and removing Entities from the world dynamically via the System. +-- * The `active` flag is whether or not the System is updated automatically. +-- Inactive Systems should be updated manually or not at all via +-- `system:update(dt)`. Defaults to true. +-- * The `entities` field is an ordered list of Entities in the System. This +-- list can be used to quickly iterate through all Entities in a System. +-- * The `interval` field is an optional field that makes Systems update at +-- certain intervals using buffered time, regardless of World update frequency. +-- For example, to make a System update once a second, set the System's interval +-- to 1. +-- * The `index` field is the System's index in the World. Lower indexed +-- Systems are processed before higher indices. The `index` is a read only +-- field; to set the `index`, use `tiny.setSystemIndex(world, system)`. +-- * The `indices` field is a table of Entity keys to their indices in the +-- `entities` list. Most Systems can ignore this. +-- * The `modified` flag is an indicator if the System has been modified in +-- the last update. If so, the `onModify` callback will be called on the System +-- in the next update, if it has one. This is usually managed by tiny-ecs, so +-- users should mostly ignore this, too. +-- +-- There is another option to (hopefully) increase performance in systems that +-- have items added to or removed from them often, and have lots of entities in +-- them. Setting the `nocache` field of the system might improve performance. +-- It is still experimental. There are some restriction to systems without +-- caching, however. +-- +-- * There is no `entities` table. +-- * Callbacks such onAdd, onRemove, and onModify will never be called +-- * Noncached systems cannot be sorted (There is no entities list to sort). +-- +-- @section System + +-- Use an empty table as a key for identifying Systems. Any table that contains +-- this key is considered a System rather than an Entity. +local systemTableKey = { "SYSTEM_TABLE_KEY" } + +-- Checks if a table is a System. +local function isSystem(table) + return table[systemTableKey] +end + +-- Update function for all Processing Systems. +local function processingSystemUpdate(system, dt) + local preProcess = system.preProcess + local process = system.process + local postProcess = system.postProcess + + if preProcess then + preProcess(system, dt) + end + + if process then + if system.nocache then + local entities = system.world.entities + local filter = system.filter + if filter then + for i = 1, #entities do + local entity = entities[i] + if filter(system, entity) then + process(system, entity, dt) + end + end + end + else + local entities = system.entities + for i = 1, #entities do + process(system, entities[i], dt) + end + end + end + + if postProcess then + postProcess(system, dt) + end +end + +-- Sorts Systems by a function system.sortDelegate(entity1, entity2) on modify. +local function sortedSystemOnModify(system) + local entities = system.entities + local indices = system.indices + local sortDelegate = system.sortDelegate + if not sortDelegate then + local compare = system.compare + sortDelegate = function(e1, e2) + return compare(system, e1, e2) + end + system.sortDelegate = sortDelegate + end + tsort(entities, sortDelegate) + for i = 1, #entities do + indices[entities[i]] = i + end +end + +--- Creates a new System or System class from the supplied table. If `table` is +-- nil, creates a new table. +function tiny.system(table) + table = table or {} + table[systemTableKey] = true + return table +end + +--- Creates a new Processing System or Processing System class. Processing +-- Systems process each entity individual, and are usually what is needed. +-- Processing Systems have three extra callbacks besides those inheritted from +-- vanilla Systems. +-- +-- function system:preProcess(dt) -- Called before iteration. +-- function system:process(entity, dt) -- Process each entity. +-- function system:postProcess(dt) -- Called after iteration. +-- +-- Processing Systems have their own `update` method, so don't implement a +-- a custom `update` callback for Processing Systems. +-- @see system +function tiny.processingSystem(table) + table = table or {} + table[systemTableKey] = true + table.update = processingSystemUpdate + return table +end + +--- Creates a new Sorted System or Sorted System class. Sorted Systems sort +-- their Entities according to a user-defined method, `system:compare(e1, e2)`, +-- which should return true if `e1` should come before `e2` and false otherwise. +-- Sorted Systems also override the default System's `onModify` callback, so be +-- careful if defining a custom callback. However, for processing the sorted +-- entities, consider `tiny.sortedProcessingSystem(table)`. +-- @see system +function tiny.sortedSystem(table) + table = table or {} + table[systemTableKey] = true + table.onModify = sortedSystemOnModify + return table +end + +--- Creates a new Sorted Processing System or Sorted Processing System class. +-- Sorted Processing Systems have both the aspects of Processing Systems and +-- Sorted Systems. +-- @see system +-- @see processingSystem +-- @see sortedSystem +function tiny.sortedProcessingSystem(table) + table = table or {} + table[systemTableKey] = true + table.update = processingSystemUpdate + table.onModify = sortedSystemOnModify + return table +end + +--- World functions. +-- A World is a container that manages Entities and Systems. Typically, a +-- program uses one World at a time. +-- +-- For all World functions except `tiny.world(...)`, object-oriented syntax can +-- be used instead of the documented syntax. For example, +-- `tiny.add(world, e1, e2, e3)` is the same as `world:add(e1, e2, e3)`. +-- @section World + +-- Forward declaration +local worldMetaTable + +--- Creates a new World. +-- Can optionally add default Systems and Entities. Returns the new World along +-- with default Entities and Systems. +function tiny.world(...) + local ret = setmetatable({ + + -- List of Entities to remove + entitiesToRemove = {}, + + -- List of Entities to change + entitiesToChange = {}, + + -- List of Entities to add + systemsToAdd = {}, + + -- List of Entities to remove + systemsToRemove = {}, + + -- Set of Entities + entities = {}, + + -- List of Systems + systems = {} + + }, worldMetaTable) + + tiny_add(ret, ...) + tiny_manageSystems(ret) + tiny_manageEntities(ret) + + return ret, ... +end + +--- Adds an Entity to the world. +-- Also call this on Entities that have changed Components such that they +-- match different Filters. Returns the Entity. +function tiny.addEntity(world, entity) + local e2c = world.entitiesToChange + e2c[#e2c + 1] = entity + return entity +end + +tiny_addEntity = tiny.addEntity + +--- Adds a System to the world. Returns the System. +function tiny.addSystem(world, system) + assert(system.world == nil, "System already belongs to a World.") + local s2a = world.systemsToAdd + s2a[#s2a + 1] = system + system.world = world + return system +end + +tiny_addSystem = tiny.addSystem + +--- Shortcut for adding multiple Entities and Systems to the World. Returns all +-- added Entities and Systems. +function tiny.add(world, ...) + for i = 1, select("#", ...) do + local obj = select(i, ...) + if obj then + if isSystem(obj) then + tiny_addSystem(world, obj) + else -- Assume obj is an Entity + tiny_addEntity(world, obj) + end + end + end + return ... +end + +tiny_add = tiny.add + +--- Removes an Entity from the World. Returns the Entity. +function tiny.removeEntity(world, entity) + local e2r = world.entitiesToRemove + e2r[#e2r + 1] = entity + return entity +end + +tiny_removeEntity = tiny.removeEntity + +--- Removes a System from the world. Returns the System. +function tiny.removeSystem(world, system) + assert(system.world == world, "System does not belong to this World.") + local s2r = world.systemsToRemove + s2r[#s2r + 1] = system + return system +end + +tiny_removeSystem = tiny.removeSystem + +--- Shortcut for removing multiple Entities and Systems from the World. Returns +-- all removed Systems and Entities +function tiny.remove(world, ...) + for i = 1, select("#", ...) do + local obj = select(i, ...) + if obj then + if isSystem(obj) then + tiny_removeSystem(world, obj) + else -- Assume obj is an Entity + tiny_removeEntity(world, obj) + end + end + end + return ... +end + +-- Adds and removes Systems that have been marked from the World. +function tiny_manageSystems(world) + local s2a, s2r = world.systemsToAdd, world.systemsToRemove + + -- Early exit + if #s2a == 0 and #s2r == 0 then + return + end + + world.systemsToAdd = {} + world.systemsToRemove = {} + + local worldEntityList = world.entities + local systems = world.systems + + -- Remove Systems + for i = 1, #s2r do + local system = s2r[i] + local index = system.index + local onRemove = system.onRemove + if onRemove and not system.nocache then + local entityList = system.entities + for j = 1, #entityList do + onRemove(system, entityList[j]) + end + end + tremove(systems, index) + for j = index, #systems do + systems[j].index = j + end + local onRemoveFromWorld = system.onRemoveFromWorld + if onRemoveFromWorld then + onRemoveFromWorld(system, world) + end + s2r[i] = nil + + -- Clean up System + system.world = nil + system.entities = nil + system.indices = nil + system.index = nil + end + + -- Add Systems + for i = 1, #s2a do + local system = s2a[i] + if systems[system.index or 0] ~= system then + if not system.nocache then + system.entities = {} + system.indices = {} + end + if system.active == nil then + system.active = true + end + system.modified = true + system.world = world + local index = #systems + 1 + system.index = index + systems[index] = system + local onAddToWorld = system.onAddToWorld + if onAddToWorld then + onAddToWorld(system, world) + end + + -- Try to add Entities + if not system.nocache then + local entityList = system.entities + local entityIndices = system.indices + local onAdd = system.onAdd + local filter = system.filter + if filter then + for j = 1, #worldEntityList do + local entity = worldEntityList[j] + if filter(system, entity) then + local entityIndex = #entityList + 1 + entityList[entityIndex] = entity + entityIndices[entity] = entityIndex + if onAdd then + onAdd(system, entity) + end + end + end + end + end + end + s2a[i] = nil + end +end + +-- Adds, removes, and changes Entities that have been marked. +function tiny_manageEntities(world) + local e2r = world.entitiesToRemove + local e2c = world.entitiesToChange + + -- Early exit + if #e2r == 0 and #e2c == 0 then + return + end + + world.entitiesToChange = {} + world.entitiesToRemove = {} + + local entities = world.entities + local systems = world.systems + + -- Change Entities + for i = 1, #e2c do + local entity = e2c[i] + -- Add if needed + if not entities[entity] then + local index = #entities + 1 + entities[entity] = index + entities[index] = entity + end + for j = 1, #systems do + local system = systems[j] + if not system.nocache then + local ses = system.entities + local seis = system.indices + local index = seis[entity] + local filter = system.filter + if filter and filter(system, entity) then + if not index then + system.modified = true + index = #ses + 1 + ses[index] = entity + seis[entity] = index + local onAdd = system.onAdd + if onAdd then + onAdd(system, entity) + end + end + elseif index then + system.modified = true + local tmpEntity = ses[#ses] + ses[index] = tmpEntity + seis[tmpEntity] = index + seis[entity] = nil + ses[#ses] = nil + local onRemove = system.onRemove + if onRemove then + onRemove(system, entity) + end + end + end + end + e2c[i] = nil + end + + -- Remove Entities + for i = 1, #e2r do + local entity = e2r[i] + e2r[i] = nil + local listIndex = entities[entity] + if listIndex then + -- Remove Entity from world state + local lastEntity = entities[#entities] + entities[lastEntity] = listIndex + entities[entity] = nil + entities[listIndex] = lastEntity + entities[#entities] = nil + -- Remove from cached systems + for j = 1, #systems do + local system = systems[j] + if not system.nocache then + local ses = system.entities + local seis = system.indices + local index = seis[entity] + if index then + system.modified = true + local tmpEntity = ses[#ses] + ses[index] = tmpEntity + seis[tmpEntity] = index + seis[entity] = nil + ses[#ses] = nil + local onRemove = system.onRemove + if onRemove then + onRemove(system, entity) + end + end + end + end + end + end +end + +--- Manages Entities and Systems marked for deletion or addition. Call this +-- before modifying Systems and Entities outside of a call to `tiny.update`. +-- Do not call this within a call to `tiny.update`. +function tiny.refresh(world) + tiny_manageSystems(world) + tiny_manageEntities(world) + local systems = world.systems + for i = #systems, 1, -1 do + local system = systems[i] + if system.active then + local onModify = system.onModify + if onModify and system.modified then + onModify(system, 0) + end + system.modified = false + end + end +end + +--- Updates the World by dt (delta time). Takes an optional parameter, `filter`, +-- which is a Filter that selects Systems from the World, and updates only those +-- Systems. If `filter` is not supplied, all Systems are updated. Put this +-- function in your main loop. +function tiny.update(world, dt, filter) + tiny_manageSystems(world) + tiny_manageEntities(world) + + local systems = world.systems + + -- Iterate through Systems IN REVERSE ORDER + for i = #systems, 1, -1 do + local system = systems[i] + if system.active then + -- Call the modify callback on Systems that have been modified. + local onModify = system.onModify + if onModify and system.modified then + onModify(system, dt) + end + local preWrap = system.preWrap + if preWrap and + ((not filter) or filter(world, system)) then + preWrap(system, dt) + end + end + end + + -- Iterate through Systems IN ORDER + for i = 1, #systems do + local system = systems[i] + if system.active and ((not filter) or filter(world, system)) then + -- Update Systems that have an update method (most Systems) + local update = system.update + if update then + local interval = system.interval + if interval then + local bufferedTime = (system.bufferedTime or 0) + dt + while bufferedTime >= interval do + bufferedTime = bufferedTime - interval + update(system, interval) + end + system.bufferedTime = bufferedTime + else + update(system, dt) + end + end + + system.modified = false + end + end + + -- Iterate through Systems IN ORDER AGAIN + for i = 1, #systems do + local system = systems[i] + local postWrap = system.postWrap + if postWrap and system.active and + ((not filter) or filter(world, system)) then + postWrap(system, dt) + end + end +end + +--- Removes all Entities from the World. +function tiny.clearEntities(world) + local el = world.entities + for i = 1, #el do + tiny_removeEntity(world, el[i]) + end +end + +--- Removes all Systems from the World. +function tiny.clearSystems(world) + local systems = world.systems + for i = #systems, 1, -1 do + tiny_removeSystem(world, systems[i]) + end +end + +--- Gets number of Entities in the World. +function tiny.getEntityCount(world) + return #world.entities +end + +--- Gets number of Systems in World. +function tiny.getSystemCount(world) + return #world.systems +end + +--- Sets the index of a System in the World, and returns the old index. Changes +-- the order in which they Systems processed, because lower indexed Systems are +-- processed first. Returns the old system.index. +function tiny.setSystemIndex(world, system, index) + tiny_manageSystems(world) + local oldIndex = system.index + local systems = world.systems + + if index < 0 then + index = tiny.getSystemCount(world) + 1 + index + end + + tremove(systems, oldIndex) + tinsert(systems, index, system) + + for i = oldIndex, index, index >= oldIndex and 1 or -1 do + systems[i].index = i + end + + return oldIndex +end + +-- Construct world metatable. +worldMetaTable = { + __index = { + add = tiny.add, + addEntity = tiny.addEntity, + addSystem = tiny.addSystem, + remove = tiny.remove, + removeEntity = tiny.removeEntity, + removeSystem = tiny.removeSystem, + refresh = tiny.refresh, + update = tiny.update, + clearEntities = tiny.clearEntities, + clearSystems = tiny.clearSystems, + getEntityCount = tiny.getEntityCount, + getSystemCount = tiny.getSystemCount, + setSystemIndex = tiny.setSystemIndex + }, + __tostring = function() + return "" + end +} + +return tiny diff --git a/two_player_cleaning_game/main.fnl b/two_player_cleaning_game/main.fnl index c621866..1bf83c4 100644 --- a/two_player_cleaning_game/main.fnl +++ b/two_player_cleaning_game/main.fnl @@ -1,199 +1,211 @@ ;; deps (local levels (. (require "levels.fnl") :levels)) -(local bump (require "bump")) -(local network (require "network.fnl")) +(local bump (require "libs/bump")) +(local tiny (require "libs/tiny")) +(local assets (require "assets.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 color-pallet { - :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] - "set color to the given color from the color pallet" - (love.graphics.setColor (unpack (. color-pallet color)))) - -;; 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 - :canvas nil})) -(local camera {:x 0 :y 0}) -(local debug true) (lambda debug-print [obj] (print (fennel.view obj))) +(debug-print assets) +; (local network (require "network.fnl")) -(local collider-debug-boxes []) -(lambda add-collider-debug-box [x y w h] - (table.insert collider-debug-boxes {: x : y :width w :height h})) +;; 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 color-pallet { +; :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] +; }) -(var bump-world nil) -(local player { :x 50 :y 50 :w 25 :h 25 :speed 80 :battery 100 :rot 0 }) +; (fn reset-color [] +; "reset color to white (no tinting)" +; (love.graphics.setColor 1 1 1)) -(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 - }) +; (lambda set-color [color] +; "set color to the given color from the color pallet" +; (love.graphics.setColor (unpack (. color-pallet color)))) -(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}))))))))) +;; 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 [] +; }) -(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))) +; (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 +; :canvas nil})) +; (local camera {:x 0 :y 0}) +; (local debug true) -(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)))) +; (local collider-debug-boxes []) +; (lambda add-collider-debug-box [x y w h] +; (table.insert collider-debug-boxes {: x : y :width w :height h})) -(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)) +; (var bump-world nil) +; (local player { :x 50 :y 50 :w 25 :h 25 :speed 80 :battery 100 :rot 0 }) -(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)) +; (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-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 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)) - (load-assets) - (load-level :tutorial) - ;; Initialize network - (network.init) - (network.send-msg "REGISTER") +; (fn love.load [] +; (love.window.setMode screen.screen-w screen.screen-h) +; (tset screen :canvas (love.graphics.newCanvas screen.canvas-w screen.canvas-h)) +; (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)) +; ;; 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 @@ -201,146 +213,149 @@ while 1 do love.event.push('stdin', io.read('*line')) end") :start)) (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 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] - (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))) +(fn love.update [dt] nil) - (let - [(items len) (bump-world:queryRect player.x player.y 25 25 #(= $1.behavior "hover"))] - (each [_ item (pairs items)] - (item:hover-cb 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))) - ;; Network updates - (network.update dt)) + ; (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) -) +; (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))) +; (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-player [] - "draw player sprite and hitbox" - (reset-color) - (love.graphics.draw - player-art.player-sprite - (. player-art.player1.quads (angle-to-direction player.rot)) - player.x player.y) - ;; draw player hitbox and direction line - (when debug - (set-color :black) - (love.graphics.rectangle "line" player.x player.y player.w player.h) - (love.graphics.push) - (let [ox (+ player.x (/ 25 2)) oy (+ player.y (/ 25 2))] - (love.graphics.translate ox oy) - (love.graphics.rotate player.rot) - (love.graphics.line 0 0 35 0)) - (love.graphics.pop) - )) +; (fn draw-player [] +; "draw player sprite and hitbox" +; (reset-color) +; (love.graphics.draw +; player-art.player-sprite +; (. player-art.player1.quads (angle-to-direction player.rot)) +; player.x player.y) +; ;; draw player hitbox and direction line +; (when debug +; (set-color :black) +; (love.graphics.rectangle "line" player.x player.y player.w player.h) +; (love.graphics.push) +; (let [ox (+ player.x (/ 25 2)) oy (+ player.y (/ 25 2))] +; (love.graphics.translate ox oy) +; (love.graphics.rotate player.rot) +; (love.graphics.line 0 0 35 0)) +; (love.graphics.pop) +; )) -(fn draw-objects [] - (each [_ obj (pairs objects.list)] - ; (print (fennel.view obj)) - (obj:draw))) +; (fn draw-objects [] +; (each [_ obj (pairs objects.list)] +; ; (print (fennel.view obj)) +; (obj:draw))) -(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)) +(fn love.draw [] nil) +; (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) + ; (network.close) false)