diff --git a/src/dbg.lua b/src/dbg.lua index b3e7795..6206c8a 100644 --- a/src/dbg.lua +++ b/src/dbg.lua @@ -6,8 +6,14 @@ function dbg.label(value, name) if type(value) == "table" then print(name .. ":") printTable(value) - else + elseif type(value) == "boolean" then + if value then + print(name .. ": " .. (value and "true" or "false")) + end + elseif value ~= nil then print(name .. ": " .. value) + else + print(name .. ": nil") end return value end diff --git a/src/main.lua b/src/main.lua index fc46a92..9caaa53 100644 --- a/src/main.lua +++ b/src/main.lua @@ -69,6 +69,7 @@ local PlayerImageBlipper = blipper.new(100, "images/game/player.png", "i local DanceBounceMs = 500 local DanceBounceCount = 4 + local FielderDanceAnimator = gfx.animator.new(1, 10, 0, utils.easingHill) FielderDanceAnimator.repeatCount = DanceBounceCount - 1 @@ -135,7 +136,7 @@ local ball = { heldBy = nil --[[@type Runner | nil]], } -local BatLength = 50 --45 +local BatLength = 50 local Offense = { batting = {}, @@ -168,12 +169,15 @@ local battingTeam = teams.away local outs = 0 local inning = 1 +---@return boolean playerIsOnSide, boolean playerIsOnOtherSide function playerIsOn(side) + local ret if PlayerTeam == battingTeam then - return side == Sides.offense + ret = side == Sides.offense else - return side == Sides.defense + ret = side == Sides.defense end + return ret, not ret end -- TODO? Replace this AND ballSizeAnimator with a ballHeightAnimator @@ -292,6 +296,9 @@ end ---@type Runner | nil local batter = newRunner() +local throwMeter = 0 +local PitchMeterLimit = 15 + --- "Throws" the ball from its current position to the given destination. ---@param destX number ---@param destY number @@ -315,6 +322,7 @@ function throwBall(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler if floaty then ballFloatAnimator:reset(flyTimeMs) end + throwMeter = 0 end local PitchAfterSeconds = 7 @@ -403,9 +411,10 @@ local secondsSinceLastRunnerMove = 0 ---@param runnerIndex integer function outRunner(runnerIndex) - outs = outs + 1 outRunners[#outRunners + 1] = runners[runnerIndex] table.remove(runners, runnerIndex) + + outs = outs + 1 updateForcedRunners() announcer:say("YOU'RE OUT!") @@ -504,9 +513,6 @@ function tryToMakeAnOut(fielder) end end -local throwMeter = 0 -local PitchMeterLimit = 15 - function readThrow() if throwMeter > PitchMeterLimit then return (PitchFlyMs / (throwMeter / PitchMeterLimit)) @@ -539,35 +545,41 @@ function buttonControlledThrow(thrower, throwFlyMs, forbidThrowHome) closestFielder.target = targetBase secondsSinceLastRunnerMove = 0 offenseMode = Offense.running - throwMeter = 0 return true end -function updateNpcFielder(fielder) +function outEligibleRunners(fielder) + local touchedBase = isTouchingBase(fielder.x, fielder.y) + local didOutRunner = false + for i, runner in pairs(runners) do + local runnerOnBase = isTouchingBase(runner.x, runner.y) + if -- Force out + touchedBase + and runner.prevBase -- Make sure the runner is not standing at home + and runner.forcedTo == touchedBase + and touchedBase ~= runnerOnBase + -- Tag out + or not runnerOnBase and utils.distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance + then + outRunner(i) + didOutRunner = true + end + end + + return didOutRunner +end + +function updateNpcFielder(fielder, outedSomeRunner) if offenseMode ~= Offense.running then return end - local touchedBase = isTouchingBase(fielder.x, fielder.y) - for i, runner in pairs(runners) do - local runnerOnBase = isTouchingBase(runner.x, runner.y) - if - ( -- Force out - touchedBase - -- and runner.prevBase -- Make sure the runner is not standing at home - and runner.forcedTo == touchedBase - and touchedBase ~= runnerOnBase - ) - -- Tag out - or (not runnerOnBase and utils.distanceBetween(runner.x, runner.y, fielder.x, fielder.y) < TagDistance) - then - outRunner(i) - playdate.timer.new(750, function() - tryToMakeAnOut(fielder) - end) - else + if outedSomeRunner then + playdate.timer.new(750, function() tryToMakeAnOut(fielder) - end + end) + else + tryToMakeAnOut(fielder) end end @@ -583,30 +595,18 @@ function updateFielder(fielder) return end + local outedSomeRunner = outEligibleRunners(fielder) + if playerIsOn(Sides.defense) then local throwFly = readThrow() if throwFly then buttonControlledThrow(fielders.pitcher, throwFly) end else - updateNpcFielder(fielder) + updateNpcFielder(fielder, outedSomeRunner) end end -function updateFielders() - for _, fielder in pairs(fielders) do - updateFielder(fielder) - end - - -- if offenseMode == Offense.batting then - -- utils.moveAtSpeed( - -- fielders.catcher, - -- fielders.catcher.speed * 2 * deltaSeconds, - -- { x = math.min(Center.x + 15, ball.x), y = fielders.catcher.y } - -- ) - -- end -end - --- Returns true only if the given runner moved during this update. ---@param runner Runner | nil ---@param runnerIndex integer | nil May only be nil if runner == batter @@ -665,6 +665,53 @@ end ---@type number local batAngleDeg +-- Used for tracking whether or not a pitch was a strike +local recordedPitchX = nil +local balls = 0 +local strikes = 0 + +local StrikeZoneStartX = Center.x - 16 +local StrikeZoneEndX = StrikeZoneStartX + 24 +local StrikeZoneStartY = Screen.H - 35 + +function recordStrikePosition() + if not recordedPitchX and ball.y > StrikeZoneStartY then + recordedPitchX = ball.x + end +end + +function nextBatter() + playdate.timer.new(2000, function() + balls = 0 + strikes = 0 + batter = newRunner() + end) +end + +function walk() + -- TODO + nextBatter() +end + +function strikeOut() + -- TODO + nextBatter() +end + +function recordPitch() + if recordedPitchX > StrikeZoneStartX and recordedPitchX < StrikeZoneEndX then + strikes = strikes + 1 + if strikes >= 3 then + strikeOut() + end + else + balls = balls + 1 + if balls >= 4 then + walk() + end + end +end + ---@param batDeg number function updateBatting(batDeg, batSpeed) if ball.y < BallOffscreen then @@ -764,19 +811,21 @@ function playerPitch(throwFly) end local npcBatDeg = 0 -local NpcBatSpeed = 1500 +local BaseNpcBatSpeed = 1500 +local npcBatSpeed = 1500 function npcBatAngle() if not catcherThrownBall and ball.y > 200 and ball.y < 230 and (ball.x < Center.x + 15) then - npcBatDeg = npcBatDeg + (deltaSeconds * NpcBatSpeed) + npcBatDeg = npcBatDeg + (deltaSeconds * npcBatSpeed) else + npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed npcBatDeg = 200 end return npcBatDeg end function npcBatChange() - return deltaSeconds * NpcBatSpeed + return deltaSeconds * npcBatSpeed end function npcRunningSpeed() @@ -785,7 +834,7 @@ function npcRunningSpeed() end local touchedBase = isTouchingBase(runners[1].x, runners[1].y) if not touchedBase or touchedBase == Bases[Home] then - return 25 + return 10 end return 0 end @@ -803,23 +852,27 @@ function updateGameState() ball.y = ballAnimatorY:currentValue() + ballFloatAnimator:currentValue() end - local playerOnOffense = playerIsOn(Sides.offense) + local playerOnOffense, playerOnDefense = playerIsOn(Sides.offense) + + if playerOnDefense then + throwMeter = math.max(0, throwMeter - (deltaSeconds * 150)) + throwMeter = throwMeter + math.abs(crankChange) + end + if offenseMode == Offense.batting then + recordStrikePosition() secondsSincePitchAllowed = secondsSincePitchAllowed + deltaSeconds if secondsSincePitchAllowed > 3.5 and not catcherThrownBall then + recordPitch() throwBall(PitchStartX, PitchStartY, playdate.easingFunctions.linear, nil, true) catcherThrownBall = true - else - throwMeter = 0 end local batSpeed if playerOnOffense then batAngleDeg, batSpeed = (playdate.getCrankPosition() + CrankOffsetDeg) % 360, crankChange else - throwMeter = math.max(0, throwMeter - (deltaSeconds * 150)) - throwMeter = throwMeter + crankChange batAngleDeg, batSpeed = npcBatAngle(), npcBatChange() end @@ -829,21 +882,17 @@ function updateGameState() updateRunner(batter, nil, crankChange) if secondsSincePitchAllowed > PitchAfterSeconds then - if playerOnOffense then - pitch(PitchFlyMs, math.random(#Pitches)) - else + if playerOnDefense then local throwFly = readThrow() if throwFly and not buttonControlledThrow(fielders.pitcher, throwFly, true) then playerPitch(throwFly) end + else + pitch(PitchFlyMs, math.random(#Pitches)) end end elseif offenseMode == Offense.running then - if not playerOnOffense then - throwMeter = math.max(0, throwMeter - (deltaSeconds * 150)) - throwMeter = throwMeter + crankChange - end - local appliedSpeed = playerIsOn(Sides.offense) and crankChange or npcRunningSpeed() + local appliedSpeed = playerOnOffense and crankChange or npcRunningSpeed() if updateRunning(appliedSpeed) then secondsSinceLastRunnerMove = 0 else @@ -859,7 +908,9 @@ function updateGameState() end end - updateFielders() + for _, fielder in pairs(fielders) do + updateFielder(fielder) + end walkAwayOutRunners() end @@ -901,7 +952,6 @@ end function playdate.update() playdate.timer.updateTimers() gfx.animation.blinker.updateAll() - updateGameState() gfx.clear() @@ -964,6 +1014,7 @@ function playdate.update() drawMinimap() end drawScoreboard(0, Screen.H * 0.77, teams, outs, battingTeam, inning) + drawBallsAndStrikes(300, Screen.H * 0.77, balls, strikes) announcer:draw(Center.x, 10) end diff --git a/src/scoreboard.lua b/src/scoreboard.lua index 2887855..177df3d 100644 --- a/src/scoreboard.lua +++ b/src/scoreboard.lua @@ -16,9 +16,15 @@ function getIndicators(teams, battingTeam) return "", IndicatorWidth, Indicator, 0 end +function drawBallsAndStrikes(x, y, balls, strikes) + local gfx = playdate.graphics + print("" .. balls .. ":" .. strikes) + gfx.setColor(gfx.kColorBlack) + gfx.fillRect(x, y, 20, ScoreboardHeight) +end + function drawScoreboard(x, y, teams, outs, battingTeam, inning) local gfx = playdate.graphics - local homeScore = teams.home.score local awayScore = teams.away.score diff --git a/src/utils.lua b/src/utils.lua index b401a7d..81cd5c1 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -14,18 +14,6 @@ function utils.easingHill(t, b, c, d) return (c * t) + b end --- Useful for quick print-the-value-in-place debugging. --- selene: allow(unused_variable) -function utils.label(value, name) - if type(value) == "table" then - print(name .. ":") - printTable(value) - else - print(name .. ": " .. value) - end - return value -end - ---@param x number ---@param y number ---@return XYPair