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:
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
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 <const> = 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)