Add basic player-controlled fielding.

Pull out a dbg.lua for functions that can be called for testing certain scenarios.
This commit is contained in:
Sage Vaillancourt 2025-02-06 21:36:51 -05:00
parent 4fc49d3631
commit 2867b4a367
3 changed files with 131 additions and 67 deletions

View File

@ -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: all:
pdc src BatterUp.pdx pdc src BatterUp.pdx

36
src/dbg.lua Normal file
View File

@ -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

View File

@ -35,6 +35,7 @@ import 'CoreLibs/ui.lua'
--- @alias EasingFunc fun(number, number, number, number): number --- @alias EasingFunc fun(number, number, number, number): number
import 'announcer.lua' import 'announcer.lua'
import 'dbg.lua'
import 'graphics.lua' import 'graphics.lua'
import 'scoreboard.lua' import 'scoreboard.lua'
import 'utils.lua' import 'utils.lua'
@ -503,18 +504,50 @@ function tryToMakeAnOut(fielder)
end end
end end
---@param fielder Fielder local throwMeter = 0
function updateFielder(fielder) local PitchMeterLimit = 15
if fielder.target ~= nil then
if not utils.moveAtSpeed(fielder, fielder.speed * deltaSeconds, fielder.target) then function readThrow()
fielder.target = nil if throwMeter > PitchMeterLimit then
return (PitchFlyMs / (throwMeter / PitchMeterLimit))
end 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 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 return
end end
local touchedBase = isTouchingBase(fielder.x, fielder.y) local touchedBase = isTouchingBase(fielder.x, fielder.y)
for i, runner in pairs(runners) do for i, runner in pairs(runners) do
local runnerOnBase = isTouchingBase(runner.x, runner.y) local runnerOnBase = isTouchingBase(runner.x, runner.y)
@ -538,6 +571,28 @@ function updateFielder(fielder)
end end
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() function updateFielders()
for _, fielder in pairs(fielders) do for _, fielder in pairs(fielders) do
updateFielder(fielder) updateFielder(fielder)
@ -694,6 +749,20 @@ function walkAwayOutRunners()
end end
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 npcBatDeg = 0
local NpcBatSpeed <const> = 1500 local NpcBatSpeed <const> = 1500
@ -721,45 +790,6 @@ function npcRunningSpeed()
return 0 return 0
end 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() function updateGameState()
deltaSeconds = playdate.getElapsedTime() or 0 deltaSeconds = playdate.getElapsedTime() or 0
playdate.resetElapsedTime() playdate.resetElapsedTime()
@ -773,8 +803,8 @@ function updateGameState()
ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue() ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue()
end end
if offenseMode == Offense.batting then
local playerOnOffense = playerIsOn(Sides.offense) local playerOnOffense = playerIsOn(Sides.offense)
if offenseMode == Offense.batting then
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then
@ -784,37 +814,35 @@ function updateGameState()
throwMeter = 0 throwMeter = 0
end 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 = math.max(0, throwMeter - (deltaSeconds * 150))
throwMeter = throwMeter + crankChange throwMeter = throwMeter + crankChange
batAngleDeg, batSpeed = npcBatAngle(), npcBatChange()
end end
updateBatting(batAngleDeg, batSpeed)
-- TODO: Ensure batter can't be nil, here
updateRunner(batter, nil, crankChange)
if secondsSincePitchAllowed > PitchAfterSeconds then if secondsSincePitchAllowed > PitchAfterSeconds then
if playerOnOffense then if playerOnOffense then
pitch(PitchFlyMs, math.random(#Pitches)) pitch(PitchFlyMs, math.random(#Pitches))
else else
local throwFly = readThrow() local throwFly = readThrow()
if throwFly and not buttonControlledThrow(fielders.pitcher, throwFly, true) then if throwFly and not buttonControlledThrow(fielders.pitcher, throwFly, true) then
local aButton = playdate.buttonIsPressed(playdate.kButtonA) playerPitch(throwFly)
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
end end
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 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() local appliedSpeed = playerIsOn(Sides.offense) and crankChange or npcRunningSpeed()
if updateRunning(appliedSpeed) then if updateRunning(appliedSpeed) then
secondsSinceLastRunnerMove = 0 secondsSinceLastRunnerMove = 0
@ -872,9 +900,9 @@ end
function playdate.update() function playdate.update()
playdate.timer.updateTimers() playdate.timer.updateTimers()
gfx.animation.blinker.updateAll()
updateGameState() updateGameState()
gfx.animation.blinker.updateAll()
gfx.clear() gfx.clear()
gfx.setColor(gfx.kColorBlack) gfx.setColor(gfx.kColorBlack)