Refactoring, typehints, better baserunning
This commit is contained in:
parent
db52923b32
commit
4093f9705a
203
src/main.lua
203
src/main.lua
|
@ -6,6 +6,14 @@ import 'CoreLibs/object.lua'
|
||||||
import 'CoreLibs/ui.lua'
|
import 'CoreLibs/ui.lua'
|
||||||
import 'utils.lua'
|
import 'utils.lua'
|
||||||
|
|
||||||
|
--- @alias Position { x: number, y: number }
|
||||||
|
|
||||||
|
--- @alias Base { x: number, y: number }
|
||||||
|
|
||||||
|
--- @alias Runner { x: number, y: number, nextBase: Base, prevBase: Base, forcedTo: Base }
|
||||||
|
|
||||||
|
--- @alias Fielder { onArrive: fun() | nil, x: number | nil, y: number | nil, targetX: number | nil, targetY: number | nil }
|
||||||
|
|
||||||
local gfx = playdate.graphics
|
local gfx = playdate.graphics
|
||||||
playdate.display.setRefreshRate(50)
|
playdate.display.setRefreshRate(50)
|
||||||
gfx.setBackgroundColor(gfx.kColorWhite)
|
gfx.setBackgroundColor(gfx.kColorWhite)
|
||||||
|
@ -38,9 +46,10 @@ local batBaseX, batBaseY = centerX - 34, 215
|
||||||
|
|
||||||
local batLength = 45
|
local batLength = 45
|
||||||
|
|
||||||
|
local tagDistance = 20
|
||||||
|
|
||||||
local ballY = ballStartY
|
local ballY = ballStartY
|
||||||
local ballX = 200
|
local ballX = 200
|
||||||
local ballVelX, ballVelY = 0, 0
|
|
||||||
local ballSize = 6
|
local ballSize = 6
|
||||||
|
|
||||||
local batTipX = 0
|
local batTipX = 0
|
||||||
|
@ -59,6 +68,7 @@ local hitMult = 10
|
||||||
|
|
||||||
local deltaTime = 0
|
local deltaTime = 0
|
||||||
|
|
||||||
|
---@type table<string, Base>
|
||||||
local bases = {
|
local bases = {
|
||||||
first = { x = screenW * 0.93, y = screenH * 0.52 },
|
first = { x = screenW * 0.93, y = screenH * 0.52 },
|
||||||
second = { x = screenW * 0.47, y = screenH * 0.19 },
|
second = { x = screenW * 0.47, y = screenH * 0.19 },
|
||||||
|
@ -66,12 +76,20 @@ local bases = {
|
||||||
home = { x = screenW * 0.474, y = screenH * 0.79 }
|
home = { x = screenW * 0.474, y = screenH * 0.79 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@type table<Base, Base>
|
||||||
|
local nextBaseMap = {
|
||||||
|
[bases.first] = bases.second,
|
||||||
|
[bases.second] = bases.third,
|
||||||
|
[bases.third] = bases.home
|
||||||
|
}
|
||||||
|
|
||||||
local fielderSpeed = 40
|
local fielderSpeed = 40
|
||||||
|
|
||||||
|
---@type table<string, Fielder>
|
||||||
local fielders = {
|
local fielders = {
|
||||||
first = {
|
first = {
|
||||||
x = nil,
|
x = nil,
|
||||||
y = nil,
|
y = nil,
|
||||||
covering = nil -- TODO?
|
|
||||||
},
|
},
|
||||||
second = {},
|
second = {},
|
||||||
shortstop = {},
|
shortstop = {},
|
||||||
|
@ -81,6 +99,7 @@ local fielders = {
|
||||||
center = {},
|
center = {},
|
||||||
right = {}
|
right = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetFielderPositions()
|
function resetFielderPositions()
|
||||||
fielders.first.x = screenW - 65
|
fielders.first.x = screenW - 65
|
||||||
fielders.first.y = screenH * 0.48
|
fielders.first.y = screenH * 0.48
|
||||||
|
@ -110,14 +129,23 @@ resetFielderPositions()
|
||||||
|
|
||||||
local playerStartingX = bases.home.x - 40
|
local playerStartingX = bases.home.x - 40
|
||||||
local playerStartingY = bases.home.y - 3
|
local playerStartingY = bases.home.y - 3
|
||||||
local player = {
|
|
||||||
|
--- @type table<Runner, Runner>
|
||||||
|
local runners = { }
|
||||||
|
|
||||||
|
---@return Runner
|
||||||
|
function newRunner()
|
||||||
|
local new = {
|
||||||
x = playerStartingX,
|
x = playerStartingX,
|
||||||
y = playerStartingY,
|
y = playerStartingY,
|
||||||
nextBase = nil,
|
nextBase = nil,
|
||||||
prevBase = nil,
|
prevBase = nil,
|
||||||
}
|
}
|
||||||
|
runners[#runners + 1] = new
|
||||||
|
return new
|
||||||
|
end
|
||||||
|
|
||||||
local runners = { }
|
local batter = newRunner()
|
||||||
|
|
||||||
function throwBall(destX, destY, easingFunc, flyTimeMs)
|
function throwBall(destX, destY, easingFunc, flyTimeMs)
|
||||||
if not flyTimeMs then
|
if not flyTimeMs then
|
||||||
|
@ -131,8 +159,6 @@ end
|
||||||
function pitch()
|
function pitch()
|
||||||
pitchAnimator:reset()
|
pitchAnimator:reset()
|
||||||
resetFielderPositions()
|
resetFielderPositions()
|
||||||
ballVelX = 0
|
|
||||||
ballVelY = 0
|
|
||||||
ballX = 200
|
ballX = 200
|
||||||
currentMode = MODES.batting
|
currentMode = MODES.batting
|
||||||
|
|
||||||
|
@ -140,9 +166,10 @@ function pitch()
|
||||||
backgroundPan.x = 0
|
backgroundPan.x = 0
|
||||||
|
|
||||||
-- TODO: Add new runners, instead
|
-- TODO: Add new runners, instead
|
||||||
runners = {}
|
--runners = {}
|
||||||
player.x = playerStartingX
|
--batter.x = playerStartingX
|
||||||
player.y = playerStartingY
|
--batter.y = playerStartingY
|
||||||
|
batter = newRunner()
|
||||||
end
|
end
|
||||||
|
|
||||||
function playdate.AButtonDown()
|
function playdate.AButtonDown()
|
||||||
|
@ -169,6 +196,34 @@ local pitchClockSec = 99
|
||||||
local elapsedTime = 0
|
local elapsedTime = 0
|
||||||
local crankChange
|
local crankChange
|
||||||
|
|
||||||
|
local baseHitbox = 13
|
||||||
|
--- Returns the base being touched by the runner at (x,y), or nil, if no base is being touched
|
||||||
|
function isTouchingBase(x, y)
|
||||||
|
for _,base in pairs(bases) do
|
||||||
|
if distanceBetween(x, y, base.x, base.y) < baseHitbox then
|
||||||
|
return base
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local ballCatchHitbox = 3
|
||||||
|
function isTouchingBall(x, y)
|
||||||
|
local ballDistance = distanceBetween(x, y, ballX, ballY)
|
||||||
|
-- print("ballDistance:")
|
||||||
|
-- print(ballDistance)
|
||||||
|
return ballDistance < ballCatchHitbox
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateForcedTos()
|
||||||
|
end
|
||||||
|
|
||||||
|
function outRunner(runnerIndex)
|
||||||
|
table.remove(runners, runnerIndex)
|
||||||
|
updateForcedTos()
|
||||||
|
end
|
||||||
|
|
||||||
function updateInfield()
|
function updateInfield()
|
||||||
if ballDestX == nil or ballDestY == nil then
|
if ballDestX == nil or ballDestY == nil then
|
||||||
return
|
return
|
||||||
|
@ -189,66 +244,93 @@ function updateInfield()
|
||||||
fielder.targetY = nil
|
fielder.targetY = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function updateRunners()
|
if isTouchingBall(fielder.x, fielder.y) then
|
||||||
local runnerSpeed = 20
|
local touchedBase = isTouchingBase(fielder.x, fielder.y)
|
||||||
for i,runner in pairs(runners) do
|
for i,runner in pairs(runners) do
|
||||||
if runner.nextBase then
|
local runnerOnBase = isTouchingBase(runner.x, runner.y)
|
||||||
local nb = runner.nextBase
|
if touchedBase and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase then
|
||||||
local x, y, distance = normalizeVector(runner.x, runner.y, nb.x, nb.y)
|
outRunner(i)
|
||||||
runner.hasArrived = distance <= 1
|
elseif not runnerOnBase then
|
||||||
|
local fielderDistance = distanceBetween(runner.x, runner.y, fielder.x, fielder.y)
|
||||||
if runner.hasArrived then
|
if fielderDistance < tagDistance then
|
||||||
-- if runner.onArrive then
|
outRunner(i)
|
||||||
-- runner.onArrive()
|
end
|
||||||
-- end
|
|
||||||
runner.targetX = nil
|
|
||||||
runner.targetY = nil
|
|
||||||
else
|
|
||||||
local mult = 1
|
|
||||||
if crankChange < 0 then
|
|
||||||
mult = -1
|
|
||||||
end
|
end
|
||||||
mult = (mult * runnerSpeed * deltaTime) + (crankChange / 20)
|
|
||||||
runner.x -= x * mult
|
|
||||||
runner.y -= y * mult
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function getNearestFielder(x, y)
|
--- Returns the nearest base, as well as the distance from that base
|
||||||
local nearestFielder, nearestDistance = nil, nil
|
---@generic T : {x: number, y: number}
|
||||||
for _, fielder in pairs(fielders) do
|
---@param array T[]
|
||||||
if nearestFielder == nil then
|
---@param x: number
|
||||||
nearestFielder = fielder
|
---@param y: number
|
||||||
nearestDistance = distanceBetween(fielder.x, fielder.y, x, y)
|
---@return T,number
|
||||||
|
function getNearestOf(array, x, y)
|
||||||
|
local nearest, nearestDistance = nil, nil
|
||||||
|
for _, element in pairs(array) do
|
||||||
|
if nearest == nil then
|
||||||
|
nearest = element
|
||||||
|
nearestDistance = distanceBetween(element.x, element.y, x, y)
|
||||||
else
|
else
|
||||||
local distance = distanceBetween(fielder.x, fielder.y, x, y)
|
local distance = distanceBetween(element.x, element.y, x, y)
|
||||||
if distance < nearestDistance then
|
if distance < nearestDistance then
|
||||||
nearestFielder = fielder
|
nearest = element
|
||||||
nearestDistance = distance
|
nearestDistance = distance
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return nearestFielder
|
return nearest, nearestDistance
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function updateRunners()
|
||||||
|
local runnerSpeed = 20
|
||||||
|
local nonPlayerRunners = filter(runners, function (runner)
|
||||||
|
return runner ~= batter
|
||||||
|
end)
|
||||||
|
for _,runner in pairs(nonPlayerRunners) do
|
||||||
|
local nearestBase, nearestBaseDistance = getNearestOf(bases, runner.x, runner.y)
|
||||||
|
if runner.nextBase then
|
||||||
|
local nb = runner.nextBase
|
||||||
|
local x, y, distance = normalizeVector(runner.x, runner.y, nb.x, nb.y)
|
||||||
|
|
||||||
|
if distance > 1 then
|
||||||
|
local mult = 1
|
||||||
|
if crankChange < 0 then
|
||||||
|
mult = -1
|
||||||
|
end
|
||||||
|
-- TODO: Drift toward nearest base?
|
||||||
|
local autoRun = nearestBaseDistance > 5 and mult * runnerSpeed * deltaTime or 0
|
||||||
|
mult = autoRun + (crankChange / 20)
|
||||||
|
runner.x -= x * mult
|
||||||
|
runner.y -= y * mult
|
||||||
|
else
|
||||||
|
runner.nextBase = nextBaseMap[runner.nextBase]
|
||||||
|
runner.forcedTo = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
function ballIsBeingThrown()
|
function ballIsBeingThrown()
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
function throwArrivedBeforeRunner()
|
function throwArrivedBeforeRunner()
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return Base[]
|
||||||
function getForcedOutTargets()
|
function getForcedOutTargets()
|
||||||
return { bases.first }
|
return { bases.first }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return number,number
|
||||||
function getNextThrowTarget()
|
function getNextThrowTarget()
|
||||||
local targets = getForcedOutTargets()
|
local targets = getForcedOutTargets()
|
||||||
return targets[1].x, targets[1].y
|
return targets[1].x, targets[1].y
|
||||||
|
@ -273,7 +355,7 @@ function updateGameState()
|
||||||
ballSize = 6
|
ballSize = 6
|
||||||
end
|
end
|
||||||
|
|
||||||
batAngle = math.rad(playdate.getCrankPosition() + 90)
|
local batAngle = math.rad(playdate.getCrankPosition() + 90)
|
||||||
batTipX = batBaseX + (batLength * math.sin(batAngle))
|
batTipX = batBaseX + (batLength * math.sin(batAngle))
|
||||||
batTipY = batBaseY + (batLength * math.cos(batAngle))
|
batTipY = batBaseY + (batLength * math.cos(batAngle))
|
||||||
|
|
||||||
|
@ -283,9 +365,9 @@ function updateGameState()
|
||||||
currentMode = MODES.running
|
currentMode = MODES.running
|
||||||
ballAngle = batAngle + math.rad(90)
|
ballAngle = batAngle + math.rad(90)
|
||||||
|
|
||||||
mult = math.abs(acceleratedChange / 15)
|
local mult = math.abs(acceleratedChange / 15)
|
||||||
ballVelX = mult * 10 * math.sin(ballAngle)
|
local ballVelX = mult * 10 * math.sin(ballAngle)
|
||||||
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
|
||||||
|
@ -293,7 +375,7 @@ function updateGameState()
|
||||||
ballDestX = ballX + (ballVelX * hitMult)
|
ballDestX = ballX + (ballVelX * hitMult)
|
||||||
ballDestY = ballY + (ballVelY * hitMult)
|
ballDestY = ballY + (ballVelY * hitMult)
|
||||||
throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000)
|
throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000)
|
||||||
local chasingFielder = getNearestFielder(ballDestX, ballDestY)
|
local chasingFielder = getNearestOf(fielders, ballDestX, ballDestY)
|
||||||
chasingFielder.targetX = ballDestX
|
chasingFielder.targetX = ballDestX
|
||||||
chasingFielder.targetY = ballDestY
|
chasingFielder.targetY = ballDestY
|
||||||
chasingFielder.onArrive = function()
|
chasingFielder.onArrive = function()
|
||||||
|
@ -303,20 +385,27 @@ function updateGameState()
|
||||||
end
|
end
|
||||||
fielders.first.targetX = bases.first.x
|
fielders.first.targetX = bases.first.x
|
||||||
fielders.first.targetY = bases.first.y
|
fielders.first.targetY = bases.first.y
|
||||||
player.nextBase = bases.first
|
batter.nextBase = bases.first
|
||||||
runners[#runners+1] = player
|
batter.forcedTo = bases.first
|
||||||
|
batter = nil -- Demote batter to a mere runner
|
||||||
end
|
end
|
||||||
|
|
||||||
if currentMode == MODES.running then
|
if currentMode == MODES.running then
|
||||||
updateRunners()
|
updateRunners()
|
||||||
updateInfield()
|
updateInfield()
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function playdate.update()
|
||||||
|
updateGameState()
|
||||||
|
playdate.graphics.animation.blinker.updateAll()
|
||||||
|
|
||||||
-- TODO: Show baserunning minimap when panning?
|
-- TODO: Show baserunning minimap when panning?
|
||||||
local ballBuffer = 5
|
local ballBuffer = 5
|
||||||
if ballY < ballBuffer then
|
if ballY < ballBuffer then
|
||||||
backgroundPan.y = math.max(ballBuffer, -1 * (ballY - ballBuffer))
|
backgroundPan.y = math.max(ballBuffer, -1 * (ballY - ballBuffer))
|
||||||
else backgroundPan.y = 0
|
else
|
||||||
|
backgroundPan.y = 0
|
||||||
end
|
end
|
||||||
if ballX < ballBuffer then
|
if ballX < ballBuffer then
|
||||||
backgroundPan.x = math.max(-400, -1 * (ballX - ballBuffer))
|
backgroundPan.x = math.max(-400, -1 * (ballX - ballBuffer))
|
||||||
|
@ -327,11 +416,6 @@ function updateGameState()
|
||||||
if ballX > 0 and ballX < (screenW - ballBuffer) then
|
if ballX > 0 and ballX < (screenW - ballBuffer) then
|
||||||
backgroundPan.x = 0
|
backgroundPan.x = 0
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
function playdate.update()
|
|
||||||
updateGameState()
|
|
||||||
playdate.graphics.animation.blinker.updateAll()
|
|
||||||
|
|
||||||
gfx.clear()
|
gfx.clear()
|
||||||
grassBackground:draw(backgroundPan.x - 400, backgroundPan.y - 240)
|
grassBackground:draw(backgroundPan.x - 400, backgroundPan.y - 240)
|
||||||
|
@ -358,13 +442,12 @@ function playdate.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO? Change blip speed depending on runner speed?
|
-- TODO? Change blip speed depending on runner speed?
|
||||||
|
for _,runner in pairs(runners) do
|
||||||
playerImageBlipper:draw(
|
playerImageBlipper:draw(
|
||||||
currentMode ~= MODES.running,
|
false,
|
||||||
player.x + backgroundPan.x,
|
runner.x + backgroundPan.x,
|
||||||
player.y + backgroundPan.y
|
runner.y + backgroundPan.y
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
-- for i,runner in pairs(runners) do
|
|
||||||
-- playerImage:draw(runner.x, runner.y)
|
|
||||||
-- end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,11 +2,40 @@ import 'CoreLibs/animation.lua'
|
||||||
import 'CoreLibs/graphics.lua'
|
import 'CoreLibs/graphics.lua'
|
||||||
|
|
||||||
--- 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
|
||||||
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
|
||||||
|
---@param array T[]
|
||||||
|
---@param condition fun(T): boolean
|
||||||
|
---@return T[]
|
||||||
|
function filter(array, condition)
|
||||||
|
local newArray = {}
|
||||||
|
for _,element in pairs(array) do
|
||||||
|
if condition(element) then
|
||||||
|
newArray[#newArray + 1] = element
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return newArray
|
||||||
|
end
|
||||||
|
|
||||||
|
---@generic TIn
|
||||||
|
---@generic TOut
|
||||||
|
---@param array TIn[]
|
||||||
|
---@param condition fun(TIn): TOut
|
||||||
|
---@return TOut[]
|
||||||
|
function map(array, mapper)
|
||||||
|
local newArray = {}
|
||||||
|
for _,element in pairs(array) do
|
||||||
|
newArray[#newArray + 1] = mapper(element)
|
||||||
|
end
|
||||||
|
return newArray
|
||||||
|
end
|
||||||
|
|
||||||
|
---@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
|
||||||
|
|
Loading…
Reference in New Issue