From bbaaca4a2de99ba6866e444452b846bc2199d178 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Sat, 15 Feb 2025 22:13:37 -0500 Subject: [PATCH] Sort runners/fielders by `y` for draw order Pulled Fielding:drawFielders() back out into main.lua for this. Also switching some table-keyed enums to string literal types. --- src/constants.lua | 18 +++---- src/fielding.lua | 15 +----- src/main.lua | 117 +++++++++++++++++++++++++++++++++------------- src/npc.lua | 2 +- 4 files changed, 93 insertions(+), 59 deletions(-) diff --git a/src/constants.lua b/src/constants.lua index a95877e..0cad138 100644 --- a/src/constants.lua +++ b/src/constants.lua @@ -31,7 +31,7 @@ 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 - 30, - y = C.Bases[C.Home].y, + y = C.Bases[C.Home].y + 10, } ---@type table @@ -94,22 +94,16 @@ C.BatLength = 35 -- TODO: enums implemented this way are probably going to be difficult to serialize! ----@alias OffenseState table +---@alias OffenseState "batting" | "running" | "walking" --- An enum for what state the offense is in ---@type table C.Offense = { - batting = {}, - running = {}, - walking = {}, + batting = "batting", + running = "running", + walking = "walking", } ----@alias Side table ---- An enum for which side (offense or defense) a team is on. ----@type table -C.Sides = { - offense = {}, - defense = {}, -} +---@alias Side "offense" | "defense" C.PitcherStartPos = { x = C.Screen.W * 0.48, diff --git a/src/fielding.lua b/src/fielding.lua index 942e668..6d2ae6f 100644 --- a/src/fielding.lua +++ b/src/fielding.lua @@ -13,7 +13,8 @@ ---@field fielderHoldingBall Fielder | nil Fielding = {} -local FielderDanceAnimator = playdate.graphics.animator.new(1, 10, 0, utils.easingHill) +-- selene: allow(unscoped_variables) +FielderDanceAnimator = playdate.graphics.animator.new(1, 10, 0, utils.easingHill) FielderDanceAnimator.repeatCount = C.DanceBounceCount - 1 ---@param name string @@ -187,18 +188,6 @@ function Fielding:celebrate() FielderDanceAnimator:reset(C.DanceBounceMs) end ----@param fielderSprites SpriteCollection ----@param ball Point3d ----@return boolean ballIsHeldByAFielder -function Fielding:drawFielders(fielderSprites, ball) - local ballIsHeld = false - local danceOffset = FielderDanceAnimator:currentValue() - for _, fielder in pairs(self.fielders) do - ballIsHeld = drawFielder(fielderSprites, ball, fielder.x, fielder.y + danceOffset) or ballIsHeld - end - return ballIsHeld -end - if not playdate or playdate.TEST_MODE then return { Fielding, newFielder } end diff --git a/src/main.lua b/src/main.lua index c67d019..be9d0f5 100644 --- a/src/main.lua +++ b/src/main.lua @@ -46,10 +46,10 @@ import 'npc.lua' local gfx , C = playdate.graphics, C ---@alias Team { score: number, benchPosition: XyPair } ----@type table +---@type table local teams = { home = { - score = 0, + score = 0, -- TODO: Extract this last bit of global mutable state. benchPosition = utils.xy(C.Screen.W + 10, C.Center.y), }, away = { @@ -58,17 +58,19 @@ local teams = { }, } +---@alias TeamId 'home' | 'away' + --- Well, maybe not "Settings", but passive state that probably won't change much, if at all, during a game. ---@class Settings ---@field finalInning number ----@field userTeam Team | nil +---@field userTeam TeamId | nil ---@field awayTeamSprites SpriteCollection ---@field homeTeamSprites SpriteCollection ---@class MutableState ---@field deltaSeconds number ---@field ball Ball ----@field battingTeam Team +---@field battingTeam TeamId ---@field catcherThrownBall boolean ---@field offenseState OffenseState ---@field inning number @@ -104,20 +106,24 @@ Game = {} ---@param state MutableState | nil ---@return Game function Game.new(settings, announcer, fielding, baserunning, npc, state) + teams.away.score = 0 + teams.home.score = 0 announcer = announcer or Announcer.new() fielding = fielding or Fielding.new() - settings.userTeam = teams.away + settings.userTeam = nil -- "away" local homeTeamBlipper = blipper.new(100, settings.homeTeamSprites.smiling, settings.homeTeamSprites.lowHat) local awayTeamBlipper = blipper.new(100, settings.awayTeamSprites.smiling, settings.awayTeamSprites.lowHat) - local battingTeam = teams.away - local runnerBlipper = battingTeam == teams.away and awayTeamBlipper or homeTeamBlipper + local battingTeam = "away" + local runnerBlipper = battingTeam == "away" and awayTeamBlipper or homeTeamBlipper local ball = Ball.new(gfx.animator) local o = setmetatable({ settings = settings, announcer = announcer, fielding = fielding, + homeTeamBlipper = homeTeamBlipper, + awayTeamBlipper = awayTeamBlipper, state = state or { batBase = utils.xy(C.Center.x - 34, 215), batTip = utils.xy(0, 0), @@ -132,8 +138,6 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state) secondsSincePitchAllowed = 0, battingTeamSprites = settings.awayTeamSprites, fieldingTeamSprites = settings.homeTeamSprites, - homeTeamBlipper = homeTeamBlipper, - awayTeamBlipper = awayTeamBlipper, runnerBlipper = runnerBlipper, }, }, { __index = Game }) @@ -196,6 +200,27 @@ local Pitches = { end, } +---@param teamId TeamId +---@return TeamId +local function getOppositeTeamId(teamId) + if teamId == "home" then + return "away" + elseif teamId == "away" then + return "home" + else + error("Unknown TeamId: " .. (teamId or "nil")) + end +end + +function Game:getBattingTeam() + return teams[self.state.battingTeam] or error("Unknown battingTeam: " .. (self.state.battingTeam or "nil")) +end + +function Game:getFieldingTeam() + return teams[getOppositeTeamId(self.state.battingTeam)] +end + +---@param side Side ---@return boolean userIsOnSide, boolean userIsOnOtherSide function Game:userIsOn(side) if self.settings.userTeam == nil then @@ -204,9 +229,9 @@ function Game:userIsOn(side) end local ret if self.settings.userTeam == self.state.battingTeam then - ret = side == C.Sides.offense + ret = side == "offense" else - ret = side == C.Sides.defense + ret = side == "defense" end return ret, not ret end @@ -242,12 +267,11 @@ end function Game:nextHalfInning() pitchTracker:reset() - local currentlyFieldingTeam = self.state.battingTeam == teams.home and teams.away or teams.home local gameOver = self.state.inning == self.settings.finalInning and teams.away.score ~= teams.home.score if not gameOver then self.fielding:celebrate() self.state.secondsSinceLastRunnerMove = -7 - self.fielding:benchTo(currentlyFieldingTeam.benchPosition) + self.fielding:benchTo(self:getFieldingTeam().benchPosition) self.announcer:say("SWITCHING SIDES...") end @@ -258,7 +282,7 @@ function Game:nextHalfInning() if self.state.battingTeam == teams.home then self.state.inning = self.state.inning + 1 end - self.state.battingTeam = currentlyFieldingTeam + self.state.battingTeam = getOppositeTeamId(self.state.battingTeam) playdate.timer.new(2000, function() if self.state.battingTeam == teams.home then self.state.battingTeamSprites = self.settings.homeTeamSprites @@ -275,7 +299,8 @@ end ---@param scoredRunCount number function Game:score(scoredRunCount) - self.state.battingTeam.score = self.state.battingTeam.score + scoredRunCount + local batting = self:getBattingTeam() + batting.score = batting.score + scoredRunCount self.announcer:say("SCORE!") end @@ -422,7 +447,7 @@ function Game:updateGameState() self.state.ball:updatePosition() - local userOnOffense, userOnDefense = self:userIsOn(C.Sides.offense) + local userOnOffense, userOnDefense = self:userIsOn("offense") if userOnDefense then throwMeter:applyCharge(self.state.deltaSeconds, crankLimited) @@ -540,28 +565,54 @@ function Game:update() GrassBackground:draw(-400, -240) - local ballIsHeld = self.fielding:drawFielders(self.state.fieldingTeamSprites, ball) + ---@type { y: number, drawAction: fun() }[] + local characterDraws = {} + function addDraw(y, drawAction) + characterDraws[#characterDraws + 1] = { y = y, drawAction = drawAction } + end + + local danceOffset = FielderDanceAnimator:currentValue() + local ballIsHeld = false + for _, fielder in pairs(self.fielding.fielders) do + addDraw(fielder.y + danceOffset, function() + ballIsHeld = drawFielder( + self.state.fieldingTeamSprites, + self.state.ball, + fielder.x, + fielder.y + danceOffset + ) or ballIsHeld + end) + end + + local playerHeightOffset = 20 + -- TODO? Scale sprites down as y increases + for _, runner in pairs(self.baserunning.runners) do + addDraw(runner.y, function() + if runner == self.baserunning.batter then + if self.state.batAngleDeg > 50 and self.state.batAngleDeg < 200 then + self.state.battingTeamSprites.back:draw(runner.x, runner.y - playerHeightOffset) + else + self.state.battingTeamSprites.smiling:draw(runner.x, runner.y - playerHeightOffset) + end + else + -- TODO? Change blip speed depending on runner speed? + self.state.runnerBlipper:draw(false, runner.x, runner.y - playerHeightOffset) + end + end) + end + + table.sort(characterDraws, function(a, b) + return a.y < b.y + end) + for _, character in pairs(characterDraws) do + character.drawAction() + end if self.state.offenseState == C.Offense.batting then gfx.setLineWidth(5) gfx.drawLine(self.state.batBase.x, self.state.batBase.y, self.state.batTip.x, self.state.batTip.y) end - local playerHeightOffset = 10 - -- TODO? Scale sprites down as y increases - for _, runner in pairs(self.baserunning.runners) do - if runner == self.baserunning.batter then - if self.state.batAngleDeg > 50 and self.state.batAngleDeg < 200 then - self.state.battingTeamSprites.back:draw(runner.x, runner.y - playerHeightOffset) - else - self.state.battingTeamSprites.smiling:draw(runner.x, runner.y - playerHeightOffset) - end - else - -- TODO? Change blip speed depending on runner speed? - self.state.runnerBlipper:draw(false, runner.x, runner.y - playerHeightOffset) - end - end - for _, runner in pairs(self.baserunning.outRunners) do self.state.battingTeamSprites.frowning:draw(runner.x, runner.y - playerHeightOffset) end @@ -580,7 +631,7 @@ function Game:update() if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then drawMinimap(self.baserunning.runners, self.fielding.fielders) end - drawScoreboard(0, C.Screen.H * 0.77, teams, self.baserunning.outs, self.state.battingTeam, self.state.inning) + drawScoreboard(0, C.Screen.H * 0.77, teams, self.baserunning.outs, self:getBattingTeam(), self.state.inning) drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes) self.announcer:draw(C.Center.x, 10) diff --git a/src/npc.lua b/src/npc.lua index 781ce3c..812be91 100644 --- a/src/npc.lua +++ b/src/npc.lua @@ -27,7 +27,7 @@ function Npc:updateBatAngle(ball, catcherThrownBall, deltaSec) npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed) else npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed - npcBatDeg = 200 + npcBatDeg = 230 end return npcBatDeg end