Implementing walks and strike-outs
* Look at limiting batting/throw power with math.log() * Extract draw/fielder.lua * Extract some values into constants.lua * Extract npc.lua for computer batting (and eventually probably more CPU behavior) * pitchTracker in utils
This commit is contained in:
parent
969de111fe
commit
881ff0e734
2
Makefile
2
Makefile
|
@ -1,4 +1,4 @@
|
|||
SOURCE_FILES := src/utils.lua src/dbg.lua src/announcer.lua src/graphics.lua src/scoreboard.lua src/main.lua
|
||||
SOURCE_FILES := src/constants.lua src/draw/* src/utils.lua src/dbg.lua src/npc.lua src/announcer.lua src/graphics.lua src/scoreboard.lua src/main.lua
|
||||
|
||||
all:
|
||||
pdc src BatterUp.pdx
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
-- selene: allow(shadowing)
|
||||
local gfx = playdate.graphics
|
||||
|
||||
local AnnouncementFont <const> = playdate.graphics.font.new("fonts/Roobert-20-Medium.pft")
|
||||
local AnnouncementTransitionMs <const> = 300
|
||||
local AnnouncerMarginX <const> = 26
|
||||
|
@ -49,7 +52,6 @@ function announcer.draw(self, x, y)
|
|||
end
|
||||
x = x - 5 -- Infield center is slightly offset from screen center
|
||||
|
||||
local gfx = playdate.graphics
|
||||
local originalDrawMode = gfx.getImageDrawMode()
|
||||
local width = math.max(150, (AnnouncerMarginX * 2) + AnnouncementFont:getTextWidth(self.textQueue[1]))
|
||||
local animY = self.animatorY:currentValue()
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
-- selene: allow(unscoped_variables)
|
||||
C = {}
|
||||
|
||||
C.Screen = {
|
||||
W = playdate.display.getWidth(),
|
||||
H = playdate.display.getHeight(),
|
||||
}
|
||||
|
||||
C.Center = utils.xy(C.Screen.W / 2, C.Screen.H / 2)
|
||||
|
||||
C.StrikeZoneStartX = C.Center.x - 16
|
||||
C.StrikeZoneEndX = C.StrikeZoneStartX + 24
|
||||
C.StrikeZoneStartY = C.Screen.H - 35
|
|
@ -0,0 +1,29 @@
|
|||
-- selene: allow(shadowing)
|
||||
local gfx = playdate.graphics
|
||||
|
||||
local Glove <const> = playdate.graphics.image.new("images/game/glove.png") --[[@as pd_image]]
|
||||
local GloveSizeX, GloveSizeY <const> = Glove:getSize()
|
||||
|
||||
local GloveHoldingBall <const> = playdate.graphics.image.new("images/game/glove-holding-ball.png") --[[@as pd_image]]
|
||||
local GloveOffX, GloveOffY <const> = GloveSizeX / 2, GloveSizeY / 2
|
||||
|
||||
---@param fielderX number
|
||||
---@param fielderY number
|
||||
---@return boolean isHoldingBall
|
||||
local function drawFielderGlove(ball, fielderX, fielderY)
|
||||
local distanceFromBall = utils.distanceBetweenZ(fielderX, fielderY, 0, ball.x, ball.y, ball.z)
|
||||
local shoulderX, shoulderY = fielderX + 10, fielderY + 5
|
||||
if distanceFromBall > 20 then
|
||||
Glove:draw(shoulderX, shoulderY)
|
||||
return false
|
||||
else
|
||||
GloveHoldingBall:draw(ball.x - GloveOffX, ball.y - GloveOffY)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
---@return boolean isHoldingBall
|
||||
function drawFielder(ball, x, y)
|
||||
gfx.fillRect(x, y, 14, 25)
|
||||
return drawFielderGlove(ball, x, y)
|
||||
end
|
240
src/main.lua
240
src/main.lua
|
@ -34,22 +34,20 @@ import 'CoreLibs/ui.lua'
|
|||
|
||||
--- @alias EasingFunc fun(number, number, number, number): number
|
||||
|
||||
import 'utils.lua'
|
||||
import 'constants.lua'
|
||||
|
||||
import 'announcer.lua'
|
||||
import 'dbg.lua'
|
||||
import 'graphics.lua'
|
||||
import 'npc.lua'
|
||||
import 'scoreboard.lua'
|
||||
import 'utils.lua'
|
||||
import 'draw/fielder'
|
||||
-- stylua: ignore end
|
||||
|
||||
-- selene: allow(shadowing)
|
||||
local gfx <const> = playdate.graphics
|
||||
|
||||
local Screen <const> = {
|
||||
W = playdate.display.getWidth(),
|
||||
H = playdate.display.getHeight(),
|
||||
}
|
||||
|
||||
local Center <const> = utils.xy(Screen.W / 2, Screen.H / 2)
|
||||
|
||||
local BootTune <const> = playdate.sound.sampleplayer.new("sounds/boot-tune.wav")
|
||||
-- local BootTune <const> = playdate.sound.sampleplayer.new("sounds/boot-tune-organy.wav")
|
||||
local TinnyBackground <const> = playdate.sound.sampleplayer.new("sounds/tinny-background.wav")
|
||||
|
@ -60,11 +58,6 @@ local PlayerFrown <const> = gfx.image.new("images/game/player-frown.png") --[[@a
|
|||
local PlayerSmile <const> = gfx.image.new("images/game/player.png") --[[@as pd_image]]
|
||||
local PlayerBack <const> = gfx.image.new("images/game/player-back.png") --[[@as pd_image]]
|
||||
|
||||
local Glove <const> = gfx.image.new("images/game/glove.png") --[[@as pd_image]]
|
||||
local GloveHoldingBall <const> = gfx.image.new("images/game/glove-holding-ball.png") --[[@as pd_image]]
|
||||
local GloveSizeX, GloveSizeY <const> = Glove:getSize()
|
||||
local GloveOffX, GloveOffY <const> = GloveSizeX / 2, GloveSizeY / 2
|
||||
|
||||
local PlayerImageBlipper <const> = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png")
|
||||
|
||||
local DanceBounceMs <const> = 500
|
||||
|
@ -122,7 +115,7 @@ local Pitches <const> = {
|
|||
local CrankOffsetDeg <const> = 90
|
||||
local BatOffset <const> = utils.xy(10, 25)
|
||||
|
||||
local batBase <const> = utils.xy(Center.x - 34, 215)
|
||||
local batBase <const> = utils.xy(C.Center.x - 34, 215)
|
||||
local batTip <const> = utils.xy(0, 0)
|
||||
|
||||
local TagDistance <const> = 15
|
||||
|
@ -130,8 +123,9 @@ local TagDistance <const> = 15
|
|||
local SmallestBallRadius <const> = 6
|
||||
|
||||
local ball <const> = {
|
||||
x = Center.x --[[@as number]],
|
||||
y = Center.y --[[@as number]],
|
||||
x = C.Center.x --[[@as number]],
|
||||
y = C.Center.y --[[@as number]],
|
||||
z = 0,
|
||||
size = SmallestBallRadius,
|
||||
heldBy = nil --[[@type Runner | nil]],
|
||||
}
|
||||
|
@ -141,6 +135,7 @@ local BatLength <const> = 50
|
|||
local Offense <const> = {
|
||||
batting = {},
|
||||
running = {},
|
||||
walking = {},
|
||||
}
|
||||
|
||||
local Sides <const> = {
|
||||
|
@ -156,11 +151,11 @@ local offenseMode = Offense.batting
|
|||
local teams <const> = {
|
||||
home = {
|
||||
score = 0,
|
||||
benchPosition = utils.xy(Screen.W + 10, Center.y),
|
||||
benchPosition = utils.xy(C.Screen.W + 10, C.Center.y),
|
||||
},
|
||||
away = {
|
||||
score = 0,
|
||||
benchPosition = utils.xy(-10, Center.y),
|
||||
benchPosition = utils.xy(-10, C.Center.y),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -194,10 +189,10 @@ local First <const>, Second <const>, Third <const>, Home <const> = 1, 2, 3, 4
|
|||
|
||||
---@type Base[]
|
||||
local Bases = {
|
||||
utils.xy(Screen.W * 0.93, Screen.H * 0.52),
|
||||
utils.xy(Screen.W * 0.47, Screen.H * 0.19),
|
||||
utils.xy(Screen.W * 0.03, Screen.H * 0.52),
|
||||
utils.xy(Screen.W * 0.474, Screen.H * 0.79),
|
||||
utils.xy(C.Screen.W * 0.93, C.Screen.H * 0.52),
|
||||
utils.xy(C.Screen.W * 0.47, C.Screen.H * 0.19),
|
||||
utils.xy(C.Screen.W * 0.03, C.Screen.H * 0.52),
|
||||
utils.xy(C.Screen.W * 0.474, C.Screen.H * 0.79),
|
||||
}
|
||||
|
||||
-- Pseudo-base for batter to target
|
||||
|
@ -230,13 +225,13 @@ local fielders <const> = {
|
|||
pitcher = newFielder("Pitcher", 30),
|
||||
catcher = newFielder("Catcher", 35),
|
||||
left = newFielder("Left", 40),
|
||||
center = newFielder("Center", 40),
|
||||
center = newFielder("C.Center", 40),
|
||||
right = newFielder("Right", 40),
|
||||
}
|
||||
|
||||
local PitcherStartPos <const> = {
|
||||
x = Screen.W * 0.48,
|
||||
y = Screen.H * 0.40,
|
||||
x = C.Screen.W * 0.48,
|
||||
y = C.Screen.H * 0.40,
|
||||
}
|
||||
|
||||
--- Actually only benches the infield, because outfielders are far away!
|
||||
|
@ -260,15 +255,15 @@ function resetFielderPositions(fromOffTheField)
|
|||
end
|
||||
end
|
||||
|
||||
fielders.first.target = utils.xy(Screen.W - 65, Screen.H * 0.48)
|
||||
fielders.second.target = utils.xy(Screen.W * 0.70, Screen.H * 0.30)
|
||||
fielders.shortstop.target = utils.xy(Screen.W * 0.30, Screen.H * 0.30)
|
||||
fielders.third.target = utils.xy(Screen.W * 0.1, Screen.H * 0.48)
|
||||
fielders.first.target = utils.xy(C.Screen.W - 65, C.Screen.H * 0.48)
|
||||
fielders.second.target = utils.xy(C.Screen.W * 0.70, C.Screen.H * 0.30)
|
||||
fielders.shortstop.target = utils.xy(C.Screen.W * 0.30, C.Screen.H * 0.30)
|
||||
fielders.third.target = utils.xy(C.Screen.W * 0.1, C.Screen.H * 0.48)
|
||||
fielders.pitcher.target = utils.xy(PitcherStartPos.x, PitcherStartPos.y)
|
||||
fielders.catcher.target = utils.xy(Screen.W * 0.475, Screen.H * 0.92)
|
||||
fielders.left.target = utils.xy(Screen.W * -1, Screen.H * -0.2)
|
||||
fielders.center.target = utils.xy(Center.x, Screen.H * -0.4)
|
||||
fielders.right.target = utils.xy(Screen.W * 2, fielders.left.target.y)
|
||||
fielders.catcher.target = utils.xy(C.Screen.W * 0.475, C.Screen.H * 0.92)
|
||||
fielders.left.target = utils.xy(C.Screen.W * -1, C.Screen.H * -0.2)
|
||||
fielders.center.target = utils.xy(C.Center.x, C.Screen.H * -0.4)
|
||||
fielders.right.target = utils.xy(C.Screen.W * 2, fielders.left.target.y)
|
||||
end
|
||||
|
||||
local BatterStartingX <const> = Bases[Home].x - 40
|
||||
|
@ -409,15 +404,22 @@ local ResetFieldersAfterSeconds <const> = 5
|
|||
-- TODO: Replace with a timer, repeatedly reset, instead of setting to 0
|
||||
local secondsSinceLastRunnerMove = 0
|
||||
|
||||
---@param runnerIndex integer
|
||||
function outRunner(runnerIndex)
|
||||
outRunners[#outRunners + 1] = runners[runnerIndex]
|
||||
table.remove(runners, runnerIndex)
|
||||
---@param runner integer | Runner
|
||||
function outRunner(runner, message)
|
||||
if type(runner) ~= "number" then
|
||||
for i, maybe in ipairs(runners) do
|
||||
if runner == maybe then
|
||||
runner = i
|
||||
end
|
||||
end
|
||||
end
|
||||
outRunners[#outRunners + 1] = runners[runner]
|
||||
table.remove(runners, runner)
|
||||
|
||||
outs = outs + 1
|
||||
updateForcedRunners()
|
||||
|
||||
announcer:say("YOU'RE OUT!")
|
||||
announcer:say(message or "YOU'RE OUT!")
|
||||
if outs == 3 then
|
||||
local currentlyFieldingTeam = battingTeam == teams.home and teams.away or teams.home
|
||||
local gameOver = inning == 9 and teams.away.score ~= teams.home.score
|
||||
|
@ -437,6 +439,7 @@ function outRunner(runnerIndex)
|
|||
if gameOver then
|
||||
announcer:say("AND THAT'S THE BALL GAME!")
|
||||
else
|
||||
resetFielderPositions()
|
||||
if battingTeam == teams.home then
|
||||
inning = inning + 1
|
||||
end
|
||||
|
@ -665,53 +668,34 @@ 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()
|
||||
batter = nil
|
||||
playdate.timer.new(2000, function()
|
||||
balls = 0
|
||||
strikes = 0
|
||||
batter = newRunner()
|
||||
pitchTracker:reset()
|
||||
if not batter then
|
||||
batter = newRunner()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function walk()
|
||||
-- TODO
|
||||
announcer:say("Walk!")
|
||||
fielders.first.target = Bases[First]
|
||||
batter.nextBase = Bases[First]
|
||||
batter.prevBase = Bases[Home]
|
||||
offenseMode = Offense.walking
|
||||
batter = nil
|
||||
updateForcedRunners()
|
||||
nextBatter()
|
||||
end
|
||||
|
||||
function strikeOut()
|
||||
-- TODO
|
||||
local outBatter = batter
|
||||
batter = nil
|
||||
outRunner(outBatter --[[@as Runner]], "Strike out!")
|
||||
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
|
||||
|
@ -728,7 +712,7 @@ function updateBatting(batDeg, batSpeed)
|
|||
|
||||
if
|
||||
batSpeed > 0
|
||||
and utils.pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, Screen.H)
|
||||
and utils.pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, C.Screen.H)
|
||||
and ball.y < 232 --not isTouchingBall(fielders.catcher.x, fielders.catcher.y)
|
||||
then
|
||||
BatCrackSound:play()
|
||||
|
@ -770,14 +754,14 @@ end
|
|||
--- Returns true only if at least one of the given runners moved during this update
|
||||
---@param appliedSpeed number
|
||||
---@return boolean
|
||||
function updateRunning(appliedSpeed)
|
||||
function updateRunning(appliedSpeed, forcedOnly)
|
||||
ball.size = ballSizeAnimator:currentValue()
|
||||
|
||||
local runnerMoved = false
|
||||
|
||||
-- TODO: Filter for the runner closest to the currently-held direction button
|
||||
for runnerIndex, runner in ipairs(runners) do
|
||||
if runner ~= batter then
|
||||
if runner ~= batter and (not forcedOnly or runner.forcedTo) then
|
||||
runnerMoved = updateRunner(runner, runnerIndex, appliedSpeed) or runnerMoved
|
||||
end
|
||||
end
|
||||
|
@ -787,7 +771,7 @@ end
|
|||
|
||||
function walkAwayOutRunners()
|
||||
for i, runner in ipairs(outRunners) do
|
||||
if runner.x < Screen.W + 50 and runner.y < Screen.H + 50 then
|
||||
if runner.x < C.Screen.W + 50 and runner.y < C.Screen.H + 50 then
|
||||
runner.x = runner.x + (deltaSeconds * 25)
|
||||
runner.y = runner.y + (deltaSeconds * 25)
|
||||
else
|
||||
|
@ -810,76 +794,66 @@ function playerPitch(throwFly)
|
|||
end
|
||||
end
|
||||
|
||||
local npcBatDeg = 0
|
||||
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)
|
||||
else
|
||||
npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed
|
||||
npcBatDeg = 200
|
||||
end
|
||||
return npcBatDeg
|
||||
end
|
||||
|
||||
function npcBatChange()
|
||||
return deltaSeconds * npcBatSpeed
|
||||
end
|
||||
|
||||
function npcRunningSpeed()
|
||||
if #runners == 0 then
|
||||
return 0
|
||||
end
|
||||
local touchedBase = isTouchingBase(runners[1].x, runners[1].y)
|
||||
if not touchedBase or touchedBase == Bases[Home] then
|
||||
return 10
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function updateGameState()
|
||||
deltaSeconds = playdate.getElapsedTime() or 0
|
||||
playdate.resetElapsedTime()
|
||||
local crankChange = playdate.getCrankChange() --[[@as number, number]]
|
||||
local crankChange = playdate.getCrankChange() --[[@as number]]
|
||||
local crankLimited = crankChange == 0 and 0 or (math.log(math.abs(crankChange)) * 10)
|
||||
if crankChange < 0 then
|
||||
crankLimited = crankLimited * -1
|
||||
end
|
||||
|
||||
if ball.heldBy then
|
||||
ball.x = ball.heldBy.x
|
||||
ball.y = ball.heldBy.y
|
||||
else
|
||||
ball.x = ballAnimatorX:currentValue()
|
||||
ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue()
|
||||
ball.z = ballFloatAnimator:currentValue()
|
||||
ball.y = ballAnimatorY:currentValue() + ball.z
|
||||
end
|
||||
|
||||
local playerOnOffense, playerOnDefense = playerIsOn(Sides.offense)
|
||||
|
||||
if playerOnDefense then
|
||||
throwMeter = math.max(0, throwMeter - (deltaSeconds * 150))
|
||||
throwMeter = throwMeter + math.abs(crankChange)
|
||||
throwMeter = throwMeter + math.abs(crankLimited)
|
||||
end
|
||||
|
||||
if offenseMode == Offense.batting then
|
||||
recordStrikePosition()
|
||||
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
|
||||
if ball.y < C.StrikeZoneStartY then
|
||||
pitchTracker.recordedPitchX = nil
|
||||
elseif not pitchTracker.recordedPitchX then
|
||||
pitchTracker.recordedPitchX = ball.x
|
||||
end
|
||||
|
||||
if utils.distanceBetween(fielders.pitcher.x, fielders.pitcher.y, PitchStartX, PitchStartY) < BaseHitbox then
|
||||
secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
|
||||
end
|
||||
|
||||
if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then
|
||||
recordPitch()
|
||||
local outcome = pitchTracker:updatePitchCounts()
|
||||
if outcome == PitchOutcomes.StrikeOut then
|
||||
strikeOut()
|
||||
elseif outcome == PitchOutcomes.Walk then
|
||||
walk()
|
||||
end
|
||||
throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true)
|
||||
catcherThrownBall = true
|
||||
end
|
||||
|
||||
local batSpeed
|
||||
if playerOnOffense then
|
||||
batAngleDeg, batSpeed = (playdate.getCrankPosition() + CrankOffsetDeg) % 360, crankChange
|
||||
batAngleDeg = (playdate.getCrankPosition() + CrankOffsetDeg) % 360
|
||||
batSpeed = crankLimited
|
||||
else
|
||||
batAngleDeg, batSpeed = npcBatAngle(), npcBatChange()
|
||||
batAngleDeg = npc.updateBatAngle(ball, catcherThrownBall, deltaSeconds)
|
||||
batSpeed = npc.batSpeed() * deltaSeconds
|
||||
end
|
||||
|
||||
updateBatting(batAngleDeg, batSpeed)
|
||||
|
||||
-- TODO: Ensure batter can't be nil, here
|
||||
updateRunner(batter, nil, crankChange)
|
||||
updateRunner(batter, nil, crankLimited)
|
||||
|
||||
if secondsSincePitchAllowed > PitchAfterSeconds then
|
||||
if playerOnDefense then
|
||||
|
@ -892,7 +866,7 @@ function updateGameState()
|
|||
end
|
||||
end
|
||||
elseif offenseMode == Offense.running then
|
||||
local appliedSpeed = playerOnOffense and crankChange or npcRunningSpeed()
|
||||
local appliedSpeed = playerOnOffense and crankLimited or npc.runningSpeed(Bases, runners)
|
||||
if updateRunning(appliedSpeed) then
|
||||
secondsSinceLastRunnerMove = 0
|
||||
else
|
||||
|
@ -906,6 +880,11 @@ function updateGameState()
|
|||
end
|
||||
end
|
||||
end
|
||||
elseif offenseMode == Offense.walking then
|
||||
updateForcedRunners()
|
||||
if not updateRunning(10, true) then
|
||||
offenseMode = Offense.batting
|
||||
end
|
||||
end
|
||||
|
||||
for _, fielder in pairs(fielders) do
|
||||
|
@ -915,11 +894,11 @@ function updateGameState()
|
|||
end
|
||||
|
||||
local MinimapSizeX, MinimapSizeY <const> = Minimap:getSize()
|
||||
local MinimapPosX, MinimapPosY = Screen.W - MinimapSizeX, Screen.H - MinimapSizeY
|
||||
local MinimapPosX, MinimapPosY = C.Screen.W - MinimapSizeX, C.Screen.H - MinimapSizeY
|
||||
|
||||
local FieldHeight <const> = Bases[Home].y - Bases[Second].y
|
||||
|
||||
local MinimapMultX <const> = 0.75 * MinimapSizeX / Screen.W
|
||||
local MinimapMultX <const> = 0.75 * MinimapSizeX / C.Screen.W
|
||||
local MinimapOffsetX <const> = MinimapPosX + 5
|
||||
local MinimapMultY <const> = 0.70 * MinimapSizeY / FieldHeight
|
||||
local MinimapOffsetY <const> = MinimapPosY - 15
|
||||
|
@ -934,21 +913,6 @@ function drawMinimap()
|
|||
end
|
||||
end
|
||||
|
||||
---@param fielder Fielder
|
||||
---@return boolean isHoldingBall
|
||||
function drawFielderGlove(fielder)
|
||||
local distanceFromBall =
|
||||
utils.distanceBetweenZ(fielder.x, fielder.y, 0, ball.x, ball.y, ballFloatAnimator:currentValue())
|
||||
local shoulderX, shoulderY = fielder.x + 10, fielder.y + FielderDanceAnimator:currentValue() + 5
|
||||
if distanceFromBall > 20 then
|
||||
Glove:draw(shoulderX, shoulderY)
|
||||
return false
|
||||
else
|
||||
GloveHoldingBall:draw(ball.x - GloveOffX, ball.y - GloveOffY)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function playdate.update()
|
||||
playdate.timer.updateTimers()
|
||||
gfx.animation.blinker.updateAll()
|
||||
|
@ -959,7 +923,7 @@ function playdate.update()
|
|||
|
||||
local offsetX, offsetY = 0, 0
|
||||
if ball.x < BallOffscreen then
|
||||
offsetX, offsetY = getDrawOffset(Screen.W, Screen.H, ball.x, ball.y)
|
||||
offsetX, offsetY = getDrawOffset(C.Screen.W, C.Screen.H, ball.x, ball.y)
|
||||
gfx.setDrawOffset(offsetX, offsetY)
|
||||
end
|
||||
|
||||
|
@ -968,9 +932,7 @@ function playdate.update()
|
|||
local fielderDanceHeight = FielderDanceAnimator:currentValue()
|
||||
local ballIsHeld = false
|
||||
for _, fielder in pairs(fielders) do
|
||||
local fielderY = fielder.y + fielderDanceHeight
|
||||
gfx.fillRect(fielder.x, fielderY, 14, 25)
|
||||
ballIsHeld = drawFielderGlove(fielder) or ballIsHeld
|
||||
ballIsHeld = drawFielder(ball, fielder.x, fielder.y + fielderDanceHeight) or ballIsHeld
|
||||
end
|
||||
|
||||
if offenseMode == Offense.batting then
|
||||
|
@ -1013,9 +975,9 @@ function playdate.update()
|
|||
if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then
|
||||
drawMinimap()
|
||||
end
|
||||
drawScoreboard(0, Screen.H * 0.77, teams, outs, battingTeam, inning)
|
||||
drawBallsAndStrikes(290, Screen.H - 20, balls, strikes)
|
||||
announcer:draw(Center.x, 10)
|
||||
drawScoreboard(0, C.Screen.H * 0.77, teams, outs, battingTeam, inning)
|
||||
drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes)
|
||||
announcer:draw(C.Center.x, 10)
|
||||
end
|
||||
|
||||
function init()
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
local npcBatDeg = 0
|
||||
local BaseNpcBatSpeed <const> = 1500
|
||||
local npcBatSpeed = 1500
|
||||
|
||||
-- selene: allow(unscoped_variables)
|
||||
npc = {}
|
||||
|
||||
function npc.updateBatAngle(ball, catcherThrownBall, deltaSec)
|
||||
if not catcherThrownBall and ball.y > 200 and ball.y < 230 and (ball.x < C.Center.x + 15) then
|
||||
npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed)
|
||||
else
|
||||
npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed
|
||||
npcBatDeg = 200
|
||||
end
|
||||
return npcBatDeg
|
||||
end
|
||||
|
||||
function npc.batSpeed()
|
||||
return npcBatSpeed
|
||||
end
|
||||
|
||||
function npc.runningSpeed(baseConstants, runners)
|
||||
if #runners == 0 then
|
||||
return 0
|
||||
end
|
||||
local touchedBase = isTouchingBase(runners[1].x, runners[1].y)
|
||||
if not touchedBase or touchedBase == baseConstants[4] then
|
||||
return 10
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
if not playdate then
|
||||
return utils
|
||||
end
|
|
@ -1,3 +1,6 @@
|
|||
-- selene: allow(shadowing)
|
||||
local gfx = playdate.graphics
|
||||
|
||||
local ScoreFont <const> = playdate.graphics.font.new("fonts/font-full-circle.pft")
|
||||
local OutBubbleRadius <const> = 5
|
||||
local ScoreboardMarginX <const> = 6
|
||||
|
@ -25,7 +28,6 @@ function drawBallsAndStrikes(x, y, balls, strikes)
|
|||
return
|
||||
end
|
||||
|
||||
local gfx = playdate.graphics
|
||||
gfx.setColor(gfx.kColorBlack)
|
||||
gfx.fillRect(x, y, BallStrikeWidth, BallStrikeHeight)
|
||||
local originalDrawMode = gfx.getImageDrawMode()
|
||||
|
@ -39,7 +41,6 @@ function drawBallsAndStrikes(x, y, balls, strikes)
|
|||
end
|
||||
|
||||
function drawScoreboard(x, y, teams, outs, battingTeam, inning)
|
||||
local gfx = playdate.graphics
|
||||
local homeScore = teams.home.score
|
||||
local awayScore = teams.away.score
|
||||
|
||||
|
|
|
@ -142,8 +142,47 @@ function utils.getNearestOf(array, x, y, extraCondition)
|
|||
return nearest, nearestDistance
|
||||
end
|
||||
|
||||
---@alias Cache { get: fun(key: `Key`): `Value` }
|
||||
-- selene: allow(unscoped_variables)
|
||||
PitchOutcomes = {
|
||||
StrikeOut = {},
|
||||
Walk = {},
|
||||
}
|
||||
|
||||
-- selene: allow(unscoped_variables)
|
||||
pitchTracker = {
|
||||
--- Position of the pitch, or nil, if one has not been recorded.
|
||||
---@type number | nil
|
||||
recordedPitchX = nil,
|
||||
|
||||
strikes = 0,
|
||||
balls = 0,
|
||||
|
||||
reset = function(self)
|
||||
self.strikes = 0
|
||||
self.balls = 0
|
||||
end,
|
||||
|
||||
updatePitchCounts = function(self)
|
||||
if not self.recordedPitchX then
|
||||
return
|
||||
end
|
||||
|
||||
if self.recordedPitchX > C.StrikeZoneStartX and self.recordedPitchX < C.StrikeZoneEndX then
|
||||
self.strikes = self.strikes + 1
|
||||
if self.strikes >= 3 then
|
||||
self:reset()
|
||||
return PitchOutcomes.StrikeOut
|
||||
end
|
||||
else
|
||||
self.balls = self.balls + 1
|
||||
if self.balls >= 4 then
|
||||
self:reset()
|
||||
return PitchOutcomes.Walk
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
if not playdate then
|
||||
return utils
|
||||
return utils, { pitchTracker = pitchTracker, PitchOutcomes = PitchOutcomes }
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue