From 2867b4a367e0841426d77e8b6a88812d48e8caa3 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Thu, 6 Feb 2025 21:36:51 -0500 Subject: [PATCH] Add basic player-controlled fielding. Pull out a dbg.lua for functions that can be called for testing certain scenarios. --- Makefile | 2 +- src/dbg.lua | 36 ++++++++++++ src/main.lua | 160 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 131 insertions(+), 67 deletions(-) create mode 100644 src/dbg.lua diff --git a/Makefile b/Makefile index bf675bc..80e414c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -SOURCE_FILES := src/utils.lua src/announcer.lua src/graphics.lua src/scoreboard.lua src/main.lua +SOURCE_FILES := src/utils.lua src/dbg.lua src/announcer.lua src/graphics.lua src/scoreboard.lua src/main.lua all: pdc src BatterUp.pdx diff --git a/src/dbg.lua b/src/dbg.lua new file mode 100644 index 0000000..b3e7795 --- /dev/null +++ b/src/dbg.lua @@ -0,0 +1,36 @@ +-- selene: allow(unscoped_variables) +dbg = {} + +-- selene: allow(unused_variable) +function dbg.label(value, name) + if type(value) == "table" then + print(name .. ":") + printTable(value) + else + print(name .. ": " .. value) + end + return value +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) + newRunner() + newRunner() + newRunner() + runners[2].x = baseConstants[1].x + runners[2].y = baseConstants[1].y + runners[2].nextBase = baseConstants[2] + + runners[3].x = baseConstants[2].x + runners[3].y = baseConstants[2].y + runners[3].nextBase = baseConstants[3] + + runners[4].x = baseConstants[3].x + runners[4].y = baseConstants[3].y + runners[4].nextBase = baseConstants[4] +end + +if not playdate then + return dbg +end diff --git a/src/main.lua b/src/main.lua index b9d8d25..fc46a92 100644 --- a/src/main.lua +++ b/src/main.lua @@ -35,6 +35,7 @@ import 'CoreLibs/ui.lua' --- @alias EasingFunc fun(number, number, number, number): number import 'announcer.lua' +import 'dbg.lua' import 'graphics.lua' import 'scoreboard.lua' import 'utils.lua' @@ -503,18 +504,50 @@ function tryToMakeAnOut(fielder) end end ----@param fielder Fielder -function updateFielder(fielder) - if fielder.target ~= nil then - if not utils.moveAtSpeed(fielder, fielder.speed * deltaSeconds, fielder.target) then - fielder.target = nil - end +local throwMeter = 0 +local PitchMeterLimit = 15 + +function readThrow() + if throwMeter > PitchMeterLimit then + return (PitchFlyMs / (throwMeter / PitchMeterLimit)) + end + return nil +end + +---@param thrower Fielder +---@param throwFlyMs number +---@return boolean didThrow +function buttonControlledThrow(thrower, throwFlyMs, forbidThrowHome) + local targetBase + if playdate.buttonIsPressed(playdate.kButtonLeft) then + targetBase = Bases[Third] + elseif playdate.buttonIsPressed(playdate.kButtonUp) then + targetBase = Bases[Second] + elseif playdate.buttonIsPressed(playdate.kButtonRight) then + targetBase = Bases[First] + elseif not forbidThrowHome and playdate.buttonIsPressed(playdate.kButtonDown) then + targetBase = Bases[Home] + else + return false end - if offenseMode ~= Offense.running or not isTouchingBall(fielder.x, fielder.y) then + local closestFielder = utils.getNearestOf(fielders, targetBase.x, targetBase.y, function(fielder) + return fielder ~= thrower + end) + + throwBall(targetBase.x, targetBase.y, playdate.easingFunctions.linear, throwFlyMs) + closestFielder.target = targetBase + secondsSinceLastRunnerMove = 0 + offenseMode = Offense.running + throwMeter = 0 + + return true +end + +function updateNpcFielder(fielder) + if offenseMode ~= Offense.running then return end - local touchedBase = isTouchingBase(fielder.x, fielder.y) for i, runner in pairs(runners) do local runnerOnBase = isTouchingBase(runner.x, runner.y) @@ -538,6 +571,28 @@ function updateFielder(fielder) end end +---@param fielder Fielder +function updateFielder(fielder) + if fielder.target ~= nil then + if not utils.moveAtSpeed(fielder, fielder.speed * deltaSeconds, fielder.target) then + fielder.target = nil + end + end + + if not isTouchingBall(fielder.x, fielder.y) then + return + end + + if playerIsOn(Sides.defense) then + local throwFly = readThrow() + if throwFly then + buttonControlledThrow(fielders.pitcher, throwFly) + end + else + updateNpcFielder(fielder) + end +end + function updateFielders() for _, fielder in pairs(fielders) do updateFielder(fielder) @@ -694,6 +749,20 @@ function walkAwayOutRunners() end end +function playerPitch(throwFly) + local aButton = playdate.buttonIsPressed(playdate.kButtonA) + local bButton = playdate.buttonIsPressed(playdate.kButtonB) + if not aButton and not bButton then + pitch(throwFly, 1) + elseif aButton and not bButton then + pitch(throwFly, 2) + elseif not aButton and bButton then + pitch(throwFly, 3) + elseif aButton and bButton then + pitch(throwFly, 4) + end +end + local npcBatDeg = 0 local NpcBatSpeed = 1500 @@ -721,45 +790,6 @@ function npcRunningSpeed() return 0 end -local throwMeter = 0 -local PitchMeterLimit = 25 - -function readThrow() - if throwMeter > PitchMeterLimit then - return (PitchFlyMs / (throwMeter / PitchMeterLimit)) - end - return nil -end - ----@param thrower Fielder ----@param throwFlyMs number ----@return boolean didThrow -function buttonControlledThrow(thrower, throwFlyMs, forbidThrowHome) - local targetBase - if playdate.buttonIsPressed(playdate.kButtonLeft) then - targetBase = Bases[Third] - elseif playdate.buttonIsPressed(playdate.kButtonUp) then - targetBase = Bases[Second] - elseif playdate.buttonIsPressed(playdate.kButtonRight) then - targetBase = Bases[First] - elseif not forbidThrowHome and playdate.buttonIsPressed(playdate.kButtonDown) then - targetBase = Bases[Home] - else - return false - end - - local closestFielder = utils.getNearestOf(fielders, targetBase.x, targetBase.y, function(fielder) - return fielder ~= thrower - end) - - throwBall(targetBase.x, targetBase.y, playdate.easingFunctions.linear, throwFlyMs) - closestFielder.target = targetBase - secondsSinceLastRunnerMove = 0 - offenseMode = Offense.running - - return true -end - function updateGameState() deltaSeconds = playdate.getElapsedTime() or 0 playdate.resetElapsedTime() @@ -773,8 +803,8 @@ function updateGameState() ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue() end + local playerOnOffense = playerIsOn(Sides.offense) if offenseMode == Offense.batting then - local playerOnOffense = playerIsOn(Sides.offense) secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then @@ -784,37 +814,35 @@ function updateGameState() throwMeter = 0 end - if not playerOnOffense then + local batSpeed + if playerOnOffense then + batAngleDeg, batSpeed = (playdate.getCrankPosition() + CrankOffsetDeg) % 360, crankChange + else throwMeter = math.max(0, throwMeter - (deltaSeconds * 150)) throwMeter = throwMeter + crankChange + batAngleDeg, batSpeed = npcBatAngle(), npcBatChange() end + updateBatting(batAngleDeg, batSpeed) + + -- TODO: Ensure batter can't be nil, here + updateRunner(batter, nil, crankChange) + if secondsSincePitchAllowed > PitchAfterSeconds then if playerOnOffense then pitch(PitchFlyMs, math.random(#Pitches)) else local throwFly = readThrow() if throwFly and not buttonControlledThrow(fielders.pitcher, throwFly, true) then - local aButton = playdate.buttonIsPressed(playdate.kButtonA) - local bButton = playdate.buttonIsPressed(playdate.kButtonB) - if not aButton and not bButton then - pitch(throwFly, 1) - elseif aButton and not bButton then - pitch(throwFly, 2) - elseif not aButton and bButton then - pitch(throwFly, 3) - elseif aButton and bButton then - pitch(throwFly, 4) - end + playerPitch(throwFly) end end end - batAngleDeg = playerOnOffense and ((playdate.getCrankPosition() + CrankOffsetDeg) % 360) or npcBatAngle() - local batSpeed = playerOnOffense and crankChange or npcBatChange() - updateBatting(batAngleDeg, batSpeed) - -- TODO: Ensure batter can't be nil, here - updateRunner(batter, nil, crankChange) elseif offenseMode == Offense.running then + if not playerOnOffense then + throwMeter = math.max(0, throwMeter - (deltaSeconds * 150)) + throwMeter = throwMeter + crankChange + end local appliedSpeed = playerIsOn(Sides.offense) and crankChange or npcRunningSpeed() if updateRunning(appliedSpeed) then secondsSinceLastRunnerMove = 0 @@ -872,9 +900,9 @@ end function playdate.update() playdate.timer.updateTimers() + gfx.animation.blinker.updateAll() updateGameState() - gfx.animation.blinker.updateAll() gfx.clear() gfx.setColor(gfx.kColorBlack)