From 80015dbe621b41ccef165bdf5fbed7fc74764e4d Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Wed, 26 Feb 2025 13:04:38 -0500 Subject: [PATCH] Extract draw/characters.lua Pulls a bunch of draw logic out of main.lua; handles z-ordering. Expand on save/load - though it's certainly not complete yet. --- src/constants.lua | 2 + .../{player.lua => character-sprites.lua} | 0 src/draw/characters.lua | 153 ++++++++++++++++++ src/draw/fielder.lua | 28 ---- src/main-menu.lua | 7 +- src/main.lua | 145 ++++------------- src/test/testMain.lua | 7 + 7 files changed, 200 insertions(+), 142 deletions(-) rename src/draw/{player.lua => character-sprites.lua} (100%) create mode 100644 src/draw/characters.lua delete mode 100644 src/draw/fielder.lua diff --git a/src/constants.lua b/src/constants.lua index a237178..d8ee848 100644 --- a/src/constants.lua +++ b/src/constants.lua @@ -114,6 +114,8 @@ C.CrankPower = 10 C.FielderRunMult = 1.3 +C.PlayerHeightOffset = 20 + C.UserThrowPower = 0.3 --- How fast baserunners move after a walk diff --git a/src/draw/player.lua b/src/draw/character-sprites.lua similarity index 100% rename from src/draw/player.lua rename to src/draw/character-sprites.lua diff --git a/src/draw/characters.lua b/src/draw/characters.lua new file mode 100644 index 0000000..7f81848 --- /dev/null +++ b/src/draw/characters.lua @@ -0,0 +1,153 @@ +---@class Characters +---@field homeSprites SpriteCollection +---@field awaySprites SpriteCollection +---@field homeBlipper table +---@field awayBlipper table +Characters = {} + +local gfx = playdate.graphics + +---@alias BatState { batBase: XyPair, batTip: XyPair, batAngleDeg: number } + +local GloveSizeX, GloveSizeY = Glove:getSize() +local GloveOffX, GloveOffY = GloveSizeX / 2, GloveSizeY / 2 + +---@param homeSprites SpriteCollection +---@param awaySprites SpriteCollection +function Characters.new(homeSprites, awaySprites) + return setmetatable({ + homeSprites = homeSprites, + awaySprites = awaySprites, + homeBlipper = blipper.new(100, homeSprites), + awayBlipper = blipper.new(100, awaySprites), + }, { __index = Characters }) +end + +---@param ball Point3d +---@param fielderX number +---@param fielderY number +---@return boolean isHoldingBall +local function drawFielderGlove(ball, fielderX, fielderY, flip) + local distanceFromBall = utils.distanceBetweenZ(fielderX, fielderY, 0, ball.x, ball.y, ball.z) + local shoulderX, shoulderY = fielderX + 10, fielderY - 5 + if distanceFromBall > 20 then + Glove:draw(shoulderX, shoulderY, flip) + return false + else + GloveHoldingBall:draw(ball.x - GloveOffX, ball.y - GloveOffY, flip) + return true + end +end + +---@param fieldingTeamSprites SpriteCollection +---@param fielder Fielder +---@param ball Point3d +---@param flip boolean | nil +---@return boolean isHoldingBall +function drawFielder(fieldingTeamSprites, fielder, ball, flip) + local danceOffset = FielderDanceAnimator:currentValue() + + local x = fielder.x + local y = fielder.y + danceOffset + fieldingTeamSprites[fielder.spriteIndex].smiling:draw(fielder.x, y - 20, flip) + return drawFielderGlove(ball, x, y) +end + +---@param batState BatState +local function drawBat(batState) + gfx.setLineWidth(7) + gfx.drawLine(batState.batBase.x, batState.batBase.y, batState.batTip.x, batState.batTip.y) + + gfx.setColor(gfx.kColorWhite) + gfx.setLineCapStyle(gfx.kLineCapStyleRound) + gfx.setLineWidth(3) + gfx.drawLine(batState.batBase.x, batState.batBase.y, batState.batTip.x, batState.batTip.y) + + gfx.setColor(gfx.kColorBlack) +end + +---@param battingTeamSprites SpriteCollection +---@param batter Runner +---@param batState BatState +local function drawBatter(battingTeamSprites, batter, batState) + local spriteCollection = battingTeamSprites[batter.spriteIndex] + if batState.batAngleDeg > 50 and batState.batAngleDeg < 200 then + drawBat(batState) + spriteCollection.back:draw(batter.x, batter.y - C.PlayerHeightOffset) + else + spriteCollection.smiling:draw(batter.x, batter.y - C.PlayerHeightOffset) + drawBat(batState) + end +end + +---@param battingTeam TeamId +---@return SpriteCollection battingTeam, SpriteCollection fieldingTeam, table runnerBlipper +function Characters:getSpriteCollections(battingTeam) + if battingTeam == "home" then + return self.homeSprites, self.awaySprites, self.homeBlipper + end + return self.awaySprites, self.homeSprites, self.awayBlipper +end + +---@param fielding Fielding +---@param baserunning Baserunning +---@param batState BatState +---@param battingTeam TeamId +---@param ball Point3d +---@return Fielder | nil ballHeldBy +function Characters:drawAll(fielding, baserunning, batState, battingTeam, ball) + ---@type { y: number, drawAction: fun() }[] + local characterDraws = {} + function addDraw(y, drawAction) + characterDraws[#characterDraws + 1] = { y = y, drawAction = drawAction } + end + + local battingTeamSprites, fieldingTeamSprites, runnerBlipper = self:getSpriteCollections(battingTeam) + ---@type Fielder | nil + local ballHeldBy + for _, fielder in pairs(fielding.fielders) do + addDraw(fielder.y, function() + local ballHeldByThisFielder = drawFielder(fieldingTeamSprites, fielder, ball) + if ballHeldByThisFielder then + ballHeldBy = fielder + end + end) + end + + for _, runner in pairs(baserunning.runners) do + addDraw(runner.y, function() + local currentBatter = baserunning.batter + if runner == currentBatter then + drawBatter(battingTeamSprites, currentBatter, batState) + else + -- TODO? Change blip speed depending on runner speed? + runnerBlipper:draw(false, runner.x, runner.y - C.PlayerHeightOffset, runner) + end + end) + end + + for _, runner in pairs(baserunning.outRunners) do + addDraw(runner.y, function() + battingTeamSprites[runner.spriteIndex].frowning:draw(runner.x, runner.y - C.PlayerHeightOffset) + end) + end + for _, runner in pairs(baserunning.scoredRunners) do + addDraw(runner.y, function() + runnerBlipper:draw(false, runner.x, runner.y - C.PlayerHeightOffset, runner) + end) + end + + table.sort(characterDraws, function(a, b) + return a.y < b.y + end) + for _, character in pairs(characterDraws) do + character.drawAction() + end + + return ballHeldBy +end + +-- luacheck: ignore +if not playdate or playdate.TEST_MODE then + return Characters +end diff --git a/src/draw/fielder.lua b/src/draw/fielder.lua deleted file mode 100644 index 0c60fca..0000000 --- a/src/draw/fielder.lua +++ /dev/null @@ -1,28 +0,0 @@ -local GloveSizeX, GloveSizeY = Glove:getSize() -local GloveOffX, GloveOffY = GloveSizeX / 2, GloveSizeY / 2 - ----@param ball Point3d ----@param fielderX number ----@param fielderY number ----@return boolean isHoldingBall -local function drawFielderGlove(ball, fielderX, fielderY, flip) - local distanceFromBall = utils.distanceBetweenZ(fielderX, fielderY, 0, ball.x, ball.y, ball.z) - local shoulderX, shoulderY = fielderX + 10, fielderY - 5 - if distanceFromBall > 20 then - Glove:draw(shoulderX, shoulderY, flip) - return false - else - GloveHoldingBall:draw(ball.x - GloveOffX, ball.y - GloveOffY, flip) - return true - end -end - ----@param playerSprites PlayerImageBundle ----@param ball Point3d ----@param x number ----@param y number ----@return boolean isHoldingBall -function drawFielder(playerSprites, ball, x, y, flip) - playerSprites.smiling:draw(x, y - 20, flip) - return drawFielderGlove(ball, x, y) -end diff --git a/src/main-menu.lua b/src/main-menu.lua index 167418e..9320879 100644 --- a/src/main-menu.lua +++ b/src/main-menu.lua @@ -136,8 +136,11 @@ function MainMenu:update() size = 6, } - local ballIsHeld = drawFielder(AwayTeamSpriteGroup[1], ball, 30, 200) - ballIsHeld = drawFielder(HomeTeamSpriteGroup[2], ball, 350, 200, playdate.graphics.kImageFlippedX) or ballIsHeld + local fielder1 = { x = 30, y = 200, spriteIndex = 1 } + local ballIsHeld = drawFielder(AwayTeamSpriteGroup, fielder1, ball) + + local fielder2 = { x = 350, y = 200, spriteIndex = 2 } + ballIsHeld = drawFielder(HomeTeamSpriteGroup, fielder2, ball, playdate.graphics.kImageFlippedX) or ballIsHeld if not ballIsHeld then gfx.setLineWidth(2) diff --git a/src/main.lua b/src/main.lua index 9b54622..896499e 100644 --- a/src/main.lua +++ b/src/main.lua @@ -50,10 +50,11 @@ import 'user-input.lua' import 'draw/box-score.lua' import 'draw/fans.lua' -import 'draw/fielder.lua' +import 'draw/characters.lua' import 'draw/overlay.lua' import 'draw/panner.lua' -import 'draw/player.lua' +import 'draw/character-sprites.lua' +import 'draw/characters.lua' import 'draw/throw-meter.lua' import 'draw/transitions.lua' -- stylua: ignore end @@ -95,21 +96,15 @@ local teams = { ---@field batBase XyPair ---@field batTip XyPair ---@field batAngleDeg number --- These are only sort-of global state. They are purely graphical, --- but they need to be kept in sync with the rest of the globals. ----@field runnerBlipper Blipper ----@field battingTeamSprites SpriteCollection ----@field fieldingTeamSprites SpriteCollection ---@class Game ---@field private settings Settings ---@field private announcer Announcer ---@field private fielding Fielding ---@field private baserunning Baserunning +---@field private characters Characters ---@field private npc InputHandler ---@field private userInput InputHandler ----@field private homeTeamBlipper Blipper ----@field private awayTeamBlipper Blipper ---@field private panner Panner ---@field private state MutableState Game = {} @@ -124,20 +119,15 @@ Game = {} function Game.new(settings, announcer, fielding, baserunning, npc, state) announcer = announcer or Announcer.new() fielding = fielding or Fielding.new() - settings.userTeam = nil -- "away" + settings.userTeam = "away" - local homeTeamBlipper = blipper.new(100, settings.homeTeamSpriteGroup) - local awayTeamBlipper = blipper.new(100, settings.awayTeamSpriteGroup) 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, panner = Panner.new(ball), state = state or { batBase = utils.xy(C.Center.x - 34, 215), @@ -150,9 +140,6 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state) inning = 1, pitchIsOver = true, didSwing = false, - battingTeamSprites = settings.awayTeamSpriteGroup, - fieldingTeamSprites = settings.homeTeamSpriteGroup, - runnerBlipper = runnerBlipper, stats = Statistics.new(), }, }, { __index = Game }) @@ -169,6 +156,7 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state) playdate.timer.new(2000, function() o:returnToPitcher() end) + o.characters = Characters.new(settings.homeTeamSpriteGroup, settings.awayTeamSpriteGroup) BootTune:play() BootTune:setFinishCallback(function() @@ -269,8 +257,6 @@ function Game:pitcherIsReady() end function Game:checkForGameOver() - Fielding.celebrate() - local state = self.state if state.stats:gameIsOver(state.inning, self.settings.finalInning, state.battingTeam) then self.announcer:say("THAT'S THE BALL GAME!") @@ -285,6 +271,8 @@ end function Game:nextHalfInning() pitchTracker:reset() + Fielding.celebrate() + if self:checkForGameOver() then return end @@ -297,17 +285,8 @@ function Game:nextHalfInning() self.state.inning = self.state.inning + 1 self.state.stats:pushInning() end - self.state.battingTeam = getOppositeTeamId(self.state.battingTeam) playdate.timer.new(2000, function() - if self.state.battingTeam == "home" then - self.state.battingTeamSprites = self.settings.homeTeamSpriteGroup - self.state.runnerBlipper = self.homeTeamBlipper - self.state.fieldingTeamSprites = self.settings.awayTeamSpriteGroup - else - self.state.battingTeamSprites = self.settings.awayTeamSpriteGroup - self.state.fieldingTeamSprites = self.settings.homeTeamSpriteGroup - self.state.runnerBlipper = self.awayTeamBlipper - end + self.state.battingTeam = getOppositeTeamId(self.state.battingTeam) end) end @@ -382,7 +361,22 @@ function Game:strikeOut() end function Game:saveToFile() - playdate.datastore.write(self, "data", true) + playdate.datastore.write({ currentGame = self }, "data", true) +end + +function Game.load() + local loaded = playdate.datastore.read("data") + ---@type Game + local loadedGame = loaded.currentGame + loadedGame.state.ball = Ball.new(gfx.animator) + local settings = { + homeTeamSpriteGroup = HomeTeamSpriteGroup, + awayTeamSpriteGroup = AwayTeamSpriteGroup, + finalInning = loadedGame.settings.finalInning, + } + local ret = Game.new(settings, nil, loadedGame.fielding, nil, nil, loadedGame.state) + ret.baserunning.outs = loadedGame.outs + return ret end local SwingBackDeg = 30 @@ -533,10 +527,10 @@ function Game:updateGameState() playdate.resetElapsedTime() self.state.ball:updatePosition() - local offenseHandler, defenseHandler = self:currentInputHandlers() - local fielderHoldingBall = self.fielding:updateFielderPositions(self.state.ball, self.state.deltaSeconds) + local offenseHandler, defenseHandler = self:currentInputHandlers() + if self.state.offenseState == C.Offense.batting then self:updatePitching(defenseHandler) self:updateBatting(offenseHandler) @@ -590,81 +584,16 @@ function Game:update() gfx.clear() gfx.setColor(gfx.kColorBlack) - local offsetX, offsetY = self.panner:get(self.state.deltaSeconds) + local state = self.state + local offsetX, offsetY = self.panner:get(state.deltaSeconds) gfx.setDrawOffset(offsetX, offsetY) fans.draw() GrassBackground:draw(-400, -720) - ---@type { y: number, drawAction: fun() }[] - local characterDraws = {} - function addDraw(y, drawAction) - characterDraws[#characterDraws + 1] = { y = y, drawAction = drawAction } - end + local ball = state.ball - local ball = self.state.ball - - local danceOffset = FielderDanceAnimator:currentValue() - ---@type Fielder | nil - local ballHeldBy - for _, fielder in pairs(self.fielding.fielders) do - addDraw(fielder.y + danceOffset, function() - local ballHeldByThisFielder = drawFielder( - self.state.fieldingTeamSprites[fielder.spriteIndex], - ball, - fielder.x, - fielder.y + danceOffset - ) - if ballHeldByThisFielder then - ballHeldBy = fielder - end - end) - end - - local playerHeightOffset = 20 - 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[runner.spriteIndex].back:draw(runner.x, runner.y - playerHeightOffset) - else - self.state.battingTeamSprites[runner.spriteIndex].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, runner) - 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(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 - - for _, runner in pairs(self.baserunning.outRunners) do - self.state.battingTeamSprites[runner.spriteIndex].frowning:draw(runner.x, runner.y - playerHeightOffset) - end - for _, runner in pairs(self.baserunning.scoredRunners) do - self.state.runnerBlipper:draw(false, runner.x, runner.y - playerHeightOffset, runner) - end + local ballHeldBy = self.characters:drawAll(self.fielding, self.baserunning, state, state.battingTeam, ball) if self:userIsOn("defense") then throwMeter:drawNearFielder(ballHeldBy) @@ -685,16 +614,8 @@ function Game:update() drawMinimap(self.baserunning.runners, self.fielding.fielders) end - local homeScore, awayScore = utils.totalScores(self.state.stats) - drawScoreboard( - 0, - C.Screen.H * 0.77, - homeScore, - awayScore, - self.baserunning.outs, - self.state.battingTeam, - self.state.inning - ) + local homeScore, awayScore = utils.totalScores(state.stats) + drawScoreboard(0, C.Screen.H * 0.77, homeScore, awayScore, self.baserunning.outs, state.battingTeam, state.inning) drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes) self.announcer:draw(C.Center.x, 10) diff --git a/src/test/testMain.lua b/src/test/testMain.lua index 75a2725..8219040 100644 --- a/src/test/testMain.lua +++ b/src/test/testMain.lua @@ -13,6 +13,13 @@ import = function(target) require(target:sub(1, #target - 4)) end +Glove = { + getSize = function() + return 10, 10 + end, +} +Characters = require("draw/characters") + local Game = require("main") local settings = {