Class-ify Announcer, Baserunning, and Fielding
Largely to enable dependency injection. I am pushing AWAY the paranoia that metatable lookups will slow things down. (You've got like 20 entities, bud, chill.) Field -> Fielding
This commit is contained in:
parent
aadaa6e0d6
commit
9c0d263a29
|
@ -11,14 +11,21 @@ local AnnouncerAnimatorOutY <const> =
|
|||
playdate.graphics.animator.new(AnnouncementTransitionMs, 0, -70, playdate.easingFunctions.outQuint)
|
||||
|
||||
-- selene: allow(unscoped_variables)
|
||||
announcer = {
|
||||
---@class Announcer
|
||||
---@field textQueue string[]
|
||||
---@field animatorY pd_animator
|
||||
Announcer = {}
|
||||
|
||||
function Announcer.new()
|
||||
return setmetatable({
|
||||
textQueue = {},
|
||||
animatorY = AnnouncerAnimatorInY,
|
||||
}
|
||||
}, { __index = Announcer })
|
||||
end
|
||||
|
||||
local DurationMs <const> = 3000
|
||||
|
||||
function announcer:popIn()
|
||||
function Announcer:popIn()
|
||||
self.animatorY = AnnouncerAnimatorInY
|
||||
self.animatorY:reset()
|
||||
|
||||
|
@ -39,14 +46,14 @@ function announcer:popIn()
|
|||
end)
|
||||
end
|
||||
|
||||
function announcer:say(text)
|
||||
function Announcer:say(text)
|
||||
self.textQueue[#self.textQueue + 1] = text
|
||||
if #self.textQueue == 1 then
|
||||
self:popIn()
|
||||
end
|
||||
end
|
||||
|
||||
function announcer:draw(x, y)
|
||||
function Announcer:draw(x, y)
|
||||
if #self.textQueue == 0 then
|
||||
return
|
||||
end
|
||||
|
|
|
@ -7,27 +7,37 @@
|
|||
--- }
|
||||
|
||||
-- selene: allow(unscoped_variables)
|
||||
baserunning = {
|
||||
---@type Runner[]
|
||||
---@class Baserunning
|
||||
---@field runners Runner[]
|
||||
---@field outRunners Runner[]
|
||||
---@field scoredRunners Runner[]
|
||||
---@field batter Runner | nil
|
||||
---@field outs number
|
||||
---@field announcer Announcer
|
||||
Baserunning = {}
|
||||
|
||||
---@param announcer any
|
||||
---@return Baserunning
|
||||
function Baserunning.new(announcer)
|
||||
local o = setmetatable({
|
||||
runners = {},
|
||||
|
||||
---@type Runner[]
|
||||
outRunners = {},
|
||||
|
||||
---@type Runner[]
|
||||
scoredRunners = {},
|
||||
|
||||
---@type Runner | nil
|
||||
batter = nil,
|
||||
|
||||
--- Since this object is what ultimately *mutates* the out count,
|
||||
--- it seems sensible to store the value here.
|
||||
outs = 0
|
||||
}
|
||||
outs = 0,
|
||||
announcer = announcer
|
||||
}, { __index = Baserunning })
|
||||
|
||||
o.batter = o:newRunner()
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
---@param runner integer | Runner
|
||||
---@param message string | nil
|
||||
function baserunning:outRunner(runner, message)
|
||||
function Baserunning:outRunner(runner, message)
|
||||
self.outs = self.outs + 1
|
||||
if type(runner) ~= "number" then
|
||||
for i, maybe in ipairs(self.runners) do
|
||||
|
@ -44,7 +54,7 @@ function baserunning:outRunner(runner, message)
|
|||
|
||||
self:updateForcedRunners()
|
||||
|
||||
announcer:say(message or "YOU'RE OUT!")
|
||||
self.announcer:say(message or "YOU'RE OUT!")
|
||||
if self.outs < 3 then
|
||||
return
|
||||
end
|
||||
|
@ -55,7 +65,7 @@ function baserunning:outRunner(runner, message)
|
|||
end
|
||||
end
|
||||
|
||||
function baserunning:outEligibleRunners(fielder)
|
||||
function Baserunning:outEligibleRunners(fielder)
|
||||
local touchedBase = utils.isTouchingBase(fielder.x, fielder.y)
|
||||
local didOutRunner = false
|
||||
for i, runner in pairs(self.runners) do
|
||||
|
@ -76,7 +86,7 @@ function baserunning:outEligibleRunners(fielder)
|
|||
return didOutRunner
|
||||
end
|
||||
|
||||
function baserunning:updateForcedRunners()
|
||||
function Baserunning:updateForcedRunners()
|
||||
local stillForced = true
|
||||
for _, base in ipairs(C.Bases) do
|
||||
local runnerTargetingBase = utils.getRunnerWithNextBase(self.runners, base)
|
||||
|
@ -93,7 +103,7 @@ function baserunning:updateForcedRunners()
|
|||
end
|
||||
|
||||
---@param deltaSeconds number
|
||||
function baserunning:walkAwayOutRunners(deltaSeconds)
|
||||
function Baserunning:walkAwayOutRunners(deltaSeconds)
|
||||
for i, runner in ipairs(self.outRunners) do
|
||||
if runner.x < C.Screen.W + 50 and runner.y < C.Screen.H + 50 then
|
||||
runner.x = runner.x + (deltaSeconds * 25)
|
||||
|
@ -105,7 +115,7 @@ function baserunning:walkAwayOutRunners(deltaSeconds)
|
|||
end
|
||||
|
||||
---@return Runner
|
||||
function baserunning:newRunner()
|
||||
function Baserunning:newRunner()
|
||||
local new = {
|
||||
x = C.RightHandedBattersBox.x - 60,
|
||||
y = C.RightHandedBattersBox.y + 60,
|
||||
|
@ -117,11 +127,9 @@ function baserunning:newRunner()
|
|||
return new
|
||||
end
|
||||
|
||||
baserunning.batter = baserunning:newRunner()
|
||||
|
||||
---@param self table
|
||||
---@param runnerIndex integer
|
||||
function baserunning:runnerScored(runnerIndex)
|
||||
function Baserunning:runnerScored(runnerIndex)
|
||||
-- TODO: outRunners/scoredRunners split
|
||||
self.outRunners[#self.outRunners + 1] = self.runners[runnerIndex]
|
||||
table.remove(self.runners, runnerIndex)
|
||||
|
@ -133,7 +141,7 @@ end
|
|||
---@param appliedSpeed number
|
||||
---@param deltaSeconds number
|
||||
---@return boolean runnerMoved, boolean runnerScored
|
||||
function baserunning:updateRunner(runner, runnerIndex, appliedSpeed, deltaSeconds)
|
||||
function Baserunning:updateRunner(runner, runnerIndex, appliedSpeed, deltaSeconds)
|
||||
local autoRunSpeed = 20 * deltaSeconds
|
||||
|
||||
if not runner or not runner.nextBase then
|
||||
|
@ -188,7 +196,7 @@ end
|
|||
--- Returns true only if at least one of the given runners moved during this update
|
||||
---@param appliedSpeed number
|
||||
---@return boolean someRunnerMoved, number runnersScored
|
||||
function baserunning:updateRunning(appliedSpeed, forcedOnly, deltaSeconds)
|
||||
function Baserunning:updateRunning(appliedSpeed, forcedOnly, deltaSeconds)
|
||||
local someRunnerMoved = false
|
||||
local runnersScored = 0
|
||||
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
-- selene: allow(unscoped_variables)
|
||||
---@class Fielding
|
||||
---@field fielders table<string, Fielder>
|
||||
---@field fielderTouchingBall Fielder | nil
|
||||
Fielding = {}
|
||||
|
||||
---@param name string
|
||||
---@param speed number
|
||||
---@return Fielder
|
||||
function newFielder(name, speed)
|
||||
local function newFielder(name, speed)
|
||||
return {
|
||||
name = name,
|
||||
speed = speed,
|
||||
}
|
||||
end
|
||||
|
||||
-- selene: allow(unscoped_variables)
|
||||
Field = {
|
||||
function Fielding.new()
|
||||
return setmetatable({
|
||||
fielders = {
|
||||
first = newFielder("First", 40),
|
||||
second = newFielder("Second", 40),
|
||||
|
@ -23,11 +29,12 @@ Field = {
|
|||
},
|
||||
---@type Fielder | nil
|
||||
fielderTouchingBall = nil,
|
||||
}
|
||||
}, { __index = Fielding })
|
||||
end
|
||||
|
||||
--- Actually only benches the infield, because outfielders are far away!
|
||||
---@param position XYPair
|
||||
function Field:benchTo(position)
|
||||
function Fielding:benchTo(position)
|
||||
self.fielders.first.target = position
|
||||
self.fielders.second.target = position
|
||||
self.fielders.shortstop.target = position
|
||||
|
@ -38,7 +45,7 @@ end
|
|||
|
||||
--- Resets the target positions of all fielders to their defaults (at their field positions).
|
||||
---@param fromOffTheField XYPair | nil If provided, also sets all runners' current position to one centralized location.
|
||||
function Field:resetFielderPositions(fromOffTheField)
|
||||
function Fielding:resetFielderPositions(fromOffTheField)
|
||||
if fromOffTheField then
|
||||
for _, fielder in pairs(self.fielders) do
|
||||
fielder.x = fromOffTheField.x
|
||||
|
@ -76,7 +83,7 @@ end
|
|||
---@param self table
|
||||
---@param ballDestX number
|
||||
---@param ballDestY number
|
||||
function Field:haveSomeoneChase(ballDestX, ballDestY)
|
||||
function Fielding:haveSomeoneChase(ballDestX, ballDestY)
|
||||
local chasingFielder = utils.getNearestOf(self.fielders, ballDestX, ballDestY)
|
||||
chasingFielder.target = { x = ballDestX, y = ballDestY }
|
||||
|
||||
|
@ -92,7 +99,7 @@ end
|
|||
---@param ball XYPair
|
||||
---@param deltaSeconds number
|
||||
---@return Fielder | nil fielderTouchingBall nil if no fielder is currently touching the ball
|
||||
function Field:updateFielderPositions(ball, deltaSeconds)
|
||||
function Fielding:updateFielderPositions(ball, deltaSeconds)
|
||||
local fielderTouchingBall = nil
|
||||
for _, fielder in pairs(self.fielders) do
|
||||
local isTouchingBall = updateFielderPosition(deltaSeconds, fielder, ball)
|
||||
|
@ -120,6 +127,7 @@ local function playerThrowToImpl(field, targetBase, throwBall, throwFlyMs)
|
|||
|
||||
closestFielder.target = targetBase
|
||||
throwBall(targetBase.x, targetBase.y, playdate.easingFunctions.linear, throwFlyMs)
|
||||
return ActionResult.Succeeded
|
||||
end
|
||||
|
||||
--- Buffer in a fielder throw action.
|
||||
|
@ -127,7 +135,7 @@ end
|
|||
---@param targetBase Base
|
||||
---@param throwBall ThrowBall
|
||||
---@param throwFlyMs number
|
||||
function Field:playerThrowTo(targetBase, throwBall, throwFlyMs)
|
||||
function Fielding:playerThrowTo(targetBase, throwBall, throwFlyMs)
|
||||
local maxTryTimeMs = 5000
|
||||
actionQueue:upsert('playerThrowTo', maxTryTimeMs, function()
|
||||
return playerThrowToImpl(self, targetBase, throwBall, throwFlyMs)
|
||||
|
|
26
src/main.lua
26
src/main.lua
|
@ -38,6 +38,10 @@ import 'draw/fielder.lua'
|
|||
-- selene: allow(shadowing)
|
||||
local gfx <const>, C <const> = playdate.graphics, C
|
||||
|
||||
local announcer = Announcer:new()
|
||||
local baserunning = Baserunning.new(announcer)
|
||||
local fielding = Fielding.new()
|
||||
|
||||
local PlayerImageBlipper <const> = blipper.new(100, Player, PlayerLowHat)
|
||||
|
||||
local FielderDanceAnimator <const> = gfx.animator.new(1, 10, 0, utils.easingHill)
|
||||
|
@ -208,7 +212,7 @@ local function outRunner(runner, message)
|
|||
if not gameOver then
|
||||
FielderDanceAnimator:reset(C.DanceBounceMs)
|
||||
secondsSinceLastRunnerMove = -7
|
||||
Field:benchTo(currentlyFieldingTeam.benchPosition)
|
||||
fielding:benchTo(currentlyFieldingTeam.benchPosition)
|
||||
announcer:say("SWITCHING SIDES...")
|
||||
end
|
||||
-- Delay to keep end-of-inning on the scoreboard for a few seconds
|
||||
|
@ -218,7 +222,7 @@ local function outRunner(runner, message)
|
|||
if gameOver then
|
||||
announcer:say("AND THAT'S THE BALL GAME!")
|
||||
else
|
||||
Field:resetFielderPositions()
|
||||
fielding:resetFielderPositions()
|
||||
if battingTeam == teams.home then
|
||||
inning = inning + 1
|
||||
end
|
||||
|
@ -259,7 +263,7 @@ local function buttonControlledThrow(throwFlyMs, forbidThrowHome)
|
|||
-- Power for this throw has already been determined
|
||||
throwMeter = 0
|
||||
|
||||
Field:playerThrowTo(targetBase, throwBall, throwFlyMs)
|
||||
fielding:playerThrowTo(targetBase, throwBall, throwFlyMs)
|
||||
secondsSinceLastRunnerMove = 0
|
||||
offenseState = C.Offense.running
|
||||
|
||||
|
@ -330,7 +334,7 @@ local function updateBatting(batDeg, batSpeed)
|
|||
baserunning.batter.forcedTo = C.Bases[C.First]
|
||||
baserunning.batter = nil -- Demote batter to a mere runner
|
||||
|
||||
Field:haveSomeoneChase(ballDestX, ballDestY)
|
||||
fielding:haveSomeoneChase(ballDestX, ballDestY)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -392,7 +396,7 @@ local function updateGameState()
|
|||
pitchTracker.recordedPitchX = ball.x
|
||||
end
|
||||
|
||||
local pitcher = Field.fielders.pitcher
|
||||
local pitcher = fielding.fielders.pitcher
|
||||
if utils.distanceBetween(pitcher.x, pitcher.y, C.PitchStartX, C.PitchStartY) < C.BaseHitbox then
|
||||
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
|
||||
end
|
||||
|
@ -441,7 +445,7 @@ local function updateGameState()
|
|||
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
|
||||
if secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
|
||||
throwBall(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||
Field:resetFielderPositions()
|
||||
fielding:resetFielderPositions()
|
||||
offenseState = C.Offense.batting
|
||||
if not baserunning.batter then
|
||||
baserunning.batter = baserunning:newRunner()
|
||||
|
@ -454,7 +458,7 @@ local function updateGameState()
|
|||
end
|
||||
end
|
||||
|
||||
local fielderHoldingBall = Field:updateFielderPositions(ball, deltaSeconds)
|
||||
local fielderHoldingBall = fielding:updateFielderPositions(ball, deltaSeconds)
|
||||
|
||||
if playerOnDefense then
|
||||
local throwFly = readThrow()
|
||||
|
@ -465,7 +469,7 @@ local function updateGameState()
|
|||
if fielderHoldingBall then
|
||||
local outedSomeRunner = baserunning:outEligibleRunners(fielderHoldingBall)
|
||||
if playerOnOffense then
|
||||
npc.fielderAction(offenseState, fielderHoldingBall, outedSomeRunner, ball, throwBall)
|
||||
npc.fielderAction(fielding, baserunning, offenseState, fielderHoldingBall, outedSomeRunner, ball, throwBall)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -488,7 +492,7 @@ function playdate.update()
|
|||
|
||||
local fielderDanceHeight = FielderDanceAnimator:currentValue()
|
||||
local ballIsHeld = false
|
||||
for _, fielder in pairs(Field.fielders) do
|
||||
for _, fielder in pairs(fielding.fielders) do
|
||||
ballIsHeld = drawFielder(ball, fielder.x, fielder.y + fielderDanceHeight) or ballIsHeld
|
||||
end
|
||||
|
||||
|
@ -531,7 +535,7 @@ function playdate.update()
|
|||
|
||||
gfx.setDrawOffset(0, 0)
|
||||
if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then
|
||||
drawMinimap(baserunning.runners, Field.fielders)
|
||||
drawMinimap(baserunning.runners, fielding.fielders)
|
||||
end
|
||||
drawScoreboard(0, C.Screen.H * 0.77, teams, outs, battingTeam, inning)
|
||||
drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes)
|
||||
|
@ -542,7 +546,7 @@ local function init()
|
|||
playdate.display.setRefreshRate(50)
|
||||
gfx.setBackgroundColor(gfx.kColorWhite)
|
||||
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
|
||||
Field:resetFielderPositions(teams.home.benchPosition)
|
||||
fielding:resetFielderPositions(teams.home.benchPosition)
|
||||
playdate.getSystemMenu():addMenuItem("Restart game", function() end) -- TODO?
|
||||
|
||||
playdate.timer.new(2000, function()
|
||||
|
|
13
src/npc.lua
13
src/npc.lua
|
@ -85,13 +85,14 @@ function npc.getNextOutTarget(runners)
|
|||
end
|
||||
end
|
||||
|
||||
---@param fielding Fielding
|
||||
---@param fielder Fielder
|
||||
---@param runners Runner[]
|
||||
---@param throwBall ThrowBall
|
||||
function npc.tryToMakeAPlay(fielder, runners, ball, throwBall)
|
||||
function npc.tryToMakeAPlay(fielding, fielder, runners, ball, throwBall)
|
||||
local targetX, targetY = npc.getNextOutTarget(runners)
|
||||
if targetX ~= nil and targetY ~= nil then
|
||||
local nearestFielder = utils.getNearestOf(Field.fielders, targetX, targetY)
|
||||
local nearestFielder = utils.getNearestOf(fielding.fielders, targetX, targetY)
|
||||
nearestFielder.target = utils.xy(targetX, targetY)
|
||||
if nearestFielder == fielder then
|
||||
ball.heldBy = fielder
|
||||
|
@ -101,22 +102,24 @@ function npc.tryToMakeAPlay(fielder, runners, ball, throwBall)
|
|||
end
|
||||
end
|
||||
|
||||
---@param fielding Fielding
|
||||
---@param baserunning Baserunning
|
||||
---@param offenseState OffenseState
|
||||
---@param fielder Fielder
|
||||
---@param outedSomeRunner boolean
|
||||
---@param ball { x: number, y: number, heldBy: Fielder | nil }
|
||||
---@param throwBall ThrowBall
|
||||
function npc.fielderAction(offenseState, fielder, outedSomeRunner, ball, throwBall)
|
||||
function npc.fielderAction(fielding, baserunning, offenseState, fielder, outedSomeRunner, ball, throwBall)
|
||||
if offenseState ~= C.Offense.running then
|
||||
return
|
||||
end
|
||||
if outedSomeRunner then
|
||||
-- Delay a little before the next play
|
||||
playdate.timer.new(750, function()
|
||||
npc.tryToMakeAPlay(fielder, baserunning.runners, ball, throwBall)
|
||||
npc.tryToMakeAPlay(fielding, fielder, baserunning.runners, ball, throwBall)
|
||||
end)
|
||||
else
|
||||
npc.tryToMakeAPlay(fielder, baserunning.runners, ball, throwBall)
|
||||
npc.tryToMakeAPlay(fielding, fielder, baserunning.runners, ball, throwBall)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue