Start supporting less accurate pitches

Fix secondsSinceRunnerLastMove nil bug
This commit is contained in:
Sage Vaillancourt 2025-02-20 00:06:43 -05:00
parent d77675b0cb
commit 08a3189780
4 changed files with 43 additions and 23 deletions

View File

@ -264,7 +264,7 @@ function Baserunning:updateNonBatterRunners(appliedSpeed, forcedOnly, deltaSecon
self.secondsSinceLastRunnerMove = 0 self.secondsSinceLastRunnerMove = 0
self:updateForcedRunners() self:updateForcedRunners()
else else
self.secondsSinceLastRunnerMove = self.secondsSinceLastRunnerMove + deltaSeconds self.secondsSinceLastRunnerMove = (self.secondsSinceLastRunnerMove or 0) + deltaSeconds
end end
return runnersStillMoving, runnersScored, self.secondsSinceLastRunnerMove return runnersStillMoving, runnersScored, self.secondsSinceLastRunnerMove

View File

@ -196,13 +196,14 @@ end
---@param pitchFlyTimeMs number | nil ---@param pitchFlyTimeMs number | nil
---@param pitchTypeIndex number | nil ---@param pitchTypeIndex number | nil
function Game:pitch(pitchFlyTimeMs, pitchTypeIndex) ---@param accuracy number The closer to 1.0, the better
function Game:pitch(pitchFlyTimeMs, pitchTypeIndex, accuracy)
self.state.ball:markUncatchable() self.state.ball:markUncatchable()
self.state.ball.heldBy = nil self.state.ball.heldBy = nil
self.state.pitchIsOver = false self.state.pitchIsOver = false
self.state.offenseState = C.Offense.batting self.state.offenseState = C.Offense.batting
local current = Pitches[pitchTypeIndex](self.state.ball) local current = Pitches[pitchTypeIndex](accuracy, self.state.ball)
self.state.ball.xAnimator = current.x self.state.ball.xAnimator = current.x
self.state.ball.yAnimator = current.y or Pitches[1](self.state.ball).y self.state.ball.yAnimator = current.y or Pitches[1](self.state.ball).y
@ -450,17 +451,17 @@ function Game:returnToPitcher()
end end
---@param throwFly number ---@param throwFly number
function Game:userPitch(throwFly) function Game:userPitch(throwFly, accuracy)
local aPressed = playdate.buttonIsPressed(playdate.kButtonA) local aPressed = playdate.buttonIsPressed(playdate.kButtonA)
local bPressed = playdate.buttonIsPressed(playdate.kButtonB) local bPressed = playdate.buttonIsPressed(playdate.kButtonB)
if not aPressed and not bPressed then if not aPressed and not bPressed then
self:pitch(throwFly, 1) self:pitch(throwFly, 1, accuracy)
elseif aPressed and not bPressed then elseif aPressed and not bPressed then
self:pitch(throwFly, 2) self:pitch(throwFly, 2, accuracy)
elseif not aPressed and bPressed then elseif not aPressed and bPressed then
self:pitch(throwFly, 3) self:pitch(throwFly, 3, accuracy)
elseif aPressed and bPressed then elseif aPressed and bPressed then
self:pitch(throwFly, 4) self:pitch(throwFly, 4, accuracy)
end end
end end
@ -519,15 +520,15 @@ function Game:updateGameState()
self:updateBatting(self.state.batAngleDeg, batSpeed) self:updateBatting(self.state.batAngleDeg, batSpeed)
-- Walk batter to the plate -- Walk batter to the plate
self.baserunning:updateRunner(self.baserunning.batter, nil, crankLimited, self.state.deltaSeconds) self.baserunning:updateRunner(self.baserunning.batter, nil, userOnOffense and crankLimited or 0, self.state.deltaSeconds)
if pitchTracker.secondsSinceLastPitch > C.PitchAfterSeconds then if pitchTracker.secondsSinceLastPitch > C.PitchAfterSeconds then
if userOnDefense then if userOnDefense then
local powerRatio, isPerfect = throwMeter:readThrow(crankChange) local powerRatio, accuracy, isPerfect = throwMeter:readThrow(crankChange)
if powerRatio then if powerRatio then
local throwFly = C.PitchFlyMs / powerRatio local throwFly = C.PitchFlyMs / powerRatio
if throwFly and not self:buttonControlledThrow(throwFly, true) then if throwFly and not self:buttonControlledThrow(throwFly, true) then
self:userPitch(throwFly) self:userPitch(throwFly, accuracy)
end end
end end
else else
@ -547,7 +548,7 @@ function Game:updateGameState()
end end
if userOnDefense then if userOnDefense then
local powerRatio, isPerfect = throwMeter:readThrow(crankChange) local powerRatio, accuracy, isPerfect = throwMeter:readThrow(crankChange)
if powerRatio then if powerRatio then
local throwFly = C.PitchFlyMs / powerRatio local throwFly = C.PitchFlyMs / powerRatio
if throwFly then if throwFly then

View File

@ -18,6 +18,7 @@ function Npc.new(runners, fielders)
}, { __index = Npc }) }, { __index = Npc })
end end
-- TODO: FAR more nuanced NPC batting.
---@param ball XyPair ---@param ball XyPair
---@param pitchIsOver boolean ---@param pitchIsOver boolean
---@param deltaSec number ---@param deltaSec number

