Backfill with <const>

Use PascalCase for all constants, explain in new README.
Split 'make check' into a separate command (less likely to shift your files out from under you than 'make lint')
Add __stub.ext.lua with quick explainer for why it's useful.
This commit is contained in:
Sage Vaillancourt 2025-02-01 17:45:10 -05:00
parent 32f9b24d1f
commit 4546902f2f
5 changed files with 210 additions and 148 deletions

View File

@ -1,6 +1,10 @@
SOURCE_FILES := src/utils.lua src/graphics.lua src/main.lua
all: all:
pdc src BatterUp.pdx pdc src BatterUp.pdx
lint: check:
cat __stub.ext.lua <(sed 's/^function/-- selene: allow(unused_variable)\nfunction/' ${PLAYDATE_SDK_PATH}/CoreLibs/__stub.lua) ${SOURCE_FILES} | grep -v '^import' | sed 's/<const>//g' | selene -
lint: check
stylua src/ stylua src/
cat <(sed 's/^function/-- selene: allow(unused_variable)\nfunction/' ${PLAYDATE_SDK_PATH}/CoreLibs/__stub.lua) src/utils.lua src/graphics.lua src/main.lua | grep -v '^import' | selene -

30
README.md Normal file
View File

@ -0,0 +1,30 @@
# Batter Up!
## Style Guide
This project uses StyLua for linting, and Selene for various other checks.
In addition to using `<const>`, where applicable, PascalCase is used for any
values which should **never** be mutated, including any elements in a table.
E.g.
```lua
local Screen <const> = { x = 400, y = 240 }
```
should be treated the same as
```lua
local ScreenX <const> = 400
local ScreenY <const> = 240
```
Though the compiler will not enforce this. Furthermore, PascalCase is also used
for values that may mutate *themselves* through method calls. For example,
instances of `playdate.graphics.animator`. The one exception is `gfx`
N.b. Selene does not understand `<const>`, so `make check` uses a `sed` hack to
filter them out.
`camelCase` tables may be `<const>`, but can still have mutable fields.

31
__stub.ext.lua Normal file
View File

@ -0,0 +1,31 @@
-- A small additional stub to keep Selene from complaining about playdate being missing.
-- Strictly, just putting `playdate = {}` would alleviate these warnings, but I've also
-- found that this quick structure, *actually* prepended to __stub.lua can help
-- SumnekoLua (an IntelliJ plugin) better provide autocompletion.
-- selene: allow(unused_variable)
-- selene: allow(unscoped_variables)
playdate = {
datastore = {},
display = {},
file = {},
frameTimer = {},
geometry = {},
graphics = {},
inputHandlers = {},
json = {},
keyboard = {},
math = {},
menu = {
item = {}
},
pathfinder = {},
simulator = {},
sound = {
sampleplayer = {}
},
string = {},
table = {},
timer = {},
ui = {}
}

View File

@ -1,4 +1,4 @@
local ballBuffer = 5 local ballBuffer <const> = 5
--- Assumes that background image is of size --- Assumes that background image is of size
--- XXX --- XXX
@ -7,7 +7,7 @@ local ballBuffer = 5
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
if ballY < ballBuffer then if ballY < ballBuffer then
offsetY = math.max(ballBuffer, -1 * (ballY - ballBuffer)) offsetY = math.max(ballBuffer, -1 * (ballY - ballBuffer))

View File

