Skeleton tracking/display of balls and strikes #1
|
@ -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
|
||||||
|
|
175
src/main.lua
175
src/main.lua
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue