Better batter-outs, scoreboard, sounds and frowns.

A bunch more refactors, SCREAMING_SNAKE constants, graphics.lua, etc.
This commit is contained in:
Sage Vaillancourt 2025-01-31 23:34:35 -05:00
parent 982b8220a6
commit 0b126919ac
5 changed files with 317 additions and 187 deletions

22
src/graphics.lua Normal file
View File

@ -0,0 +1,22 @@
local ballBuffer = 5
--- Assumes that background image is of size
--- XXX
--- XOX
--- Where each character is the size of the screen, and 'O' is the default view.
function getDrawOffset(screenW, screenH, ballX, ballY)
local offsetX, offsetY
if ballY < ballBuffer then
offsetY = math.max(ballBuffer, -1 * (ballY - ballBuffer))
else
offsetY = 0
end
if ballX > 0 and ballX < (screenW - ballBuffer) then
offsetX = 0
elseif ballX < ballBuffer then
offsetX = math.max(-1 * screenW, -1 * (ballX - ballBuffer))
elseif ballX > (screenW - ballBuffer) then
offsetX = math.min(screenW * 2, -1 * (ballX - ballBuffer))
end
return offsetX, offsetY
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

View File

@ -4,22 +4,30 @@ import 'CoreLibs/easing.lua'
import 'CoreLibs/graphics.lua' import 'CoreLibs/graphics.lua'
import 'CoreLibs/object.lua' import 'CoreLibs/object.lua'
import 'CoreLibs/ui.lua' import 'CoreLibs/ui.lua'
import 'graphics.lua'
import 'utils.lua' import 'utils.lua'
--- @alias Position { x: number, y: number } --- @alias XYPair { x: number, y: number }
--- @alias Base { x: number, y: number } --- @alias Base { x: number, y: number }
--- @alias Runner { x: number, y: number, nextBase: Base, prevBase: Base, forcedTo: Base } --- @alias Runner { x: number, y: number, nextBase: Base, prevBase: Base, forcedTo: Base }
--- @alias Fielder { onArrive: fun() | nil, x: number | nil, y: number | nil, target: Position | nil, speed: number } --- @alias Fielder { onArrive: fun() | nil, x: number | nil, y: number | nil, target: XYPair | nil, speed: number }
local gfx = playdate.graphics local gfx = playdate.graphics
playdate.display.setRefreshRate(50)
gfx.setBackgroundColor(gfx.kColorWhite)
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
local grassBackground = gfx.image.new("images/game/grass.png") or {} local SCREEN = {
W = playdate.display.getWidth(),
H = playdate.display.getHeight()
}
local CENTER = xy(SCREEN.W / 2, SCREEN.H / 2)
local batCrackSound = playdate.sound.sampleplayer.new("sounds/bat-crack-reverb.wav")
local grassBackground = gfx.image.new("images/game/grass.png") --[[@as PlaydateGraphicsImage]]
local playerFrown = gfx.image.new("images/game/player-frown.png") --[[@as PlaydateGraphicsImage]]
local playerImageBlipper = blipper.new( local playerImageBlipper = blipper.new(
100, 100,
@ -27,92 +35,90 @@ local playerImageBlipper = blipper.new(
"images/game/player-lowhat.png" "images/game/player-lowhat.png"
) )
---@type Position local BALL_OFFSCREEN = 999
local backgroundPan = {
x = 0, local danceBounceMs = 500
y = 0, local danceBounceCount = 4
} local fielderDanceAnimator = gfx.animator.new(danceBounceMs, 10, 0, easingHill)--, -1 * danceBounceMs * danceBounceCount)
fielderDanceAnimator.repeatCount = danceBounceCount - 1
local pitchFlyTimeMs = 2500 local pitchFlyTimeMs = 2500
local ballStartY, endY = 90, 250 local PITCH_START_X = 200
local easingFunction = playdate.easingFunctions.outQuint local PITCH_START_Y, PITCH_END_Y = 90, 250
local pitchAnimator = gfx.animator.new(pitchFlyTimeMs, ballStartY, endY, easingFunction) local pitchAnimator = gfx.animator.new(pitchFlyTimeMs, PITCH_START_Y, PITCH_END_Y, playdate.easingFunctions.outQuint)
local ballSizeMs = 2000 local CRANK_OFFSET_DEG = 90
local ballSizeAnimator = gfx.animator.new(ballSizeMs, 9, 6, playdate.easingFunctions.outBounce) local batBase = xy(CENTER.x - 34, 215)
local batTip = xy(0, 0)
local screenW, screenH = 400, 240 local TAG_DISTANCE = 20
local centerX, centerY = screenW / 2, screenH / 2
local batBaseX, batBaseY = centerX - 34, 215
local batLength = 45 local ball = {
x = CENTER.x,
y = BALL_OFFSCREEN,
size = 6
}
local tagDistance = 20 local BAT_LENGTH = 45
local ballY = 999 -- ballStartY
local ballX = 200
local ballSize = 6
local batTipX = 0
local batTipY = 0
local MODES = { local MODES = {
batting = {}, batting = {},
running = {} running = {}
} }
local currentMode = MODES.batting local currentMode = MODES.batting
local hitAnimatorY local ballAnimatorY = gfx.animator.new(0, BALL_OFFSCREEN, BALL_OFFSCREEN, playdate.easingFunctions.linear)
local hitAnimatorX local ballAnimatorX = gfx.animator.new(0, BALL_OFFSCREEN, BALL_OFFSCREEN, playdate.easingFunctions.linear)
-- TODO? Replace this AND ballSizeAnimator with a ballHeightAnimator
-- ...that might lose some of the magic of both. Compromise available? idk
local ballFloatAnimator = gfx.animator.new(2000, -60, 0, easingHill)
local ballSizeMs = 2000
local ballSizeAnimator = gfx.animator.new(ballSizeMs, 9, 6, easingHill)
local hitMult = 10 local HIT_MULT = 10
local deltaTime = 0 local deltaSeconds = 0
---@type table<string, Base> local FIRST, SECOND, THIRD, HOME = 1, 2, 3, 4
---@type Base[]
local bases = { local bases = {
first = { x = screenW * 0.93, y = screenH * 0.52 }, xy(SCREEN.W * 0.93, SCREEN.H * 0.52),
second = { x = screenW * 0.47, y = screenH * 0.19 }, xy(SCREEN.W * 0.47, SCREEN.H * 0.19),
third = { x = screenW * 0.03, y = screenH * 0.52 }, xy(SCREEN.W * 0.03, SCREEN.H * 0.52),
home = { x = screenW * 0.474, y = screenH * 0.79 } xy(SCREEN.W * 0.474, SCREEN.H * 0.79)
} }
---@type table<Base, Base> ---@type table<Base, Base>
local nextBaseMap = { local nextBaseMap = {
[bases.first] = bases.second, [bases[FIRST]] = bases[SECOND],
[bases.second] = bases.third, [bases[SECOND]] = bases[THIRD],
[bases.third] = bases.home [bases[THIRD]] = bases[HOME]
} }
function newFielder(speed)
return {
speed = speed
}
end
---@type table<string, Fielder> ---@type table<string, Fielder>
local fielders = { local fielders = {
first = { first = newFielder(40),
speed = 40, second = newFielder(40),
}, shortstop = newFielder(40),
second = { third = newFielder(40),
speed = 40, pitcher = newFielder(30),
}, catcher = newFielder(20),
shortstop = { left = newFielder(40),
speed = 40, center = newFielder(40),
}, right = newFielder(40)
third = { }
speed = 40,
}, local PITCHER_POS = {
pitcher = { x = SCREEN.W * 0.48,
speed = 30, y = SCREEN.H * 0.40
},
catcher = {
speed = 20,
},
left = {
speed = 40,
},
center = {
speed = 40,
},
right = {
speed = 40,
}
} }
--- Resets the target positions of all fielders to their defaults (at their field positions). --- Resets the target positions of all fielders to their defaults (at their field positions).
@ -120,92 +126,103 @@ local fielders = {
function resetFielderPositions(fromOffTheField) function resetFielderPositions(fromOffTheField)
if fromOffTheField then if fromOffTheField then
for _,fielder in pairs(fielders) do for _,fielder in pairs(fielders) do
fielder.x = centerX fielder.x = CENTER.x
fielder.y = screenH fielder.y = SCREEN.H
end end
end end
fielders.first.target = { x = screenW - 65, y = screenH * 0.48 } fielders.first.target = xy(SCREEN.W - 65, SCREEN.H * 0.48)
fielders.second.target = { x = screenW * 0.70, y = screenH * 0.30 } fielders.second.target = xy(SCREEN.W * 0.70, SCREEN.H * 0.30)
fielders.shortstop.target = { x = screenW * 0.30, y = screenH * 0.30 } fielders.shortstop.target = xy(SCREEN.W * 0.30, SCREEN.H * 0.30)
fielders.third.target = { x = screenW * 0.1, y = screenH * 0.48 } fielders.third.target = xy(SCREEN.W * 0.1, SCREEN.H * 0.48)
fielders.pitcher.target = { x = screenW * 0.48, y = screenH * 0.40 } fielders.pitcher.target = xy(PITCHER_POS.x, PITCHER_POS.y)
fielders.catcher.target = { x = screenW * 0.475, y = screenH * 0.92 } fielders.catcher.target = xy(SCREEN.W * 0.475, SCREEN.H * 0.92)
fielders.left.target = { x = screenW * -1, y = screenH * -0.2 } fielders.left.target = xy(SCREEN.W * -1, SCREEN.H * -0.2)
fielders.center.target = { x = centerX, y = screenH * -0.4 } fielders.center.target = xy(CENTER.x, SCREEN.H * -0.4)
fielders.right.target = { x = screenW * 2, y = screenH * fielders.left.target.y } fielders.right.target = xy(SCREEN.W * 2, SCREEN.H * fielders.left.target.y)
end end
resetFielderPositions(true)
local playerStartingX = bases.home.x - 40 local PLAYER_STARTING_X = bases[HOME].x - 40
local playerStartingY = bases.home.y - 3 local PLAYER_STARTING_Y = bases[HOME].y - 3
--- @type Runner[] --- @type Runner[]
local runners = { } local runners = { }
--- @type Runner[]
local outRunners = { }
local nameI = 1
local runnerNames = {"Barbara", "Steve", "Jerry", "Beatrice"}
---@return Runner ---@return Runner
function newRunner() function newRunner()
local new = { local new = {
x = playerStartingX, x = PLAYER_STARTING_X,
y = playerStartingY, y = PLAYER_STARTING_Y,
nextBase = nil, nextBase = nil,
prevBase = nil, prevBase = nil,
name = runnerNames[nameI]
} }
nameI = nameI + 1
runners[#runners + 1] = new runners[#runners + 1] = new
return new return new
end end
---@type Runner | nil ---@type Runner | nil
local batter = newRunner() local batter = newRunner()
batter.nextBase = bases[FIRST]
batter.forcedTo = bases[FIRST]
function throwBall(destX, destY, easingFunc, flyTimeMs) function throwBall(destX, destY, easingFunc, flyTimeMs, floaty)
if not flyTimeMs then if not flyTimeMs then
flyTimeMs = distanceBetween(ballX, ballY, destX, destY) * 5 flyTimeMs = distanceBetween(ball.x, ball.y, destX, destY) * 5
end end
ballSizeAnimator:reset(flyTimeMs) ballSizeAnimator:reset(flyTimeMs)
hitAnimatorY = gfx.animator.new(flyTimeMs, ballY, destY, easingFunc) ballAnimatorY = gfx.animator.new(flyTimeMs, ball.y, destY, easingFunc)
hitAnimatorX = gfx.animator.new(flyTimeMs, ballX, destX, easingFunc) ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc)
if floaty then
ballFloatAnimator:reset(flyTimeMs)
end
end end
function pitch() function pitch()
pitchAnimator:reset()
ballY = ballStartY
ballX = 200
currentMode = MODES.batting currentMode = MODES.batting
ballAnimatorX = gfx.animator.new(0, PITCH_START_X, PITCH_START_X, playdate.easingFunctions.linear)
backgroundPan.y = 0 ballAnimatorY = pitchAnimator
backgroundPan.x = 0 pitchAnimator:reset()
end end
function playdate.AButtonDown() function playdate.AButtonDown()
if not batter then
return
end
pitch() pitch()
end end
function playdate.upButtonDown() function playdate.upButtonDown()
batBaseY = batBaseY - 1 batBase.y = batBase.y - 1
end end
function playdate.downButtonDown() function playdate.downButtonDown()
batBaseY = batBaseY + 1 batBase.y = batBase.y + 1
end end
function playdate.rightButtonDown() function playdate.rightButtonDown()
batBaseX = batBaseX + 1 batBase.x = batBase.x + 1
end end
function playdate.leftButtonDown() function playdate.leftButtonDown()
batBaseX = batBaseX - 1 batBase.x = batBase.x - 1
end end
local pitchClockSec = 99 local elapsedSec = 0
local elapsedTime = 0 local crankChange = 0
local crankChange
local baseHitbox = 13 local BASE_HITBOX = 13
--- Returns the base being touched by the runner at (x,y), or nil, if no base is being touched --- Returns the base being touched by the runner at (x,y), or nil, if no base is being touched
function isTouchingBase(x, y) function isTouchingBase(x, y)
for _,base in pairs(bases) do for _,base in ipairs(bases) do
if distanceBetween(x, y, base.x, base.y) < baseHitbox then if distanceBetween(x, y, base.x, base.y) < BASE_HITBOX then
return base return base
end end
end end
@ -213,18 +230,26 @@ function isTouchingBase(x, y)
return nil return nil
end end
local ballCatchHitbox = 3 local BALL_CATCH_HITBOX = 3
function isTouchingBall(x, y) function isTouchingBall(x, y)
local ballDistance = distanceBetween(x, y, ballX, ballY) local ballDistance = distanceBetween(x, y, ball.x, ball.y)
return ballDistance < ballCatchHitbox return ballDistance < BALL_CATCH_HITBOX
end end
function updateForcedTos() function updateForcedTos()
end end
local outs = 0
local homeScore = 0
local awayScore = 0
function outRunner(runnerIndex) function outRunner(runnerIndex)
print("You're out, runner" .. runnerIndex .. "!")
outs = math.min(3, outs + 1)
outRunners[#outRunners+1] = runners[runnerIndex]
table.remove(runners, runnerIndex) table.remove(runners, runnerIndex)
updateForcedTos() updateForcedTos()
fielderDanceAnimator:reset()
end end
function updateFielders() function updateFielders()
@ -241,8 +266,8 @@ function updateFielders()
local x, y, distance = normalizeVector(fielder.x, fielder.y, fielder.target.x, fielder.target.y) local x, y, distance = normalizeVector(fielder.x, fielder.y, fielder.target.x, fielder.target.y)
if distance > 1 then if distance > 1 then
fielder.x = fielder.x - (x * fielder.speed * deltaTime) fielder.x = fielder.x - (x * fielder.speed * deltaSeconds)
fielder.y = fielder.y - (y * fielder.speed * deltaTime) fielder.y = fielder.y - (y * fielder.speed * deltaSeconds)
else else
if fielder.onArrive then if fielder.onArrive then
fielder.onArrive() fielder.onArrive()
@ -251,15 +276,18 @@ function updateFielders()
end end
end end
if isTouchingBall(fielder.x, fielder.y) then if currentMode == MODES.running and isTouchingBall(fielder.x, fielder.y) then
local touchedBase = isTouchingBase(fielder.x, fielder.y) local touchedBase = isTouchingBase(fielder.x, fielder.y)
for i,runner in pairs(runners) do for i,runner in pairs(runners) do
local runnerOnBase = touchingBaseCache.get(runner) local runnerOnBase = touchingBaseCache.get(runner)
if touchedBase and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase then if touchedBase and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase then
print("Force out of runner:")
printTable(runner)
outRunner(i) outRunner(i)
elseif not runnerOnBase then elseif not runnerOnBase then
local fielderDistance = distanceBetween(runner.x, runner.y, fielder.x, fielder.y) local fielderDistance = distanceBetween(runner.x, runner.y, fielder.x, fielder.y)
if fielderDistance < tagDistance then if fielderDistance < TAG_DISTANCE then
print("Tagged out!")
outRunner(i) outRunner(i)
end end
end end
@ -291,7 +319,7 @@ function updateRunners()
end end
local prevX, prevY = runner.x, runner.y local prevX, prevY = runner.x, runner.y
-- TODO: Drift toward nearest base? -- TODO: Drift toward nearest base?
local autoRun = nearestBaseDistance > 5 and mult * autoRunSpeed * deltaTime or 0 local autoRun = nearestBaseDistance > 5 and mult * autoRunSpeed * deltaSeconds or 0
mult = autoRun + (crankChange / 20) mult = autoRun + (crankChange / 20)
runner.x = runner.x - (x * mult) runner.x = runner.x - (x * mult)
runner.y = runner.y - (y * mult) runner.y = runner.y - (y * mult)
@ -328,7 +356,7 @@ end
---@return Base[] ---@return Base[]
function getForcedOutTargets() function getForcedOutTargets()
local targets = {} local targets = {}
for _,base in pairs(bases) do for _,base in ipairs(bases) do
local runnerTargetingBase = getRunnerTargeting(base) local runnerTargetingBase = getRunnerTargeting(base)
if runnerTargetingBase then if runnerTargetingBase then
targets[#targets+1] = base targets[#targets+1] = base
@ -337,10 +365,11 @@ function getForcedOutTargets()
end end
end end
return targets return targets
-- return { bases.first } -- return { bases[FIRST] }
end end
--- Returns the position,distance of the basest closest to the runner furthest from a base --- Returns the position,distance of the basest closest to the runner furthest from a base
---@return { x: number, y: number } | nil, number | nil
function getBaseOfStrandedRunner() function getBaseOfStrandedRunner()
local farRunnersBase, farDistance local farRunnersBase, farDistance
for _,runner in pairs(runners) do for _,runner in pairs(runners) do
@ -364,45 +393,47 @@ end
function getNextThrowTarget() function getNextThrowTarget()
-- TODO: Handle missed throws, check for fielders at target, etc. -- TODO: Handle missed throws, check for fielders at target, etc.
local targets = getForcedOutTargets() local targets = getForcedOutTargets()
print("forcedOutTargets:")
printTable(targets)
if #targets ~= 0 then if #targets ~= 0 then
return targets[1].x, targets[1].y return targets[1].x, targets[1].y
end end
-- local baseCloseToStrandedRunner = getBaseOfStrandedRunner() local baseCloseToStrandedRunner = getBaseOfStrandedRunner()
-- return baseCloseToStrandedRunner.x, baseCloseToStrandedRunner.y if baseCloseToStrandedRunner then
return baseCloseToStrandedRunner.x, baseCloseToStrandedRunner.y
end
end end
local resetFieldersAfterSeconds = 5 local resetFieldersAfterSeconds = 4
local secondsSinceLastRunnerMove = 0 local secondsSinceLastRunnerMove = 0
function updateGameState() local pitchAfterSeconds = 4
deltaTime = playdate.getElapsedTime() or 0 local secondsSincePitchAllowed = -5
playdate.resetElapsedTime()
elapsedTime = elapsedTime + deltaTime
-- if elapsedTime > pitchClockSec then function init()
-- elapsedTime = 0 playdate.display.setRefreshRate(50)
-- pitch() gfx.setBackgroundColor(gfx.kColorWhite)
-- end playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))
resetFielderPositions(true)
end
if currentMode == MODES.running then function updateBatting()
ballX = hitAnimatorX:currentValue() secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds
ballY = hitAnimatorY:currentValue() if secondsSincePitchAllowed > pitchAfterSeconds then
ballSize = ballSizeAnimator:currentValue() pitch()
elseif ballY < 999 then secondsSincePitchAllowed = 0
ballY = pitchAnimator:currentValue() end
ballSize = 6 if ball.y < BALL_OFFSCREEN then
ball.y = ballAnimatorY:currentValue()
ball.size = 6
end end
local batAngle = math.rad(playdate.getCrankPosition() + 90) local batAngle = math.rad(playdate.getCrankPosition() + CRANK_OFFSET_DEG)
batTipX = batBaseX + (batLength * math.sin(batAngle)) batTip.x = batBase.x + (BAT_LENGTH * math.sin(batAngle))
batTipY = batBaseY + (batLength * math.cos(batAngle)) batTip.y = batBase.y + (BAT_LENGTH * math.cos(batAngle))
crankChange, acceleratedChange = playdate.getCrankChange() if acceleratedChange >= 0 and
if currentMode == MODES.batting and acceleratedChange >= 0 and pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, SCREEN.H) then
pointDirectlyUnderLine(ballX, ballY, batBaseX, batBaseY, batTipX, batTipY, screenH) then batCrackSound:play()
currentMode = MODES.running currentMode = MODES.running
ballAngle = batAngle + math.rad(90) ballAngle = batAngle + math.rad(90)
@ -413,13 +444,13 @@ function updateGameState()
ballVelX = ballVelX * -1 ballVelX = ballVelX * -1
ballVelY = ballVelY * -1 ballVelY = ballVelY * -1
end end
ballDestX = ballX + (ballVelX * hitMult) ballDestX = ball.x + (ballVelX * HIT_MULT)
ballDestY = ballY + (ballVelY * hitMult) ballDestY = ball.y + (ballVelY * HIT_MULT)
throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000) throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000)
fielders.first.target = bases.first fielders.first.target = bases[FIRST]
batter.nextBase = bases.first batter.nextBase = bases[FIRST]
batter.forcedTo = bases.first batter.forcedTo = bases[FIRST]
batter = nil -- Demote batter to a mere runner batter = nil -- Demote batter to a mere runner
local chasingFielder = getNearestOf(fielders, ballDestX, ballDestY) local chasingFielder = getNearestOf(fielders, ballDestX, ballDestY)
@ -427,80 +458,133 @@ function updateGameState()
chasingFielder.onArrive = function() chasingFielder.onArrive = function()
local targetX, targetY = getNextThrowTarget() local targetX, targetY = getNextThrowTarget()
if targetX ~= nil then if targetX ~= nil then
throwBall(targetX, targetY, playdate.easingFunctions.linear) throwBall(targetX, targetY, playdate.easingFunctions.linear, nil, true)
chasingFielder.onArrive = nil chasingFielder.onArrive = nil
end end
end end
end end
end
if currentMode == MODES.running then function updateRunning()
secondsSincePitchAllowed = 0
ball.size = ballSizeAnimator:currentValue()
if updateRunners() then if updateRunners() then
secondsSinceLastRunnerMove = 0 secondsSinceLastRunnerMove = 0
else else
secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaTime secondsSinceLastRunnerMove = secondsSinceLastRunnerMove + deltaSeconds
if secondsSinceLastRunnerMove > resetFieldersAfterSeconds then if secondsSinceLastRunnerMove > resetFieldersAfterSeconds then
throwBall(PITCH_START_X, PITCH_START_Y, playdate.easingFunctions.linear, nil, true)
resetFielderPositions(false) resetFielderPositions(false)
currentMode = MODES.batting currentMode = MODES.batting
batter = newRunner() batter = newRunner()
batter.nextBase = bases[FIRST]
batter.forcedTo = bases[FIRST]
end end
end end
end
function updateOutRunners()
for i, runner in ipairs(outRunners) do
if runner.x < SCREEN.W + 50 and runner.y < SCREEN.H + 50 then
runner.x = runner.x + (deltaSeconds * 25)
runner.y = runner.y + (deltaSeconds * 25)
else
table.remove(outRunners, i)
end end
end
end
function updateGameState()
deltaSeconds = playdate.getElapsedTime() or 0
playdate.resetElapsedTime()
elapsedSec = elapsedSec + deltaSeconds
crankChange, acceleratedChange = playdate.getCrankChange() --[[@as number, number]]
ball.x = ballAnimatorX:currentValue()
ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue()
if currentMode == MODES.batting then
updateBatting()
elseif currentMode == MODES.running then
updateRunning()
end
updateFielders() updateFielders()
updateOutRunners()
end
local OUT_BUBBLE_SIZE = 6
function drawScoreboard()
gfx.setDrawOffset(0, 0)
local y = SCREEN.H * 0.95
local x = SCREEN.W * 0.05
gfx.setLineWidth(1)
gfx.setColor(gfx.kColorBlack)
gfx.fillRect(x - 15, y - 44, 73, 55)
gfx.setColor(gfx.kColorWhite)
for i=outs,2 do
gfx.drawCircleAtPoint(x + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE)
end
for i=0,(outs - 1) do
gfx.fillCircleAtPoint(x + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE)
end
local originalDrawMode = gfx.getImageDrawMode()
gfx.setImageDrawMode(playdate.graphics.kDrawModeInverted)
gfx.drawText("Home: " .. homeScore, x - 7, y - 40, 100, 20)
gfx.drawText("Away: " .. awayScore, x - 7, y - 25, 100, 20)
gfx.setImageDrawMode(originalDrawMode)
end end
function playdate.update() function playdate.update()
updateGameState() updateGameState()
playdate.graphics.animation.blinker.updateAll() playdate.graphics.animation.blinker.updateAll()
-- TODO: Show baserunning minimap when panning?
local ballBuffer = 5
if ballY < ballBuffer then
backgroundPan.y = math.max(ballBuffer, -1 * (ballY - ballBuffer))
else
backgroundPan.y = 0
end
if ballX < ballBuffer then
backgroundPan.x = math.max(-400, -1 * (ballX - ballBuffer))
elseif ballX > (screenW - ballBuffer) then
backgroundPan.x = math.min(800, -1 * (ballX - ballBuffer))
end
if ballX > 0 and ballX < (screenW - ballBuffer) then
backgroundPan.x = 0
end
gfx.clear() gfx.clear()
grassBackground:draw(backgroundPan.x - 400, backgroundPan.y - 240)
if ball.x < BALL_OFFSCREEN then
-- TODO: Show baserunning minimap when panning?
local offsetX, offsetY = getDrawOffset(SCREEN.W, SCREEN.H, ball.x, ball.y)
gfx.setDrawOffset(offsetX, offsetY)
end
grassBackground:draw(-400, -240)
gfx.setColor(gfx.kColorBlack) gfx.setColor(gfx.kColorBlack)
gfx.setLineWidth(2) gfx.setLineWidth(2)
gfx.drawCircleAtPoint(ballX + backgroundPan.x, ballY + backgroundPan.y, ballSize)
gfx.drawCircleAtPoint(ball.x, ball.y, ball.size)
local fielderDanceHeight = fielderDanceAnimator:currentValue()
for _,fielder in pairs(fielders) do for _,fielder in pairs(fielders) do
gfx.fillRect(fielder.x + backgroundPan.x, fielder.y + backgroundPan.y, 14, 25) gfx.fillRect(fielder.x, fielder.y - fielderDanceHeight, 14, 25)
end end
gfx.setLineWidth(5)
if currentMode == MODES.batting then if currentMode == MODES.batting then
gfx.setLineWidth(5)
gfx.drawLine( gfx.drawLine(
batBaseX + backgroundPan.x, batBaseY + backgroundPan.y, batBase.x, batBase.y,
batTipX + backgroundPan.x, batTipY + backgroundPan.y batTip.x, batTip.y
) )
end end
if playdate.isCrankDocked() or (crankChange < 2 and currentMode == MODES.running) then if playdate.isCrankDocked() then -- or (crankChange < 2 and currentMode == MODES.running) then
playdate.ui.crankIndicator:draw() playdate.ui.crankIndicator:draw()
end end
-- TODO? Change blip speed depending on runner speed? -- TODO? Change blip speed depending on runner speed?
for _,runner in pairs(runners) do for _,runner in pairs(runners) do
-- TODO? Scale sprites down as y increases -- TODO? Scale sprites down as y increases
playerImageBlipper:draw( playerImageBlipper:draw(false, runner.x, runner.y)
false, end
runner.x + backgroundPan.x, for _,runner in pairs(outRunners) do
runner.y + backgroundPan.y playerFrown:draw(runner.x, runner.y)
)
end end
drawScoreboard()
end end
init()

Binary file not shown.

View File

@ -1,6 +1,30 @@
import 'CoreLibs/animation.lua' import 'CoreLibs/animation.lua'
import 'CoreLibs/graphics.lua' import 'CoreLibs/graphics.lua'
-- number = ((number * 2) - 1)
-- return number * number
-- c = c + 0.0 -- convert to float to prevent integer overflow
-- return c * t / d + b
function easingHill(t, b, c, d)
c = c + 0.0 -- convert to float to prevent integer overflow
t = t / d
t = ((t * 2) - 1)
t = t * t
return (c * t) + b
end
---@param x number
---@param y number
---@return XYPair
function xy(x, y)
return {
x = x,
y = y
}
end
--- Returns the normalized vector as two values, plus the distance between the given points. --- Returns the normalized vector as two values, plus the distance between the given points.
---@return number, number, number ---@return number, number, number
function normalizeVector(x1, y1, x2, y2) function normalizeVector(x1, y1, x2, y2)