Compare commits

..

No commits in common. "4c9fbcdee7360d76581e60b58d9aa01a5ddfcfe0" and "6007ac971fefaa824fb7b30ac19f4484168e4275" have entirely different histories.

18 changed files with 64 additions and 819 deletions

View File

@ -44,7 +44,6 @@ function Announcer:popIn()
end)
end
---@param text string
function Announcer:say(text)
self.textQueue[#self.textQueue + 1] = text
if #self.textQueue == 1 then

View File

@ -8,8 +8,6 @@ Glove = playdate.graphics.image.new("images/game/Glove.png")
-- luacheck: ignore
PlayerFrown = playdate.graphics.image.new("images/game/PlayerFrown.png")
-- luacheck: ignore
BigBat = playdate.graphics.image.new("images/game/BigBat.png")
-- luacheck: ignore
GloveHoldingBall = playdate.graphics.image.new("images/game/GloveHoldingBall.png")
-- luacheck: ignore
GameLogo = playdate.graphics.image.new("images/game/GameLogo.png")
@ -26,8 +24,6 @@ Minimap = playdate.graphics.image.new("images/game/Minimap.png")
-- luacheck: ignore
GrassBackground = playdate.graphics.image.new("images/game/GrassBackground.png")
-- luacheck: ignore
GrassBackgroundSmall = playdate.graphics.image.new("images/game/GrassBackgroundSmall.png")
-- luacheck: ignore
LightPlayerBase = playdate.graphics.image.new("images/game/LightPlayerBase.png")
-- luacheck: ignore
LightPlayerBack = playdate.graphics.image.new("images/game/LightPlayerBack.png")

View File

@ -91,6 +91,8 @@ C.SmallestBallRadius = 6
C.BatLength = 35
-- TODO: enums implemented this way are probably going to be difficult to serialize!
---@alias OffenseState "batting" | "running" | "walking"
--- An enum for what state the offense is in
---@type table<string, OffenseState>
@ -123,18 +125,6 @@ C.WalkedRunnerSpeed = 10
C.ResetFieldersAfterSeconds = 2.5
C.OutfieldWall = {
{ x = 0, y = 137 },
{ x = 233, y = 32 },
{ x = 450, y = 29 },
{ x = 550, y = 59 },
{ x = 739, y = 64 },
{ x = 850, y = 19 },
{ x = 1100, y = 31 },
{ x = 1185, y = 181 },
{ x = 1201, y = 224 },
}
if not playdate then
return C
end

View File

@ -35,67 +35,6 @@ function dbg.loadTheBases(br)
br.runners[4].nextBase = C.Bases[C.Home]
end
local hitSamples = {
away = {
{
utils.xy(7.88733, -16.3434),
utils.xy(378.3376, 30.49521),
utils.xy(367.1036, 21.55336),
},
{
utils.xy(379.8051, -40.82794),
utils.xy(-444.5791, -30.30901),
utils.xy(-30.43079, -30.50307),
},
{
utils.xy(227.8881, -14.56854),
utils.xy(293.5208, 39.38919),
utils.xy(154.4738, -26.55899),
},
},
home = {
{
utils.xy(146.2505, -89.12155),
utils.xy(429.5428, 59.62944),
utils.xy(272.4666, -78.578),
},
{
utils.xy(485.0516, 112.8341),
utils.xy(290.9232, -4.946442),
utils.xy(263.4262, -6.482407),
},
{
utils.xy(260.6927, -63.63049),
utils.xy(392.1548, -44.22421),
utils.xy(482.5545, 105.3476),
utils.xy(125.5928, 18.53091),
},
},
}
---@return Statistics
function dbg.mockStatistics(inningCount)
inningCount = inningCount or 9
local stats = Statistics.new()
for i = 1, inningCount - 1 do
stats.innings[i].home.score = math.floor(math.random() * 5)
stats.innings[i].away.score = math.floor(math.random() * 5)
if hitSamples.home[i] ~= nil then
stats.innings[i].home.hits = hitSamples.home[i]
end
if hitSamples.away[i] ~= nil then
stats.innings[i].away.hits = hitSamples.away[i]
end
stats:pushInning()
end
local homeScore, awayScore = utils.totalScores(stats)
if homeScore == awayScore then
stats.innings[#stats.innings].home.score = 1 + stats.innings[#stats.innings].home.score
end
return stats
end
---@param points XyPair[]
function dbg.drawLine(points)
for i = 2, #points do

View File

@ -1,248 +0,0 @@
---@alias TeamInningData { score: number, pitching: { balls: number, strikes: number }, hits: XyPair[] }
--- E.g. statistics[1].home.pitching.balls
---@class Statistics
---@field innings: (table<TeamId, TeamInningData>)[]
Statistics = {}
local function newTeamInning()
return {
score = 0,
pitching = {
balls = 0,
strikes = 0,
},
hits = {},
}
end
---@return table<TeamId, TeamInningData>
local function newInning()
return {
home = newTeamInning(),
away = newTeamInning(),
}
end
---@return Statistics
function Statistics.new()
return setmetatable({
innings = { newInning() },
}, { __index = Statistics })
end
function Statistics:pushInning()
self.innings[#self.innings + 1] = newInning()
end
---@class BoxScore
---@field stats Statistics
---@field private targetY number
BoxScore = {}
---@param stats Statistics
function BoxScore.new(stats)
return setmetatable({
stats = stats,
targetY = 0,
}, { __index = BoxScore })
end
-- TODO: Convert the box-score into a whole "scene"
-- * Scroll left and right through games that go into extra innings
-- * Scroll up and down through other stats.
-- + Balls and strikes
-- + Batting average
-- + Graph of team scores over time
-- + Farthest hit ball
local MarginY <const> = 70
local SmallFont <const> = playdate.graphics.font.new("fonts/font-full-circle.pft")
local ScoreFont <const> = playdate.graphics.font.new("fonts/Asheville-Sans-14-Bold.pft")
local NumWidth <const> = ScoreFont:getTextWidth("0")
local NumHeight <const> = ScoreFont:getHeight()
local AwayWidth <const> = ScoreFont:getTextWidth("AWAY")
local InningMargin = 4
local InningDrawWidth <const> = (InningMargin * 2) + (NumWidth * 2)
local ScoreDrawHeight = NumHeight * 2
-- luacheck: ignore 143
---@type pd_graphics_lib
local gfx = playdate.graphics
local function formatScore(n)
if n <= 9 then
return " " .. n
elseif n <= 19 then
return " " .. n
else
return tostring(n)
end
end
local HomeY <const> = -4 + (NumHeight * 2) + MarginY
local AwayY <const> = -4 + (NumHeight * 3) + MarginY
local function drawInning(x, inningNumber, homeScore, awayScore)
gfx.setColor(gfx.kColorBlack)
gfx.setColor(gfx.kColorWhite)
gfx.setLineWidth(1)
gfx.drawRect(x, 34 + MarginY, InningDrawWidth, ScoreDrawHeight)
inningNumber = " " .. inningNumber
homeScore = formatScore(homeScore)
awayScore = formatScore(awayScore)
x = x - 8 + (InningDrawWidth / 2)
ScoreFont:drawTextAligned(inningNumber, x, -4 + NumHeight + MarginY, gfx.kAlignRight)
ScoreFont:drawTextAligned(awayScore, x, HomeY, gfx.kAlignRight)
ScoreFont:drawTextAligned(homeScore, x, AwayY, gfx.kAlignRight)
end
function BoxScore:drawBoxScore()
local inningStart = 4 + (AwayWidth * 1.5)
local widthAndMarg = InningDrawWidth + 4
ScoreFont:drawTextAligned(" HOME", 10, HomeY, gfx.kAlignRight)
ScoreFont:drawTextAligned("AWAY", 10, AwayY, gfx.kAlignRight)
for i = 1, #self.stats.innings do
local inningStats = self.stats.innings[i]
drawInning(inningStart + ((i - 1) * widthAndMarg), i, inningStats.home.score, inningStats.away.score)
end
local homeScore, awayScore = utils.totalScores(self.stats)
drawInning(4 + inningStart + (widthAndMarg * #self.stats.innings), "F", homeScore, awayScore)
ScoreFont:drawTextAligned("v", C.Center.x, C.Screen.H - 40, gfx.kAlignCenter)
end
local GraphM = 10
local GraphW = C.Screen.W - (GraphM * 2)
local GraphH = C.Screen.H - (GraphM * 2)
function BoxScore:drawScoreGraph(y)
-- TODO: Actually draw score legend
-- Offset by 2 to support a) the zero-index b) the score legend
local segmentWidth = GraphW / (#self.stats.innings + 2)
local legendX = segmentWidth * (#self.stats.innings + 2) - GraphM
gfx.drawLine(GraphM / 2, y + GraphM + GraphH, legendX, y + GraphM + GraphH)
gfx.drawLine(legendX, y + GraphM, legendX, y + GraphH + GraphM)
gfx.setLineWidth(3)
local homeScore, awayScore = utils.totalScores(self.stats)
local highestScore = math.max(homeScore, awayScore)
local heightPerPoint = (GraphH - 6) / highestScore
function point(inning, score)
return utils.xy(GraphM + (inning * segmentWidth), y + GraphM + GraphH + (score * -heightPerPoint))
end
function drawLine(teamId)
local linePoints = { point(0, 0) }
local scoreTotal = 0
for i, inning in ipairs(self.stats.innings) do
scoreTotal = scoreTotal + inning[teamId].score
linePoints[#linePoints + 1] = point(i, scoreTotal)
end
dbg.drawLine(linePoints)
local finalPoint = linePoints[#linePoints]
SmallFont:drawTextAligned(string.upper(teamId), finalPoint.x + 3, finalPoint.y - 7, gfx.kAlignRight)
end
drawLine("home")
gfx.setDitherPattern(0.5)
drawLine("away")
gfx.setDitherPattern(0)
end
---@param realHit XyPair
---@return XyPair
function convertHitToMini(realHit)
-- Convert to all-positive y
local y = realHit.y + C.Screen.H
y = y / 2
local x = realHit.x + C.Screen.W
x = x / 3
return utils.xy(x, y)
end
function BoxScore:drawHitChart(y)
local leftMargin = 8
GrassBackgroundSmall:drawCentered(C.Center.x, y + C.Center.y + 54)
gfx.setLineWidth(1)
ScoreFont:drawTextAligned("AWAY", leftMargin, y + C.Screen.H - NumHeight, gfx.kAlignRight)
gfx.setColor(gfx.kColorBlack)
gfx.setDitherPattern(0.5, gfx.image.kDitherTypeBayer2x2)
gfx.fillRect(leftMargin, y + C.Screen.H - NumHeight, ScoreFont:getTextWidth("AWAY"), NumHeight)
gfx.setColor(gfx.kColorWhite)
gfx.setDitherPattern(0.5)
for _, inning in ipairs(self.stats.innings) do
for _, hit in ipairs(inning.away.hits) do
local miniHitPos = convertHitToMini(hit)
gfx.fillCircleAtPoint(miniHitPos.x + 10, miniHitPos.y + y, 4)
end
end
gfx.setColor(gfx.kColorWhite)
gfx.setDitherPattern(0)
ScoreFont:drawTextAligned(" HOME", leftMargin, y + C.Screen.H - (NumHeight * 2), gfx.kAlignRight)
for _, inning in ipairs(self.stats.innings) do
for _, hit in ipairs(inning.home.hits) do
local miniHitPos = convertHitToMini(hit)
gfx.fillCircleAtPoint(miniHitPos.x + 10, miniHitPos.y + y, 4)
end
end
end
local screens = {
BoxScore.drawBoxScore,
BoxScore.drawScoreGraph,
BoxScore.drawHitChart,
}
function BoxScore:render()
local originalDrawMode = gfx.getImageDrawMode()
gfx.clear(gfx.kColorBlack)
gfx.setImageDrawMode(gfx.kDrawModeInverted)
gfx.setColor(gfx.kColorBlack)
for i, screen in ipairs(screens) do
screen(self, (i - 1) * C.Screen.H)
end
gfx.setImageDrawMode(originalDrawMode)
end
local renderedImage
function BoxScore:update()
if not renderedImage then
renderedImage = gfx.image.new(C.Screen.W, C.Screen.H * #screens)
gfx.pushContext(renderedImage)
self:render()
gfx.popContext()
end
local deltaSeconds = playdate.getElapsedTime()
playdate.resetElapsedTime()
gfx.setDrawOffset(0, self.targetY)
renderedImage:draw(0, 0)
local crankChange = playdate.getCrankChange()
if crankChange ~= 0 then
self.targetY = self.targetY - (crankChange * 0.8)
else
local closestScreen = math.floor(0.5 + (self.targetY / C.Screen.H)) * C.Screen.H
if math.abs(self.targetY - closestScreen) > 3 then
local needsIncrease = self.targetY < closestScreen
local change = needsIncrease and 200 * deltaSeconds or -200 * deltaSeconds
self.targetY = self.targetY + change
end
end
self.targetY = math.max(math.min(self.targetY, 0), -C.Screen.H * (#screens - 1))
end

View File

@ -83,10 +83,11 @@ local ScoreboardHeight <const> = 55
local Indicator = "> "
local IndicatorWidth <const> = ScoreFont:getTextWidth(Indicator)
---@param teams any
---@param battingTeam any
---@return string, number, string, number
function getIndicators(battingTeam)
if battingTeam == "home" then
function getIndicators(teams, battingTeam)
if teams.home == battingTeam then
return Indicator, 0, "", IndicatorWidth
end
return "", IndicatorWidth, Indicator, 0
@ -100,11 +101,11 @@ local stats = {
battingTeam = nil,
}
function drawScoreboardImpl(x, y)
function drawScoreboardImpl(x, y, teams)
local homeScore = stats.homeScore
local awayScore = stats.awayScore
local homeIndicator, homeOffset, awayIndicator, awayOffset = getIndicators(stats.battingTeam)
local homeIndicator, homeOffset, awayIndicator, awayOffset = getIndicators(teams, stats.battingTeam)
local homeScoreText = homeIndicator .. "HOME " .. (homeScore > 9 and homeScore or " " .. homeScore)
local awayScoreText = awayIndicator .. "AWAY " .. (awayScore > 9 and awayScore or " " .. awayScore)
@ -145,17 +146,17 @@ end
local newStats = stats
function drawScoreboard(x, y, homeScore, awayScore, outs, battingTeam, inning)
function drawScoreboard(x, y, teams, outs, battingTeam, inning)
if
newStats.homeScore ~= homeScore
or newStats.awayScore ~= awayScore
newStats.homeScore ~= teams.home.score
or newStats.awayScore ~= teams.away.score
or newStats.outs ~= outs
or newStats.inning ~= inning
or newStats.battingTeam ~= battingTeam
then
newStats = {
homeScore = homeScore,
awayScore = awayScore,
homeScore = teams.home.score,
awayScore = teams.away.score,
outs = outs,
inning = inning,
battingTeam = battingTeam,
@ -164,5 +165,5 @@ function drawScoreboard(x, y, homeScore, awayScore, outs, battingTeam, inning)
stats = newStats
end)
end
drawScoreboardImpl(x, y)
drawScoreboardImpl(x, y, teams)
end

View File

@ -1,92 +0,0 @@
Transitions = {
---@type Scene | nil
nextScene = nil,
---@type Scene | nil
previousScene = nil,
}
local gfx = playdate.graphics
local previousSceneImage
local previousSceneMask
local nextSceneImage
local batImageTable = {}
local batOffset = 80
local degStep = 3
function loadBatImageTable()
for deg = 90 - (degStep * 3), 270 + (degStep * 3), degStep do
local img = gfx.image.new(C.Screen.W, C.Screen.H)
gfx.pushContext(img)
BigBat:drawRotated(C.Center.x, C.Screen.H + batOffset, 90 + deg)
gfx.popContext()
batImageTable[deg] = img
end
end
loadBatImageTable()
local function update()
local lastAngle
local seamAngle = math.rad(270)
while seamAngle > math.rad(90) do
local deltaSeconds = playdate.getElapsedTime()
playdate.resetElapsedTime()
seamAngle = seamAngle - (deltaSeconds * 3)
local seamAngleDeg = math.floor(math.deg(seamAngle))
seamAngleDeg = seamAngleDeg - (seamAngleDeg % degStep)
-- Skip re-drawing if no change
if lastAngle ~= seamAngleDeg then
lastAngle = seamAngleDeg
nextSceneImage:draw(0, 0)
gfx.pushContext(previousSceneMask)
gfx.setImageDrawMode(gfx.kDrawModeFillBlack)
batImageTable[seamAngleDeg]:draw(0, 0)
gfx.popContext()
previousSceneImage:setMaskImage(previousSceneMask)
previousSceneImage:draw(0, 0)
batImageTable[seamAngleDeg]:draw(0, 0)
end
coroutine.yield()
end
playdate.update = function()
Transitions.nextScene:update()
end
end
---@param nextScene fun() The next playdate.update function
function transitionTo(nextScene)
if not Transitions.nextScene then
error("Expected Transitions to already have nextScene defined! E.g. by calling transitionBetween")
end
local previousScene = Transitions.nextScene
transitionBetween(previousScene, nextScene)
end
---@param previousScene Scene Has the current playdate.update function
---@param nextScene Scene Has the next playdate.update function
function transitionBetween(previousScene, nextScene)
playdate.wait(2) -- TODO: There's some sort of timing wack here.
playdate.update = update
previousSceneImage = gfx.image.new(C.Screen.W, C.Screen.H)
gfx.pushContext(previousSceneImage)
previousScene:update()
gfx.popContext()
nextSceneImage = gfx.image.new(C.Screen.W, C.Screen.H)
gfx.pushContext(nextSceneImage)
nextScene:update()
gfx.popContext()
previousSceneMask = gfx.image.new(C.Screen.W, C.Screen.H, gfx.kColorWhite)
previousSceneImage:setMaskImage(previousSceneMask)
Transitions.nextScene = nextScene
Transitions.previousScene = previousScene
end

View File

@ -5,6 +5,8 @@
--- @field target XyPair | nil
--- @field speed number
-- TODO: Run down baserunners in a pickle.
---@class Fielding
---@field fielders table<string, Fielder>
---@field fielderHoldingBall Fielder | nil
@ -180,7 +182,7 @@ function Fielding:userThrowTo(targetBase, ball, throwFlyMs)
end)
end
function Fielding.celebrate()
function Fielding:celebrate()
FielderDanceAnimator:reset(C.DanceBounceMs)
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,295 +0,0 @@
tracking=1
space 3
! 2
" 5
# 9
$ 8
% 12
& 11
' 3
( 5
) 5
* 8
+ 8
, 3
- 6
. 2
/ 6
0 9
1 4
2 9
3 9
4 9
5 9
6 9
7 9
8 10
9 9
: 2
; 2
< 7
= 7
> 7
? 9
@ 11
A 10
B 9
C 9
D 9
E 8
F 8
G 9
H 9
I 2
J 8
K 10
L 9
M 12
N 9
O 9
P 9
Q 9
R 9
S 9
T 10
U 9
V 10
W 14
X 8
Y 8
Z 8
[ 3
\ 6
] 3
^ 6
_ 8
` 3
a 8
b 8
c 8
d 8
e 8
f 6
g 8
h 8
i 2
j 4
k 8
l 2
m 12
n 8
o 8
p 8
q 8
r 6
s 8
t 6
u 8
v 8
w 12
x 9
y 8
z 8
{ 6
| 2
} 6
~ 10
… 8
¥ 8
‼ 5
™ 8
© 11
® 11
。 16
、 16
ぁ 16
あ 16
ぃ 16
い 16
ぅ 16
う 16
ぇ 16
え 16
ぉ 16
お 16
か 16
が 16
き 16
ぎ 16
く 16
ぐ 16
け 16
げ 16
こ 16
ご 16
さ 16
ざ 16
し 16
じ 16
す 16
ず 16
せ 16
ぜ 16
そ 16
ぞ 16
た 16
だ 16
ち 16
ぢ 16
っ 16
つ 16
づ 16
て 16
で 16
と 16
ど 16
な 16
に 16
ぬ 16
ね 16
の 16
は 16
ば 16
ぱ 16
ひ 16
び 16
ぴ 16
ふ 16
ぶ 16
ぷ 16
へ 16
べ 16
ぺ 16
ほ 16
ぼ 16
ぽ 16
ま 16
み 16
む 16
め 16
も 16
ゃ 16
や 16
ゅ 16
ゆ 16
ょ 16
よ 16
ら 16
り 16
る 16
れ 16
ろ 16
ゎ 16
わ 16
ゐ 16
ゑ 16
を 16
ん 16
ゔ 16
ゕ 16
ゖ 16
゛ 1
゜ 0
ゝ 16
ゞ 16
ゟ 16
16
ァ 16
ア 16
ィ 16
イ 16
ゥ 16
ウ 16
ェ 16
エ 16
ォ 16
オ 16
カ 16
ガ 16
キ 16
ギ 16
ク 16
グ 16
ケ 16
ゲ 16
コ 16
ゴ 16
サ 16
ザ 16
シ 16
ジ 16
ス 16
ズ 16
セ 16
ゼ 16
ソ 16
ゾ 16
タ 16
ダ 16
チ 16
ヂ 16
ッ 16
ツ 16
ヅ 16
テ 16
デ 16
ト 16
ド 16
ナ 16
ニ 16
ヌ 16
ネ 16
16
ハ 16
バ 16
パ 16
ヒ 16
ビ 16
ピ 16
フ 16
ブ 16
プ 16
ヘ 16
ベ 16
ペ 16
ホ 16
ボ 16
ポ 16
マ 16
ミ 16
ム 16
メ 16
モ 16
ャ 16
ヤ 16
ュ 16
ユ 16
ョ 16
ヨ 16
ラ 16
リ 16
ル 16
レ 16
ロ 16
ヮ 16
ワ 16
ヰ 16
ヱ 16
ヲ 16
ン 16
ヴ 16
ヵ 16
ヶ 16
ヷ 16
ヸ 16
ヹ 16
ヺ 16
・ 16
ー 16
ヽ 16
ヾ 16
ヿ 16
「 16
」 16
円 16
<EFBFBD> 13

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -24,7 +24,9 @@ local function startGame()
awayTeamSprites = AwayTeamSprites,
})
playdate.resetElapsedTime()
transitionBetween(MainMenu, next)
playdate.update = function()
next:update()
end
end
local function pausingEaser(baseEaser)
@ -62,10 +64,9 @@ local function arrayElementFromCrank(array, crankPosition)
return array[i]
end
local currentLogo
local currentLogo = nil
--luacheck: ignore
function MainMenu:update()
function MainMenu.update()
playdate.timer.updateTimers()
crankStartPos = crankStartPos or playdate.getCrankPosition()

View File

@ -8,8 +8,6 @@ import 'CoreLibs/timer.lua'
import 'CoreLibs/ui.lua'
-- stylua: ignore end
--- @alias Scene { update: fun(self: self) }
--- @alias EasingFunc fun(number, number, number, number): number
--- @alias LaunchBall fun(
@ -26,11 +24,9 @@ import 'CoreLibs/ui.lua'
import 'utils.lua'
import 'constants.lua'
import 'assets.lua'
import 'draw/box-score.lua'
import 'draw/fielder.lua'
import 'draw/overlay.lua'
import 'draw/player.lua'
import 'draw/transitions.lua'
import 'draw/overlay.lua'
import 'draw/fielder.lua'
import 'main-menu.lua'
@ -48,13 +44,15 @@ import 'npc.lua'
local gfx <const>, C <const> = playdate.graphics, C
---@alias Team { benchPosition: XyPair }
---@alias Team { score: number, benchPosition: XyPair }
---@type table<TeamId, Team>
local teams <const> = {
home = {
score = 0, -- TODO: Extract this last bit of global mutable state.
benchPosition = utils.xy(C.Screen.W + 10, C.Center.y),
},
away = {
score = 0,
benchPosition = utils.xy(-10, C.Center.y),
},
}
@ -75,7 +73,6 @@ local teams <const> = {
---@field catcherThrownBall boolean
---@field offenseState OffenseState
---@field inning number
---@field stats Statistics
---@field batBase XyPair
---@field batTip XyPair
---@field batAngleDeg number
@ -107,6 +104,8 @@ Game = {}
---@param state MutableState | nil
---@return Game
function Game.new(settings, announcer, fielding, baserunning, npc, state)
teams.away.score = 0
teams.home.score = 0
announcer = announcer or Announcer.new()
fielding = fielding or Fielding.new()
settings.userTeam = nil -- "away"
@ -138,7 +137,6 @@ function Game.new(settings, announcer, fielding, baserunning, npc, state)
battingTeamSprites = settings.awayTeamSprites,
fieldingTeamSprites = settings.homeTeamSprites,
runnerBlipper = runnerBlipper,
stats = Statistics.new(),
},
}, { __index = Game })
@ -212,6 +210,10 @@ local function getOppositeTeamId(teamId)
end
end
function Game:getBattingTeam()
return teams[self.state.battingTeam] or error("Unknown battingTeam: " .. (self.state.battingTeam or "nil"))
end
function Game:getFieldingTeam()
return teams[getOppositeTeamId(self.state.battingTeam)]
end
@ -263,58 +265,40 @@ end
function Game:nextHalfInning()
pitchTracker:reset()
local homeScore, awayScore = utils.totalScores(self.state.stats)
-- TODO end the game if away team just batted and home team is winning
local gameOver = self.state.battingTeam == "home"
and self.state.inning == self.settings.finalInning
and awayScore ~= homeScore
local gameOver = self.state.inning == self.settings.finalInning and teams.away.score ~= teams.home.score
if not gameOver then
self.fielding:celebrate()
self.state.secondsSinceLastRunnerMove = -7
self.fielding:benchTo(self:getFieldingTeam().benchPosition)
self.announcer:say("SWITCHING SIDES...")
end
if gameOver then
self.announcer:say("AND THAT'S THE BALL GAME!")
playdate.timer.new(3000, function()
transitionTo(BoxScore.new(self.state.stats))
end)
return
end
Fielding.celebrate()
self.state.secondsSinceLastRunnerMove = -7
self.fielding:benchTo(self:getFieldingTeam().benchPosition)
self.announcer:say("SWITCHING SIDES...")
self.fielding:resetFielderPositions()
if self.state.battingTeam == "home" then
self.state.inning = self.state.inning + 1
self.state.stats:pushInning()
end
self.state.battingTeam = getOppositeTeamId(self.state.battingTeam)
playdate.timer.new(2000, function()
if self.state.battingTeam == "home" then
self.state.battingTeamSprites = self.settings.homeTeamSprites
self.state.runnerBlipper = self.homeTeamBlipper
self.state.fieldingTeamSprites = self.settings.awayTeamSprites
else
self.state.battingTeamSprites = self.settings.awayTeamSprites
self.state.fieldingTeamSprites = self.settings.homeTeamSprites
self.state.runnerBlipper = self.awayTeamBlipper
else
self.fielding:resetFielderPositions()
if self.state.battingTeam == teams.home then
self.state.inning = self.state.inning + 1
end
end)
end
---@return TeamInningData
function Game:battingTeamCurrentInning()
return self.state.stats.innings[self.state.inning][self.state.battingTeam]
end
---@return TeamInningData
function Game:fieldingTeamCurrentInning()
return self.state.stats.innings[self.state.inning][getOppositeTeamId(self.state.battingTeam)]
self.state.battingTeam = getOppositeTeamId(self.state.battingTeam)
playdate.timer.new(2000, function()
if self.state.battingTeam == teams.home then
self.state.battingTeamSprites = self.settings.homeTeamSprites
self.state.runnerBlipper = self.homeTeamBlipper
self.state.fieldingTeamSprites = self.settings.awayTeamSprites
else
self.state.battingTeamSprites = self.settings.awayTeamSprites
self.state.fieldingTeamSprites = self.settings.homeTeamSprites
self.state.runnerBlipper = self.awayTeamBlipper
end
end)
end
end
---@param scoredRunCount number
function Game:score(scoredRunCount)
self:battingTeamCurrentInning().score = self:battingTeamCurrentInning().score + scoredRunCount
local batting = self:getBattingTeam()
batting.score = batting.score + scoredRunCount
self.announcer:say("SCORE!")
end
@ -410,8 +394,6 @@ function Game:updateBatting(batDeg, batSpeed)
local hitBallScaler = gfx.animator.new(2000, 9 + (mult * mult * 0.5), C.SmallestBallRadius, utils.easingHill)
self.state.ball:launch(ballDestX, ballDestY, playdate.easingFunctions.outQuint, 2000, nil, hitBallScaler)
-- TODO? A dramatic eye-level view on a home-run could be sick.
local battingTeamStats = self:battingTeamCurrentInning()
battingTeamStats.hits[#battingTeamStats.hits + 1] = utils.xy(ballDestX, ballDestY)
if utils.isFoulBall(ballDestX, ballDestY) then
self.announcer:say("Foul ball!")
@ -419,6 +401,7 @@ function Game:updateBatting(batDeg, batSpeed)
-- TODO: Have a fielder chase for the fly-out
return
end
self.baserunning:convertBatterToRunner()
self.fielding:haveSomeoneChase(ballDestX, ballDestY)
@ -482,13 +465,6 @@ function Game:updateGameState()
if self.state.secondsSincePitchAllowed > C.ReturnToPitcherAfterSeconds and not self.state.catcherThrownBall then
local outcome = pitchTracker:updatePitchCounts()
local currentPitchingStats = self:fieldingTeamCurrentInning().pitching
if outcome == PitchOutcomes.Strike or outcome == PitchOutcomes.StrikeOut then
currentPitchingStats.strikes = currentPitchingStats.strikes + 1
end
if outcome == PitchOutcomes.Ball or outcome == PitchOutcomes.Walk then
currentPitchingStats.balls = currentPitchingStats.balls + 1
end
if outcome == PitchOutcomes.StrikeOut then
self:strikeOut()
elseif outcome == PitchOutcomes.Walk then
@ -512,6 +488,7 @@ function Game:updateGameState()
self:updateBatting(self.state.batAngleDeg, batSpeed)
-- Walk batter to the plate
-- TODO: Ensure batter can't be nil, here
self.baserunning:updateRunner(self.baserunning.batter, nil, crankLimited, self.state.deltaSeconds)
if self.state.secondsSincePitchAllowed > C.PitchAfterSeconds then
@ -536,8 +513,7 @@ function Game:updateGameState()
if self.state.secondsSinceLastRunnerMove > C.ResetFieldersAfterSeconds then
-- End of play. Throw the ball back to the pitcher
self.state.ball:launch(C.PitchStartX, C.PitchStartY, playdate.easingFunctions.linear, nil, true)
-- This is ugly, and ideally would not be necessary if Fielding handled the return throw directly.
self.fielding:markAllIneligible()
self.fielding:markAllIneligible() -- This is ugly, and ideally would not be necessary if Fielding handled the return throw directly.
self.fielding:resetFielderPositions()
self.state.offenseState = C.Offense.batting
-- TODO: Remove, or replace with nextBatter()
@ -571,6 +547,8 @@ function Game:updateGameState()
actionQueue:runWaiting(self.state.deltaSeconds)
end
-- TODO: Swappable update() for main menu, etc.
function Game:update()
playdate.timer.updateTimers()
gfx.animation.blinker.updateAll()
@ -651,21 +629,11 @@ function Game:update()
if math.abs(offsetX) > 10 or math.abs(offsetY) > 10 then
drawMinimap(self.baserunning.runners, self.fielding.fielders)
end
local homeScore, awayScore = utils.totalScores(self.state.stats)
drawScoreboard(
0,
C.Screen.H * 0.77,
homeScore,
awayScore,
self.baserunning.outs,
self.state.battingTeam,
self.state.inning
)
drawScoreboard(0, C.Screen.H * 0.77, teams, self.baserunning.outs, self:getBattingTeam(), self.state.inning)
drawBallsAndStrikes(290, C.Screen.H - 20, pitchTracker.balls, pitchTracker.strikes)
self.announcer:draw(C.Center.x, 10)
if playdate.isCrankDocked() then
-- luacheck: ignore
playdate.ui.crankIndicator:draw()
end
end

View File

@ -22,7 +22,6 @@ end
---@param catcherThrownBall boolean
---@param deltaSec number
---@return number
-- luacheck: no unused
function Npc:updateBatAngle(ball, catcherThrownBall, deltaSec)
if not catcherThrownBall and ball.y > 200 and ball.y < 230 and (ball.x < C.Center.x + 15) then
npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed)

View File

@ -232,23 +232,8 @@ function utils.getNearestOf(array, x, y, extraCondition)
return nearest, nearestDistance
end
---@param stats Statistics
---@return number homeScore, number awayScore
function utils.totalScores(stats)
local homeScore = 0
local awayScore = 0
for _, inning in pairs(stats.innings) do
homeScore = homeScore + inning.home.score
awayScore = awayScore + inning.away.score
end
return homeScore, awayScore
end
PitchOutcomes = {
Strike = {},
StrikeOut = {},
Ball = {},
Walk = {},
}