View File

@ -4,35 +4,47 @@
---@type pd_graphics_lib ---@type pd_graphics_lib
local gfx <const> = playdate.graphics local gfx <const> = playdate.graphics
local StrikeZoneWidth <const> = C.StrikeZoneEndX - C.StrikeZoneStartX
-- TODO? Also degrade speed
function getPitchMissBy(accuracy)
accuracy = accuracy or 1.0
local missBy = (1 - accuracy) * StrikeZoneWidth * 3
if math.random() > 0.5 then
missBy = missBy * -1
end
return missBy
end
---@type Pitch[] ---@type Pitch[]
Pitches = { Pitches = {
-- Fastball -- Fastball
function() function(accuracy)
return { return {
x = gfx.animator.new(0, C.PitchStart.x, C.PitchStart.x, playdate.easingFunctions.linear), x = gfx.animator.new(0, C.PitchStart.x, getPitchMissBy(accuracy) + C.PitchStart.x, playdate.easingFunctions.linear),
y = gfx.animator.new(C.PitchFlyMs / 1.3, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear), y = gfx.animator.new(C.PitchFlyMs / 1.3, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
} }
end, end,
-- Curve ball -- Curve ball
function() function(accuracy)
return { return {
x = gfx.animator.new(C.PitchFlyMs, C.PitchStart.x + 20, C.PitchStart.x, utils.easingHill), x = gfx.animator.new(C.PitchFlyMs, getPitchMissBy(accuracy) + C.PitchStart.x + 20, C.PitchStart.x, utils.easingHill),
y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear), y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
} }
end, end,
-- Slider -- Slider
function() function(accuracy)
return { return {
x = gfx.animator.new(C.PitchFlyMs, C.PitchStart.x - 20, C.PitchStart.x, utils.easingHill), x = gfx.animator.new(C.PitchFlyMs, getPitchMissBy(accuracy) + C.PitchStart.x - 20, C.PitchStart.x, utils.easingHill),
y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear), y = gfx.animator.new(C.PitchFlyMs, C.PitchStart.y, C.PitchEndY, playdate.easingFunctions.linear),
} }
end, end,
-- Wobbbleball -- Wobbbleball
function(ball) function(accuracy, ball)
return { return {
x = { x = {
currentValue = function() currentValue = function()
return C.PitchStart.x + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10)) return getPitchMissBy(accuracy) + C.PitchStart.x + (10 * math.sin((ball.yAnimator:currentValue() - C.PitchStart.y) / 10))
end, end,
reset = function() end, reset = function() end,
}, },
@ -117,14 +129,20 @@ local crankQueue = {}
--- Returns nil when a throw is NOT requested. --- Returns nil when a throw is NOT requested.
---@param chargeAmount number ---@param chargeAmount number
---@return number | nil powerRatio, boolean isPerfect ---@return number | nil powerRatio, number | nil accuracy, boolean isPerfect
function throwMeter:readThrow(chargeAmount) function throwMeter:readThrow(chargeAmount)
local ret = self:applyCharge(chargeAmount) local ret = self:applyCharge(chargeAmount)
if ret then if ret then
local ratio = ret / self.idealPower local ratio = ret / self.idealPower
return ratio, math.abs(ratio - 1) < 0.05 local accuracy
if ratio >= 1 then
accuracy = 1 / ratio / 2
else
accuracy = 1 -- Accuracy is perfect on slow throws
end
return ratio, accuracy, math.abs(ratio - 1) < 0.05
end end
return nil, false return nil, nil, false
end end
--- If (within approx. a third of a second) the crank has moved more than 45 degrees, call that a throw. --- If (within approx. a third of a second) the crank has moved more than 45 degrees, call that a throw.