diff --git a/Makefile b/Makefile index d05e21e..bdd74f7 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +lint: + stylua src/ + all: pdc src BatterUp.pdx diff --git a/src/ecs.lua b/src/ecs.lua index 10f6fbf..fa585fa 100644 --- a/src/ecs.lua +++ b/src/ecs.lua @@ -1,4 +1,4 @@ -ecs = { } +ecs = {} local ALL_ENTITIES = {} @@ -12,9 +12,9 @@ function ecs.addEntity(entity) ALL_ENTITIES[entity] = true for _, system in pairs(SYSTEMS) do if entityMatchesShapes(entity, system.shapes) then - system.entityCache[entity] = true + system.entityCache[entity] = true else - system.entityCache[entity] = nil + system.entityCache[entity] = nil end end end @@ -22,7 +22,7 @@ end function ecs.removeEntity(entity) ALL_ENTITIES[entity] = nil for _, system in pairs(SYSTEMS) do - system.entityCache[entity] = nil + system.entityCache[entity] = nil end end @@ -36,7 +36,7 @@ end function allKeysIncluded(entity, filter) for k, _ in pairs(filter) do if not entity[k] then - return false + return false end end return true @@ -45,7 +45,7 @@ end function entityMatchesShapes(entity, shapes) for _, shape in pairs(shapes) do if not allKeysIncluded(entity, shape) then - return false + return false end end return true @@ -61,25 +61,26 @@ end ---@param wShape W? ---@return fun(callback: fun(componentT: T, componentU: U, componentV: V, componentW: W)) function ecs.entitiesHavingShapes(tShape, uShape, vShape, wShape) - return function() - end + return function() end end -- Print contents of `tbl`, with indentation. -- `indent` sets the initial level of indentation. -function tprint (tbl, indent) - if not indent then indent = 0 end - for k, v in pairs(tbl) do - formatting = string.rep(" ", indent) .. k .. ": " - if type(v) == "table" then - print(formatting) - tprint(v, indent+1) - elseif type(v) == 'boolean' then - print(formatting .. tostring(v)) - else - print(formatting .. v) - end - end +function tprint(tbl, indent) + if not indent then + indent = 0 + end + for k, v in pairs(tbl) do + formatting = string.rep(" ", indent) .. k .. ": " + if type(v) == "table" then + print(formatting) + tprint(v, indent + 1) + elseif type(v) == "boolean" then + print(formatting .. tostring(v)) + else + print(formatting .. v) + end + end end function addSystem(callback, keys, shapes) @@ -87,7 +88,7 @@ function addSystem(callback, keys, shapes) callback = callback, keys = keys, shapes = shapes, - entityCache = nil + entityCache = nil, } end @@ -98,18 +99,26 @@ end ---@param deltaSeconds number function ecs.update(deltaSeconds) - for _,system in pairs(SYSTEMS) do + for _, system in pairs(SYSTEMS) do if not system.entityCache then - system.entityCache = {} - for entity,_ in pairs(ALL_ENTITIES) do + system.entityCache = {} + for entity, _ in pairs(ALL_ENTITIES) do if entityMatchesShapes(entity, system.shapes) then system.entityCache[entity] = true end end end local keys = system.keys - for entity,_ in pairs(system.entityCache) do - system.callback(deltaSeconds, entity, entity[keys[1]], entity[keys[2]], entity[keys[3]], entity[keys[4]], entity) + for entity, _ in pairs(system.entityCache) do + system.callback( + deltaSeconds, + entity, + entity[keys[1]], + entity[keys[2]], + entity[keys[3]], + entity[keys[4]], + entity + ) end end end @@ -130,21 +139,21 @@ end ---@param wShape { [WKey]: W } | fun(entity: any, componentT: T, componentU: U, componentV: V, any) | nil ---@param finalFunc fun(entity: any, componentT: T, componentU: U, componentV: V, componentW: W, any) | nil function ecs.forEntitiesWith(tShape, uShape, vShape, wShape, finalFunc) - local maybeShapes = {tShape, uShape, vShape, wShape, finalFunc} + local maybeShapes = { tShape, uShape, vShape, wShape, finalFunc } local shapes = {} local callback - for _,maybeShape in pairs(maybeShapes) do + for _, maybeShape in pairs(maybeShapes) do if type(maybeShape) == "table" then - shapes[#shapes + 1] = maybeShape + shapes[#shapes + 1] = maybeShape elseif type(maybeShape) == "function" then callback = maybeShape end end local keys = {} - for _,shape in pairs(shapes) do - for key,_ in pairs(shape) do + for _, shape in pairs(shapes) do + for key, _ in pairs(shape) do keys[#keys + 1] = key end end @@ -159,15 +168,15 @@ local Target = { target = XYPair } local Velocity = { velocity = XYPair } function ecs.overlayOnto(entity, value) - for key,v in pairs(value) do + for key, v in pairs(value) do entity[key] = v end ecs.addEntity(entity) end local data = { - position = {x = 0, y = 0}, - velocity = {x = 1, y = 2}, + position = { x = 0, y = 0 }, + velocity = { x = 1, y = 2 }, } ---@generic T @@ -183,14 +192,14 @@ end ---@param shape `T` ---@param value `T` function ecs.set(entity, shape, value) - for key,v in pairs(shape) do + for key, v in pairs(shape) do entity[key] = value[v] end end ecs.addEntity(data) -ecs.forEntitiesWith(Position, Velocity, function (delta, e, pos, vel) +ecs.forEntitiesWith(Position, Velocity, function(delta, e, pos, vel) pos.x = pos.x + (delta * vel.x) pos.y = pos.y + (delta * vel.y) print("position") @@ -200,12 +209,12 @@ ecs.forEntitiesWith(Position, Velocity, function (delta, e, pos, vel) }) end) -ecs.forEntitiesWith(Target, function (delta, e, pos, vel) +ecs.forEntitiesWith(Target, function(delta, e, pos, vel) pos.x = pos.x + (delta * vel.x) pos.y = pos.y + (delta * vel.y) print("position") tprint(pos, 1) - ecs.set(e, Target, 'hallo') + ecs.set(e, Target, "hallo") end) -ecs.update(1) \ No newline at end of file +ecs.update(1) diff --git a/src/graphics.lua b/src/graphics.lua index 0970d64..8421c22 100644 --- a/src/graphics.lua +++ b/src/graphics.lua @@ -5,18 +5,18 @@ local ballBuffer = 5 --- 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 \ No newline at end of file + 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 diff --git a/src/main.lua b/src/main.lua index 0b8da10..a148ce6 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,3 +1,4 @@ +-- stylua: ignore start import 'CoreLibs/animation.lua' import 'CoreLibs/animator.lua' import 'CoreLibs/easing.lua' @@ -7,6 +8,7 @@ import 'CoreLibs/ui.lua' import 'graphics.lua' import 'utils.lua' +-- stylua: ignore end --- @alias XYPair { x: number, y: number } @@ -20,7 +22,7 @@ local gfx = playdate.graphics local SCREEN = { W = playdate.display.getWidth(), - H = playdate.display.getHeight() + H = playdate.display.getHeight(), } local CENTER = xy(SCREEN.W / 2, SCREEN.H / 2) @@ -29,17 +31,13 @@ local batCrackSound = playdate.sound.sampleplayer.new("sounds/bat-crack-reverb.w 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( - 100, - "images/game/player.png", - "images/game/player-lowhat.png" -) +local playerImageBlipper = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png") local BALL_OFFSCREEN = 999 local danceBounceMs = 500 local danceBounceCount = 4 -local fielderDanceAnimator = gfx.animator.new(danceBounceMs, 10, 0, easingHill)--, -1 * danceBounceMs * danceBounceCount) +local fielderDanceAnimator = gfx.animator.new(danceBounceMs, 10, 0, easingHill) --, -1 * danceBounceMs * danceBounceCount) fielderDanceAnimator.repeatCount = danceBounceCount - 1 local pitchFlyTimeMs = 2500 @@ -56,14 +54,14 @@ local TAG_DISTANCE = 20 local ball = { x = CENTER.x, y = BALL_OFFSCREEN, - size = 6 + size = 6, } local BAT_LENGTH = 45 local MODES = { - batting = {}, - running = {} + batting = {}, + running = {}, } local currentMode = MODES.batting @@ -84,88 +82,88 @@ local FIRST, SECOND, THIRD, HOME = 1, 2, 3, 4 ---@type Base[] local bases = { - xy(SCREEN.W * 0.93, SCREEN.H * 0.52), - xy(SCREEN.W * 0.47, SCREEN.H * 0.19), - xy(SCREEN.W * 0.03, SCREEN.H * 0.52), - xy(SCREEN.W * 0.474, SCREEN.H * 0.79) + xy(SCREEN.W * 0.93, SCREEN.H * 0.52), + xy(SCREEN.W * 0.47, SCREEN.H * 0.19), + xy(SCREEN.W * 0.03, SCREEN.H * 0.52), + xy(SCREEN.W * 0.474, SCREEN.H * 0.79), } ---@type table local nextBaseMap = { - [bases[FIRST]] = bases[SECOND], - [bases[SECOND]] = bases[THIRD], - [bases[THIRD]] = bases[HOME] + [bases[FIRST]] = bases[SECOND], + [bases[SECOND]] = bases[THIRD], + [bases[THIRD]] = bases[HOME], } function newFielder(speed) return { - speed = speed + speed = speed, } end ---@type table local fielders = { - first = newFielder(40), - second = newFielder(40), - shortstop = newFielder(40), - third = newFielder(40), - pitcher = newFielder(30), - catcher = newFielder(20), - left = newFielder(40), - center = newFielder(40), - right = newFielder(40) + first = newFielder(40), + second = newFielder(40), + shortstop = newFielder(40), + third = newFielder(40), + pitcher = newFielder(30), + catcher = newFielder(20), + left = newFielder(40), + center = newFielder(40), + right = newFielder(40), } local PITCHER_POS = { x = SCREEN.W * 0.48, - y = SCREEN.H * 0.40 + y = SCREEN.H * 0.40, } --- Resets the target positions of all fielders to their defaults (at their field positions). ---@param fromOffTheField boolean If provided, also sets all runners' current position to one centralized location. function resetFielderPositions(fromOffTheField) if fromOffTheField then - for _,fielder in pairs(fielders) do + for _, fielder in pairs(fielders) do fielder.x = CENTER.x fielder.y = SCREEN.H end end - fielders.first.target = xy(SCREEN.W - 65, SCREEN.H * 0.48) - fielders.second.target = xy(SCREEN.W * 0.70, SCREEN.H * 0.30) - fielders.shortstop.target = xy(SCREEN.W * 0.30, SCREEN.H * 0.30) - fielders.third.target = xy(SCREEN.W * 0.1, SCREEN.H * 0.48) - fielders.pitcher.target = xy(PITCHER_POS.x, PITCHER_POS.y) - fielders.catcher.target = xy(SCREEN.W * 0.475, SCREEN.H * 0.92) - fielders.left.target = xy(SCREEN.W * -1, SCREEN.H * -0.2) - fielders.center.target = xy(CENTER.x, SCREEN.H * -0.4) - fielders.right.target = xy(SCREEN.W * 2, SCREEN.H * fielders.left.target.y) + fielders.first.target = xy(SCREEN.W - 65, SCREEN.H * 0.48) + fielders.second.target = xy(SCREEN.W * 0.70, SCREEN.H * 0.30) + fielders.shortstop.target = xy(SCREEN.W * 0.30, SCREEN.H * 0.30) + fielders.third.target = xy(SCREEN.W * 0.1, SCREEN.H * 0.48) + fielders.pitcher.target = xy(PITCHER_POS.x, PITCHER_POS.y) + fielders.catcher.target = xy(SCREEN.W * 0.475, SCREEN.H * 0.92) + fielders.left.target = xy(SCREEN.W * -1, SCREEN.H * -0.2) + fielders.center.target = xy(CENTER.x, SCREEN.H * -0.4) + fielders.right.target = xy(SCREEN.W * 2, SCREEN.H * fielders.left.target.y) end local PLAYER_STARTING_X = bases[HOME].x - 40 local PLAYER_STARTING_Y = bases[HOME].y - 3 --- @type Runner[] -local runners = { } +local runners = {} --- @type Runner[] -local outRunners = { } +local outRunners = {} local nameI = 1 -local runnerNames = {"Barbara", "Steve", "Jerry", "Beatrice"} +local runnerNames = { "Barbara", "Steve", "Jerry", "Beatrice" } ---@return Runner function newRunner() - local new = { - x = PLAYER_STARTING_X, - y = PLAYER_STARTING_Y, - nextBase = nil, - prevBase = nil, - name = runnerNames[nameI] - } - nameI = nameI + 1 - runners[#runners + 1] = new - return new + local new = { + x = PLAYER_STARTING_X, + y = PLAYER_STARTING_Y, + nextBase = nil, + prevBase = nil, + name = runnerNames[nameI], + } + nameI = nameI + 1 + runners[#runners + 1] = new + return new end ---@type Runner | nil @@ -174,45 +172,45 @@ batter.nextBase = bases[FIRST] batter.forcedTo = bases[FIRST] function throwBall(destX, destY, easingFunc, flyTimeMs, floaty) - if not flyTimeMs then - flyTimeMs = distanceBetween(ball.x, ball.y, destX, destY) * 5 - end - ballSizeAnimator:reset(flyTimeMs) - ballAnimatorY = gfx.animator.new(flyTimeMs, ball.y, destY, easingFunc) - ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc) - if floaty then - ballFloatAnimator:reset(flyTimeMs) - end + if not flyTimeMs then + flyTimeMs = distanceBetween(ball.x, ball.y, destX, destY) * 5 + end + ballSizeAnimator:reset(flyTimeMs) + ballAnimatorY = gfx.animator.new(flyTimeMs, ball.y, destY, easingFunc) + ballAnimatorX = gfx.animator.new(flyTimeMs, ball.x, destX, easingFunc) + if floaty then + ballFloatAnimator:reset(flyTimeMs) + end end function pitch() - currentMode = MODES.batting + currentMode = MODES.batting ballAnimatorX = gfx.animator.new(0, PITCH_START_X, PITCH_START_X, playdate.easingFunctions.linear) - ballAnimatorY = pitchAnimator - pitchAnimator:reset() + ballAnimatorY = pitchAnimator + pitchAnimator:reset() end function playdate.AButtonDown() if not batter then return end - pitch() + pitch() end function playdate.upButtonDown() - batBase.y = batBase.y - 1 + batBase.y = batBase.y - 1 end function playdate.downButtonDown() - batBase.y = batBase.y + 1 + batBase.y = batBase.y + 1 end function playdate.rightButtonDown() - batBase.x = batBase.x + 1 + batBase.x = batBase.x + 1 end function playdate.leftButtonDown() - batBase.x = batBase.x - 1 + batBase.x = batBase.x - 1 end local elapsedSec = 0 @@ -221,23 +219,22 @@ local crankChange = 0 local BASE_HITBOX = 13 --- Returns the base being touched by the runner at (x,y), or nil, if no base is being touched function isTouchingBase(x, y) - for _,base in ipairs(bases) do - if distanceBetween(x, y, base.x, base.y) < BASE_HITBOX then - return base - end - end + for _, base in ipairs(bases) do + if distanceBetween(x, y, base.x, base.y) < BASE_HITBOX then + return base + end + end - return nil + return nil end local BALL_CATCH_HITBOX = 3 function isTouchingBall(x, y) - local ballDistance = distanceBetween(x, y, ball.x, ball.y) - return ballDistance < BALL_CATCH_HITBOX + local ballDistance = distanceBetween(x, y, ball.x, ball.y) + return ballDistance < BALL_CATCH_HITBOX end -function updateForcedTos() -end +function updateForcedTos() end local outs = 0 local homeScore = 0 @@ -246,106 +243,104 @@ local awayScore = 0 function outRunner(runnerIndex) print("You're out, runner" .. runnerIndex .. "!") outs = math.min(3, outs + 1) - outRunners[#outRunners+1] = runners[runnerIndex] - table.remove(runners, runnerIndex) - updateForcedTos() - fielderDanceAnimator:reset() + outRunners[#outRunners + 1] = runners[runnerIndex] + table.remove(runners, runnerIndex) + updateForcedTos() + fielderDanceAnimator:reset() end function updateFielders() - local touchingBaseCache = buildCache( - function(runner) - return isTouchingBase(runner.x, runner.y) - end - ) + local touchingBaseCache = buildCache(function(runner) + return isTouchingBase(runner.x, runner.y) + end) - for _,fielder in pairs(fielders) do - -- TODO: Target unforced runners (or their target bases) for tagging - -- With new Position-based scheme, fielders are now able to set `fielder.target = runner` to track directly - if fielder.target ~= nil then - local x, y, distance = normalizeVector(fielder.x, fielder.y, fielder.target.x, fielder.target.y) + for _, fielder in pairs(fielders) do + -- TODO: Target unforced runners (or their target bases) for tagging + -- With new Position-based scheme, fielders are now able to set `fielder.target = runner` to track directly + if fielder.target ~= nil then + local x, y, distance = normalizeVector(fielder.x, fielder.y, fielder.target.x, fielder.target.y) - if distance > 1 then - fielder.x = fielder.x - (x * fielder.speed * deltaSeconds) - fielder.y = fielder.y - (y * fielder.speed * deltaSeconds) - else - if fielder.onArrive then - fielder.onArrive() - end - fielder.target = nil - end - end + if distance > 1 then + fielder.x = fielder.x - (x * fielder.speed * deltaSeconds) + fielder.y = fielder.y - (y * fielder.speed * deltaSeconds) + else + if fielder.onArrive then + fielder.onArrive() + end + fielder.target = nil + end + end - if currentMode == MODES.running and isTouchingBall(fielder.x, fielder.y) then - local touchedBase = isTouchingBase(fielder.x, fielder.y) - for i,runner in pairs(runners) do - local runnerOnBase = touchingBaseCache.get(runner) - if touchedBase and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase then + if currentMode == MODES.running and isTouchingBall(fielder.x, fielder.y) then + local touchedBase = isTouchingBase(fielder.x, fielder.y) + for i, runner in pairs(runners) do + local runnerOnBase = touchingBaseCache.get(runner) + if touchedBase and runner.forcedTo == touchedBase and touchedBase ~= runnerOnBase then print("Force out of runner:") printTable(runner) - outRunner(i) - elseif not runnerOnBase then - local fielderDistance = distanceBetween(runner.x, runner.y, fielder.x, fielder.y) - if fielderDistance < TAG_DISTANCE then + outRunner(i) + elseif not runnerOnBase then + local fielderDistance = distanceBetween(runner.x, runner.y, fielder.x, fielder.y) + if fielderDistance < TAG_DISTANCE then print("Tagged out!") - outRunner(i) - end - end - end - end - end + outRunner(i) + end + end + end + end + end end --- Returns true if at least one runner is still moving ---@return boolean function updateRunners() - local autoRunSpeed = 20 - --autoRunSpeed = 140 - local nonPlayerRunners = filter(runners, function (runner) - return runner ~= batter - end) + local autoRunSpeed = 20 + --autoRunSpeed = 140 + local nonPlayerRunners = filter(runners, function(runner) + return runner ~= batter + end) - local runnerMoved = false - for _,runner in pairs(nonPlayerRunners) do - local _, nearestBaseDistance = getNearestOf(bases, runner.x, runner.y) - if runner.nextBase then - local nb = runner.nextBase - local x, y, distance = normalizeVector(runner.x, runner.y, nb.x, nb.y) + local runnerMoved = false + for _, runner in pairs(nonPlayerRunners) do + local _, nearestBaseDistance = getNearestOf(bases, runner.x, runner.y) + 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 - local prevX, prevY = runner.x, runner.y - -- TODO: Drift toward nearest base? - local autoRun = nearestBaseDistance > 5 and mult * autoRunSpeed * deltaSeconds or 0 - mult = autoRun + (crankChange / 20) - runner.x = runner.x - (x * mult) + if distance > 1 then + local mult = 1 + if crankChange < 0 then + mult = -1 + end + local prevX, prevY = runner.x, runner.y + -- TODO: Drift toward nearest base? + local autoRun = nearestBaseDistance > 5 and mult * autoRunSpeed * deltaSeconds or 0 + mult = autoRun + (crankChange / 20) + runner.x = runner.x - (x * mult) runner.y = runner.y - (y * mult) runnerMoved = runnerMoved or prevX ~= runner.x or prevY ~= runner.y - else - runner.nextBase = nextBaseMap[runner.nextBase] - runner.forcedTo = nil - end - end - end + else + runner.nextBase = nextBaseMap[runner.nextBase] + runner.forcedTo = nil + end + end + end - return runnerMoved + return runnerMoved end ---@return boolean function ballIsBeingThrown() - return false + return false end ---@return boolean function throwArrivedBeforeRunner() - return false + return false end function getRunnerTargeting(base) - for _,runner in pairs(runners) do + for _, runner in pairs(runners) do if runner.nextBase == base then return runner end @@ -356,44 +351,44 @@ end ---@return Base[] function getForcedOutTargets() local targets = {} - for _,base in ipairs(bases) do + for _, base in ipairs(bases) do local runnerTargetingBase = getRunnerTargeting(base) if runnerTargetingBase then - targets[#targets+1] = base + targets[#targets + 1] = base else return targets end end return targets - -- return { bases[FIRST] } + -- return { bases[FIRST] } end --- 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() local farRunnersBase, farDistance - for _,runner in pairs(runners) do - local base, distance = getNearestOf(bases, runner.x, runner.y, function(base) + for _, runner in pairs(runners) do + local base, distance = getNearestOf(bases, runner.x, runner.y, function(base) return runner.nextBase == base - end) + end) if farRunnersBase == nil then - farRunnersBase = base - farDistance = distance + farRunnersBase = base + farDistance = distance elseif farDistance < distance then farRunnersBase = base farDistance = distance end - end + end - return farRunnersBase, farDistance + return farRunnersBase, farDistance end --- Returns x,y of the throw target ---@return number|nil, number|nil function getNextThrowTarget() -- TODO: Handle missed throws, check for fielders at target, etc. - local targets = getForcedOutTargets() - if #targets ~= 0 then + local targets = getForcedOutTargets() + if #targets ~= 0 then return targets[1].x, targets[1].y end @@ -427,42 +422,44 @@ function updateBatting() ball.size = 6 end - local batAngle = math.rad(playdate.getCrankPosition() + CRANK_OFFSET_DEG) - batTip.x = batBase.x + (BAT_LENGTH * math.sin(batAngle)) - batTip.y = batBase.y + (BAT_LENGTH * math.cos(batAngle)) + local batAngle = math.rad(playdate.getCrankPosition() + CRANK_OFFSET_DEG) + batTip.x = batBase.x + (BAT_LENGTH * math.sin(batAngle)) + batTip.y = batBase.y + (BAT_LENGTH * math.cos(batAngle)) - if acceleratedChange >= 0 and - pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, SCREEN.H) then - batCrackSound:play() - currentMode = MODES.running - ballAngle = batAngle + math.rad(90) + if + acceleratedChange >= 0 + and pointDirectlyUnderLine(ball.x, ball.y, batBase.x, batBase.y, batTip.x, batTip.y, SCREEN.H) + then + batCrackSound:play() + currentMode = MODES.running + ballAngle = batAngle + math.rad(90) - local mult = math.abs(acceleratedChange / 15) - local ballVelX = mult * 10 * math.sin(ballAngle) - local ballVelY = mult * 5 * math.cos(ballAngle) - if ballVelY > 0 then - ballVelX = ballVelX * -1 - ballVelY = ballVelY * -1 - end - ballDestX = ball.x + (ballVelX * HIT_MULT) - ballDestY = ball.y + (ballVelY * HIT_MULT) - throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000) + local mult = math.abs(acceleratedChange / 15) + local ballVelX = mult * 10 * math.sin(ballAngle) + local ballVelY = mult * 5 * math.cos(ballAngle) + if ballVelY > 0 then + ballVelX = ballVelX * -1 + ballVelY = ballVelY * -1 + end + ballDestX = ball.x + (ballVelX * HIT_MULT) + ballDestY = ball.y + (ballVelY * HIT_MULT) + throwBall(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000) - fielders.first.target = bases[FIRST] - batter.nextBase = bases[FIRST] - batter.forcedTo = bases[FIRST] - batter = nil -- Demote batter to a mere runner + fielders.first.target = bases[FIRST] + batter.nextBase = bases[FIRST] + batter.forcedTo = bases[FIRST] + batter = nil -- Demote batter to a mere runner - local chasingFielder = getNearestOf(fielders, ballDestX, ballDestY) - chasingFielder.target = { x = ballDestX, y = ballDestY } - chasingFielder.onArrive = function() - local targetX, targetY = getNextThrowTarget() - if targetX ~= nil then + local chasingFielder = getNearestOf(fielders, ballDestX, ballDestY) + chasingFielder.target = { x = ballDestX, y = ballDestY } + chasingFielder.onArrive = function() + local targetX, targetY = getNextThrowTarget() + if targetX ~= nil then throwBall(targetX, targetY, playdate.easingFunctions.linear, nil, true) chasingFielder.onArrive = nil - end - end - end + end + end + end end function updateRunning() @@ -495,19 +492,19 @@ function updateOutRunners() end function updateGameState() - deltaSeconds = playdate.getElapsedTime() or 0 - playdate.resetElapsedTime() - elapsedSec = elapsedSec + deltaSeconds - crankChange, acceleratedChange = playdate.getCrankChange() --[[@as number, number]] + 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 + if currentMode == MODES.batting then updateBatting() - elseif currentMode == MODES.running then + elseif currentMode == MODES.running then updateRunning() - end + end updateFielders() updateOutRunners() @@ -520,15 +517,15 @@ function drawScoreboard() local y = SCREEN.H * 0.95 local x = SCREEN.W * 0.05 - gfx.setLineWidth(1) - gfx.setColor(gfx.kColorBlack) + 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.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 + for i = 0, (outs - 1) do gfx.fillCircleAtPoint(x + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE) end @@ -540,51 +537,48 @@ function drawScoreboard() end function playdate.update() - updateGameState() - playdate.graphics.animation.blinker.updateAll() + updateGameState() + playdate.graphics.animation.blinker.updateAll() - gfx.clear() + gfx.clear() 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 + end - grassBackground:draw(-400, -240) + grassBackground:draw(-400, -240) - gfx.setColor(gfx.kColorBlack) - gfx.setLineWidth(2) + gfx.setColor(gfx.kColorBlack) + gfx.setLineWidth(2) - gfx.drawCircleAtPoint(ball.x, ball.y, ball.size) + gfx.drawCircleAtPoint(ball.x, ball.y, ball.size) - local fielderDanceHeight = fielderDanceAnimator:currentValue() - for _,fielder in pairs(fielders) do - gfx.fillRect(fielder.x, fielder.y - fielderDanceHeight, 14, 25) - end + local fielderDanceHeight = fielderDanceAnimator:currentValue() + for _, fielder in pairs(fielders) do + gfx.fillRect(fielder.x, fielder.y - fielderDanceHeight, 14, 25) + end - if currentMode == MODES.batting then + if currentMode == MODES.batting then gfx.setLineWidth(5) - gfx.drawLine( - batBase.x, batBase.y, - batTip.x, batTip.y - ) - end + gfx.drawLine(batBase.x, batBase.y, batTip.x, batTip.y) + end - if playdate.isCrankDocked() then -- or (crankChange < 2 and currentMode == MODES.running) then - playdate.ui.crankIndicator:draw() - end + if playdate.isCrankDocked() then -- or (crankChange < 2 and currentMode == MODES.running) then + playdate.ui.crankIndicator:draw() + end - -- TODO? Change blip speed depending on runner speed? - for _,runner in pairs(runners) do + -- TODO? Change blip speed depending on runner speed? + for _, runner in pairs(runners) do -- TODO? Scale sprites down as y increases - playerImageBlipper:draw(false, runner.x, runner.y) - end - for _,runner in pairs(outRunners) do + playerImageBlipper:draw(false, runner.x, runner.y) + end + for _, runner in pairs(outRunners) do playerFrown:draw(runner.x, runner.y) - end + end - drawScoreboard() + drawScoreboard() end -init() \ No newline at end of file +init() diff --git a/src/utils.lua b/src/utils.lua index 07cd0f4..5a693c3 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -1,5 +1,7 @@ +-- stylua: ignore start import 'CoreLibs/animation.lua' import 'CoreLibs/graphics.lua' +-- stylua: ignore end -- number = ((number * 2) - 1) -- return number * number @@ -8,28 +10,28 @@ import 'CoreLibs/graphics.lua' -- 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 + 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 - } + return { + x = x, + y = y, + } end --- Returns the normalized vector as two values, plus the distance between the given points. ---@return number, number, number function normalizeVector(x1, y1, x2, y2) - local distance, a, b = distanceBetween(x1, y1, x2, y2) - return a / distance, b / distance, distance + local distance, a, b = distanceBetween(x1, y1, x2, y2) + return a / distance, b / distance, distance end ---@generic T @@ -37,13 +39,13 @@ end ---@param condition fun(T): boolean ---@return T[] function filter(array, condition) - local newArray = {} - for _,element in pairs(array) do - if condition(element) then - newArray[#newArray + 1] = element - end - end - return newArray + local newArray = {} + for _, element in pairs(array) do + if condition(element) then + newArray[#newArray + 1] = element + end + end + return newArray end ---@generic TIn @@ -52,39 +54,39 @@ end ---@param mapper fun(TIn): TOut ---@return TOut[] function map(array, mapper) - local newArray = {} - for _,element in pairs(array) do - newArray[#newArray + 1] = mapper(element) - end - return newArray + local newArray = {} + for _, element in pairs(array) do + newArray[#newArray + 1] = mapper(element) + end + return newArray end ---@return number, number, number function distanceBetween(x1, y1, x2, y2) - local a = x1 - x2 - local b = y1 - y2 - return math.sqrt((a*a) + (b*b)), a, b + local a = x1 - x2 + local b = y1 - y2 + return math.sqrt((a * a) + (b * b)), a, b end --- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound --- @return boolean function pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound) - -- This check currently assumes right-handedness. - -- I.e. it assumes the ball is to the right of batBaseX - if pointX < lineX1 or pointX > lineX2 or pointY > bottomBound then - return false - end + -- This check currently assumes right-handedness. + -- I.e. it assumes the ball is to the right of batBaseX + if pointX < lineX1 or pointX > lineX2 or pointY > bottomBound then + return false + end - local m = (lineY2 - lineY1) / (lineX2 - lineX1) + local m = (lineY2 - lineY1) / (lineX2 - lineX1) - -- y = mx + b - -- b = y1 - (m * x1) - local b = lineY1 - (m * lineX1) - local yOnLine = (m * pointX) + b - local yP = pointY - local yDelta = yOnLine - yP + -- y = mx + b + -- b = y1 - (m * x1) + local b = lineY1 - (m * lineX1) + local yOnLine = (m * pointX) + b + local yP = pointY + local yDelta = yOnLine - yP - return yDelta <= 0 + return yDelta <= 0 end --- Returns the nearest position object from the given point, as well as its distance from that point @@ -94,9 +96,9 @@ end ---@param y number ---@return T,number|nil function getNearestOf(array, x, y, extraCondition) - local nearest, nearestDistance = nil, nil - for _, element in pairs(array) do - if not extraCondition or extraCondition(element) then + local nearest, nearestDistance = nil, nil + for _, element in pairs(array) do + if not extraCondition or extraCondition(element) then if nearest == nil then nearest = element nearestDistance = distanceBetween(element.x, element.y, x, y) @@ -107,29 +109,29 @@ function getNearestOf(array, x, y, extraCondition) nearestDistance = distance end end - end - end + end + end - return nearest, nearestDistance + return nearest, nearestDistance end local NO_VALUE = {} function buildCache(fetcher) - local cacheData = {} - return { - cacheDate = cacheData, - get = function(key) - if cacheData[key] == NO_VALUE then - return nil - end - if cacheData[key] ~= nil then - return cacheData[key] - end - cacheData[key] = fetcher(key) or NO_VALUE - return cacheData[key] - end - } + local cacheData = {} + return { + cacheDate = cacheData, + get = function(key) + if cacheData[key] == NO_VALUE then + return nil + end + if cacheData[key] ~= nil then + return cacheData[key] + end + cacheData[key] = fetcher(key) or NO_VALUE + return cacheData[key] + end, + } end blipper = {} @@ -146,6 +148,6 @@ function blipper.new(msInterval, imagePath1, imagePath2) draw = function(self, disableBlipping, x, y) local currentImage = (disableBlipping or self.blinker.on) and self.image2 or self.image1 currentImage:draw(x, y) - end + end, } -end \ No newline at end of file +end