diff --git a/src/images/game/minimap.png b/src/images/game/minimap.png new file mode 100644 index 0000000..18aa8e8 Binary files /dev/null and b/src/images/game/minimap.png differ diff --git a/src/main.lua b/src/main.lua index 6f1430a..b9d8d25 100644 --- a/src/main.lua +++ b/src/main.lua @@ -54,6 +54,7 @@ local BootTune = playdate.sound.sampleplayer.new("sounds/boot-tune.wav") local TinnyBackground = playdate.sound.sampleplayer.new("sounds/tinny-background.wav") local BatCrackSound = playdate.sound.sampleplayer.new("sounds/bat-crack-reverb.wav") local GrassBackground = gfx.image.new("images/game/grass.png") --[[@as pd_image]] +local Minimap = gfx.image.new("images/game/minimap.png") --[[@as pd_image]] local PlayerFrown = gfx.image.new("images/game/player-frown.png") --[[@as pd_image]] local PlayerSmile = gfx.image.new("images/game/player.png") --[[@as pd_image]] local PlayerBack = gfx.image.new("images/game/player-back.png") --[[@as pd_image]] @@ -327,7 +328,7 @@ function pitch(pitchFlyTimeMs, pitchTypeIndex) catcherThrownBall = false offenseMode = Offense.batting - local current = Pitches[pitchTypeIndex or math.random(#Pitches)] + local current = Pitches[pitchTypeIndex] ballAnimatorX = current.x ballAnimatorY = current.y or Pitches[1].y @@ -355,13 +356,9 @@ local BaseHitbox = 10 ---@param y number ---@return Base | nil function isTouchingBase(x, y) - for _, base in ipairs(Bases) do - if utils.distanceBetween(x, y, base.x, base.y) < BaseHitbox then - return base - end - end - - return nil + return utils.first(Bases, function(base) + return utils.distanceBetween(x, y, base.x, base.y) < BaseHitbox + end) end local BallCatchHitbox = 3 @@ -377,19 +374,16 @@ end ---@param base Base ---@return Runner | nil -function getRunnerTargeting(base) - for _, runner in pairs(runners) do - if runner.nextBase == base then - return runner - end - end - return nil +function getRunnerWithNextBase(base) + return utils.first(runners, function(runner) + return runner.nextBase == base + end) end function updateForcedRunners() local stillForced = true for _, base in ipairs(Bases) do - local runnerTargetingBase = getRunnerTargeting(base) + local runnerTargetingBase = getRunnerWithNextBase(base) if runnerTargetingBase then if stillForced then runnerTargetingBase.forcedTo = base @@ -453,7 +447,7 @@ end function getForcedOutTargets() local targets = {} for _, base in ipairs(Bases) do - local runnerTargetingBase = getRunnerTargeting(base) + local runnerTargetingBase = getRunnerWithNextBase(base) if runnerTargetingBase then targets[#targets + 1] = base else @@ -490,7 +484,6 @@ function getNextOutTarget() end local baseCloseToStrandedRunner = getBaseOfStrandedRunner() - -- TODO: If another fielder is closer, throw it to them, instead if baseCloseToStrandedRunner then return baseCloseToStrandedRunner.x, baseCloseToStrandedRunner.y end @@ -510,28 +503,10 @@ function tryToMakeAnOut(fielder) end end ---- Push the given obect at the given speed toward a target. Speed should be pre-multiplied by the frame's delta time. ---- Stops when within 1. Returns true only if the object did actually move. ----@param mover { x: number, y: number } ----@param speed number ----@param target { x: number, y: number } ----@return boolean -function moveAtSpeed(mover, speed, target) - local x, y, distance = utils.normalizeVector(mover.x, mover.y, target.x, target.y) - - if distance > 1 then - mover.x = mover.x - (x * speed) - mover.y = mover.y - (y * speed) - return true - end - - return false -end - ---@param fielder Fielder function updateFielder(fielder) if fielder.target ~= nil then - if not moveAtSpeed(fielder, fielder.speed * deltaSeconds, fielder.target) then + if not utils.moveAtSpeed(fielder, fielder.speed * deltaSeconds, fielder.target) then fielder.target = nil end end @@ -540,7 +515,6 @@ function updateFielder(fielder) return end - -- TODO: Check for double-plays or other available outs. local touchedBase = isTouchingBase(fielder.x, fielder.y) for i, runner in pairs(runners) do local runnerOnBase = isTouchingBase(runner.x, runner.y) @@ -570,7 +544,7 @@ function updateFielders() end -- if offenseMode == Offense.batting then - -- moveAtSpeed( + -- utils.moveAtSpeed( -- fielders.catcher, -- fielders.catcher.speed * 2 * deltaSeconds, -- { x = math.min(Center.x + 15, ball.x), y = fielders.catcher.y } @@ -623,7 +597,6 @@ function updateRunner(runner, runnerIndex, appliedSpeed) end end - -- TODO: Also move if forced to 😅 local autoRun = (nearestBaseDistance > 40 or runner.forcedTo) and mult * autoRunSpeed or nearestBaseDistance < 5 and 0 or (nearestBase == runner.nextBase and autoRunSpeed or -1 * autoRunSpeed) @@ -698,15 +671,13 @@ end function updateRunning(appliedSpeed) ball.size = ballSizeAnimator:currentValue() - local nonBatterRunners = utils.filter(runners, function(runner) - return runner ~= batter - end) - local runnerMoved = false -- TODO: Filter for the runner closest to the currently-held direction button - for runnerIndex, runner in ipairs(nonBatterRunners) do - runnerMoved = updateRunner(runner, runnerIndex, appliedSpeed) or runnerMoved + for runnerIndex, runner in ipairs(runners) do + if runner ~= batter then + runnerMoved = updateRunner(runner, runnerIndex, appliedSpeed) or runnerMoved + end end return runnerMoved @@ -768,7 +739,7 @@ function buttonControlledThrow(thrower, throwFlyMs, forbidThrowHome) if playdate.buttonIsPressed(playdate.kButtonLeft) then targetBase = Bases[Third] elseif playdate.buttonIsPressed(playdate.kButtonUp) then - targetBase = Bases[Second] -- TODO - or shortstop - whoever's closer + targetBase = Bases[Second] elseif playdate.buttonIsPressed(playdate.kButtonRight) then targetBase = Bases[First] elseif not forbidThrowHome and playdate.buttonIsPressed(playdate.kButtonDown) then @@ -820,7 +791,7 @@ function updateGameState() if secondsSincePitchAllowed > PitchAfterSeconds then if playerOnOffense then - pitch(PitchFlyMs) + pitch(PitchFlyMs, math.random(#Pitches)) else local throwFly = readThrow() if throwFly and not buttonControlledThrow(fielders.pitcher, throwFly, true) then @@ -864,8 +835,25 @@ function updateGameState() walkAwayOutRunners() end --- TODO -function drawMinimap() end +local MinimapSizeX, MinimapSizeY = Minimap:getSize() +local MinimapPosX, MinimapPosY = Screen.W - MinimapSizeX, Screen.H - MinimapSizeY + +local FieldHeight = Bases[Home].y - Bases[Second].y + +local MinimapMultX = 0.75 * MinimapSizeX / Screen.W +local MinimapOffsetX = MinimapPosX + 5 +local MinimapMultY = 0.70 * MinimapSizeY / FieldHeight +local MinimapOffsetY = MinimapPosY - 15 + +function drawMinimap() + Minimap:draw(MinimapPosX, MinimapPosY) + gfx.setColor(gfx.kColorBlack) + for _, runner in pairs(runners) do + local x = (MinimapMultX * runner.x) + MinimapOffsetX + local y = (MinimapMultY * runner.y) + MinimapOffsetY + gfx.fillRect(x, y, 8, 8) + end +end ---@param fielder Fielder ---@return boolean isHoldingBall @@ -916,7 +904,7 @@ function playdate.update() playdate.ui.crankIndicator:draw() end - -- TODO? Change blip speed depending on runner speed? + -- TODO? Scale sprites down as y increases for _, runner in pairs(runners) do if runner == batter then if batAngleDeg > 50 and batAngleDeg < 200 then @@ -925,7 +913,7 @@ function playdate.update() PlayerSmile:draw(runner.x, runner.y) end else - -- TODO? Scale sprites down as y increases + -- TODO? Change blip speed depending on runner speed? PlayerImageBlipper:draw(false, runner.x, runner.y) end end diff --git a/src/utils.lua b/src/utils.lua index 3de3407..b401a7d 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -47,6 +47,24 @@ function utils.normalizeVector(x1, y1, x2, y2) return x / distance, y / distance, distance end +--- Push the given obect at the given speed toward a target. Speed should be pre-multiplied by the frame's delta time. +--- Stops when within 1. Returns true only if the object did actually move. +---@param mover { x: number, y: number } +---@param speed number +---@param target { x: number, y: number } +---@return boolean +function utils.moveAtSpeed(mover, speed, target) + local x, y, distance = utils.normalizeVector(mover.x, mover.y, target.x, target.y) + + if distance > 1 then + mover.x = mover.x - (x * speed) + mover.y = mover.y - (y * speed) + return true + end + + return false +end + ---@generic T ---@param array T[] ---@param condition fun(T): boolean @@ -61,6 +79,19 @@ function utils.filter(array, condition) return newArray end +---@generic T +---@param array T[] +---@param condition fun(T): boolean +---@return T | nil +function utils.first(array, condition) + for _, element in ipairs(array) do + if condition(element) then + return element + end + end + return nil +end + ---@return number distance, number x, number y function utils.distanceBetween(x1, y1, x2, y2) local x = x1 - x2 @@ -102,7 +133,7 @@ end ---@param array T[] ---@param x number ---@param y number ----@return T,number|nil +---@return T nearest,number |nil distance function utils.getNearestOf(array, x, y, extraCondition) local nearest, nearestDistance = nil, nil for _, element in pairs(array) do