Switch to spaces.

This commit is contained in:
Sage Vaillancourt 2025-02-04 13:06:41 -05:00
parent 44ba30ee97
commit 9df836e0bf
7 changed files with 732 additions and 732 deletions

View File

@ -4,8 +4,8 @@ all:
pdc src BatterUp.pdx pdc src BatterUp.pdx
check: check:
stylua -c src/ stylua -c --indent-type Spaces src/
cat __stub.ext.lua <(sed 's/^function/-- selene: allow(unused_variable)\nfunction/' ${PLAYDATE_SDK_PATH}/CoreLibs/__types.lua) ${SOURCE_FILES} | grep -v '^import' | sed 's/<const>//g' | selene - cat __stub.ext.lua <(sed 's/^function/-- selene: allow(unused_variable)\nfunction/' ${PLAYDATE_SDK_PATH}/CoreLibs/__types.lua) ${SOURCE_FILES} | grep -v '^import' | sed 's/<const>//g' | selene -
lint: lint:
stylua src/ stylua --indent-type Spaces src/

View File

@ -3,60 +3,60 @@ local AnnouncementTransitionMs <const> = 300
local AnnouncerMarginX <const> = 26 local AnnouncerMarginX <const> = 26
local AnnouncerAnimatorInY <const> = local AnnouncerAnimatorInY <const> =
playdate.graphics.animator.new(AnnouncementTransitionMs, -70, 0, playdate.easingFunctions.outBounce) playdate.graphics.animator.new(AnnouncementTransitionMs, -70, 0, playdate.easingFunctions.outBounce)
local AnnouncerAnimatorOutY <const> = local AnnouncerAnimatorOutY <const> =
playdate.graphics.animator.new(AnnouncementTransitionMs, 0, -70, playdate.easingFunctions.outQuint) playdate.graphics.animator.new(AnnouncementTransitionMs, 0, -70, playdate.easingFunctions.outQuint)
-- selene: allow(unscoped_variables) -- selene: allow(unscoped_variables)
announcer = { announcer = {
textQueue = {}, textQueue = {},
animatorY = AnnouncerAnimatorInY, animatorY = AnnouncerAnimatorInY,
} }
local DurationMs <const> = 3000 local DurationMs <const> = 3000
function announcer.popIn(self) function announcer.popIn(self)
self.animatorY = AnnouncerAnimatorInY self.animatorY = AnnouncerAnimatorInY
self.animatorY:reset() self.animatorY:reset()
playdate.timer.new(DurationMs, function() playdate.timer.new(DurationMs, function()
self.animatorY = AnnouncerAnimatorOutY self.animatorY = AnnouncerAnimatorOutY
self.animatorY:reset() self.animatorY:reset()
-- If this popIn() call was inside a timer, successive messages would be -- If this popIn() call was inside a timer, successive messages would be
-- allowed to transition out. However, the Out animation, shortly followed by -- allowed to transition out. However, the Out animation, shortly followed by
-- a new message popping in, is actually *more* jarring than the interrupt. -- a new message popping in, is actually *more* jarring than the interrupt.
if #self.textQueue ~= 1 then if #self.textQueue ~= 1 then
self:popIn() self:popIn()
table.remove(self.textQueue, 1) table.remove(self.textQueue, 1)
else else
playdate.timer.new(AnnouncementTransitionMs, function() playdate.timer.new(AnnouncementTransitionMs, function()
table.remove(self.textQueue, 1) table.remove(self.textQueue, 1)
end) end)
end end
end) end)
end end
function announcer.say(self, text) function announcer.say(self, text)
self.textQueue[#self.textQueue + 1] = text self.textQueue[#self.textQueue + 1] = text
if #self.textQueue == 1 then if #self.textQueue == 1 then
self:popIn() self:popIn()
end end
end end
function announcer.draw(self, x, y) function announcer.draw(self, x, y)
if #self.textQueue == 0 then if #self.textQueue == 0 then
return return
end end
x = x - 5 -- Infield center is slightly offset from screen center x = x - 5 -- Infield center is slightly offset from screen center
local gfx = playdate.graphics local gfx = playdate.graphics
local originalDrawMode = gfx.getImageDrawMode() local originalDrawMode = gfx.getImageDrawMode()
local width = math.max(150, (AnnouncerMarginX * 2) + AnnouncementFont:getTextWidth(self.textQueue[1])) local width = math.max(150, (AnnouncerMarginX * 2) + AnnouncementFont:getTextWidth(self.textQueue[1]))
local animY = self.animatorY:currentValue() local animY = self.animatorY:currentValue()
gfx.setColor(gfx.kColorBlack) gfx.setColor(gfx.kColorBlack)
gfx.fillRect(x - (width / 2), y + animY, width, 50) gfx.fillRect(x - (width / 2), y + animY, width, 50)
gfx.setImageDrawMode(gfx.kDrawModeInverted) gfx.setImageDrawMode(gfx.kDrawModeInverted)
AnnouncementFont:drawTextAligned(self.textQueue[1], x, y + 10 + animY, kTextAlignment.center) AnnouncementFont:drawTextAligned(self.textQueue[1], x, y + 10 + animY, kTextAlignment.center)
gfx.setImageDrawMode(originalDrawMode) gfx.setImageDrawMode(originalDrawMode)
end end

View File

@ -9,46 +9,46 @@ local systems <const> = {}
-- TODO: Add entity to any existing systems -- TODO: Add entity to any existing systems
function ecs.addEntity(entity) function ecs.addEntity(entity)
allEntities[entity] = true allEntities[entity] = true
for _, system in pairs(systems) do for _, system in pairs(systems) do
if entityMatchesShapes(entity, system.shapes) then if entityMatchesShapes(entity, system.shapes) then
system.entityCache[entity] = true system.entityCache[entity] = true
else else
system.entityCache[entity] = nil system.entityCache[entity] = nil
end end
end end
end end
function ecs.removeEntity(entity) function ecs.removeEntity(entity)
allEntities[entity] = nil allEntities[entity] = nil
for _, system in pairs(systems) do for _, system in pairs(systems) do
system.entityCache[entity] = nil system.entityCache[entity] = nil
end end
end end
local Placeholder = {} local Placeholder = {}
---@generic T ---@generic T
---@return T ---@return T
function ecs.field() function ecs.field()
return Placeholder return Placeholder
end end
function allKeysIncluded(entity, filter) function allKeysIncluded(entity, filter)
for k, _ in pairs(filter) do for k, _ in pairs(filter) do
if not entity[k] then if not entity[k] then
return false return false
end end
end end
return true return true
end end
function entityMatchesShapes(entity, shapes) function entityMatchesShapes(entity, shapes)
for _, shape in pairs(shapes) do for _, shape in pairs(shapes) do
if not allKeysIncluded(entity, shape) then if not allKeysIncluded(entity, shape) then
return false return false
end end
end end
return true return true
end end
---@generic T ---@generic T
@ -61,66 +61,66 @@ end
---@param wShape W? ---@param wShape W?
---@return fun(callback: fun(componentT: T, componentU: U, componentV: V, componentW: W)) ---@return fun(callback: fun(componentT: T, componentU: U, componentV: V, componentW: W))
function ecs.entitiesHavingShapes(tShape, uShape, vShape, wShape) function ecs.entitiesHavingShapes(tShape, uShape, vShape, wShape)
return function() end return function() end
end end
-- Print contents of `tbl`, with indentation. -- Print contents of `tbl`, with indentation.
-- `indent` sets the initial level of indentation. -- `indent` sets the initial level of indentation.
function tprint(tbl, indent) function tprint(tbl, indent)
if not indent then if not indent then
indent = 0 indent = 0
end end
for k, v in pairs(tbl) do for k, v in pairs(tbl) do
formatting = string.rep(" ", indent) .. k .. ": " formatting = string.rep(" ", indent) .. k .. ": "
if type(v) == "table" then if type(v) == "table" then
print(formatting) print(formatting)
tprint(v, indent + 1) tprint(v, indent + 1)
elseif type(v) == "boolean" then elseif type(v) == "boolean" then
print(formatting .. tostring(v)) print(formatting .. tostring(v))
else else
print(formatting .. v) print(formatting .. v)
end end
end end
end end
function addSystem(callback, keys, shapes) function addSystem(callback, keys, shapes)
systems[#systems + 1] = { systems[#systems + 1] = {
callback = callback, callback = callback,
keys = keys, keys = keys,
shapes = shapes, shapes = shapes,
entityCache = nil, entityCache = nil,
} }
end end
---@return boolean ---@return boolean
function is(entity, shape) function is(entity, shape)
return allKeysIncluded(entity, shape) return allKeysIncluded(entity, shape)
end end
---@param deltaSeconds number ---@param deltaSeconds number
function ecs.update(deltaSeconds) function ecs.update(deltaSeconds)
for _, system in pairs(systems) do for _, system in pairs(systems) do
if not system.entityCache then if not system.entityCache then
system.entityCache = {} system.entityCache = {}
for entity, _ in pairs(allEntities) do for entity, _ in pairs(allEntities) do
if entityMatchesShapes(entity, system.shapes) then if entityMatchesShapes(entity, system.shapes) then
system.entityCache[entity] = true system.entityCache[entity] = true
end end
end end
end end
local keys = system.keys local keys = system.keys
for entity, _ in pairs(system.entityCache) do for entity, _ in pairs(system.entityCache) do
system.callback( system.callback(
deltaSeconds, deltaSeconds,
entity, entity,
entity[keys[1]], entity[keys[1]],
entity[keys[2]], entity[keys[2]],
entity[keys[3]], entity[keys[3]],
entity[keys[4]], entity[keys[4]],
entity entity
) )
end end
end end
end end
--- Returns a function that accepts a callback. This callback will receive one argument for each Shape provided. --- Returns a function that accepts a callback. This callback will receive one argument for each Shape provided.
@ -139,26 +139,26 @@ end
---@param wShape { [WKey]: W } | fun(entity: any, componentT: T, componentU: U, componentV: V, any) | nil ---@param wShape { [WKey]: W } | fun(entity: any, componentT: T, componentU: U, componentV: V, any) | nil
---@param finalFunc fun(entity: any, componentT: T, componentU: U, componentV: V, componentW: W, any) | nil ---@param finalFunc fun(entity: any, componentT: T, componentU: U, componentV: V, componentW: W, any) | nil
function ecs.forEntitiesWith(tShape, uShape, vShape, wShape, finalFunc) function ecs.forEntitiesWith(tShape, uShape, vShape, wShape, finalFunc)
local maybeShapes = { tShape, uShape, vShape, wShape, finalFunc } local maybeShapes = { tShape, uShape, vShape, wShape, finalFunc }
local shapes = {} local shapes = {}
local callback local callback
for _, maybeShape in pairs(maybeShapes) do for _, maybeShape in pairs(maybeShapes) do
if type(maybeShape) == "table" then if type(maybeShape) == "table" then
shapes[#shapes + 1] = maybeShape shapes[#shapes + 1] = maybeShape
elseif type(maybeShape) == "function" then elseif type(maybeShape) == "function" then
callback = maybeShape callback = maybeShape
end end
end end
local keys = {} local keys = {}
for _, shape in pairs(shapes) do for _, shape in pairs(shapes) do
for key, _ in pairs(shape) do for key, _ in pairs(shape) do
keys[#keys + 1] = key keys[#keys + 1] = key
end end
end end
addSystem(callback, keys, shapes) addSystem(callback, keys, shapes)
end end
local f = ecs.field() local f = ecs.field()
@ -168,15 +168,15 @@ local Target = { target = XYPair }
local Velocity = { velocity = XYPair } local Velocity = { velocity = XYPair }
function ecs.overlayOnto(entity, value) function ecs.overlayOnto(entity, value)
for key, v in pairs(value) do for key, v in pairs(value) do
entity[key] = v entity[key] = v
end end
ecs.addEntity(entity) ecs.addEntity(entity)
end end
local data = { local data = {
position = { x = 0, y = 0 }, position = { x = 0, y = 0 },
velocity = { x = 1, y = 2 }, velocity = { x = 1, y = 2 },
} }
---@generic T ---@generic T
@ -184,7 +184,7 @@ local data = {
---@param entity unknown ---@param entity unknown
---@return T ---@return T
function ecs.get(shape, entity) function ecs.get(shape, entity)
return entity return entity
end end
---@generic T ---@generic T
@ -192,29 +192,29 @@ end
---@param shape `T` ---@param shape `T`
---@param value `T` ---@param value `T`
function ecs.set(entity, shape, value) function ecs.set(entity, shape, value)
for key, v in pairs(shape) do for key, v in pairs(shape) do
entity[key] = value[v] entity[key] = value[v]
end end
end end
ecs.addEntity(data) ecs.addEntity(data)
ecs.forEntitiesWith(Position, Velocity, function(delta, e, pos, vel) ecs.forEntitiesWith(Position, Velocity, function(delta, e, pos, vel)
pos.x = pos.x + (delta * vel.x) pos.x = pos.x + (delta * vel.x)
pos.y = pos.y + (delta * vel.y) pos.y = pos.y + (delta * vel.y)
print("position") print("position")
tprint(pos, 1) tprint(pos, 1)
ecs.set(Target, e, { ecs.set(Target, e, {
--target = { x = 10, y = 10} --target = { x = 10, y = 10}
}) })
end) end)
ecs.forEntitiesWith(Target, function(delta, e, pos, vel) ecs.forEntitiesWith(Target, function(delta, e, pos, vel)
pos.x = pos.x + (delta * vel.x) pos.x = pos.x + (delta * vel.x)
pos.y = pos.y + (delta * vel.y) pos.y = pos.y + (delta * vel.y)
print("position") print("position")
tprint(pos, 1) tprint(pos, 1)
ecs.set(e, Target, "hallo") ecs.set(e, Target, "hallo")
end) end)
ecs.update(1) ecs.update(1)

View File

@ -3,21 +3,21 @@
--- XOX --- XOX
--- Where each character is the size of the screen, and 'O' is the default view. --- Where each character is the size of the screen, and 'O' is the default view.
function getDrawOffset(screenW, screenH, ballX, ballY) function getDrawOffset(screenW, screenH, ballX, ballY)
local offsetX, offsetY local offsetX, offsetY
if ballY > screenH then if ballY > screenH then
return 0, 0 return 0, 0
end end
offsetY = math.max(0, -1 * ballY) offsetY = math.max(0, -1 * ballY)
if ballX > 0 and ballX < screenW then if ballX > 0 and ballX < screenW then
offsetX = 0 offsetX = 0
elseif ballX < 0 then elseif ballX < 0 then
offsetX = math.max(-1 * screenW, ballX * -1) offsetX = math.max(-1 * screenW, ballX * -1)
elseif ballX > screenW then elseif ballX > screenW then
offsetX = math.min(screenW * 2, (ballX * -1) + screenW) offsetX = math.min(screenW * 2, (ballX * -1) + screenW)
end end
return offsetX * 1.3, offsetY * 1.5 return offsetX * 1.3, offsetY * 1.5
end end
-- selene: allow(unscoped_variables) -- selene: allow(unscoped_variables)
@ -26,15 +26,15 @@ blipper = {}
--- Build an object that simply "blips" between the given images at the given interval. --- Build an object that simply "blips" between the given images at the given interval.
--- Expects `playdate.graphics.animation.blinker.updateAll()` to be called on every update. --- Expects `playdate.graphics.animation.blinker.updateAll()` to be called on every update.
function blipper.new(msInterval, imagePath1, imagePath2) function blipper.new(msInterval, imagePath1, imagePath2)
local blinker = playdate.graphics.animation.blinker.new(msInterval, msInterval, true) local blinker = playdate.graphics.animation.blinker.new(msInterval, msInterval, true)
blinker:start() blinker:start()
return { return {
blinker = blinker, blinker = blinker,
image1 = playdate.graphics.image.new(imagePath1), image1 = playdate.graphics.image.new(imagePath1),
image2 = playdate.graphics.image.new(imagePath2), image2 = playdate.graphics.image.new(imagePath2),
draw = function(self, disableBlipping, x, y) draw = function(self, disableBlipping, x, y)
local currentImage = (disableBlipping or self.blinker.on) and self.image2 or self.image1 local currentImage = (disableBlipping or self.blinker.on) and self.image2 or self.image1
currentImage:draw(x, y) currentImage:draw(x, y)
end, end,
} }
end end

File diff suppressed because it is too large Load Diff

View File

@ -10,53 +10,53 @@ local IndicatorWidth <const> = ScoreFont:getTextWidth(Indicator)
---@param battingTeam any ---@param battingTeam any
---@return string, number, string, number ---@return string, number, string, number
function getIndicators(teams, battingTeam) function getIndicators(teams, battingTeam)
if teams.home == battingTeam then if teams.home == battingTeam then
return Indicator, 0, "", IndicatorWidth return Indicator, 0, "", IndicatorWidth
end end
return "", IndicatorWidth, Indicator, 0 return "", IndicatorWidth, Indicator, 0
end end
function drawScoreboard(x, y, teams, outs, battingTeam, inning) function drawScoreboard(x, y, teams, outs, battingTeam, inning)
local gfx = playdate.graphics local gfx = playdate.graphics
local homeScore = teams.home.score local homeScore = teams.home.score
local awayScore = teams.away.score local awayScore = teams.away.score
local homeIndicator, homeOffset, awayIndicator, awayOffset = getIndicators(teams, battingTeam) local homeIndicator, homeOffset, awayIndicator, awayOffset = getIndicators(teams, battingTeam)
local homeScoreText = homeIndicator .. "HOME " .. (homeScore > 9 and homeScore or " " .. homeScore) local homeScoreText = homeIndicator .. "HOME " .. (homeScore > 9 and homeScore or " " .. homeScore)
local awayScoreText = awayIndicator .. "AWAY " .. (awayScore > 9 and awayScore or " " .. awayScore) local awayScoreText = awayIndicator .. "AWAY " .. (awayScore > 9 and awayScore or " " .. awayScore)
local rectWidth = (ScoreboardMarginX * 2) local rectWidth = (ScoreboardMarginX * 2)
+ ScoreboardMarginRight + ScoreboardMarginRight
+ ScoreFont:getTextWidth(homeScoreText) + ScoreFont:getTextWidth(homeScoreText)
+ homeOffset + homeOffset
gfx.setLineWidth(1) gfx.setLineWidth(1)
gfx.setColor(gfx.kColorBlack) gfx.setColor(gfx.kColorBlack)
gfx.fillRect(x, y, rectWidth, ScoreboardHeight) gfx.fillRect(x, y, rectWidth, ScoreboardHeight)
local originalDrawMode = gfx.getImageDrawMode() local originalDrawMode = gfx.getImageDrawMode()
gfx.setImageDrawMode(gfx.kDrawModeInverted) gfx.setImageDrawMode(gfx.kDrawModeInverted)
ScoreFont:drawText(homeScoreText, x + ScoreboardMarginX + homeOffset, y + 6) ScoreFont:drawText(homeScoreText, x + ScoreboardMarginX + homeOffset, y + 6)
ScoreFont:drawText(awayScoreText, x + ScoreboardMarginX + awayOffset, y + 22) ScoreFont:drawText(awayScoreText, x + ScoreboardMarginX + awayOffset, y + 22)
local inningOffsetX = (x + ScoreboardMarginX + IndicatorWidth) + (4 * 2.5 * OutBubbleRadius) local inningOffsetX = (x + ScoreboardMarginX + IndicatorWidth) + (4 * 2.5 * OutBubbleRadius)
ScoreFont:drawText(inning, inningOffsetX, y + 39) ScoreFont:drawText(inning, inningOffsetX, y + 39)
gfx.setImageDrawMode(originalDrawMode) gfx.setImageDrawMode(originalDrawMode)
gfx.setColor(gfx.kColorWhite) gfx.setColor(gfx.kColorWhite)
function circleParams(i) function circleParams(i)
local circleOffset = i * 2.5 * OutBubbleRadius local circleOffset = i * 2.5 * OutBubbleRadius
return (x + ScoreboardMarginX + OutBubbleRadius + IndicatorWidth) + circleOffset, y + 46, OutBubbleRadius return (x + ScoreboardMarginX + OutBubbleRadius + IndicatorWidth) + circleOffset, y + 46, OutBubbleRadius
end end
for i = outs, 2 do for i = outs, 2 do
gfx.drawCircleAtPoint(circleParams(i)) gfx.drawCircleAtPoint(circleParams(i))
end end
for i = 0, (outs - 1) do for i = 0, (outs - 1) do
gfx.fillCircleAtPoint(circleParams(i)) gfx.fillCircleAtPoint(circleParams(i))
end end
end end

View File

@ -4,40 +4,40 @@ import 'CoreLibs/graphics.lua'
-- stylua: ignore end -- stylua: ignore end
function easingHill(t, b, c, d) function easingHill(t, b, c, d)
c = c + 0.0 -- convert to float to prevent integer overflow c = c + 0.0 -- convert to float to prevent integer overflow
t = t / d t = t / d
t = ((t * 2) - 1) t = ((t * 2) - 1)
t = t * t t = t * t
return (c * t) + b return (c * t) + b
end end
-- Useful for quick print-the-value-in-place debugging. -- Useful for quick print-the-value-in-place debugging.
-- selene: allow(unused_variable) -- selene: allow(unused_variable)
function label(value, name) function label(value, name)
if type(value) == "table" then if type(value) == "table" then
print(name .. ":") print(name .. ":")
printTable(value) printTable(value)
else else
print(name .. ": " .. value) print(name .. ": " .. value)
end end
return value return value
end end
---@param x number ---@param x number
---@param y number ---@param y number
---@return XYPair ---@return XYPair
function xy(x, y) function xy(x, y)
return { return {
x = x, x = x,
y = y, y = y,
} }
end end
--- Returns the normalized vector as two values, plus the distance between the given points. --- Returns the normalized vector as two values, plus the distance between the given points.
---@return number, number, number ---@return number, number, number
function normalizeVector(x1, y1, x2, y2) function normalizeVector(x1, y1, x2, y2)
local distance, a, b = distanceBetween(x1, y1, x2, y2) local distance, a, b = distanceBetween(x1, y1, x2, y2)
return a / distance, b / distance, distance return a / distance, b / distance, distance
end end
---@generic T ---@generic T
@ -45,41 +45,41 @@ end
---@param condition fun(T): boolean ---@param condition fun(T): boolean
---@return T[] ---@return T[]
function filter(array, condition) function filter(array, condition)
local newArray = {} local newArray = {}
for _, element in pairs(array) do for _, element in pairs(array) do
if condition(element) then if condition(element) then
newArray[#newArray + 1] = element newArray[#newArray + 1] = element
end end
end end
return newArray return newArray
end end
---@return number, number, number ---@return number, number, number
function distanceBetween(x1, y1, x2, y2) function distanceBetween(x1, y1, x2, y2)
local a = x1 - x2 local a = x1 - x2
local b = y1 - y2 local b = y1 - y2
return math.sqrt((a * a) + (b * b)), a, b return math.sqrt((a * a) + (b * b)), a, b
end end
--- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound --- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound
--- @return boolean --- @return boolean
function pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound) function pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound)
-- This check currently assumes right-handedness. -- This check currently assumes right-handedness.
-- I.e. it assumes the ball is to the right of batBaseX -- I.e. it assumes the ball is to the right of batBaseX
if pointX < lineX1 or pointX > lineX2 or pointY > bottomBound then if pointX < lineX1 or pointX > lineX2 or pointY > bottomBound then
return false return false
end end
local m = (lineY2 - lineY1) / (lineX2 - lineX1) local m = (lineY2 - lineY1) / (lineX2 - lineX1)
-- y = mx + b -- y = mx + b
-- b = y1 - (m * x1) -- b = y1 - (m * x1)
local b = lineY1 - (m * lineX1) local b = lineY1 - (m * lineX1)
local yOnLine = (m * pointX) + b local yOnLine = (m * pointX) + b
local yP = pointY local yP = pointY
local yDelta = yOnLine - yP local yDelta = yOnLine - yP
return yDelta <= 0 return yDelta <= 0
end end
--- Returns the nearest position object from the given point, as well as its distance from that point --- Returns the nearest position object from the given point, as well as its distance from that point
@ -89,23 +89,23 @@ end
---@param y number ---@param y number
---@return T,number|nil ---@return T,number|nil
function getNearestOf(array, x, y, extraCondition) function getNearestOf(array, x, y, extraCondition)
local nearest, nearestDistance = nil, nil local nearest, nearestDistance = nil, nil
for _, element in pairs(array) do for _, element in pairs(array) do
if not extraCondition or extraCondition(element) then if not extraCondition or extraCondition(element) then
if nearest == nil then if nearest == nil then
nearest = element nearest = element
nearestDistance = distanceBetween(element.x, element.y, x, y) nearestDistance = distanceBetween(element.x, element.y, x, y)
else else
local distance = distanceBetween(element.x, element.y, x, y) local distance = distanceBetween(element.x, element.y, x, y)
if distance < nearestDistance then if distance < nearestDistance then
nearest = element nearest = element
nearestDistance = distance nearestDistance = distance
end end
end end
end end
end end
return nearest, nearestDistance return nearest, nearestDistance
end end
--- Marker used by buildCache to indicate a cached `nil` value. --- Marker used by buildCache to indicate a cached `nil` value.
@ -122,18 +122,18 @@ local NoValue <const> = {}
---@generic Value ---@generic Value
---@return { get: fun(key: Key): Value } ---@return { get: fun(key: Key): Value }
function buildCache(fetcher) function buildCache(fetcher)
local cacheData = {} local cacheData = {}
return { return {
get = function(key) get = function(key)
if cacheData[key] == NoValue then if cacheData[key] == NoValue then
return nil return nil
end end
if cacheData[key] ~= nil then if cacheData[key] ~= nil then
return cacheData[key] return cacheData[key]
end end
local fetched = fetcher(key) local fetched = fetcher(key)
cacheData[key] = fetched ~= nil and fetched or NoValue cacheData[key] = fetched ~= nil and fetched or NoValue
return cacheData[key] return cacheData[key]
end, end,
} }
end end