BatterUp/src/main.lua

358 lines
9.6 KiB
Lua

import 'CoreLibs/animator.lua'
import 'CoreLibs/easing.lua'
import 'CoreLibs/graphics.lua'
import 'CoreLibs/object.lua'
import 'CoreLibs/ui.lua'
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")
local playerHighHat = gfx.image.new("images/game/player.png")
local playerLowHat = gfx.image.new("images/game/player-lowhat.png")
local playerImage = playerHighHat
local secPerFrame = 0.1
local playerFrameElapsed = 0
local backgroundPan = -240
local pitchFlyTimeMs = 2500
local ballStartY, endY = 90, 250
local easingFunction = playdate.easingFunctions.outQuint
local pitchAnimator = gfx.animator.new(pitchFlyTimeMs, ballStartY, endY, easingFunction)
local ballSizeMs = 2000
local ballSizeAnimator = gfx.animator.new(ballSizeMs, 9, 6, playdate.easingFunctions.outBounce)
local screenW, screenH = 400, 240
local centerX, centerY = screenW / 2, screenH / 2
local batBaseX, batBaseY = centerX - 34, 215
local batLength = 45
local ballY = ballStartY
local ballX = 200
local ballVelX, ballVelY = 0, 0
local ballSize = 6
local batTipX = 0
local batTipY = 0
local MODES = {
batting = 0,
running = 1
}
local currentMode = MODES.batting
local hitAnimatorY
local hitAnimatorX
local hitMult = 10
local deltaTime = 0
local basePositions = {
first = { x = screenW * 0.93, y = screenH * 0.52 },
second = { x = screenW * 0.47, y = screenH * 0.19 },
third = { x = screenW * 0.03, y = screenH * 0.52 },
home = { x = screenW * 0.474, y = screenH * 0.79 }
}
local fielders = {
first = {},
second = {},
shortstop = {},
third = {},
pitcher = {}
}
function resetFielderPositions()
fielders.first.x = screenW - 65
fielders.first.y = screenH * 0.48
fielders.second.x = screenW * 0.70
fielders.second.y = screenH * 0.30
fielders.shortstop.x = screenW * 0.30
fielders.shortstop.y = screenH * 0.30
fielders.third.x = screenW * 0.1
fielders.third.y = screenH * 0.48
fielders.pitcher.x = screenW * 0.48
fielders.pitcher.y = screenH * 0.40
end
resetFielderPositions()
local playerStartingX = basePositions.home.x - 40
local playerStartingY = basePositions.home.y - 3
local player = {
x = playerStartingX,
y = playerStartingY,
nextBase = nil,
prevBase = nil,
}
local runners = { }
function hitBall(destX, destY, hitFlyTime)
ballSizeAnimator:reset(ballSizeMs)
hitAnimatorY = gfx.animator.new(hitFlyTime, ballY, destY, playdate.easingFunctions.outQuint)
hitAnimatorX = gfx.animator.new(hitFlyTime, ballX, destX, playdate.easingFunctions.outQuint)
end
function throwBall(destX, destY)
local throwFlyTime = distanceBetween(ballX, ballY, destX, destY) * 5
ballSizeAnimator:reset(throwFlyTime)
hitAnimatorY = gfx.animator.new(throwFlyTime, ballY, destY, playdate.easingFunctions.linear)
hitAnimatorX = gfx.animator.new(throwFlyTime, ballX, destX, playdate.easingFunctions.linear)
end
function getNextThrowTarget()
return basePositions.first.x, basePositions.first.y
end
function pitch()
pitchAnimator:reset()
resetFielderPositions()
ballVelX = 0
ballVelY = 0
ballX = 200
currentMode = MODES.batting
-- TODO: Add new runners, instead
runners = {}
player.x = playerStartingX
player.y = playerStartingY
end
function playdate.AButtonDown()
pitch()
end
function playdate.upButtonDown()
batBaseY -= 1
end
function playdate.downButtonDown()
batBaseY += 1
end
function playdate.rightButtonDown()
batBaseX += 1
end
function playdate.leftButtonDown()
batBaseX -= 1
end
local pitchClockSec = 99
local elapsedTime = 0
local crankChange
function ballPassedThruBat(ballX, ballY, batBaseX, batBaseY, batTipX, batTipY)
-- This check currently assumes right-handedness.
-- I.e. it assumes the ball is to the right of batBaseX
if ballX < batBaseX or ballX > batTipX or ballY > screenH then
return false
end
local m = (batTipY - batBaseY) / (batTipX - batBaseX)
-- y = mx + b
-- b = y1 - (m * x1)
local b = batBaseY - (m * batBaseX)
local yOnLine = (m * ballX) + b
local yP = ballY
local yDelta = yOnLine - yP
return yDelta <= 0
end
function updateInfield()
if ballDestX == nil or ballDestY == nil then
return
end
local fielderSpeed = 40
for title,fielder in pairs(fielders) do
if fielder.targetX ~= nil and fielder.targetY ~= nil then
local x, y, distance = normalizeVector(fielder.x, fielder.y, fielder.targetX, fielder.targetY)
if distance > 1 then
fielder.x -= x * fielderSpeed * deltaTime
fielder.y -= y * fielderSpeed * deltaTime
else
if fielder.onArrive then
fielder.onArrive()
end
fielder.targetX = nil
fielder.targetY = nil
end
end
end
end
function updateRunners()
local runnerSpeed = 20
for i,runner in pairs(runners) do
if runner.nextBase then
local nb = runner.nextBase
local x, y, distance = normalizeVector(runner.x, runner.y, nb.x, nb.y)
if distance > 1 then
local mult = 1
if crankChange < 0 then
mult = -1
end
mult = (mult * runnerSpeed * deltaTime) + (crankChange / 20)
runner.x -= x * mult
runner.y -= y * mult
else
if runner.onArrive then
runner.onArrive()
end
runner.targetX = nil
runner.targetY = nil
end
end
end
end
function normalizeVector(x1, y1, x2, y2)
local distance, a, b = distanceBetween(x1, y1, x2, y2)
return a / distance, b / distance, distance
end
function distanceBetween(x1, y1, x2, y2)
local a = x1 - x2
local b = y1 - y2
return math.sqrt((a*a) + (b*b)), a, b
end
function getNearestFielder(x, y)
local nearestFielder, nearestDistance = nil, nil
for title,fielder in pairs(fielders) do
if nearestFielder == nil then
nearestFielder = fielder
nearestDistance = distanceBetween(fielder.x, fielder.y, x, y)
else
local distance = distanceBetween(fielder.x, fielder.y, x, y)
if distance < nearestDistance then
nearestFielder = fielder
nearestDistance = distance
end
end
end
return nearestFielder
end
function ballIsBeingThrown()
return false
end
function throwArrivedBeforeRunner()
return false
end
function updateGameState()
deltaTime = playdate.getElapsedTime()
playdate.resetElapsedTime()
elapsedTime = elapsedTime + deltaTime
if elapsedTime > pitchClockSec then
elapsedTime = 0
pitch()
end
if currentMode == MODES.running then
ballX = hitAnimatorX:currentValue()
ballY = hitAnimatorY:currentValue()
ballSize = ballSizeAnimator:currentValue()
else
ballY = pitchAnimator:currentValue()
ballSize = 6
end
batAngle = math.rad(playdate.getCrankPosition() + 90)
batTipX = batBaseX + (batLength * math.sin(batAngle))
batTipY = batBaseY + (batLength * math.cos(batAngle))
crankChange, acceleratedChange = playdate.getCrankChange()
if currentMode == MODES.batting and acceleratedChange >= 0 and
ballPassedThruBat(ballX, ballY, batBaseX, batBaseY, batTipX, batTipY) then
ballAngle = batAngle + math.rad(90)
mult = math.abs(acceleratedChange / 15)
ballVelX = mult * 10 * math.sin(ballAngle)
ballVelY = mult * 5 * math.cos(ballAngle)
if ballVelY > 0 then
ballVelX = ballVelX * -1
ballVelY = ballVelY * -1
end
ballDestX = ballX + (ballVelX * hitMult)
ballDestY = ballY + (ballVelY * hitMult)
hitBall(ballDestX, ballDestY, 2000)
local chasingFielder = getNearestFielder(ballDestX, ballDestY)
chasingFielder.targetX = ballDestX
chasingFielder.targetY = ballDestY
chasingFielder.onArrive = function()
throwBall(getNextThrowTarget())
chasingFielder.onArrive = nil
end
fielders.first.targetX = basePositions.first.x
fielders.first.targetY = basePositions.first.y
currentMode = MODES.running
player.nextBase = basePositions.first
runners[#runners+1] = player
end
if currentMode == MODES.running then
updateRunners()
updateInfield()
end
end
function playdate.update()
updateGameState()
grassBackground:draw(0, backgroundPan)
gfx.setColor(gfx.kColorBlack)
gfx.setLineWidth(2)
gfx.drawCircleAtPoint(ballX, ballY, ballSize)
for title,fielder in pairs(fielders) do
gfx.fillRect(fielder.x, fielder.y, 14, 25)
end
gfx.setLineWidth(5)
if currentMode == MODES.batting then
gfx.drawLine(batBaseX, batBaseY, batTipX, batTipY)
end
if playdate.isCrankDocked() then
playdate.ui.crankIndicator:draw()
end
playerImage:draw(player.x, player.y)
-- TODO: Use gfx.animation.blinker instead
if currentMode == MODES.running and playerFrameElapsed > secPerFrame then
playerFrameElapsed = 0
if playerImage == playerHighHat then
playerImage = playerLowHat
else
playerImage = playerHighHat
end
else
playerFrameElapsed += deltaTime
end
-- for i,runner in pairs(runners) do
-- playerImage:draw(runner.x, runner.y)
-- end
end