Skeleton tracking/display of balls and strikes #1

Merged
sage merged 1 commits from balls-and-strikes into main 2025-02-07 00:18:19 -05:00
4 changed files with 127 additions and 76 deletions
Showing only changes of commit a341abed31 - Show all commits

View File

@ -6,8 +6,14 @@ function dbg.label(value, name)
if type(value) == "table" then if type(value) == "table" then
print(name .. ":") print(name .. ":")
printTable(value) printTable(value)
else elseif type(value) == "boolean" then
if value then
print(name .. ": " .. (value and "true" or "false"))
end
elseif value ~= nil then
print(name .. ": " .. value) print(name .. ": " .. value)
else
print(name .. ": nil")
end end
return value return value
end end

View File

@ -69,6 +69,7 @@ local PlayerImageBlipper <const> = blipper.new(100, "images/game/player.png", "i
local DanceBounceMs <const> = 500 local DanceBounceMs <const> = 500
local DanceBounceCount <const> = 4 local DanceBounceCount <const> = 4
local FielderDanceAnimator <const> = gfx.animator.new(1, 10, 0, utils.easingHill) local FielderDanceAnimator <const> = gfx.animator.new(1, 10, 0, utils.easingHill)
FielderDanceAnimator.repeatCount = DanceBounceCount - 1 FielderDanceAnimator.repeatCount = DanceBounceCount - 1
@ -135,7 +136,7 @@ local ball <const> = {
heldBy = nil --[[@type Runner | nil]], heldBy = nil --[[@type Runner | nil]],
} }
local BatLength <const> = 50 --45 local BatLength <const> = 50
local Offense <const> = { local Offense <const> = {
batting = {}, batting = {},
@ -168,12 +169,15 @@ local battingTeam = teams.away
local outs = 0 local outs = 0
local inning = 1 local inning = 1
---@return boolean playerIsOnSide, boolean playerIsOnOtherSide
function playerIsOn(side) function playerIsOn(side)
local ret
if PlayerTeam == battingTeam then if PlayerTeam == battingTeam then
return side == Sides.offense ret = side == Sides.offense
else else
return side == Sides.defense ret = side == Sides.defense
end end
return ret, not ret
end end
-- TODO? Replace this AND ballSizeAnimator with a ballHeightAnimator -- TODO? Replace this AND ballSizeAnimator with a ballHeightAnimator
@ -292,6 +296,9 @@ end
---@type Runner | nil ---@type Runner | nil
local batter = newRunner() local batter = newRunner()
local throwMeter = 0
local PitchMeterLimit = 15
--- "Throws" the ball from its current position to the given destination. --- "Throws" the ball from its current position to the given destination.
---@param destX number ---@param destX number
---@param destY number ---@param destY number
@ -315,6 +322,7 @@ function throwBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler
if floaty then if floaty then
ballFloatAnimator:reset(flyTimeMs) ballFloatAnimator:reset(flyTimeMs)
end end
throwMeter = 0
end end
local PitchAfterSeconds = 7 local PitchAfterSeconds = 7
@ -403,9 +411,10 @@ local secondsSinceLastRunnerMove = 0
---@param runnerIndex integer ---@param runnerIndex integer
function outRunner(runnerIndex) function outRunner(runnerIndex)
outs = outs + 1
outRunners[#outRunners + 1] = runners[runnerIndex] outRunners[#outRunners + 1] = runners[runnerIndex]
table.remove(runners, runnerIndex) table.remove(runners, runnerIndex)
outs = outs + 1
updateForcedRunners() updateForcedRunners()
announcer:say("YOU'RE OUT!") announcer:say("YOU'RE OUT!")
@ -504,9 +513,6 @@ function tryToMakeAnOut(fielder)
end end
end end
local throwMeter = 0
local PitchMeterLimit = 15
function readThrow() function readThrow()
if throwMeter > PitchMeterLimit then if throwMeter > PitchMeterLimit then
return (PitchFlyMs / (throwMeter / PitchMeterLimit)) return (PitchFlyMs / (throwMeter / PitchMeterLimit))
@ -539,35 +545,41 @@ function buttonControlledThrow(thrower, throwFlyMs, forbidThrowHome)
closestFielder.target = targetBase closestFielder.target = targetBase
secondsSinceLastRunnerMove = 0 secondsSinceLastRunnerMove = 0
offenseMode = Offense.running offenseMode = Offense.running
throwMeter = 0
return true return true
end end
function updateNpcFielder(fielder) function outEligibleRunners(fielder)
local touchedBase = isTouchingBase(fielder.x, fielder.y)
local didOutRunner = false
for i, runner in pairs(runners) do
local runnerOnBase = isTouchingBase(runner.x, runner.y)
if -- Force out
touchedBase
and runner.prevBase -- Make sure the runner is not standing at home
and runner.forcedTo == touchedBase
and touchedBase ~= runnerOnBase
-- Tag out
or not runnerOnBase and utils.distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance
then
outRunner(i)
didOutRunner = true
end
end
return didOutRunner
end
function updateNpcFielder(fielder, outedSomeRunner)
if offenseMode ~= Offense.running then if offenseMode ~= Offense.running then
return return
end end
local touchedBase = isTouchingBase(fielder.x, fielder.y) if outedSomeRunner then
for i, runner in pairs(runners) do playdate.timer.new(750, function()
local runnerOnBase = isTouchingBase(runner.x, runner.y)
if
( -- Force out
touchedBase
-- and runner.prevBase -- Make sure the runner is not standing at home
and runner.forcedTo == touchedBase
and touchedBase ~= runnerOnBase
)
-- Tag out
or (not runnerOnBase and utils.distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance)
then
outRunner(i)
playdate.timer.new(750, function()
tryToMakeAnOut(fielder)
end)
else
tryToMakeAnOut(fielder) tryToMakeAnOut(fielder)
end end)
else
tryToMakeAnOut(fielder)
end end
end end
@ -583,30 +595,18 @@ function updateFielder(fielder)
return return
end end
local outedSomeRunner = outEligibleRunners(fielder)
if playerIsOn(Sides.defense) then if playerIsOn(Sides.defense) then
local throwFly = readThrow() local throwFly = readThrow()
if throwFly then if throwFly then
buttonControlledThrow(fielders.pitcher, throwFly) buttonControlledThrow(fielders.pitcher, throwFly)
end end
else else
updateNpcFielder(fielder) updateNpcFielder(fielder, outedSomeRunner)
end end
end end
function updateFielders()
for _, fielder in pairs(fielders) do
updateFielder(fielder)
end
-- if offenseMode == Offense.batting then
-- utils.moveAtSpeed(
-- fielders.catcher,
-- fielders.catcher.speed * 2 * deltaSeconds,
-- { x = math.min(Center.x + 15, ball.x), y = fielders.catcher.y }
-- )
-- end
end
--- Returns true only if the given runner moved during this update. --- Returns true only if the given runner moved during this update.
---@param runner Runner | nil ---@param runner Runner | nil
---@param runnerIndex integer | nil May only be nil if runner == batter ---@param runnerIndex integer | nil May only be nil if runner == batter
@ -665,6 +665,53 @@ end
---@type number ---@type number
local batAngleDeg local batAngleDeg
-- Used for tracking whether or not a pitch was a strike
local recordedPitchX = nil
local balls = 0
local strikes = 0
local StrikeZoneStartX <const> = Center.x - 16
local StrikeZoneEndX <const> = StrikeZoneStartX + 24
local StrikeZoneStartY <const> = Screen.H - 35
function recordStrikePosition()
if not recordedPitchX and ball.y > StrikeZoneStartY then
recordedPitchX = ball.x
end
end
function nextBatter()
playdate.timer.new(2000, function()
balls = 0
strikes = 0
batter = newRunner()
end)
end
function walk()
-- TODO
nextBatter()
end
function strikeOut()
-- TODO
nextBatter()
end
function recordPitch()
if recordedPitchX > StrikeZoneStartX and recordedPitchX < StrikeZoneEndX then
strikes = strikes + 1
if strikes >= 3 then
strikeOut()
end
else
balls = balls + 1
if balls >= 4 then
walk()
end
end
end
---@param batDeg number ---@param batDeg number
function updateBatting(batDeg, batSpeed) function updateBatting(batDeg, batSpeed)
if ball.y < BallOffscreen then if ball.y < BallOffscreen then
@ -764,19 +811,21 @@ function playerPitch(throwFly)
end end
local npcBatDeg = 0 local npcBatDeg = 0
local NpcBatSpeed <const> = 1500 local BaseNpcBatSpeed <const> = 1500
local npcBatSpeed = 1500
function npcBatAngle() function npcBatAngle()
if not catcherThrownBall and ball.y > 200 and ball.y < 230 and (ball.x < Center.x + 15) then if not catcherThrownBall and ball.y > 200 and ball.y < 230 and (ball.x < Center.x + 15) then
npcBatDeg = npcBatDeg + (deltaSeconds * NpcBatSpeed) npcBatDeg = npcBatDeg + (deltaSeconds * npcBatSpeed)
else else
npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed
npcBatDeg = 200 npcBatDeg = 200
end end
return npcBatDeg return npcBatDeg
end end
function npcBatChange() function npcBatChange()
return deltaSeconds * NpcBatSpeed return deltaSeconds * npcBatSpeed
end end
function npcRunningSpeed() function npcRunningSpeed()
@ -785,7 +834,7 @@ function npcRunningSpeed()
end end
local touchedBase = isTouchingBase(runners[1].x, runners[1].y) local touchedBase = isTouchingBase(runners[1].x, runners[1].y)
if not touchedBase or touchedBase == Bases[Home] then if not touchedBase or touchedBase == Bases[Home] then
return 25 return 10
end end
return 0 return 0
end end
@ -803,23 +852,27 @@ function updateGameState()
ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue() ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue()
end end
local playerOnOffense = playerIsOn(Sides.offense) local playerOnOffense, playerOnDefense = playerIsOn(Sides.offense)
if playerOnDefense then
throwMeter = math.max(0, throwMeter - (deltaSeconds * 150))
throwMeter = throwMeter + math.abs(crankChange)
end
if offenseMode == Offense.batting then if offenseMode == Offense.batting then
recordStrikePosition()
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then
recordPitch()
throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true) throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true)
catcherThrownBall = true catcherThrownBall = true
else
throwMeter = 0
end end
local batSpeed local batSpeed
if playerOnOffense then if playerOnOffense then
batAngleDeg, batSpeed = (playdate.getCrankPosition() + CrankOffsetDeg) % 360, crankChange batAngleDeg, batSpeed = (playdate.getCrankPosition() + CrankOffsetDeg) % 360, crankChange
else else
throwMeter = math.max(0, throwMeter - (deltaSeconds * 150))
throwMeter = throwMeter + crankChange
batAngleDeg, batSpeed = npcBatAngle(), npcBatChange() batAngleDeg, batSpeed = npcBatAngle(), npcBatChange()
end end
@ -829,21 +882,17 @@ function updateGameState()
updateRunner(batter, nil, crankChange) updateRunner(batter, nil, crankChange)
if secondsSincePitchAllowed > PitchAfterSeconds then if secondsSincePitchAllowed > PitchAfterSeconds then
if playerOnOffense then if playerOnDefense then
pitch(PitchFlyMs, math.random(#Pitches))
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
playerPitch(throwFly) playerPitch(throwFly)
end end
else
pitch(PitchFlyMs, math.random(#Pitches))
end end
end end
elseif offenseMode == Offense.running then elseif offenseMode == Offense.running then
if not playerOnOffense then local appliedSpeed = playerOnOffense and crankChange or npcRunningSpeed()
throwMeter = math.max(0, throwMeter - (deltaSeconds * 150))
throwMeter = throwMeter + crankChange
end
local appliedSpeed = playerIsOn(Sides.offense) and crankChange or npcRunningSpeed()
if updateRunning(appliedSpeed) then if updateRunning(appliedSpeed) then
secondsSinceLastRunnerMove = 0 secondsSinceLastRunnerMove = 0
else else
@ -859,7 +908,9 @@ function updateGameState()
end end
end end
updateFielders() for _, fielder in pairs(fielders) do
updateFielder(fielder)
end
walkAwayOutRunners() walkAwayOutRunners()
end end
@ -901,7 +952,6 @@ end
function playdate.update() function playdate.update()
playdate.timer.updateTimers() playdate.timer.updateTimers()
gfx.animation.blinker.updateAll() gfx.animation.blinker.updateAll()
updateGameState() updateGameState()
gfx.clear() gfx.clear()
@ -964,6 +1014,7 @@ function playdate.update()
drawMinimap() drawMinimap()
end end
drawScoreboard(0, Screen.H * 0.77, teams, outs, battingTeam, inning) drawScoreboard(0, Screen.H * 0.77, teams, outs, battingTeam, inning)
drawBallsAndStrikes(300, Screen.H * 0.77, balls, strikes)
announcer:draw(Center.x, 10) announcer:draw(Center.x, 10)
end end

View File

@ -16,9 +16,15 @@ function getIndicators(teams, battingTeam)
return "", IndicatorWidth, Indicator, 0 return "", IndicatorWidth, Indicator, 0
end end
function drawBallsAndStrikes(x, y, balls, strikes)
local gfx = playdate.graphics
print("" .. balls .. ":" .. strikes)
gfx.setColor(gfx.kColorBlack)
gfx.fillRect(x, y, 20, ScoreboardHeight)
end
function drawScoreboard(x, y, teams, outs, battingTeam, inning) function drawScoreboard(x, y, teams, outs, battingTeam, inning)
local gfx = playdate.graphics local gfx = playdate.graphics
local homeScore = teams.home.score local homeScore = teams.home.score
local awayScore = teams.away.score local awayScore = teams.away.score

View File

@ -14,18 +14,6 @@ function utils.easingHill(t, b, c, d)
return (c * t) + b return (c * t) + b
end end
-- Useful for quick print-the-value-in-place debugging.
-- selene: allow(unused_variable)
function utils.label(value, name)
if type(value) == "table" then
print(name .. ":")
printTable(value)
else
print(name .. ": " .. value)
end
return value
end
---@param x number ---@param x number
---@param y number ---@param y number
---@return XYPair ---@return XYPair