Refactoring and better type hinting.
This commit is contained in:
parent
841b7e3fea
commit
45a20cbb70
227
src/main.lua
227
src/main.lua
|
@ -26,12 +26,14 @@ import 'CoreLibs/ui.lua'
|
||||||
--- }
|
--- }
|
||||||
|
|
||||||
--- @alias Fielder {
|
--- @alias Fielder {
|
||||||
--- x: number | nil,
|
--- x: number,
|
||||||
--- y: number | nil,
|
--- y: number,
|
||||||
--- target: XYPair | nil,
|
--- target: XYPair | nil,
|
||||||
--- speed: number,
|
--- speed: number,
|
||||||
--- }
|
--- }
|
||||||
|
|
||||||
|
--- @alias EasingFunc fun(number, number, number, number): number
|
||||||
|
|
||||||
import 'announcer.lua'
|
import 'announcer.lua'
|
||||||
import 'graphics.lua'
|
import 'graphics.lua'
|
||||||
import 'scoreboard.lua'
|
import 'scoreboard.lua'
|
||||||
|
@ -77,6 +79,10 @@ local PitchStartY <const>, PitchEndY <const> = 105, 240
|
||||||
local ballAnimatorY = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
|
local ballAnimatorY = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
|
||||||
local ballAnimatorX = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
|
local ballAnimatorX = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
|
||||||
|
|
||||||
|
---@alias PseudoAnimator { currentValue: fun(self): number; reset: fun(self) }
|
||||||
|
---@alias Pitch { x: PseudoAnimator, y: PseudoAnimator, z: PseudoAnimator | nil }
|
||||||
|
|
||||||
|
---@type Pitch[]
|
||||||
local Pitches <const> = {
|
local Pitches <const> = {
|
||||||
-- Fastball
|
-- Fastball
|
||||||
{
|
{
|
||||||
|
@ -154,13 +160,17 @@ local Bases = {
|
||||||
-- Pseudo-base for batter to target
|
-- Pseudo-base for batter to target
|
||||||
local RightHandedBattersBox <const> = xy(Bases[Home].x - 35, Bases[Home].y)
|
local RightHandedBattersBox <const> = xy(Bases[Home].x - 35, Bases[Home].y)
|
||||||
|
|
||||||
---@type table<Base, Base>
|
---@type table<Base, Base | nil>
|
||||||
local NextBaseMap <const> = {
|
local NextBaseMap <const> = {
|
||||||
|
[RightHandedBattersBox] = nil, -- Runner should not escape the box before a hit!
|
||||||
[Bases[First]] = Bases[Second],
|
[Bases[First]] = Bases[Second],
|
||||||
[Bases[Second]] = Bases[Third],
|
[Bases[Second]] = Bases[Third],
|
||||||
[Bases[Third]] = Bases[Home],
|
[Bases[Third]] = Bases[Home],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param speed number
|
||||||
|
---@return Fielder
|
||||||
function newFielder(name, speed)
|
function newFielder(name, speed)
|
||||||
return {
|
return {
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -233,6 +243,12 @@ end
|
||||||
local batter = newRunner()
|
local batter = newRunner()
|
||||||
|
|
||||||
--- "Throws" the ball from its current position to the given destination.
|
--- "Throws" the ball from its current position to the given destination.
|
||||||
|
---@param destX number
|
||||||
|
---@param destY number
|
||||||
|
---@param easingFunc EasingFunc
|
||||||
|
---@param flyTimeMs number | nil
|
||||||
|
---@param floaty boolean | nil
|
||||||
|
---@param customBallScaler pd_animator | nil
|
||||||
function throwBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
function throwBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
|
||||||
if not flyTimeMs then
|
if not flyTimeMs then
|
||||||
flyTimeMs = distanceBetween(ball.x, ball.y, destX, destY) * 5
|
flyTimeMs = distanceBetween(ball.x, ball.y, destX, destY) * 5
|
||||||
|
@ -282,7 +298,11 @@ local crankChange = 0
|
||||||
local acceleratedChange
|
local acceleratedChange
|
||||||
|
|
||||||
local BaseHitbox = 10
|
local BaseHitbox = 10
|
||||||
--- Returns the base being touched by the runner at (x,y), or nil, if no base is being touched
|
|
||||||
|
--- Returns the base being touched by the player at (x,y), or nil, if no base is being touched
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@return Base | nil
|
||||||
function isTouchingBase(x, y)
|
function isTouchingBase(x, y)
|
||||||
for _, base in ipairs(Bases) do
|
for _, base in ipairs(Bases) do
|
||||||
if distanceBetween(x, y, base.x, base.y) < BaseHitbox then
|
if distanceBetween(x, y, base.x, base.y) < BaseHitbox then
|
||||||
|
@ -294,11 +314,19 @@ function isTouchingBase(x, y)
|
||||||
end
|
end
|
||||||
|
|
||||||
local BallCatchHitbox = 3
|
local BallCatchHitbox = 3
|
||||||
|
|
||||||
|
--- Returns true only if the given point is touching the ball at its current position
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@return boolean
|
||||||
function isTouchingBall(x, y)
|
function isTouchingBall(x, y)
|
||||||
local ballDistance = distanceBetween(x, y, ball.x, ball.y)
|
local ballDistance = distanceBetween(x, y, ball.x, ball.y)
|
||||||
return ballDistance < BallCatchHitbox
|
return ballDistance < BallCatchHitbox
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@alias Team { score: number }
|
||||||
|
|
||||||
|
---@type table<string, Team>
|
||||||
local teams <const> = {
|
local teams <const> = {
|
||||||
home = {
|
home = {
|
||||||
score = 0,
|
score = 0,
|
||||||
|
@ -372,7 +400,7 @@ function outRunner(runnerIndex)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: Away score
|
---@param runnerIndex number
|
||||||
function score(runnerIndex)
|
function score(runnerIndex)
|
||||||
outRunners[#outRunners + 1] = runners[runnerIndex]
|
outRunners[#outRunners + 1] = runners[runnerIndex]
|
||||||
table.remove(runners, runnerIndex)
|
table.remove(runners, runnerIndex)
|
||||||
|
@ -427,52 +455,66 @@ function getNextOutTarget()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function tryToMakeAnOut(thrower)
|
---@param fielder Fielder
|
||||||
|
function tryToMakeAnOut(fielder)
|
||||||
local targetX, targetY = getNextOutTarget()
|
local targetX, targetY = getNextOutTarget()
|
||||||
if targetX ~= nil and targetY ~= nil then
|
if targetX ~= nil and targetY ~= nil then
|
||||||
local nearestFielder = getNearestOf(fielders, targetX, targetY)
|
local nearestFielder = getNearestOf(fielders, targetX, targetY)
|
||||||
nearestFielder.target = xy(targetX, targetY)
|
nearestFielder.target = xy(targetX, targetY)
|
||||||
if nearestFielder == thrower then
|
if nearestFielder == fielder then
|
||||||
ball.heldBy = thrower
|
ball.heldBy = fielder
|
||||||
else
|
else
|
||||||
throwBall(targetX, targetY, playdate.easingFunctions.linear, nil, true)
|
throwBall(targetX, targetY, playdate.easingFunctions.linear, nil, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function updateFielders()
|
--- Push the given obect at the given speed toward a target. Speed should be pre-multiplied by the frame's delta time.
|
||||||
local touchingBaseCache = buildCache(function(runner)
|
--- Stops when within 1. Returns true only if the object did actually move.
|
||||||
return isTouchingBase(runner.x, runner.y)
|
---@param mover { x: number, y: number }
|
||||||
end)
|
---@param speed number
|
||||||
|
---@param target { x: number, y: number }
|
||||||
|
---@return boolean
|
||||||
|
function moveAtSpeed(mover, speed, target)
|
||||||
|
local x, y, distance = normalizeVector(mover.x, mover.y, target.x, target.y)
|
||||||
|
|
||||||
for _, fielder in pairs(fielders) do
|
if distance > 1 then
|
||||||
|
mover.x = mover.x - (x * speed)
|
||||||
|
mover.y = mover.y - (y * speed)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param fielder Fielder
|
||||||
|
---@param runnerBaseCache Cache<Runner, Base>
|
||||||
|
function updateFielder(fielder, runnerBaseCache)
|
||||||
-- TODO: Target unforced runners (or their target bases) for tagging
|
-- TODO: Target unforced runners (or their target bases) for tagging
|
||||||
-- With new Position-based scheme, fielders are now able to set `fielder.target = runner` to track directly
|
-- With new Position-based scheme, fielders are now able to set `fielder.target = runner` to track directly
|
||||||
|
|
||||||
if fielder.target ~= nil then
|
if fielder.target ~= nil then
|
||||||
local x, y, distance = normalizeVector(fielder.x, fielder.y, fielder.target.x, fielder.target.y)
|
if not moveAtSpeed(fielder, fielder.speed * deltaSeconds, fielder.target) then
|
||||||
|
|
||||||
if distance > 1 then
|
|
||||||
fielder.x = fielder.x - (x * fielder.speed * deltaSeconds)
|
|
||||||
fielder.y = fielder.y - (y * fielder.speed * deltaSeconds)
|
|
||||||
else
|
|
||||||
fielder.target = nil
|
fielder.target = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if currentMode == Modes.running and isTouchingBall(fielder.x, fielder.y) then
|
if currentMode ~= Modes.running or not isTouchingBall(fielder.x, fielder.y) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- TODO: Check for double-plays or other available outs.
|
-- TODO: Check for double-plays or other available outs.
|
||||||
local touchedBase = isTouchingBase(fielder.x, fielder.y)
|
local touchedBase = isTouchingBase(fielder.x, fielder.y)
|
||||||
for i, runner in pairs(runners) do
|
for i, runner in pairs(runners) do
|
||||||
if
|
if
|
||||||
( -- Force out
|
( -- Force out
|
||||||
touchedBase
|
touchedBase
|
||||||
and runner.prevBase -- Make sure the runner is not standing at home
|
-- and runner.prevBase -- Make sure the runner is not standing at home
|
||||||
and runner.forcedTo == touchedBase
|
and runner.forcedTo == touchedBase
|
||||||
and touchedBase ~= touchingBaseCache.get(runner)
|
and touchedBase ~= runnerBaseCache.get(runner)
|
||||||
)
|
)
|
||||||
or ( -- Tag out
|
or ( -- Tag out
|
||||||
not touchingBaseCache.get(runner)
|
not runnerBaseCache.get(runner)
|
||||||
and distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance
|
and distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance
|
||||||
)
|
)
|
||||||
then
|
then
|
||||||
|
@ -484,36 +526,60 @@ function updateFielders()
|
||||||
tryToMakeAnOut(fielder)
|
tryToMakeAnOut(fielder)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns true if at least one runner is still moving
|
function updateFielders()
|
||||||
|
local runnerBaseCache = buildCache(function(runner)
|
||||||
|
return isTouchingBase(runner.x, runner.y)
|
||||||
|
end)
|
||||||
|
|
||||||
|
for _, fielder in pairs(fielders) do
|
||||||
|
updateFielder(fielder, runnerBaseCache)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if currentMode == Modes.batting then
|
||||||
|
-- moveAtSpeed(
|
||||||
|
-- fielders.catcher,
|
||||||
|
-- fielders.catcher.speed * 2 * deltaSeconds,
|
||||||
|
-- { x = math.min(Center.x + 15, ball.x), y = fielders.catcher.y }
|
||||||
|
-- )
|
||||||
|
-- end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns true only if the given runner moved during this update.
|
||||||
|
---@param runner Runner | nil
|
||||||
|
---@param runnerIndex integer | nil May only be nil if runner == batter
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function updateRunners(currentRunners)
|
function updateRunner(runner, runnerIndex)
|
||||||
local autoRunSpeed = 20 * deltaSeconds
|
local autoRunSpeed = 20 * deltaSeconds
|
||||||
--autoRunSpeed = 140
|
--autoRunSpeed = 140
|
||||||
-- TODO: Filter for the runner closest to the currently-held direction button
|
|
||||||
|
|
||||||
local runnerMoved = false
|
if not runner or not runner.nextBase then
|
||||||
for runnerIndex, runner in ipairs(currentRunners) do
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local appliedSpeed = crankChange -- TODO: Allow for individual runner control via buttons
|
local appliedSpeed = crankChange -- TODO: Allow for individual runner control via buttons
|
||||||
local nearestBase, nearestBaseDistance = getNearestOf(Bases, runner.x, runner.y)
|
local nearestBase, nearestBaseDistance = getNearestOf(Bases, runner.x, runner.y)
|
||||||
|
|
||||||
if
|
if
|
||||||
nearestBaseDistance < 5
|
nearestBaseDistance < 5
|
||||||
and runner.prevBase
|
and runnerIndex ~= nil
|
||||||
|
and runner ~= batter --runner.prevBase
|
||||||
and runner.nextBase == Bases[Home]
|
and runner.nextBase == Bases[Home]
|
||||||
and nearestBase == Bases[Home]
|
and nearestBase == Bases[Home]
|
||||||
then
|
then
|
||||||
score(runnerIndex)
|
score(runnerIndex)
|
||||||
end
|
end
|
||||||
|
|
||||||
if runner.nextBase then
|
|
||||||
local nb = runner.nextBase
|
local nb = runner.nextBase
|
||||||
local x, y, distance = normalizeVector(runner.x, runner.y, nb.x, nb.y)
|
local x, y, distance = normalizeVector(runner.x, runner.y, nb.x, nb.y)
|
||||||
|
|
||||||
if distance > 1 then
|
if distance < 1 then
|
||||||
|
runner.nextBase = NextBaseMap[runner.nextBase]
|
||||||
|
runner.forcedTo = nil
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local prevX, prevY = runner.x, runner.y
|
local prevX, prevY = runner.x, runner.y
|
||||||
local mult = 1
|
local mult = 1
|
||||||
if appliedSpeed < 0 then
|
if appliedSpeed < 0 then
|
||||||
|
@ -533,37 +599,14 @@ function updateRunners(currentRunners)
|
||||||
runner.x = runner.x - (x * mult)
|
runner.x = runner.x - (x * mult)
|
||||||
runner.y = runner.y - (y * mult)
|
runner.y = runner.y - (y * mult)
|
||||||
|
|
||||||
runnerMoved = runnerMoved or prevX ~= runner.x or prevY ~= runner.y
|
return prevX ~= runner.x or prevY ~= runner.y
|
||||||
else
|
|
||||||
runner.nextBase = NextBaseMap[runner.nextBase]
|
|
||||||
runner.forcedTo = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return runnerMoved
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local ResetFieldersAfterSeconds = 2
|
local ResetFieldersAfterSeconds <const> = 5
|
||||||
-- TODO: Replace with a timer, repeatedly reset instead of setting to 0
|
-- TODO: Replace with a timer, repeatedly reset, instead of setting to 0
|
||||||
local secondsSinceLastRunnerMove = 0
|
local secondsSinceLastRunnerMove = 0
|
||||||
|
|
||||||
function init()
|
---@type number
|
||||||
playdate.display.setRefreshRate(50)
|
|
||||||
gfx.setBackgroundColor(gfx.kColorWhite)
|
|
||||||
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
|
|
||||||
resetFielderPositions(true)
|
|
||||||
playdate.getSystemMenu():addMenuItem("Restart game", function() end) -- TODO?
|
|
||||||
|
|
||||||
playdate.timer.new(2000, function()
|
|
||||||
throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, false)
|
|
||||||
end)
|
|
||||||
BootTune:play()
|
|
||||||
BootTune:setFinishCallback(function()
|
|
||||||
TinnyBackground:play()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local batAngleDeg
|
local batAngleDeg
|
||||||
|
|
||||||
function updateBatting()
|
function updateBatting()
|
||||||
|
@ -581,8 +624,9 @@ function updateBatting()
|
||||||
batTip.y = batBase.y + (BatLength * math.cos(batAngle))
|
batTip.y = batBase.y + (BatLength * math.cos(batAngle))
|
||||||
|
|
||||||
if
|
if
|
||||||
acceleratedChange >= 0
|
acceleratedChange > 0
|
||||||
and pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, Screen.H)
|
and pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, Screen.H)
|
||||||
|
and ball.y < 232 --not isTouchingBall(fielders.catcher.x, fielders.catcher.y)
|
||||||
then
|
then
|
||||||
BatCrackSound:play()
|
BatCrackSound:play()
|
||||||
currentMode = Modes.running
|
currentMode = Modes.running
|
||||||
|
@ -619,25 +663,27 @@ function updateBatting()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Update non-batter runners.
|
||||||
|
--- Returns true only if at least one of the given runners moved during this update
|
||||||
|
---@return boolean
|
||||||
function updateRunning()
|
function updateRunning()
|
||||||
|
ball.size = ballSizeAnimator:currentValue()
|
||||||
|
|
||||||
local nonBatterRunners = filter(runners, function(runner)
|
local nonBatterRunners = filter(runners, function(runner)
|
||||||
return runner ~= batter
|
return runner ~= batter
|
||||||
end)
|
end)
|
||||||
ball.size = ballSizeAnimator:currentValue()
|
|
||||||
if updateRunners(nonBatterRunners) then
|
local runnerMoved = false
|
||||||
secondsSinceLastRunnerMove = 0
|
|
||||||
else
|
-- TODO: Filter for the runner closest to the currently-held direction button
|
||||||
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
|
for runnerIndex, runner in ipairs(nonBatterRunners) do
|
||||||
if secondsSinceLastRunnerMove > ResetFieldersAfterSeconds then
|
runnerMoved = updateRunner(runner, runnerIndex) or runnerMoved
|
||||||
throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true)
|
|
||||||
resetFielderPositions(false)
|
|
||||||
currentMode = Modes.batting
|
|
||||||
batter = newRunner()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return runnerMoved
|
||||||
end
|
end
|
||||||
|
|
||||||
function updateOutRunners()
|
function walkAwayOutRunners()
|
||||||
for i, runner in ipairs(outRunners) do
|
for i, runner in ipairs(outRunners) do
|
||||||
if runner.x < Screen.W + 50 and runner.y < Screen.H + 50 then
|
if runner.x < Screen.W + 50 and runner.y < Screen.H + 50 then
|
||||||
runner.x = runner.x + (deltaSeconds * 25)
|
runner.x = runner.x + (deltaSeconds * 25)
|
||||||
|
@ -672,13 +718,24 @@ function updateGameState()
|
||||||
pitch()
|
pitch()
|
||||||
end
|
end
|
||||||
updateBatting()
|
updateBatting()
|
||||||
updateRunners({ batter })
|
-- TODO: Ensure batter can't be nil, here
|
||||||
|
updateRunner(batter)
|
||||||
elseif currentMode == Modes.running then
|
elseif currentMode == Modes.running then
|
||||||
updateRunning()
|
if updateRunning() then
|
||||||
|
secondsSinceLastRunnerMove = 0
|
||||||
|
else
|
||||||
|
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
|
||||||
|
if secondsSinceLastRunnerMove > ResetFieldersAfterSeconds then
|
||||||
|
throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||||
|
resetFielderPositions(false)
|
||||||
|
currentMode = Modes.batting
|
||||||
|
batter = newRunner()
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
updateFielders()
|
updateFielders()
|
||||||
updateOutRunners()
|
walkAwayOutRunners()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO
|
-- TODO
|
||||||
|
@ -741,11 +798,27 @@ function playdate.update()
|
||||||
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
|
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
|
||||||
|
|
||||||
gfx.setDrawOffset(0, 0)
|
gfx.setDrawOffset(0, 0)
|
||||||
if offsetX > 0 or offsetY > 0 then
|
if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then
|
||||||
drawMinimap()
|
drawMinimap()
|
||||||
end
|
end
|
||||||
drawScoreboard(0, Screen.H * 0.77, teams, outs, battingTeam, inning)
|
drawScoreboard(0, Screen.H * 0.77, teams, outs, battingTeam, inning)
|
||||||
announcer:draw(Center.x, 10)
|
announcer:draw(Center.x, 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function init()
|
||||||
|
playdate.display.setRefreshRate(50)
|
||||||
|
gfx.setBackgroundColor(gfx.kColorWhite)
|
||||||
|
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
|
||||||
|
resetFielderPositions(true)
|
||||||
|
playdate.getSystemMenu():addMenuItem("Restart game", function() end) -- TODO?
|
||||||
|
|
||||||
|
playdate.timer.new(2000, function()
|
||||||
|
throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, false)
|
||||||
|
end)
|
||||||
|
BootTune:play()
|
||||||
|
BootTune:setFinishCallback(function()
|
||||||
|
TinnyBackground:play()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|
|
@ -34,7 +34,11 @@ function xy(x, 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
|
---@param x1 number
|
||||||
|
---@param y1 number
|
||||||
|
---@param x2 number
|
||||||
|
---@param y2 number
|
||||||
|
---@return number x, number y, number distance
|
||||||
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
|
||||||
|
@ -108,6 +112,8 @@ function getNearestOf(array, x, y, extraCondition)
|
||||||
return nearest, nearestDistance
|
return nearest, nearestDistance
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@alias Cache { get: fun(key: `Key`): `Value` }
|
||||||
|
|
||||||
--- Marker used by buildCache to indicate a cached `nil` value.
|
--- Marker used by buildCache to indicate a cached `nil` value.
|
||||||
local NoValue <const> = {}
|
local NoValue <const> = {}
|
||||||
|
|
||||||
|
@ -120,7 +126,7 @@ local NoValue <const> = {}
|
||||||
---
|
---
|
||||||
---@generic Key
|
---@generic Key
|
||||||
---@generic Value
|
---@generic Value
|
||||||
---@return { get: fun(key: Key): Value }
|
---@return Cache<Key, Value>
|
||||||
function buildCache(fetcher)
|
function buildCache(fetcher)
|
||||||
local cacheData = {}
|
local cacheData = {}
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Reference in New Issue