Skeleton tracking/display of balls and strikes

This commit is contained in:
Sage Vaillancourt 2025-02-07 00:17:24 -05:00
parent 2867b4a367
commit a341abed31
4 changed files with 127 additions and 76 deletions

View File

@ -6,8 +6,14 @@ function dbg.label(value, name)
if type(value) == "table" then
print(name .. ":")
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)
else
print(name .. ": nil")
end
return value
end

View File

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

View File

@ -16,9 +16,15 @@ function getIndicators(teams, battingTeam)
return "", IndicatorWidth, Indicator, 0
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)
local gfx = playdate.graphics
local homeScore = teams.home.score
local awayScore = teams.away.score

View File

@ -14,18 +14,6 @@ function utils.easingHill(t, b, c, d)
return (c * t) + b
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 y number
---@return XYPair