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