StyLua pass

Add 'lint' recipe to Makefile
This commit is contained in:
Sage Vaillancourt 2025-02-01 07:39:09 -05:00
parent 0b126919ac
commit 4fc37b133e
5 changed files with 355 additions and 347 deletions

View File

@ -1,3 +1,6 @@
lint:
stylua src/
all: all:
pdc src BatterUp.pdx pdc src BatterUp.pdx

View File

@ -1,4 +1,4 @@
ecs = { } ecs = {}
local ALL_ENTITIES = {} local ALL_ENTITIES = {}
@ -12,9 +12,9 @@ function ecs.addEntity(entity)
ALL_ENTITIES[entity] = true ALL_ENTITIES[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
@ -22,7 +22,7 @@ end
function ecs.removeEntity(entity) function ecs.removeEntity(entity)
ALL_ENTITIES[entity] = nil ALL_ENTITIES[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
@ -36,7 +36,7 @@ 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
@ -45,7 +45,7 @@ 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
@ -61,25 +61,26 @@ 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() return function() end
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 indent = 0 end if not indent then
for k, v in pairs(tbl) do indent = 0
formatting = string.rep(" ", indent) .. k .. ": " end
if type(v) == "table" then for k, v in pairs(tbl) do
print(formatting) formatting = string.rep(" ", indent) .. k .. ": "
tprint(v, indent+1) if type(v) == "table" then
elseif type(v) == 'boolean' then print(formatting)
print(formatting .. tostring(v)) tprint(v, indent + 1)
else elseif type(v) == "boolean" then
print(formatting .. v) print(formatting .. tostring(v))
end else
end print(formatting .. v)
end
end
end end
function addSystem(callback, keys, shapes) function addSystem(callback, keys, shapes)
@ -87,7 +88,7 @@ function addSystem(callback, keys, shapes)
callback = callback, callback = callback,
keys = keys, keys = keys,
shapes = shapes, shapes = shapes,
entityCache = nil entityCache = nil,
} }
end end
@ -98,18 +99,26 @@ 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(ALL_ENTITIES) do for entity, _ in pairs(ALL_ENTITIES) 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(deltaSeconds, entity, entity[keys[1]], entity[keys[2]], entity[keys[3]], entity[keys[4]], entity) system.callback(
deltaSeconds,
entity,
entity[keys[1]],
entity[keys[2]],
entity[keys[3]],
entity[keys[4]],
entity
)
end end
end end
end end
@ -130,21 +139,21 @@ 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
@ -159,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
@ -183,14 +192,14 @@ 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")
@ -200,12 +209,12 @@ ecs.forEntitiesWith(Position, Velocity, function (delta, e, pos, vel)
}) })
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

@ -5,18 +5,18 @@ local ballBuffer = 5
--- 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 < ballBuffer then if ballY < ballBuffer then
offsetY = math.max(ballBuffer, -1 * (ballY - ballBuffer)) offsetY = math.max(ballBuffer, -1 * (ballY - ballBuffer))
else else
offsetY = 0 offsetY = 0
end end
if ballX > 0 and ballX < (screenW - ballBuffer) then if ballX > 0 and ballX < (screenW - ballBuffer) then
offsetX = 0 offsetX = 0
elseif ballX < ballBuffer then elseif ballX < ballBuffer then
offsetX = math.max(-1 * screenW, -1 * (ballX - ballBuffer)) offsetX = math.max(-1 * screenW, -1 * (ballX - ballBuffer))
elseif ballX > (screenW - ballBuffer) then elseif ballX > (screenW - ballBuffer) then
offsetX = math.min(screenW * 2, -1 * (ballX - ballBuffer)) offsetX = math.min(screenW * 2, -1 * (ballX - ballBuffer))
end end
return offsetX, offsetY return offsetX, offsetY
end end

View File

