diff --git a/Makefile b/Makefile
index 7ab9abb..21a7fc3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-SOURCE_FILES := src/constants.lua src/draw/* src/utils.lua src/dbg.lua src/npc.lua src/announcer.lua src/graphics.lua src/scoreboard.lua src/main.lua
+SOURCE_FILES := src/utils.lua src/constants.lua src/draw/* src/dbg.lua src/npc.lua src/announcer.lua src/graphics.lua src/main.lua
all:
pdc src BatterUp.pdx
diff --git a/src/constants.lua b/src/constants.lua
index 663edeb..e112d31 100644
--- a/src/constants.lua
+++ b/src/constants.lua
@@ -11,3 +11,88 @@ C.Center = utils.xy(C.Screen.W / 2, C.Screen.H / 2)
C.StrikeZoneStartX = C.Center.x - 16
C.StrikeZoneEndX = C.StrikeZoneStartX + 24
C.StrikeZoneStartY = C.Screen.H - 35
+
+--- @alias Base {
+--- x: number,
+--- y: number,
+--- }
+
+---@type Base[]
+C.Bases = {
+ utils.xy(C.Screen.W * 0.93, C.Screen.H * 0.52),
+ utils.xy(C.Screen.W * 0.47, C.Screen.H * 0.19),
+ utils.xy(C.Screen.W * 0.03, C.Screen.H * 0.52),
+ utils.xy(C.Screen.W * 0.474, C.Screen.H * 0.79),
+}
+
+C.First, C.Second, C.Third, C.Home = 1, 2, 3, 4
+C.FieldHeight = C.Bases[C.Home].y - C.Bases[C.Second].y
+
+-- Pseudo-base for batter to target
+C.RightHandedBattersBox = {
+ x = C.Bases[C.Home].x - 35,
+ y = C.Bases[C.Home].y,
+}
+
+---@type table
+C.NextBaseMap = {
+ [C.RightHandedBattersBox] = nil, -- Runner should not escape the box before a hit!
+ [C.Bases[C.First]] = C.Bases[C.Second],
+ [C.Bases[C.Second]] = C.Bases[C.Third],
+ [C.Bases[C.Third]] = C.Bases[C.Home],
+}
+
+--- Angle to align the bat to
+C.CrankOffsetDeg = 90
+
+C.DanceBounceMs = 500
+C.DanceBounceCount = 4
+
+--- Used to draw the ball well out of bounds, and
+--- generally as a check for whether or not it's in play.
+C.BallOffscreen = 999
+
+C.PitchAfterSeconds = 7
+C.PitchFlyMs = 1050
+C.PitchStartX = 195
+C.PitchStartY, C.PitchEndY = 105, 240
+
+--- The max distance at which a fielder can tag out a runner.
+C.TagDistance = 15
+
+--- The max distance at which a runner can be considered on base.
+C.BaseHitbox = 10
+
+C.BattingPower = 20
+
+C.SmallestBallRadius = 6
+
+C.BatLength = 50
+
+--- An enum for what state the offense is in
+C.Offense = {
+ batting = {},
+ running = {},
+ walking = {},
+}
+
+--- An enum for which side (offense or defense) a team is on.
+C.Sides = {
+ offense = {},
+ defense = {},
+}
+
+C.PitcherStartPos = {
+ x = C.Screen.W * 0.48,
+ y = C.Screen.H * 0.40,
+}
+
+C.ThrowMeterMax = 15
+C.ThrowMeterDrainPerSec = 150
+
+--- Controls how hard the ball can be hit, and
+--- how fast the ball can be thrown.
+C.CrankPower = 10
+
+--- How fast baserunners move after a walk
+C.WalkedRunnerSpeed = 10
diff --git a/src/dbg.lua b/src/dbg.lua
index 6206c8a..1cba6fc 100644
--- a/src/dbg.lua
+++ b/src/dbg.lua
@@ -20,21 +20,21 @@ end
-- Only works if called with the bases empty (i.e. the only runner should be the batter.
-- selene: allow(unused_variable)
-function dbg.loadTheBases(baseConstants, runners)
+function dbg.loadTheBases(runners)
newRunner()
newRunner()
newRunner()
- runners[2].x = baseConstants[1].x
- runners[2].y = baseConstants[1].y
- runners[2].nextBase = baseConstants[2]
+ runners[2].x = C.Bases[C.First].x
+ runners[2].y = C.Bases[C.First].y
+ runners[2].nextBase = C.Bases[C.Second]
- runners[3].x = baseConstants[2].x
- runners[3].y = baseConstants[2].y
- runners[3].nextBase = baseConstants[3]
+ runners[3].x = C.Bases[C.Second].x
+ runners[3].y = C.Bases[C.Second].y
+ runners[3].nextBase = C.Bases[C.Third]
- runners[4].x = baseConstants[3].x
- runners[4].y = baseConstants[3].y
- runners[4].nextBase = baseConstants[4]
+ runners[4].x = C.Bases[C.Third].x
+ runners[4].y = C.Bases[C.Third].y
+ runners[4].nextBase = C.Bases[C.Home]
end
if not playdate then
diff --git a/src/scoreboard.lua b/src/draw/overlay.lua
similarity index 78%
rename from src/scoreboard.lua
rename to src/draw/overlay.lua
index 58e7336..6e1340a 100644
--- a/src/scoreboard.lua
+++ b/src/draw/overlay.lua
@@ -2,27 +2,31 @@
local gfx = playdate.graphics
local ScoreFont = playdate.graphics.font.new("fonts/font-full-circle.pft")
-local OutBubbleRadius = 5
-local ScoreboardMarginX = 6
-local ScoreboardMarginRight = 4
-local ScoreboardHeight = 55
-local Indicator = "> "
-local IndicatorWidth = ScoreFont:getTextWidth(Indicator)
+
+local MinimapBackground = gfx.image.new("images/game/minimap.png") --[[@as pd_image]]
+
+local MinimapSizeX, MinimapSizeY = MinimapBackground:getSize()
+local MinimapPosX, MinimapPosY = C.Screen.W - MinimapSizeX, C.Screen.H - MinimapSizeY
+
+local MinimapMultX = 0.75 * MinimapSizeX / C.Screen.W
+local MinimapOffsetX = MinimapPosX + 5
+local MinimapMultY = 0.70 * MinimapSizeY / C.FieldHeight
+local MinimapOffsetY = MinimapPosY - 15
+
+function drawMinimap(runners)
+ MinimapBackground:draw(MinimapPosX, MinimapPosY)
+ gfx.setColor(gfx.kColorBlack)
+ for _, runner in pairs(runners) do
+ local x = (MinimapMultX * runner.x) + MinimapOffsetX
+ local y = (MinimapMultY * runner.y) + MinimapOffsetY
+ gfx.fillRect(x, y, 8, 8)
+ end
+end
local BallStrikeMarginY = 4
local BallStrikeWidth = 60
local BallStrikeHeight = (BallStrikeMarginY * 2) + ScoreFont:getHeight()
----@param teams any
----@param battingTeam any
----@return string, number, string, number
-function getIndicators(teams, battingTeam)
- if teams.home == battingTeam then
- return Indicator, 0, "", IndicatorWidth
- end
- return "", IndicatorWidth, Indicator, 0
-end
-
function drawBallsAndStrikes(x, y, balls, strikes)
if balls == 0 and strikes == 0 then
return
@@ -40,6 +44,23 @@ function drawBallsAndStrikes(x, y, balls, strikes)
gfx.setImageDrawMode(originalDrawMode)
end
+local OutBubbleRadius = 5
+local ScoreboardMarginX = 6
+local ScoreboardMarginRight = 4
+local ScoreboardHeight = 55
+local Indicator = "> "
+local IndicatorWidth = ScoreFont:getTextWidth(Indicator)
+
+---@param teams any
+---@param battingTeam any
+---@return string, number, string, number
+function getIndicators(teams, battingTeam)
+ if teams.home == battingTeam then
+ return Indicator, 0, "", IndicatorWidth
+ end
+ return "", IndicatorWidth, Indicator, 0
+end
+
function drawScoreboard(x, y, teams, outs, battingTeam, inning)
local homeScore = teams.home.score
local awayScore = teams.away.score
diff --git a/src/main.lua b/src/main.lua
index 58e00a1..9c88885 100644
--- a/src/main.lua
+++ b/src/main.lua
@@ -7,16 +7,6 @@ import 'CoreLibs/object.lua'
import 'CoreLibs/timer.lua'
import 'CoreLibs/ui.lua'
---- @alias XYPair {
---- x: number,
---- y: number,
---- }
-
---- @alias Base {
---- x: number,
---- y: number,
---- }
-
--- @alias Runner {
--- x: number,
--- y: number,
@@ -41,44 +31,37 @@ import 'announcer.lua'
import 'dbg.lua'
import 'graphics.lua'
import 'npc.lua'
-import 'scoreboard.lua'
+import 'draw/overlay'
import 'draw/fielder'
-- stylua: ignore end
-- selene: allow(shadowing)
local gfx = playdate.graphics
+-- selene: allow(shadowing)
+local C = C
+
local BootTune = playdate.sound.sampleplayer.new("sounds/boot-tune.wav")
-- local BootTune = playdate.sound.sampleplayer.new("sounds/boot-tune-organy.wav")
local TinnyBackground = playdate.sound.sampleplayer.new("sounds/tinny-background.wav")
local BatCrackSound = playdate.sound.sampleplayer.new("sounds/bat-crack-reverb.wav")
local GrassBackground = gfx.image.new("images/game/grass.png") --[[@as pd_image]]
-local Minimap = gfx.image.new("images/game/minimap.png") --[[@as pd_image]]
local PlayerFrown = gfx.image.new("images/game/player-frown.png") --[[@as pd_image]]
local PlayerSmile = gfx.image.new("images/game/player.png") --[[@as pd_image]]
local PlayerBack = gfx.image.new("images/game/player-back.png") --[[@as pd_image]]
local PlayerImageBlipper = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png")
-local DanceBounceMs = 500
-local DanceBounceCount = 4
-
local FielderDanceAnimator = gfx.animator.new(1, 10, 0, utils.easingHill)
-FielderDanceAnimator.repeatCount = DanceBounceCount - 1
+FielderDanceAnimator.repeatCount = C.DanceBounceCount - 1
-- selene: allow(unused_variable)
function fieldersDance()
- FielderDanceAnimator:reset(DanceBounceMs)
+ FielderDanceAnimator:reset(C.DanceBounceMs)
end
-local BallOffscreen = 999
-
-local PitchFlyMs = 1050
-local PitchStartX = 195
-local PitchStartY , PitchEndY = 105, 240
-
-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, C.BallOffscreen, C.BallOffscreen, playdate.easingFunctions.linear)
+local ballAnimatorX = gfx.animator.new(0, C.BallOffscreen, C.BallOffscreen, playdate.easingFunctions.linear)
---@alias PseudoAnimator { currentValue: fun(self): number; reset: fun(self, durationMs: number | nil) }
---@alias Pitch { x: PseudoAnimator, y: PseudoAnimator, z: PseudoAnimator | nil }
@@ -87,64 +70,44 @@ local ballAnimatorX = gfx.animator.new(0, BallOffscreen, BallOffscreen, playdate
local Pitches = {
-- Fastball
{
- x = gfx.animator.new(0, PitchStartX, PitchStartX, playdate.easingFunctions.linear),
- y = gfx.animator.new(PitchFlyMs / 1.3, PitchStartY, PitchEndY, playdate.easingFunctions.linear),
+ x = gfx.animator.new(0, C.PitchStartX, C.PitchStartX, playdate.easingFunctions.linear),
+ y = gfx.animator.new(C.PitchFlyMs / 1.3, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
},
-- Curve ball
{
- x = gfx.animator.new(PitchFlyMs, PitchStartX + 20, PitchStartX, utils.easingHill),
- y = gfx.animator.new(PitchFlyMs, PitchStartY, PitchEndY, playdate.easingFunctions.linear),
+ x = gfx.animator.new(C.PitchFlyMs, C.PitchStartX + 20, C.PitchStartX, utils.easingHill),
+ y = gfx.animator.new(C.PitchFlyMs, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
},
-- Slider
{
- x = gfx.animator.new(PitchFlyMs, PitchStartX - 20, PitchStartX, utils.easingHill),
- y = gfx.animator.new(PitchFlyMs, PitchStartY, PitchEndY, playdate.easingFunctions.linear),
+ x = gfx.animator.new(C.PitchFlyMs, C.PitchStartX - 20, C.PitchStartX, utils.easingHill),
+ y = gfx.animator.new(C.PitchFlyMs, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
},
-- Wobbbleball
{
x = {
currentValue = function()
- return PitchStartX + (10 * math.sin((ballAnimatorY:currentValue() - PitchStartY) / 10))
+ return C.PitchStartX + (10 * math.sin((ballAnimatorY:currentValue() - C.PitchStartY) / 10))
end,
reset = function() end,
},
- y = gfx.animator.new(PitchFlyMs * 1.3, PitchStartY, PitchEndY, playdate.easingFunctions.linear),
+ y = gfx.animator.new(C.PitchFlyMs * 1.3, C.PitchStartY, C.PitchEndY, playdate.easingFunctions.linear),
},
}
-local CrankOffsetDeg = 90
-local BatOffset = utils.xy(10, 25)
+local BatterHandPos = utils.xy(10, 25)
local batBase = utils.xy(C.Center.x - 34, 215)
local batTip = utils.xy(0, 0)
-local TagDistance = 15
-
-local SmallestBallRadius = 6
-
local ball = {
x = C.Center.x --[[@as number]],
y = C.Center.y --[[@as number]],
z = 0,
- size = SmallestBallRadius,
+ size = C.SmallestBallRadius,
heldBy = nil --[[@type Runner | nil]],
}
-local BatLength = 50
-
-local Offense = {
- batting = {},
- running = {},
- walking = {},
-}
-
-local Sides = {
- offense = {},
- defense = {},
-}
-
-local offenseMode = Offense.batting
-
---@alias Team { score: number, benchPosition: XYPair }
---@type table
@@ -163,14 +126,15 @@ local PlayerTeam = teams.home
local battingTeam = teams.away
local outs = 0
local inning = 1
+local offenseMode = C.Offense.batting
---@return boolean playerIsOnSide, boolean playerIsOnOtherSide
function playerIsOn(side)
local ret
if PlayerTeam == battingTeam then
- ret = side == Sides.offense
+ ret = side == C.Sides.offense
else
- ret = side == Sides.defense
+ ret = side == C.Sides.defense
end
return ret, not ret
end
@@ -179,33 +143,10 @@ end
-- ...that might lose some of the magic of both. Compromise available? idk
local ballFloatAnimator = gfx.animator.new(2000, -60, 0, utils.easingHill)
local BallSizeMs = 2000
-local ballSizeAnimator = gfx.animator.new(BallSizeMs, 9, SmallestBallRadius, utils.easingHill)
-
-local HitMult = 20
+local ballSizeAnimator = gfx.animator.new(BallSizeMs, 9, C.SmallestBallRadius, utils.easingHill)
local deltaSeconds = 0
-local First , Second , Third , Home = 1, 2, 3, 4
-
----@type Base[]
-local Bases = {
- utils.xy(C.Screen.W * 0.93, C.Screen.H * 0.52),
- utils.xy(C.Screen.W * 0.47, C.Screen.H * 0.19),
- utils.xy(C.Screen.W * 0.03, C.Screen.H * 0.52),
- utils.xy(C.Screen.W * 0.474, C.Screen.H * 0.79),
-}
-
--- Pseudo-base for batter to target
-local RightHandedBattersBox = utils.xy(Bases[Home].x - 35, Bases[Home].y)
-
----@type table
-local NextBaseMap = {
- [RightHandedBattersBox] = nil, -- Runner should not escape the box before a hit!
- [Bases[First]] = Bases[Second],
- [Bases[Second]] = Bases[Third],
- [Bases[Third]] = Bases[Home],
-}
-
---@param name string
---@param speed number
---@return Fielder
@@ -229,11 +170,6 @@ local fielders = {
right = newFielder("Right", 40),
}
-local PitcherStartPos = {
- x = C.Screen.W * 0.48,
- y = C.Screen.H * 0.40,
-}
-
--- Actually only benches the infield, because outfielders are far away!
---@param position XYPair
function benchAllFielders(position)
@@ -259,16 +195,13 @@ function resetFielderPositions(fromOffTheField)
fielders.second.target = utils.xy(C.Screen.W * 0.70, C.Screen.H * 0.30)
fielders.shortstop.target = utils.xy(C.Screen.W * 0.30, C.Screen.H * 0.30)
fielders.third.target = utils.xy(C.Screen.W * 0.1, C.Screen.H * 0.48)
- fielders.pitcher.target = utils.xy(PitcherStartPos.x, PitcherStartPos.y)
+ fielders.pitcher.target = utils.xy(C.PitcherStartPos.x, C.PitcherStartPos.y)
fielders.catcher.target = utils.xy(C.Screen.W * 0.475, C.Screen.H * 0.92)
fielders.left.target = utils.xy(C.Screen.W * -1, C.Screen.H * -0.2)
fielders.center.target = utils.xy(C.Center.x, C.Screen.H * -0.4)
fielders.right.target = utils.xy(C.Screen.W * 2, fielders.left.target.y)
end
-local BatterStartingX = Bases[Home].x - 40
-local BatterStartingY = Bases[Home].y - 3
-
--- @type Runner[]
local runners = {}
@@ -278,11 +211,11 @@ local outRunners = {}
---@return Runner
function newRunner()
local new = {
- x = BatterStartingX - 60,
- y = BatterStartingY + 60,
- nextBase = RightHandedBattersBox,
+ x = C.RightHandedBattersBox.x - 60,
+ y = C.RightHandedBattersBox.y + 60,
+ nextBase = C.RightHandedBattersBox,
prevBase = nil,
- forcedTo = Bases[First],
+ forcedTo = C.Bases[C.First],
}
runners[#runners + 1] = new
return new
@@ -292,7 +225,6 @@ end
local batter = newRunner()
local throwMeter = 0
-local PitchMeterLimit = 15
--- "Throws" the ball from its current position to the given destination.
---@param destX number
@@ -302,35 +234,35 @@ local PitchMeterLimit = 15
---@param floaty boolean | nil
---@param customBallScaler pd_animator | nil
function throwBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
+ ball.heldBy = nil
+ throwMeter = 0
+
if not flyTimeMs then
flyTimeMs = utils.distanceBetween(ball.x, ball.y, destX, destY) * 5
end
- ball.heldBy = nil
+
if customBallScaler then
ballSizeAnimator = customBallScaler
else
-- TODO? Scale based on distance?
- ballSizeAnimator = gfx.animator.new(flyTimeMs, 9, SmallestBallRadius, utils.easingHill)
+ ballSizeAnimator = gfx.animator.new(flyTimeMs, 9, C.SmallestBallRadius, utils.easingHill)
end
ballAnimatorY = gfx.animator.new(flyTimeMs, ball.y, destY, easingFunc)
ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc)
if floaty then
ballFloatAnimator:reset(flyTimeMs)
end
- throwMeter = 0
end
-local PitchAfterSeconds = 7
-- TODO: Replace with a timer, repeatedly reset instead of setting to 0
local secondsSincePitchAllowed = -5
-
local catcherThrownBall = false
---@param pitchFlyTimeMs number | nil
---@param pitchTypeIndex number | nil
function pitch(pitchFlyTimeMs, pitchTypeIndex)
catcherThrownBall = false
- offenseMode = Offense.batting
+ offenseMode = C.Offense.batting
local current = Pitches[pitchTypeIndex]
ballAnimatorX = current.x
@@ -353,15 +285,13 @@ function pitch(pitchFlyTimeMs, pitchTypeIndex)
secondsSincePitchAllowed = 0
end
-local BaseHitbox = 10
-
--- 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)
- return utils.first(Bases, function(base)
- return utils.distanceBetween(x, y, base.x, base.y) < BaseHitbox
+ return utils.first(C.Bases, function(base)
+ return utils.distanceBetween(x, y, base.x, base.y) < C.BaseHitbox
end)
end
@@ -386,7 +316,7 @@ end
function updateForcedRunners()
local stillForced = true
- for _, base in ipairs(Bases) do
+ for _, base in ipairs(C.Bases) do
local runnerTargetingBase = getRunnerWithNextBase(base)
if runnerTargetingBase then
if stillForced then
@@ -462,7 +392,7 @@ end
---@return Base[]
function getForcedOutTargets()
local targets = {}
- for _, base in ipairs(Bases) do
+ for _, base in ipairs(C.Bases) do
local runnerTargetingBase = getRunnerWithNextBase(base)
if runnerTargetingBase then
targets[#targets + 1] = base
@@ -479,7 +409,7 @@ function getBaseOfStrandedRunner()
local farRunnersBase, farDistance
for _, runner in pairs(runners) do
if runner ~= batter then
- local nearestBase, distance = utils.getNearestOf(Bases, runner.x, runner.y)
+ local nearestBase, distance = utils.getNearestOf(C.Bases, runner.x, runner.y)
if farRunnersBase == nil or farDistance < distance then
farRunnersBase = nearestBase
farDistance = distance
@@ -520,8 +450,8 @@ function tryToMakeAnOut(fielder)
end
function readThrow()
- if throwMeter > PitchMeterLimit then
- return (PitchFlyMs / (throwMeter / PitchMeterLimit))
+ if throwMeter > C.ThrowMeterMax then
+ return (C.PitchFlyMs / (throwMeter / C.ThrowMeterMax))
end
return nil
end
@@ -532,13 +462,13 @@ end
function buttonControlledThrow(thrower, throwFlyMs, forbidThrowHome)
local targetBase
if playdate.buttonIsPressed(playdate.kButtonLeft) then
- targetBase = Bases[Third]
+ targetBase = C.Bases[C.Third]
elseif playdate.buttonIsPressed(playdate.kButtonUp) then
- targetBase = Bases[Second]
+ targetBase = C.Bases[C.Second]
elseif playdate.buttonIsPressed(playdate.kButtonRight) then
- targetBase = Bases[First]
+ targetBase = C.Bases[C.First]
elseif not forbidThrowHome and playdate.buttonIsPressed(playdate.kButtonDown) then
- targetBase = Bases[Home]
+ targetBase = C.Bases[C.Home]
else
return false
end
@@ -550,7 +480,7 @@ function buttonControlledThrow(thrower, throwFlyMs, forbidThrowHome)
throwBall(targetBase.x, targetBase.y, playdate.easingFunctions.linear, throwFlyMs)
closestFielder.target = targetBase
secondsSinceLastRunnerMove = 0
- offenseMode = Offense.running
+ offenseMode = C.Offense.running
return true
end
@@ -566,7 +496,7 @@ function outEligibleRunners(fielder)
and runner.forcedTo == touchedBase
and touchedBase ~= runnerOnBase
-- Tag out
- or not runnerOnBase and utils.distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance
+ or not runnerOnBase and utils.distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < C.TagDistance
then
outRunner(i)
didOutRunner = true
@@ -577,7 +507,7 @@ function outEligibleRunners(fielder)
end
function updateNpcFielder(fielder, outedSomeRunner)
- if offenseMode ~= Offense.running then
+ if offenseMode ~= C.Offense.running then
return
end
if outedSomeRunner then
@@ -603,7 +533,7 @@ function updateFielder(fielder)
local outedSomeRunner = outEligibleRunners(fielder)
- if playerIsOn(Sides.defense) then
+ if playerIsOn(C.Sides.defense) then
local throwFly = readThrow()
if throwFly then
buttonControlledThrow(fielders.pitcher, throwFly)
@@ -626,14 +556,14 @@ function updateRunner(runner, runnerIndex, appliedSpeed)
return false
end
- local nearestBase, nearestBaseDistance = utils.getNearestOf(Bases, runner.x, runner.y)
+ local nearestBase, nearestBaseDistance = utils.getNearestOf(C.Bases, runner.x, runner.y)
if
nearestBaseDistance < 5
and runnerIndex ~= nil
and runner ~= batter --runner.prevBase
- and runner.nextBase == Bases[Home]
- and nearestBase == Bases[Home]
+ and runner.nextBase == C.Bases[C.Home]
+ and nearestBase == C.Bases[C.Home]
then
score(runnerIndex)
end
@@ -642,7 +572,7 @@ function updateRunner(runner, runnerIndex, appliedSpeed)
local x, y, distance = utils.normalizeVector(runner.x, runner.y, nb.x, nb.y)
if distance < 2 then
- runner.nextBase = NextBaseMap[runner.nextBase]
+ runner.nextBase = C.NextBaseMap[runner.nextBase]
runner.forcedTo = nil
return false
end
@@ -683,10 +613,10 @@ end
function walk()
announcer:say("Walk!")
- fielders.first.target = Bases[First]
- batter.nextBase = Bases[First]
- batter.prevBase = Bases[Home]
- offenseMode = Offense.walking
+ fielders.first.target = C.Bases[C.First]
+ batter.nextBase = C.Bases[C.First]
+ batter.prevBase = C.Bases[C.Home]
+ offenseMode = C.Offense.walking
batter = nil
updateForcedRunners()
nextBatter()
@@ -701,25 +631,25 @@ end
---@param batDeg number
function updateBatting(batDeg, batSpeed)
- if ball.y < BallOffscreen then
+ if ball.y < C.BallOffscreen then
ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue()
- ball.size = SmallestBallRadius -- ballFloatAnimator:currentValue()
+ ball.size = C.SmallestBallRadius -- ballFloatAnimator:currentValue()
end
local batAngle = math.rad(batDeg)
-- TODO: animate bat-flip or something
- batBase.x = batter and (batter.x + BatOffset.x) or 0
- batBase.y = batter and (batter.y + BatOffset.y) or 0
- batTip.x = batBase.x + (BatLength * math.sin(batAngle))
- batTip.y = batBase.y + (BatLength * math.cos(batAngle))
+ batBase.x = batter and (batter.x + BatterHandPos.x) or 0
+ batBase.y = batter and (batter.y + BatterHandPos.y) or 0
+ batTip.x = batBase.x + (C.BatLength * math.sin(batAngle))
+ batTip.y = batBase.y + (C.BatLength * math.cos(batAngle))
if
batSpeed > 0
and utils.pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, C.Screen.H)
- and ball.y < 232 --not isTouchingBall(fielders.catcher.x, fielders.catcher.y)
+ and ball.y < 232
then
BatCrackSound:play()
- offenseMode = Offense.running
+ offenseMode = C.Offense.running
local ballAngle = batAngle + math.rad(90)
local mult = math.abs(batSpeed / 15)
@@ -729,23 +659,17 @@ function updateBatting(batDeg, batSpeed)
ballVelX = ballVelX * -1
ballVelY = ballVelY * -1
end
- local ballDestX = ball.x + (ballVelX * HitMult)
- local ballDestY = ball.y + (ballVelY * HitMult)
+ local ballDestX = ball.x + (ballVelX * C.BattingPower)
+ local ballDestY = ball.y + (ballVelY * C.BattingPower)
-- Hit!
- throwBall(
- ballDestX,
- ballDestY,
- playdate.easingFunctions.outQuint,
- 2000,
- nil,
- gfx.animator.new(2000, 9 + (mult * mult * 0.5), SmallestBallRadius, utils.easingHill)
- )
+ local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill)
+ throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000, nil, hitBallScaler)
- fielders.first.target = Bases[First]
- batter.nextBase = Bases[First]
- batter.prevBase = Bases[Home]
+ fielders.first.target = C.Bases[C.First]
+ batter.nextBase = C.Bases[C.First]
+ batter.prevBase = C.Bases[C.Home]
updateForcedRunners()
- batter.forcedTo = Bases[First]
+ batter.forcedTo = C.Bases[C.First]
batter = nil -- Demote batter to a mere runner
local chasingFielder = utils.getNearestOf(fielders, ballDestX, ballDestY)
@@ -801,7 +725,7 @@ function updateGameState()
deltaSeconds = playdate.getElapsedTime() or 0
playdate.resetElapsedTime()
local crankChange = playdate.getCrankChange() --[[@as number]]
- local crankLimited = crankChange == 0 and 0 or (math.log(math.abs(crankChange)) * 10)
+ local crankLimited = crankChange == 0 and 0 or (math.log(math.abs(crankChange)) * C.CrankPower)
if crankChange < 0 then
crankLimited = crankLimited * -1
end
@@ -815,21 +739,22 @@ function updateGameState()
ball.y = ballAnimatorY:currentValue() + ball.z
end
- local playerOnOffense, playerOnDefense = playerIsOn(Sides.offense)
+ local playerOnOffense, playerOnDefense = playerIsOn(C.Sides.offense)
if playerOnDefense then
- throwMeter = math.max(0, throwMeter - (deltaSeconds * 150))
+ throwMeter = math.max(0, throwMeter - (deltaSeconds * C.ThrowMeterDrainPerSec))
throwMeter = throwMeter + math.abs(crankLimited)
end
- if offenseMode == Offense.batting then
+ if offenseMode == C.Offense.batting then
if ball.y < C.StrikeZoneStartY then
pitchTracker.recordedPitchX = nil
elseif not pitchTracker.recordedPitchX then
pitchTracker.recordedPitchX = ball.x
end
- if utils.distanceBetween(fielders.pitcher.x, fielders.pitcher.y, PitchStartX, PitchStartY) < BaseHitbox then
+ local pitcher = fielders.pitcher
+ if utils.distanceBetween(pitcher.x, pitcher.y, C.PitchStartX, C.PitchStartY) < C.BaseHitbox then
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
end
@@ -840,13 +765,13 @@ function updateGameState()
elseif outcome == PitchOutcomes.Walk then
walk()
end
- throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true)
+ throwBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
catcherThrownBall = true
end
local batSpeed
if playerOnOffense then
- batAngleDeg = (playdate.getCrankPosition() + CrankOffsetDeg) % 360
+ batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360
batSpeed = crankLimited
else
batAngleDeg = npc.updateBatAngle(ball, catcherThrownBall, deltaSeconds)
@@ -858,35 +783,35 @@ function updateGameState()
-- TODO: Ensure batter can't be nil, here
updateRunner(batter, nil, crankLimited)
- if secondsSincePitchAllowed > PitchAfterSeconds then
+ if secondsSincePitchAllowed > C.PitchAfterSeconds then
if playerOnDefense then
local throwFly = readThrow()
- if throwFly and not buttonControlledThrow(fielders.pitcher, throwFly, true) then
+ if throwFly and not buttonControlledThrow(pitcher, throwFly, true) then
playerPitch(throwFly)
end
else
- pitch(PitchFlyMs, math.random(#Pitches))
+ pitch(C.PitchFlyMs, math.random(#Pitches))
end
end
- elseif offenseMode == Offense.running then
- local appliedSpeed = playerOnOffense and crankLimited or npc.runningSpeed(Bases, runners)
+ elseif offenseMode == C.Offense.running then
+ local appliedSpeed = playerOnOffense and crankLimited or npc.runningSpeed(runners)
if updateRunning(appliedSpeed) then
secondsSinceLastRunnerMove = 0
else
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
if secondsSinceLastRunnerMove > ResetFieldersAfterSeconds then
- throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true)
+ throwBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
resetFielderPositions()
- offenseMode = Offense.batting
+ offenseMode = C.Offense.batting
if not batter then
batter = newRunner()
end
end
end
- elseif offenseMode == Offense.walking then
+ elseif offenseMode == C.Offense.walking then
updateForcedRunners()
- if not updateRunning(10, true) then
- offenseMode = Offense.batting
+ if not updateRunning(C.WalkedRunnerSpeed, true) then
+ offenseMode = C.Offense.batting
end
end
@@ -896,26 +821,6 @@ function updateGameState()
walkAwayOutRunners()
end
-local MinimapSizeX, MinimapSizeY = Minimap:getSize()
-local MinimapPosX, MinimapPosY = C.Screen.W - MinimapSizeX, C.Screen.H - MinimapSizeY
-
-local FieldHeight = Bases[Home].y - Bases[Second].y
-
-local MinimapMultX = 0.75 * MinimapSizeX / C.Screen.W
-local MinimapOffsetX = MinimapPosX + 5
-local MinimapMultY = 0.70 * MinimapSizeY / FieldHeight
-local MinimapOffsetY = MinimapPosY - 15
-
-function drawMinimap()
- Minimap:draw(MinimapPosX, MinimapPosY)
- gfx.setColor(gfx.kColorBlack)
- for _, runner in pairs(runners) do
- local x = (MinimapMultX * runner.x) + MinimapOffsetX
- local y = (MinimapMultY * runner.y) + MinimapOffsetY
- gfx.fillRect(x, y, 8, 8)
- end
-end
-
function playdate.update()
playdate.timer.updateTimers()
gfx.animation.blinker.updateAll()
@@ -925,7 +830,7 @@ function playdate.update()
gfx.setColor(gfx.kColorBlack)
local offsetX, offsetY = 0, 0
- if ball.x < BallOffscreen then
+ if ball.x < C.BallOffscreen then
offsetX, offsetY = getDrawOffset(C.Screen.W, C.Screen.H, ball.x, ball.y)
gfx.setDrawOffset(offsetX, offsetY)
end
@@ -938,7 +843,7 @@ function playdate.update()
ballIsHeld = drawFielder(ball, fielder.x, fielder.y + fielderDanceHeight) or ballIsHeld
end
- if offenseMode == Offense.batting then
+ if offenseMode == C.Offense.batting then
gfx.setLineWidth(5)
gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y)
end
@@ -976,7 +881,7 @@ function playdate.update()
gfx.setDrawOffset(0, 0)
if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then
- drawMinimap()
+ drawMinimap(runners)
end
drawScoreboard(0, C.Screen.H * 0.77, teams, outs, battingTeam, inning)
drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes)
@@ -991,7 +896,7 @@ function init()
playdate.getSystemMenu():addMenuItem("Restart game", function() end) -- TODO?
playdate.timer.new(2000, function()
- throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, false)
+ throwBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, false)
end)
BootTune:play()
BootTune:setFinishCallback(function()
diff --git a/src/npc.lua b/src/npc.lua
index cb0f958..de71bee 100644
--- a/src/npc.lua
+++ b/src/npc.lua
@@ -19,12 +19,12 @@ function npc.batSpeed()
return npcBatSpeed
end
-function npc.runningSpeed(baseConstants, runners)
+function npc.runningSpeed(runners)
if #runners == 0 then
return 0
end
local touchedBase = isTouchingBase(runners[1].x, runners[1].y)
- if not touchedBase or touchedBase == baseConstants[4] then
+ if not touchedBase or touchedBase == C.Bases[C.Home] then
return 10
end
return 0
diff --git a/src/utils.lua b/src/utils.lua
index 5a352ab..a0c2531 100644
--- a/src/utils.lua
+++ b/src/utils.lua
@@ -1,8 +1,3 @@
--- stylua: ignore start
-import 'CoreLibs/animation.lua'
-import 'CoreLibs/graphics.lua'
--- stylua: ignore end
-
-- selene: allow(unscoped_variables)
utils = {}
@@ -14,6 +9,11 @@ function utils.easingHill(t, b, c, d)
return (c * t) + b
end
+--- @alias XYPair {
+--- x: number,
+--- y: number,
+--- }
+
---@param x number
---@param y number
---@return XYPair