@ -18,85 +18,88 @@ import 'utils.lua'
--- @alias Fielder { onArrive: fun() | nil, x: number | nil, y: number | nil, target: XYPair | nil, speed: number } --- @alias Fielder { onArrive: fun() | nil, x: number | nil, y: number | nil, target: XYPair | nil, speed: number }
local gfx = playdate.graphics local gfx <const> = playdate.graphics
local SCREEN = { local Screen <const> = {
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 <const> = xy(Screen.W / 2, Screen.H / 2)
local batCrackSound = playdate.sound.sampleplayer.new("sounds/bat-crack-reverb.wav") local BatCrackSound <const> = playdate.sound.sampleplayer.new("sounds/bat-crack-reverb.wav")
local grassBackground = gfx.image.new("images/game/grass.png") --[[@as PlaydateGraphicsImage]] local GrassBackground <const> = gfx.image.new("images/game/grass.png") --[[@as PlaydateGraphicsImage]]
local playerFrown = gfx.image.new("images/game/player-frown.png") --[[@as PlaydateGraphicsImage]] local PlayerFrown <const> = gfx.image.new("images/game/player-frown.png") --[[@as PlaydateGraphicsImage]]
local playerImageBlipper = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png") local PlayerImageBlipper <const> = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png")
local BALL_OFFSCREEN = 999 local DanceBounceMs <const> = 500
local DanceBounceCount <const> = 4
local FielderDanceAnimator <const> = gfx.animator.new(DanceBounceMs, 10, 0, easingHill) --, -1 * DanceBounceMs * DanceBounceCount)
FielderDanceAnimator.repeatCount = DanceBounceCount - 1
local danceBounceMs = 500 local BallOffscreen <const> = 999
local danceBounceCount = 4
local fielderDanceAnimator = gfx.animator.new(danceBounceMs, 10, 0, easingHill) --, -1 * danceBounceMs * danceBounceCount)
fielderDanceAnimator.repeatCount = danceBounceCount - 1
local pitchFlyTimeMs = 2500 local PitchFlyMs <const> = 2500
local PITCH_START_X = 200 local PitchStartX <const> = 200
local PITCH_START_Y, PITCH_END_Y = 90, 250 local PitchStartY <const>, PitchEndY <const> = 90, 250
local pitchAnimator = gfx.animator.new(pitchFlyTimeMs, PITCH_START_Y, PITCH_END_Y, playdate.easingFunctions.outQuint)
local CRANK_OFFSET_DEG = 90 local PitchAnimator <const> = gfx.animator.new(PitchFlyMs, PitchStartY, PitchEndY, playdate.easingFunctions.outQuint)
local BAT_OFFSET = xy(10, 25)
local batBase = xy(CENTER.x - 34, 215)
local batTip = xy(0, 0)
local TAG_DISTANCE = 20 local CrankOffsetDeg <const> = 90
local BatOffset <const> = xy(10, 25)
local ball = { local batBase <const> = xy(Center.x - 34, 215)
x = CENTER.x, local batTip <const> = xy(0, 0)
y = CENTER.y,
local TagDistance <const> = 20
local ball <const> = {
x = Center.x,
y = Center.y,
size = 6, size = 6,
} }
local BAT_LENGTH = 45 local BatLength <const> = 45
local MODES = { local Modes <const> = {
batting = {}, batting = {},
running = {}, running = {},
} }
local currentMode = MODES.batting local currentMode = Modes.batting
local ballAnimatorY = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
local ballAnimatorX = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate.easingFunctions.linear)
local ballAnimatorY = gfx.animator.new(0, BALL_OFFSCREEN, BALL_OFFSCREEN, playdate.easingFunctions.linear)
local ballAnimatorX = gfx.animator.new(0, BALL_OFFSCREEN, BALL_OFFSCREEN, playdate.easingFunctions.linear)
-- TODO? Replace this AND ballSizeAnimator with a ballHeightAnimator -- TODO? Replace this AND ballSizeAnimator with a ballHeightAnimator
-- ...that might lose some of the magic of both. Compromise available? idk -- ...that might lose some of the magic of both. Compromise available? idk
local ballFloatAnimator = gfx.animator.new(2000, -60, 0, easingHill) local ballFloatAnimator <const> = gfx.animator.new(2000, -60, 0, easingHill)
local ballSizeMs = 2000 local BallSizeMs = 2000
local ballSizeAnimator = gfx.animator.new(ballSizeMs, 9, 6, easingHill) local ballSizeAnimator = gfx.animator.new(BallSizeMs, 9, 6, easingHill)
local HIT_MULT = 10 local HitMult = 10
local deltaSeconds = 0 local deltaSeconds = 0
local FIRST, SECOND, THIRD, HOME = 1, 2, 3, 4 local First <const>, Second <const>, Third <const>, Home <const> = 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),
} }
-- Pseudo-base for batter to target -- Pseudo-base for batter to target
local rightHandedBattersBox = 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>
local nextBaseMap = { local NextBaseMap <const> = {
[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)
@ -106,7 +109,7 @@ function newFielder(speed)
end end
---@type table<string, Fielder> ---@type table<string, Fielder>
local fielders = { local fielders <const> = {
first = newFielder(40), first = newFielder(40),
second = newFielder(40), second = newFielder(40),
shortstop = newFielder(40), shortstop = newFielder(40),
@ -118,9 +121,9 @@ local fielders = {
right = newFielder(40), right = newFielder(40),
} }
local PITCHER_POS = { local PitcherStartPos <const> = {
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).
@ -128,45 +131,40 @@ local PITCHER_POS = {
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(PitcherStartPos.x, PitcherStartPos.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 BatterStartingX <const> = Bases[Home].x - 40
local PLAYER_STARTING_Y = bases[HOME].y - 3 local BatterStartingY <const> = Bases[Home].y - 3
--- @type Runner[] --- @type Runner[]
local runners = {} local runners <const> = {}
--- @type Runner[] --- @type Runner[]
local outRunners = {} local outRunners <const> = {}
local nameI = 1
local runnerNames = { "Barbara", "Steve", "Jerry", "Beatrice" }
---@return Runner ---@return Runner
function newRunner() function newRunner()
local new = { local new = {
x = PLAYER_STARTING_X - 60, x = BatterStartingX - 60,
y = PLAYER_STARTING_Y + 60, y = BatterStartingY + 60,
nextBase = rightHandedBattersBox, nextBase = RightHandedBattersBox,
prevBase = nil, prevBase = nil,
name = runnerNames[nameI], forcedTo = Bases[First],
forcedTo = bases[FIRST]
} }
nameI = nameI + 1
runners[#runners + 1] = new runners[#runners + 1] = new
return new return new
end end
@ -188,10 +186,10 @@ function throwBall(destX, destY, easingFunc, flyTimeMs, floaty)
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, PitchStartX, PitchStartX, playdate.easingFunctions.linear)
ballAnimatorY = pitchAnimator ballAnimatorY = PitchAnimator
pitchAnimator:reset() PitchAnimator:reset()
end end
function playdate.AButtonDown() function playdate.AButtonDown()
@ -201,31 +199,23 @@ function playdate.AButtonDown()
pitch() pitch()
end end
function playdate.upButtonDown() function playdate.upButtonDown() end
batBase.y = batBase.y - 1
end
function playdate.downButtonDown() function playdate.downButtonDown() end
batBase.y = batBase.y + 1
end
function playdate.rightButtonDown() function playdate.rightButtonDown() end
batBase.x = batBase.x + 1
end
function playdate.leftButtonDown() function playdate.leftButtonDown() end
batBase.x = batBase.x - 1
end
local elapsedSec = 0 local elapsedSec = 0
local crankChange = 0 local crankChange = 0
local acceleratedChange local acceleratedChange
local BASE_HITBOX = 13 local BaseHitbox = 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) < BaseHitbox then
return base return base
end end
end end
@ -233,14 +223,12 @@ function isTouchingBase(x, y)
return nil return nil
end end
local BALL_CATCH_HITBOX = 3 local BallCatchHitbox = 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 < BallCatchHitbox
end end
function updateForcedTos() end
local outs = 0 local outs = 0
local homeScore = 0 local homeScore = 0
local awayScore = 0 local awayScore = 0
@ -249,15 +237,13 @@ function outRunner(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() FielderDanceAnimator:reset()
fielderDanceAnimator:reset()
end end
-- TODO: Away score -- TODO: Away score
function score(runnerIndex) function score(runnerIndex)
outRunners[#outRunners + 1] = runners[runnerIndex] outRunners[#outRunners + 1] = runners[runnerIndex]
table.remove(runners, runnerIndex) table.remove(runners, runnerIndex)
updateForcedTos()
homeScore = homeScore + 1 homeScore = homeScore + 1
end end
@ -283,14 +269,19 @@ function updateFielders()
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.prevBase and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase then if
touchedBase
and runner.prevBase
and runner.forcedTo == touchedBase
and touchedBase ~= runnerOnBase
then
outRunner(i) outRunner(i)
elseif not runnerOnBase then elseif not runnerOnBase then
if distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TAG_DISTANCE then if distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance then
outRunner(i) outRunner(i)
end end
end end
@ -304,14 +295,20 @@ end
function updateRunners() function updateRunners()
local autoRunSpeed = 20 local autoRunSpeed = 20
--autoRunSpeed = 140 --autoRunSpeed = 140
local currentRunners = currentMode == MODES.batting and {batter} or filter(runners, function(runner) local currentRunners = currentMode == Modes.batting and { batter }
return runner ~= batter or filter(runners, function(runner)
end) return runner ~= batter
end)
local runnerMoved = false local runnerMoved = false
for runnerIndex, runner in ipairs(currentRunners) do for runnerIndex, runner in ipairs(currentRunners) do
local nearestBase, nearestBaseDistance = getNearestOf(bases, runner.x, runner.y) local nearestBase, nearestBaseDistance = getNearestOf(Bases, runner.x, runner.y)
if nearestBaseDistance < 5 and runner.prevBase and runner.nextBase == bases[HOME] and nearestBase == bases[HOME] then if
nearestBaseDistance < 5
and runner.prevBase
and runner.nextBase == Bases[Home]
and nearestBase == Bases[Home]
then
score(runnerIndex) score(runnerIndex)
end end
if runner.nextBase then if runner.nextBase then
@ -331,7 +328,7 @@ function updateRunners()
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
@ -352,7 +349,7 @@ 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
@ -361,7 +358,6 @@ function getForcedOutTargets()
end end
end end
return targets return targets
-- 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
@ -369,7 +365,7 @@ end
function getBaseOfStrandedRunner() function getBaseOfStrandedRunner()
local farRunnersBase, farDistance local farRunnersBase, farDistance
for _, runner in pairs(runners) do for _, runner in pairs(runners) do
local nearestBase, distance = getNearestOf(bases, runner.x, runner.y, function(base) local nearestBase, distance = getNearestOf(Bases, runner.x, runner.y, function(base)
return runner.nextBase == base return runner.nextBase == base
end) end)
if farRunnersBase == nil or farDistance < distance then if farRunnersBase == nil or farDistance < distance then
@ -396,10 +392,10 @@ function getNextThrowTarget()
end end
end end
local resetFieldersAfterSeconds = 4 local ResetFieldersAfterSeconds = 4
local secondsSinceLastRunnerMove = 0 local secondsSinceLastRunnerMove = 0
local pitchAfterSeconds = 5 local PitchAfterSeconds = 5
local secondsSincePitchAllowed = -5 local secondsSincePitchAllowed = -5
function init() function init()
@ -407,32 +403,33 @@ function init()
gfx.setBackgroundColor(gfx.kColorWhite) gfx.setBackgroundColor(gfx.kColorWhite)
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png")) playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
resetFielderPositions(true) resetFielderPositions(true)
playdate.getSystemMenu():addMenuItem("Restart game", function() end)
end end
function updateBatting() function updateBatting()
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
if secondsSincePitchAllowed > pitchAfterSeconds then if secondsSincePitchAllowed > PitchAfterSeconds then
pitch() pitch()
secondsSincePitchAllowed = 0 secondsSincePitchAllowed = 0
end end
if ball.y < BALL_OFFSCREEN then if ball.y < BallOffscreen then
ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue() ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue()
ball.size = 6 ball.size = 6
end end
local batAngle = math.rad(playdate.getCrankPosition() + CRANK_OFFSET_DEG) local batAngle = math.rad(playdate.getCrankPosition() + CrankOffsetDeg)
-- TODO: animate bat-flip or something -- TODO: animate bat-flip or something
batBase.x = batter and (batter.x + BAT_OFFSET.x) or 0 batBase.x = batter and (batter.x + BatOffset.x) or 0
batBase.y = batter and (batter.y + BAT_OFFSET.y) or 0 batBase.y = batter and (batter.y + BatOffset.y) or 0
batTip.x = batBase.x + (BAT_LENGTH * math.sin(batAngle)) batTip.x = batBase.x + (BatLength * math.sin(batAngle))
batTip.y = batBase.y + (BAT_LENGTH * 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)
then then
batCrackSound:play() BatCrackSound:play()
currentMode = MODES.running currentMode = Modes.running
local ballAngle = batAngle + math.rad(90) local ballAngle = batAngle + math.rad(90)
local mult = math.abs(crankChange / 15) local mult = math.abs(crankChange / 15)
@ -442,15 +439,15 @@ function updateBatting()
ballVelX = ballVelX * -1 ballVelX = ballVelX * -1
ballVelY = ballVelY * -1 ballVelY = ballVelY * -1
end end
local ballDestX = ball.x + (ballVelX * HIT_MULT) local ballDestX = ball.x + (ballVelX * HitMult)
local ballDestY = ball.y + (ballVelY * HIT_MULT) local ballDestY = ball.y + (ballVelY * HitMult)
-- Hit! -- Hit!
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.prevBase = bases[HOME] batter.prevBase = Bases[Home]
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)
@ -472,10 +469,10 @@ function updateRunning()
secondsSinceLastRunnerMove = 0 secondsSinceLastRunnerMove = 0
else else
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
if secondsSinceLastRunnerMove > resetFieldersAfterSeconds then if secondsSinceLastRunnerMove > ResetFieldersAfterSeconds then
throwBall(PITCH_START_X, PITCH_START_Y, playdate.easingFunctions.linear, nil, true) throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true)
resetFielderPositions(false) resetFielderPositions(false)
currentMode = MODES.batting currentMode = Modes.batting
batter = newRunner() batter = newRunner()
end end
end end
@ -483,7 +480,7 @@ end
function updateOutRunners() function updateOutRunners()
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)
runner.y = runner.y + (deltaSeconds * 25) runner.y = runner.y + (deltaSeconds * 25)
else else
@ -501,10 +498,10 @@ function updateGameState()
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()
updateRunners() updateRunners()
elseif currentMode == MODES.running then elseif currentMode == Modes.running then
updateRunning() updateRunning()
end end
@ -516,8 +513,8 @@ local OUT_BUBBLE_SIZE = 6
function drawScoreboard() function drawScoreboard()
gfx.setDrawOffset(0, 0) gfx.setDrawOffset(0, 0)
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)
@ -544,40 +541,40 @@ function playdate.update()
gfx.clear() gfx.clear()
if ball.x < BALL_OFFSCREEN then if ball.x < BallOffscreen 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(batBase.x, batBase.y, batTip.x, batTip.y) gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y)
end 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()