@ -1,3 +1,4 @@
-- stylua: ignore start
import 'CoreLibs/animation.lua' import 'CoreLibs/animation.lua'
import 'CoreLibs/animator.lua' import 'CoreLibs/animator.lua'
import 'CoreLibs/easing.lua' import 'CoreLibs/easing.lua'
@ -7,6 +8,7 @@ import 'CoreLibs/ui.lua'
import 'graphics.lua' import 'graphics.lua'
import 'utils.lua' import 'utils.lua'
-- stylua: ignore end
--- @alias XYPair { x: number, y: number } --- @alias XYPair { x: number, y: number }
@ -20,7 +22,7 @@ local gfx = playdate.graphics
local SCREEN = { local SCREEN = {
W = playdate.display.getWidth(), W = playdate.display.getWidth(),
H = playdate.display.getHeight() H = playdate.display.getHeight(),
} }
local CENTER = xy(SCREEN.W / 2, SCREEN.H / 2) local CENTER = xy(SCREEN.W / 2, SCREEN.H / 2)
@ -29,17 +31,13 @@ local batCrackSound = playdate.sound.sampleplayer.new("sounds/bat-crack-reverb.w
local grassBackground = gfx.image.new("images/game/grass.png") --[[@as PlaydateGraphicsImage]] local grassBackground = gfx.image.new("images/game/grass.png") --[[@as PlaydateGraphicsImage]]
local playerFrown = gfx.image.new("images/game/player-frown.png") --[[@as PlaydateGraphicsImage]] local playerFrown = gfx.image.new("images/game/player-frown.png") --[[@as PlaydateGraphicsImage]]
local playerImageBlipper = blipper.new( local playerImageBlipper = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png")
100,
"images/game/player.png",
"images/game/player-lowhat.png"
)
local BALL_OFFSCREEN = 999 local BALL_OFFSCREEN = 999
local danceBounceMs = 500 local danceBounceMs = 500
local danceBounceCount = 4 local danceBounceCount = 4
local fielderDanceAnimator = gfx.animator.new(danceBounceMs, 10, 0, easingHill)--, -1 * danceBounceMs * danceBounceCount) local fielderDanceAnimator = gfx.animator.new(danceBounceMs, 10, 0, easingHill) --, -1 * danceBounceMs * danceBounceCount)
fielderDanceAnimator.repeatCount = danceBounceCount - 1 fielderDanceAnimator.repeatCount = danceBounceCount - 1
local pitchFlyTimeMs = 2500 local pitchFlyTimeMs = 2500
@ -56,14 +54,14 @@ local TAG_DISTANCE = 20
local ball = { local ball = {
x = CENTER.x, x = CENTER.x,
y = BALL_OFFSCREEN, y = BALL_OFFSCREEN,
size = 6 size = 6,
} }
local BAT_LENGTH = 45 local BAT_LENGTH = 45
local MODES = { local MODES = {
batting = {}, batting = {},
running = {} running = {},
} }
local currentMode = MODES.batting local currentMode = MODES.batting
@ -84,88 +82,88 @@ local FIRST, SECOND, THIRD, HOME = 1, 2, 3, 4
---@type Base[] ---@type Base[]
local bases = { local bases = {
xy(SCREEN.W * 0.93, SCREEN.H * 0.52), xy(SCREEN.W * 0.93, SCREEN.H * 0.52),
xy(SCREEN.W * 0.47, SCREEN.H * 0.19), xy(SCREEN.W * 0.47, SCREEN.H * 0.19),
xy(SCREEN.W * 0.03, SCREEN.H * 0.52), xy(SCREEN.W * 0.03, SCREEN.H * 0.52),
xy(SCREEN.W * 0.474, SCREEN.H * 0.79) xy(SCREEN.W * 0.474, SCREEN.H * 0.79),
} }
---@type table<Base, Base> ---@type table<Base, Base>
local nextBaseMap = { local nextBaseMap = {
[bases[FIRST]] = bases[SECOND], [bases[FIRST]] = bases[SECOND],
[bases[SECOND]] = bases[THIRD], [bases[SECOND]] = bases[THIRD],
[bases[THIRD]] = bases[HOME] [bases[THIRD]] = bases[HOME],
} }
function newFielder(speed) function newFielder(speed)
return { return {
speed = speed speed = speed,
} }
end end
---@type table<string, Fielder> ---@type table<string, Fielder>
local fielders = { local fielders = {
first = newFielder(40), first = newFielder(40),
second = newFielder(40), second = newFielder(40),
shortstop = newFielder(40), shortstop = newFielder(40),
third = newFielder(40), third = newFielder(40),
pitcher = newFielder(30), pitcher = newFielder(30),
catcher = newFielder(20), catcher = newFielder(20),
left = newFielder(40), left = newFielder(40),
center = newFielder(40), center = newFielder(40),
right = newFielder(40) right = newFielder(40),
} }
local PITCHER_POS = { local PITCHER_POS = {
x = SCREEN.W * 0.48, x = SCREEN.W * 0.48,
y = SCREEN.H * 0.40 y = SCREEN.H * 0.40,
} }
--- Resets the target positions of all fielders to their defaults (at their field positions). --- Resets the target positions of all fielders to their defaults (at their field positions).
---@param fromOffTheField boolean If provided, also sets all runners' current position to one centralized location. ---@param fromOffTheField boolean If provided, also sets all runners' current position to one centralized location.
function resetFielderPositions(fromOffTheField) function resetFielderPositions(fromOffTheField)
if fromOffTheField then if fromOffTheField then
for _,fielder in pairs(fielders) do for _, fielder in pairs(fielders) do
fielder.x = CENTER.x fielder.x = CENTER.x
fielder.y = SCREEN.H fielder.y = SCREEN.H
end end
end end
fielders.first.target = xy(SCREEN.W - 65, SCREEN.H * 0.48) fielders.first.target = xy(SCREEN.W - 65, SCREEN.H * 0.48)
fielders.second.target = xy(SCREEN.W * 0.70, SCREEN.H * 0.30) fielders.second.target = xy(SCREEN.W * 0.70, SCREEN.H * 0.30)
fielders.shortstop.target = xy(SCREEN.W * 0.30, SCREEN.H * 0.30) fielders.shortstop.target = xy(SCREEN.W * 0.30, SCREEN.H * 0.30)
fielders.third.target = xy(SCREEN.W * 0.1, SCREEN.H * 0.48) fielders.third.target = xy(SCREEN.W * 0.1, SCREEN.H * 0.48)
fielders.pitcher.target = xy(PITCHER_POS.x, PITCHER_POS.y) fielders.pitcher.target = xy(PITCHER_POS.x, PITCHER_POS.y)
fielders.catcher.target = xy(SCREEN.W * 0.475, SCREEN.H * 0.92) fielders.catcher.target = xy(SCREEN.W * 0.475, SCREEN.H * 0.92)
fielders.left.target = xy(SCREEN.W * -1, SCREEN.H * -0.2) fielders.left.target = xy(SCREEN.W * -1, SCREEN.H * -0.2)
fielders.center.target = xy(CENTER.x, SCREEN.H * -0.4) fielders.center.target = xy(CENTER.x, SCREEN.H * -0.4)
fielders.right.target = xy(SCREEN.W * 2, SCREEN.H * fielders.left.target.y) fielders.right.target = xy(SCREEN.W * 2, SCREEN.H * fielders.left.target.y)
end end
local PLAYER_STARTING_X = bases[HOME].x - 40 local PLAYER_STARTING_X = bases[HOME].x - 40
local PLAYER_STARTING_Y = bases[HOME].y - 3 local PLAYER_STARTING_Y = bases[HOME].y - 3
--- @type Runner[] --- @type Runner[]
local runners = { } local runners = {}
--- @type Runner[] --- @type Runner[]
local outRunners = { } local outRunners = {}
local nameI = 1 local nameI = 1
local runnerNames = {"Barbara", "Steve", "Jerry", "Beatrice"} local runnerNames = { "Barbara", "Steve", "Jerry", "Beatrice" }
---@return Runner ---@return Runner
function newRunner() function newRunner()
local new = { local new = {
x = PLAYER_STARTING_X, x = PLAYER_STARTING_X,
y = PLAYER_STARTING_Y, y = PLAYER_STARTING_Y,
nextBase = nil, nextBase = nil,
prevBase = nil, prevBase = nil,
name = runnerNames[nameI] name = runnerNames[nameI],
} }
nameI = nameI + 1 nameI = nameI + 1
runners[#runners + 1] = new runners[#runners + 1] = new
return new return new
end end
---@type Runner | nil ---@type Runner | nil
@ -174,45 +172,45 @@ batter.nextBase = bases[FIRST]
batter.forcedTo = bases[FIRST] batter.forcedTo = bases[FIRST]
function throwBall(destX, destY, easingFunc, flyTimeMs, floaty) function throwBall(destX, destY, easingFunc, flyTimeMs, floaty)
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
end end
ballSizeAnimator:reset(flyTimeMs) ballSizeAnimator:reset(flyTimeMs)
ballAnimatorY = gfx.animator.new(flyTimeMs, ball.y, destY, easingFunc) ballAnimatorY = gfx.animator.new(flyTimeMs, ball.y, destY, easingFunc)
ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc) ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc)
if floaty then if floaty then
ballFloatAnimator:reset(flyTimeMs) ballFloatAnimator:reset(flyTimeMs)
end end
end end
function pitch() function pitch()
currentMode = MODES.batting currentMode = MODES.batting
ballAnimatorX = gfx.animator.new(0, PITCH_START_X, PITCH_START_X, playdate.easingFunctions.linear) ballAnimatorX = gfx.animator.new(0, PITCH_START_X, PITCH_START_X, playdate.easingFunctions.linear)
ballAnimatorY = pitchAnimator ballAnimatorY = pitchAnimator
pitchAnimator:reset() pitchAnimator:reset()
end end
function playdate.AButtonDown() function playdate.AButtonDown()
if not batter then if not batter then
return return
end end
pitch() pitch()
end end
function playdate.upButtonDown() function playdate.upButtonDown()
batBase.y = batBase.y - 1 batBase.y = batBase.y - 1
end end
function playdate.downButtonDown() function playdate.downButtonDown()
batBase.y = batBase.y + 1 batBase.y = batBase.y + 1
end end
function playdate.rightButtonDown() function playdate.rightButtonDown()
batBase.x = batBase.x + 1 batBase.x = batBase.x + 1
end end
function playdate.leftButtonDown() function playdate.leftButtonDown()
batBase.x = batBase.x - 1 batBase.x = batBase.x - 1
end end
local elapsedSec = 0 local elapsedSec = 0
@ -221,23 +219,22 @@ local crankChange = 0
local BASE_HITBOX = 13 local BASE_HITBOX = 13
--- 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 runner at (x,y), or nil, if no base is being touched
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) < BASE_HITBOX then if distanceBetween(x, y, base.x, base.y) < BASE_HITBOX then
return base return base
end end
end end
return nil return nil
end end
local BALL_CATCH_HITBOX = 3 local BALL_CATCH_HITBOX = 3
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 < BALL_CATCH_HITBOX return ballDistance < BALL_CATCH_HITBOX
end end
function updateForcedTos() function updateForcedTos() end
end
local outs = 0 local outs = 0
local homeScore = 0 local homeScore = 0
@ -246,106 +243,104 @@ local awayScore = 0
function outRunner(runnerIndex) function outRunner(runnerIndex)
print("You're out, runner" .. runnerIndex .. "!") print("You're out, runner" .. runnerIndex .. "!")
outs = math.min(3, outs + 1) outs = math.min(3, outs + 1)
outRunners[#outRunners+1] = runners[runnerIndex] outRunners[#outRunners + 1] = runners[runnerIndex]
table.remove(runners, runnerIndex) table.remove(runners, runnerIndex)
updateForcedTos() updateForcedTos()
fielderDanceAnimator:reset() fielderDanceAnimator:reset()
end end
function updateFielders() function updateFielders()
local touchingBaseCache = buildCache( local touchingBaseCache = buildCache(function(runner)
function(runner) return isTouchingBase(runner.x, runner.y)
return isTouchingBase(runner.x, runner.y) end)
end
)
for _,fielder in pairs(fielders) do for _, fielder in pairs(fielders) do
-- 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) local x, y, distance = normalizeVector(fielder.x, fielder.y, fielder.target.x, fielder.target.y)
if distance > 1 then if distance > 1 then
fielder.x = fielder.x - (x * fielder.speed * deltaSeconds) fielder.x = fielder.x - (x * fielder.speed * deltaSeconds)
fielder.y = fielder.y - (y * fielder.speed * deltaSeconds) fielder.y = fielder.y - (y * fielder.speed * deltaSeconds)
else else
if fielder.onArrive then if fielder.onArrive then
fielder.onArrive() fielder.onArrive()
end end
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 and isTouchingBall(fielder.x, fielder.y) then
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
local runnerOnBase = touchingBaseCache.get(runner) local runnerOnBase = touchingBaseCache.get(runner)
if touchedBase and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase then if touchedBase and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase then
print("Force out of runner:") print("Force out of runner:")
printTable(runner) printTable(runner)
outRunner(i) outRunner(i)
elseif not runnerOnBase then elseif not runnerOnBase then
local fielderDistance = distanceBetween(runner.x, runner.y, fielder.x, fielder.y) local fielderDistance = distanceBetween(runner.x, runner.y, fielder.x, fielder.y)
if fielderDistance < TAG_DISTANCE then if fielderDistance < TAG_DISTANCE then
print("Tagged out!") print("Tagged out!")
outRunner(i) outRunner(i)
end end
end end
end end
end end
end end
end end
--- Returns true if at least one runner is still moving --- Returns true if at least one runner is still moving
---@return boolean ---@return boolean
function updateRunners() function updateRunners()
local autoRunSpeed = 20 local autoRunSpeed = 20
--autoRunSpeed = 140 --autoRunSpeed = 140
local nonPlayerRunners = filter(runners, function (runner) local nonPlayerRunners = filter(runners, function(runner)
return runner ~= batter return runner ~= batter
end) end)
local runnerMoved = false local runnerMoved = false
for _,runner in pairs(nonPlayerRunners) do for _, runner in pairs(nonPlayerRunners) do
local _, nearestBaseDistance = getNearestOf(bases, runner.x, runner.y) local _, nearestBaseDistance = getNearestOf(bases, runner.x, runner.y)
if runner.nextBase then 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
local mult = 1 local mult = 1
if crankChange < 0 then if crankChange < 0 then
mult = -1 mult = -1
end end
local prevX, prevY = runner.x, runner.y local prevX, prevY = runner.x, runner.y
-- TODO: Drift toward nearest base? -- TODO: Drift toward nearest base?
local autoRun = nearestBaseDistance > 5 and mult * autoRunSpeed * deltaSeconds or 0 local autoRun = nearestBaseDistance > 5 and mult * autoRunSpeed * deltaSeconds or 0
mult = autoRun + (crankChange / 20) mult = autoRun + (crankChange / 20)
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 runnerMoved = runnerMoved or prevX ~= runner.x or prevY ~= runner.y
else else
runner.nextBase = nextBaseMap[runner.nextBase] runner.nextBase = nextBaseMap[runner.nextBase]
runner.forcedTo = nil runner.forcedTo = nil
end end
end end
end end
return runnerMoved return runnerMoved
end end
---@return boolean ---@return boolean
function ballIsBeingThrown() function ballIsBeingThrown()
return false return false
end end
---@return boolean ---@return boolean
function throwArrivedBeforeRunner() function throwArrivedBeforeRunner()
return false return false
end end
function getRunnerTargeting(base) function getRunnerTargeting(base)
for _,runner in pairs(runners) do for _, runner in pairs(runners) do
if runner.nextBase == base then if runner.nextBase == base then
return runner return runner
end end
@ -356,44 +351,44 @@ end
---@return Base[] ---@return Base[]
function getForcedOutTargets() function getForcedOutTargets()
local targets = {} local targets = {}
for _,base in ipairs(bases) do for _, base in ipairs(bases) do
local runnerTargetingBase = getRunnerTargeting(base) local runnerTargetingBase = getRunnerTargeting(base)
if runnerTargetingBase then if runnerTargetingBase then
targets[#targets+1] = base targets[#targets + 1] = base
else else
return targets return targets
end end
end end
return targets return targets
-- return { bases[FIRST] } -- return { bases[FIRST] }
end end
--- Returns the position,distance of the basest closest to the runner furthest from a base --- Returns the position,distance of the basest closest to the runner furthest from a base
---@return { x: number, y: number } | nil, number | nil ---@return { x: number, y: number } | nil, number | nil
function getBaseOfStrandedRunner() function getBaseOfStrandedRunner()
local farRunnersBase, farDistance local farRunnersBase, farDistance
for _,runner in pairs(runners) do for _, runner in pairs(runners) do
local base, distance = getNearestOf(bases, runner.x, runner.y, function(base) local base, distance = getNearestOf(bases, runner.x, runner.y, function(base)
return runner.nextBase == base return runner.nextBase == base
end) end)
if farRunnersBase == nil then if farRunnersBase == nil then
farRunnersBase = base farRunnersBase = base
farDistance = distance farDistance = distance
elseif farDistance < distance then elseif farDistance < distance then
farRunnersBase = base farRunnersBase = base
farDistance = distance farDistance = distance
end end
end end
return farRunnersBase, farDistance return farRunnersBase, farDistance
end end
--- Returns x,y of the throw target --- Returns x,y of the throw target
---@return number|nil, number|nil ---@return number|nil, number|nil
function getNextThrowTarget() function getNextThrowTarget()
-- TODO: Handle missed throws, check for fielders at target, etc. -- TODO: Handle missed throws, check for fielders at target, etc.
local targets = getForcedOutTargets() local targets = getForcedOutTargets()
if #targets ~= 0 then if #targets ~= 0 then
return targets[1].x, targets[1].y return targets[1].x, targets[1].y
end end
@ -427,42 +422,44 @@ function updateBatting()
ball.size = 6 ball.size = 6
end end
local batAngle = math.rad(playdate.getCrankPosition() + CRANK_OFFSET_DEG) local batAngle = math.rad(playdate.getCrankPosition() + CRANK_OFFSET_DEG)
batTip.x = batBase.x + (BAT_LENGTH * math.sin(batAngle)) batTip.x = batBase.x + (BAT_LENGTH * math.sin(batAngle))
batTip.y = batBase.y + (BAT_LENGTH * math.cos(batAngle)) batTip.y = batBase.y + (BAT_LENGTH * math.cos(batAngle))
if acceleratedChange >= 0 and if
pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, SCREEN.H) then acceleratedChange >= 0
batCrackSound:play() and pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, SCREEN.H)
currentMode = MODES.running then
ballAngle = batAngle + math.rad(90) batCrackSound:play()
currentMode = MODES.running
ballAngle = batAngle + math.rad(90)
local mult = math.abs(acceleratedChange / 15) local mult = math.abs(acceleratedChange / 15)
local ballVelX = mult * 10 * math.sin(ballAngle) local ballVelX = mult * 10 * math.sin(ballAngle)
local ballVelY = mult * 5 * math.cos(ballAngle) local ballVelY = mult * 5 * math.cos(ballAngle)
if ballVelY > 0 then if ballVelY > 0 then
ballVelX = ballVelX * -1 ballVelX = ballVelX * -1
ballVelY = ballVelY * -1 ballVelY = ballVelY * -1
end end
ballDestX = ball.x + (ballVelX * HIT_MULT) ballDestX = ball.x + (ballVelX * HIT_MULT)
ballDestY = ball.y + (ballVelY * HIT_MULT) ballDestY = ball.y + (ballVelY * HIT_MULT)
throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000) throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000)
fielders.first.target = bases[FIRST] fielders.first.target = bases[FIRST]
batter.nextBase = bases[FIRST] batter.nextBase = bases[FIRST]
batter.forcedTo = bases[FIRST] batter.forcedTo = bases[FIRST]
batter = nil -- Demote batter to a mere runner batter = nil -- Demote batter to a mere runner
local chasingFielder = getNearestOf(fielders, ballDestX, ballDestY) local chasingFielder = getNearestOf(fielders, ballDestX, ballDestY)
chasingFielder.target = { x = ballDestX, y = ballDestY } chasingFielder.target = { x = ballDestX, y = ballDestY }
chasingFielder.onArrive = function() chasingFielder.onArrive = function()
local targetX, targetY = getNextThrowTarget() local targetX, targetY = getNextThrowTarget()
if targetX ~= nil then if targetX ~= nil then
throwBall(targetX, targetY, playdate.easingFunctions.linear, nil, true) throwBall(targetX, targetY, playdate.easingFunctions.linear, nil, true)
chasingFielder.onArrive = nil chasingFielder.onArrive = nil
end end
end end
end end
end end
function updateRunning() function updateRunning()
@ -495,19 +492,19 @@ function updateOutRunners()
end end
function updateGameState() function updateGameState()
deltaSeconds = playdate.getElapsedTime() or 0 deltaSeconds = playdate.getElapsedTime() or 0
playdate.resetElapsedTime() playdate.resetElapsedTime()
elapsedSec = elapsedSec + deltaSeconds elapsedSec = elapsedSec + deltaSeconds
crankChange, acceleratedChange = playdate.getCrankChange() --[[@as number, number]] crankChange, acceleratedChange = playdate.getCrankChange() --[[@as number, number]]
ball.x = ballAnimatorX:currentValue() ball.x = ballAnimatorX:currentValue()
ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue() ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue()
if currentMode == MODES.batting then if currentMode == MODES.batting then
updateBatting() updateBatting()
elseif currentMode == MODES.running then elseif currentMode == MODES.running then
updateRunning() updateRunning()
end end
updateFielders() updateFielders()
updateOutRunners() updateOutRunners()
@ -520,15 +517,15 @@ function drawScoreboard()
local y = SCREEN.H * 0.95 local y = SCREEN.H * 0.95
local x = SCREEN.W * 0.05 local x = SCREEN.W * 0.05
gfx.setLineWidth(1) gfx.setLineWidth(1)
gfx.setColor(gfx.kColorBlack) gfx.setColor(gfx.kColorBlack)
gfx.fillRect(x - 15, y - 44, 73, 55) gfx.fillRect(x - 15, y - 44, 73, 55)
gfx.setColor(gfx.kColorWhite) gfx.setColor(gfx.kColorWhite)
for i=outs,2 do for i = outs, 2 do
gfx.drawCircleAtPoint(x + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE) gfx.drawCircleAtPoint(x + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE)
end end
for i=0,(outs - 1) do for i = 0, (outs - 1) do
gfx.fillCircleAtPoint(x + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE) gfx.fillCircleAtPoint(x + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE)
end end
@ -540,51 +537,48 @@ function drawScoreboard()
end end
function playdate.update() function playdate.update()
updateGameState() updateGameState()
playdate.graphics.animation.blinker.updateAll() playdate.graphics.animation.blinker.updateAll()
gfx.clear() gfx.clear()
if ball.x < BALL_OFFSCREEN then if ball.x < BALL_OFFSCREEN then
-- TODO: Show baserunning minimap when panning? -- TODO: Show baserunning minimap when panning?
local offsetX, offsetY = getDrawOffset(SCREEN.W, SCREEN.H, ball.x, ball.y) local offsetX, offsetY = getDrawOffset(SCREEN.W, SCREEN.H, ball.x, ball.y)
gfx.setDrawOffset(offsetX, offsetY) gfx.setDrawOffset(offsetX, offsetY)
end end
grassBackground:draw(-400, -240) grassBackground:draw(-400, -240)
gfx.setColor(gfx.kColorBlack) gfx.setColor(gfx.kColorBlack)
gfx.setLineWidth(2) gfx.setLineWidth(2)
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size) gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
local fielderDanceHeight = fielderDanceAnimator:currentValue() local fielderDanceHeight = fielderDanceAnimator:currentValue()
for _,fielder in pairs(fielders) do for _, fielder in pairs(fielders) do
gfx.fillRect(fielder.x, fielder.y - fielderDanceHeight, 14, 25) gfx.fillRect(fielder.x, fielder.y - fielderDanceHeight, 14, 25)
end end
if currentMode == MODES.batting then if currentMode == MODES.batting then
gfx.setLineWidth(5) gfx.setLineWidth(5)
gfx.drawLine( gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y)
batBase.x, batBase.y, end
batTip.x, batTip.y
)
end
if playdate.isCrankDocked() then -- or (crankChange < 2 and currentMode == MODES.running) then if playdate.isCrankDocked() then -- or (crankChange < 2 and currentMode == MODES.running) then
playdate.ui.crankIndicator:draw() playdate.ui.crankIndicator:draw()
end end
-- TODO? Change blip speed depending on runner speed? -- TODO? Change blip speed depending on runner speed?
for _,runner in pairs(runners) do for _, runner in pairs(runners) do
-- TODO? Scale sprites down as y increases -- TODO? Scale sprites down as y increases
playerImageBlipper:draw(false, runner.x, runner.y) playerImageBlipper:draw(false, runner.x, runner.y)
end end
for _,runner in pairs(outRunners) do for _, runner in pairs(outRunners) do
playerFrown:draw(runner.x, runner.y) playerFrown:draw(runner.x, runner.y)
end end
drawScoreboard() drawScoreboard()
end end
init() init()

View File

@ -1,5 +1,7 @@
-- stylua: ignore start
import 'CoreLibs/animation.lua' import 'CoreLibs/animation.lua'
import 'CoreLibs/graphics.lua' import 'CoreLibs/graphics.lua'
-- stylua: ignore end
-- number = ((number * 2) - 1) -- number = ((number * 2) - 1)
-- return number * number -- return number * number
@ -8,28 +10,28 @@ import 'CoreLibs/graphics.lua'
-- return c * t / d + b -- return c * t / d + b
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
---@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
@ -37,13 +39,13 @@ 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
---@generic TIn ---@generic TIn
@ -52,39 +54,39 @@ end
---@param mapper fun(TIn): TOut ---@param mapper fun(TIn): TOut
---@return TOut[] ---@return TOut[]
function map(array, mapper) function map(array, mapper)
local newArray = {} local newArray = {}
for _,element in pairs(array) do for _, element in pairs(array) do
newArray[#newArray + 1] = mapper(element) newArray[#newArray + 1] = mapper(element)
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
@ -94,9 +96,9 @@ 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)
@ -107,29 +109,29 @@ function getNearestOf(array, x, y, extraCondition)
nearestDistance = distance nearestDistance = distance
end end
end end
end end
end end
return nearest, nearestDistance return nearest, nearestDistance
end end
local NO_VALUE = {} local NO_VALUE = {}
function buildCache(fetcher) function buildCache(fetcher)
local cacheData = {} local cacheData = {}
return { return {
cacheDate = cacheData, cacheDate = cacheData,
get = function(key) get = function(key)
if cacheData[key] == NO_VALUE then if cacheData[key] == NO_VALUE 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
cacheData[key] = fetcher(key) or NO_VALUE cacheData[key] = fetcher(key) or NO_VALUE
return cacheData[key] return cacheData[key]
end end,
} }
end end
blipper = {} blipper = {}
@ -146,6 +148,6 @@ function blipper.new(msInterval, imagePath1, 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