diff --git a/src/action-queue.lua b/src/action-queue.lua index f9e2677..39756df 100644 --- a/src/action-queue.lua +++ b/src/action-queue.lua @@ -26,6 +26,10 @@ function actionQueue:upsert(id, maxTimeMs, action) } end +--- The new action will not be added if an entry with the current id already exists in the queue. +---@param id any +---@param maxTimeMs number +---@param action Action function actionQueue:newOnly(id, maxTimeMs, action) if self.queue[id] then return @@ -38,6 +42,7 @@ end --- Must be called on every playdate.update() to check for (and run) any waiting tasks. --- Actions that return NeedsMoreTime will not be removed from the queue unless they have expired. +---@param deltaSeconds number function actionQueue:runWaiting(deltaSeconds) local currentTimeMs = playdate.getCurrentTimeMilliseconds() diff --git a/src/announcer.lua b/src/announcer.lua index 971848d..130d10e 100644 --- a/src/announcer.lua +++ b/src/announcer.lua @@ -52,6 +52,8 @@ function Announcer:say(text) end end +---@param x number +---@param y number function Announcer:draw(x, y) if #self.textQueue == 0 then return diff --git a/src/baserunning.lua b/src/baserunning.lua index e9094f9..23524dd 100644 --- a/src/baserunning.lua +++ b/src/baserunning.lua @@ -21,8 +21,9 @@ Baserunning = {} -- TODO: Implement slides? Would require making fielders' gloves "real objects" whose state is tracked. ---@param announcer Announcer +---@param onThirdOutCallback fun() ---@return Baserunning -function Baserunning.new(announcer, onThirdOut) +function Baserunning.new(announcer, onThirdOutCallback) local o = setmetatable({ runners = {}, outRunners = {}, @@ -32,7 +33,7 @@ function Baserunning.new(announcer, onThirdOut) --- it seems sensible to store the value here. outs = 0, announcer = announcer, - onThirdOut = onThirdOut, + onThirdOut = onThirdOutCallback, }, { __index = Baserunning }) o:pushNewBatter() @@ -133,6 +134,9 @@ function Baserunning:convertBatterToRunner() self.batter = nil -- Demote batter to a mere runner end +---@param deltaSeconds number +---@param runner Runner +---@return boolean isStillWalking local function walkWayOutRunner(deltaSeconds, runner) if runner.x < C.Screen.W + 50 and runner.y < C.Screen.H + 50 then runner.x = runner.x + (deltaSeconds * 25) @@ -156,7 +160,7 @@ function Baserunning:walkAwayOutRunners(deltaSeconds) end end ----@return Runner +---@return Runner theBatterPushed function Baserunning:pushNewBatter() local new = { -- imageSet = math.random() < C.WokeMeter and FemmeSet or MascSet, -- TODO? lol. @@ -182,6 +186,7 @@ end ---@param runner Runner | nil ---@param runnerIndex number | nil May only be nil if runner == batter ---@param appliedSpeed number +---@param isAutoRun boolean ---@param deltaSeconds number ---@return boolean runnerMoved, boolean runnerScored function Baserunning:updateRunner(runner, runnerIndex, appliedSpeed, isAutoRun, deltaSeconds) @@ -248,7 +253,9 @@ end --- Update non-batter runners. --- Returns true only if at least one of the given runners moved during this update ---@param appliedSpeed number | fun(runner: Runner): number +---@param forcedOnly boolean If true, only move forced runners (e.g. for a walk) ---@param isAutoRun boolean If true, does not attempt to hug the bases +---@param deltaSeconds number ---@return boolean runnersStillMoving, number runnersScored, number secondsSinceLastMove function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, isAutoRun, deltaSeconds) local runnersStillMoving = false diff --git a/src/control-screen.lua b/src/control-screen.lua index 9a224e3..950e5c4 100644 --- a/src/control-screen.lua +++ b/src/control-screen.lua @@ -52,7 +52,7 @@ local function detail(text) return { text = text, font = DetailFont } end ----@class +---@class ControlScreen ---@field sceneToReturnTo Scene ---@field private renderedImage pd_image Static image doesn't need to be constantly re-rendered. ControlScreen = {} @@ -81,6 +81,7 @@ local function draw() end ---@param sceneToReturnTo Scene +---@return ControlScreen function ControlScreen.new(sceneToReturnTo) return setmetatable({ sceneToReturnTo = sceneToReturnTo, diff --git a/src/dbg.lua b/src/dbg.lua index 7005789..a5c4740 100644 --- a/src/dbg.lua +++ b/src/dbg.lua @@ -16,7 +16,8 @@ function dbg.label(value, name) return value end --- Only works if called with the bases empty (i.e. the only runner should be the batter. +--- Only works if called with the bases empty (i.e. the only runner should be the batter. +---@param br Baserunning function dbg.loadTheBases(br) br:pushNewBatter() br:pushNewBatter() @@ -73,6 +74,7 @@ local hitSamples = { }, } +---@param inningCount number Number of innings to mock ---@return Statistics function dbg.mockStatistics(inningCount) inningCount = inningCount or 9 diff --git a/src/main-menu.lua b/src/main-menu.lua index f64e036..167418e 100644 --- a/src/main-menu.lua +++ b/src/main-menu.lua @@ -41,6 +41,8 @@ local function startGame() MenuMusic:setPaused(true) end +---@param baseEaser EasingFunc +---@return EasingFunc local function pausingEaser(baseEaser) --- t: elapsedTime --- d: duration @@ -65,6 +67,7 @@ local animatorY = gfx.animator.new(2000, 60, 200, pausingEaser(utils.easingHill) animatorY.repeatCount = -1 animatorY.reverses = true +---@type number local crankStartPos ---@generic T @@ -76,6 +79,7 @@ local function arrayElementFromCrank(array, crankPosition) return array[i] end +---@type pd_image local currentLogo --luacheck: ignore diff --git a/src/main.lua b/src/main.lua index 685e945..1e057f9 100644 --- a/src/main.lua +++ b/src/main.lua @@ -406,15 +406,7 @@ function Game:updateBatting(offenseHandler) local ballWasHit = batSpeed > 0 and ball.y < 232 - and utils.pointDirectlyUnderLine( - ball.x, - ball.y, - self.state.batBase.x, - self.state.batBase.y, - self.state.batTip.x, - self.state.batTip.y, - C.Screen.H - ) + and utils.pointDirectlyUnderLine(ball, self.state.batBase, self.state.batTip, C.Screen.H) if not ballWasHit then return @@ -472,6 +464,8 @@ function Game:updateBatting(offenseHandler) end ---@param appliedSpeed number | fun(runner: Runner): number +---@param forcedOnly boolean +---@param isAutoRun boolean ---@return boolean runnersStillMoving, number secondsSinceLastRunnerMove function Game:updateNonBatterRunners(appliedSpeed, forcedOnly, isAutoRun) local runnersStillMoving, runnersScored, secondsSinceLastRunnerMove = @@ -649,10 +643,12 @@ function Game:update() if self.state.offenseState == C.Offense.batting then gfx.setLineWidth(7) gfx.drawLine(self.state.batBase.x, self.state.batBase.y, self.state.batTip.x, self.state.batTip.y) + gfx.setColor(gfx.kColorWhite) gfx.setLineCapStyle(gfx.kLineCapStyleRound) gfx.setLineWidth(3) gfx.drawLine(self.state.batBase.x, self.state.batBase.y, self.state.batTip.x, self.state.batTip.y) + gfx.setColor(gfx.kColorBlack) end @@ -681,6 +677,7 @@ function Game:update() if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then drawMinimap(self.baserunning.runners, self.fielding.fielders) end + local homeScore, awayScore = utils.totalScores(self.state.stats) drawScoreboard( 0, diff --git a/src/npc.lua b/src/npc.lua index be0eb24..897076c 100644 --- a/src/npc.lua +++ b/src/npc.lua @@ -21,11 +21,11 @@ end function Npc.update() end -- TODO: FAR more nuanced NPC batting. +-- luacheck: no unused ---@param ball XyPair ---@param pitchIsOver boolean ---@param deltaSec number ---@return number batAngleDeg, number batSpeed --- luacheck: no unused function Npc:updateBat(ball, pitchIsOver, deltaSec) if not pitchIsOver and ball.y > 200 and ball.y < 230 and (ball.x < C.Center.x + 15) then npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed) @@ -36,10 +36,12 @@ function Npc:updateBat(ball, pitchIsOver, deltaSec) return npcBatDeg, (self:batSpeed() * deltaSec) end +---@return number function Npc:batSpeed() return npcBatSpeed / 1.5 end +---@return number flyTimeMs, number pitchId, number accuracy function Npc:pitch() return C.PitchFlyMs / self:pitchSpeed(), math.random(#Pitches), 0.9 end diff --git a/src/pitching.lua b/src/pitching.lua index 18d9aa3..893eaa6 100644 --- a/src/pitching.lua +++ b/src/pitching.lua @@ -7,6 +7,8 @@ local gfx = playdate.graphics local StrikeZoneWidth = C.StrikeZoneEndX - C.StrikeZoneStartX -- TODO? Also degrade speed +---@param accuracy number +---@return number xValueToMissBy function getPitchMissBy(accuracy) accuracy = accuracy or 1.0 local missBy = (1 - accuracy) * StrikeZoneWidth * 3 @@ -71,6 +73,9 @@ Pitches = { end, } +---@alias PitchOutcome "StrikeOut" | "Walk" + +---@type table PitchOutcomes = { StrikeOut = "StrikeOut", Walk = "Walk", @@ -93,6 +98,7 @@ function pitchTracker:reset() self.balls = 0 end +---@param ball XyPair function pitchTracker:recordIfPassed(ball) if ball.y < C.StrikeZoneStartY then self.recordedPitchX = nil @@ -103,6 +109,7 @@ end ---@param didSwing boolean ---@param fieldingTeamInningData TeamInningData +---@return PitchOutcome | nil function pitchTracker:updatePitchCounts(didSwing, fieldingTeamInningData) if not self.recordedPitchX then return @@ -149,8 +156,6 @@ throwMeter = { wasPerfect = false, } -local crankQueue = {} - local MaxPowerRatio = 1.5 --- Returns nil when a throw is NOT requested. @@ -176,6 +181,11 @@ end local CrankRecordSec = 0.33 +---@alias CrankQueueEntry { time: number, chargeAmount: number } + +---@type CrankQueueEntry[] +local crankQueue = {} + --- If (within approx. a third of a second) the crank has moved more than 45 degrees, call that a throw. ---@param chargeAmount number ---@return number | nil diff --git a/src/statistics.lua b/src/statistics.lua index fb61a2f..b2f210c 100644 --- a/src/statistics.lua +++ b/src/statistics.lua @@ -5,6 +5,7 @@ -- + Batting average -- + Farthest hit ball +---@return TeamInningData local function newTeamInning() return { score = 0, diff --git a/src/user-input.lua b/src/user-input.lua index 02a78b9..0f34ee2 100644 --- a/src/user-input.lua +++ b/src/user-input.lua @@ -2,6 +2,7 @@ ---@field buttonControlledThrow: fun(throwFlyMs: number, forbidThrowHome: boolean): boolean didThrow UserInput = {} +---@return UserInput function UserInput.new(buttonControlledThrow) return setmetatable({ buttonControlledThrow = buttonControlledThrow, @@ -14,6 +15,7 @@ function UserInput:update() self.crankLimited = math.abs(crankLimited) end +---@return number batAngleDeg, number batSpeed function UserInput:updateBat() local batAngleDeg = (playdate.getCrankPosition() + C.CrankOffsetDeg) % 360 local batSpeed = self.crankLimited @@ -43,6 +45,7 @@ local function userPitch(throwFlyMs, accuracy) return nil, nil, nil end +---@return number | nil pitchFlyTimeMs, number | nil pitchTypeIndex, number | nil accuracy function UserInput:pitch() local powerRatio, accuracy = throwMeter:readThrow(self.crankChange) if powerRatio then diff --git a/src/utils.lua b/src/utils.lua index 5bc364b..ba0643c 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -90,8 +90,12 @@ function utils.moveAtSpeed(mover, speed, target, tau) return true end -function utils.within(within, n1, n2) - return math.abs(n1 - n2) < within +---@param acceptableGap number +---@param n1 number +---@param n2 number +---@return boolean n1 is within acceptableGap of n2 +function utils.within(acceptableGap, n1, n2) + return math.abs(n1 - n2) < acceptableGap end ---@generic T @@ -121,6 +125,10 @@ function utils.first(array, condition) return nil end +---@param x1 number +---@param y1 number +---@param x2 number +---@param y2 number ---@return number distance, number x, number y function utils.distanceBetween(x1, y1, x2, y2) local x = x1 - x2 @@ -137,6 +145,12 @@ function utils.distanceBetweenPoints(point1, point2) return sqrt((x * x) + (y * y)), x, y end +---@param x1 number +---@param y1 number +---@param z1 number +---@param x2 number +---@param y2 number +---@param z2 number ---@return number distance, number x, number y, number z function utils.distanceBetweenZ(x1, y1, z1, x2, y2, z2) local x = x1 - x2 @@ -164,15 +178,19 @@ function utils.getRunnerWithNextBase(runners, base) end --- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound. +---@param point XyPair +---@param line1 XyPair +---@param line2 XyPair +---@param bottomBound number ---@return boolean -function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound) +function utils.pointDirectlyUnderLine(point, line1, line2, bottomBound) -- This check currently assumes right-handedness. -- I.e. it assumes the ball is to the right of batBaseX - if pointX < lineX1 or pointX > lineX2 or pointY > bottomBound then + if point.x < line1.x or point.x > line2.x or point.y > bottomBound then return false end - return utils.pointUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2) + return utils.pointUnderLine(point.x, point.y, line1.x, line1.y, line2.x, line2.y) end --- Returns true if the given point is anywhere above the given line, with no upper bound. @@ -192,6 +210,12 @@ function utils.pointIsSquarelyAboveLine(point, linePoints) end --- Returns true only if the point is below the given line. +---@param pointX number +---@param pointY number +---@param lineX1 number +---@param lineY1 number +---@param lineX2 number +---@param lineY2 number ---@return boolean function utils.pointUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2) local m = (lineY2 - lineY1) / (lineX2 - lineX1) @@ -222,6 +246,7 @@ end ---@param array T[] ---@param x number ---@param y number +---@param extraCondition fun(t: T): boolean ---@return T nearest,number |nil distance function utils.getNearestOf(array, x, y, extraCondition) local nearest, nearestDistance = nil, nil