Better batter-outs, scoreboard, sounds and frowns.
A bunch more refactors, SCREAMING_SNAKE constants, graphics.lua, etc.
This commit is contained in:
parent
982b8220a6
commit
0b126919ac
|
@ -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 |
438
src/main.lua
438
src/main.lua
|
@ -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.
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue