From 5fca82bb3a80bef710e481bc98c360576974e46b Mon Sep 17 00:00:00 2001
From: Sage Vaillancourt <sage@sagev.space>
Date: Sun, 2 Feb 2025 17:36:25 -0500
Subject: [PATCH] Queue up announcement messages.

Extract drawScoreboard() to new scoreboard.lua
Very, very rudimentary team-switching.
---
 Makefile           |  2 +-
 __stub.ext.lua     |  4 +++
 src/main.lua       | 81 ++++++++++++++++------------------------------
 src/scoreboard.lua | 41 +++++++++++++++++++++++
 src/utils.lua      | 17 ++++++----
 5 files changed, 85 insertions(+), 60 deletions(-)
 create mode 100644 src/scoreboard.lua

diff --git a/Makefile b/Makefile
index a8cba8b..09674bf 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-SOURCE_FILES := src/utils.lua src/graphics.lua src/main.lua
+SOURCE_FILES := src/utils.lua src/graphics.lua src/scoreboard.lua src/main.lua
 
 all:
 	pdc src BatterUp.pdx
diff --git a/__stub.ext.lua b/__stub.ext.lua
index da90630..1f41e90 100644
--- a/__stub.ext.lua
+++ b/__stub.ext.lua
@@ -13,3 +13,7 @@ json = json
 -- selene: allow(unused_variable)
 -- selene: allow(unscoped_variables)
 kTextAlignment = kTextAlignment
+
+-- selene: allow(unused_variable)
+-- selene: allow(unscoped_variables)
+printTable = printTable
\ No newline at end of file
diff --git a/src/main.lua b/src/main.lua
index 1040a06..dbdf366 100644
--- a/src/main.lua
+++ b/src/main.lua
@@ -7,18 +7,16 @@ import 'CoreLibs/object.lua'
 import 'CoreLibs/timer.lua'
 import 'CoreLibs/ui.lua'
 
+--- @alias XYPair { x: number, y: number }
+--- @alias Base { x: number, y: number }
+--- @alias Runner { x: number, y: number, nextBase: Base, prevBase: Base | nil, forcedTo: Base }
+--- @alias Fielder { onArrive: fun() | nil, x: number | nil, y: number | nil, target: XYPair | nil, speed: number }
+
 import 'graphics.lua'
+import 'scoreboard.lua'
 import 'utils.lua'
 -- stylua: ignore end
 
---- @alias XYPair { x: number, y: number }
-
---- @alias Base { x: number, y: number }
-
---- @alias Runner { x: number, y: number, nextBase: Base, prevBase: Base | nil, forcedTo: Base }
-
---- @alias Fielder { onArrive: fun() | nil, x: number | nil, y: number | nil, target: XYPair | nil, speed: number }
-
 local gfx <const> = playdate.graphics
 
 local Screen <const> = {
@@ -32,8 +30,6 @@ local BatCrackSound <const> = playdate.sound.sampleplayer.new("sounds/bat-crack-
 local GrassBackground <const> = gfx.image.new("images/game/grass.png") --[[@as pd_image]]
 local PlayerFrown <const> = gfx.image.new("images/game/player-frown.png") --[[@as pd_image]]
 
-local ScoreFont <const> = gfx.font.new("fonts/font-full-circle.pft")
-
 local PlayerImageBlipper <const> = blipper.new(100, "images/game/player.png", "images/game/player-lowhat.png")
 
 local DanceBounceMs <const> = 500
@@ -234,26 +230,38 @@ function isTouchingBall(x, y)
 	return ballDistance < BallCatchHitbox
 end
 
-local outs = 0
-local homeScore = 0
-local awayScore = 0
+local teams <const> = {
+	home = {
+		score = 0,
+	},
+	away = {
+		score = 0,
+	},
+}
 
+local battingTeam = teams.away
+local outs = 0
 function updateForcedRunners() end
 
 function outRunner(runnerIndex)
-	outs = math.min(3, outs + 1)
+	outs = outs + 1
 	outRunners[#outRunners + 1] = runners[runnerIndex]
 	table.remove(runners, runnerIndex)
 	FielderDanceAnimator:reset()
 	updateForcedRunners()
 	announcer:say("YOU'RE OUT!")
+	if outs == 3 then
+		outs = 0
+		announcer:say("SWITCHING SIDES...")
+		battingTeam = battingTeam == teams.home and teams.away or teams.home
+	end
 end
 
 -- TODO: Away score
 function score(runnerIndex)
 	outRunners[#outRunners + 1] = runners[runnerIndex]
 	table.remove(runners, runnerIndex)
-	homeScore = homeScore + 1
+	battingTeam.score = battingTeam.score + 1
 	announcer:say("SCORE!")
 end
 
@@ -542,46 +550,12 @@ function updateGameState()
 	updateOutRunners()
 end
 
-local OUT_BUBBLE_SIZE = 5
-
-function drawScoreboard()
-	gfx.setFont(ScoreFont)
-	gfx.setDrawOffset(0, 0)
-	local y = Screen.H * 0.97
-	local x = 15
-
-	gfx.setLineWidth(1)
-	gfx.setColor(gfx.kColorBlack)
-	gfx.fillRect(x - 15, y - 44, 75, 55)
-
-	gfx.setColor(gfx.kColorWhite)
-	for i = outs, 2 do
-		gfx.drawCircleAtPoint(x - 2 + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE)
-	end
-	for i = 0, (outs - 1) do
-		gfx.fillCircleAtPoint(x - 2 + (i * 2.5 * OUT_BUBBLE_SIZE), y, OUT_BUBBLE_SIZE)
-	end
-
-	local originalDrawMode = gfx.getImageDrawMode()
-	gfx.setImageDrawMode(playdate.graphics.kDrawModeInverted)
-	local numOffsetX = gfx.getTextSize("AWAY ")
-
-	gfx.drawText("HOME ", x - 7, y - 38)
-	local homeScoreText = homeScore > 9 and homeScore or "  " .. homeScore
-	gfx.drawText("" .. homeScoreText, x - 7 + numOffsetX, y - 38)
-
-	gfx.drawText("AWAY ", x - 7, y - 22)
-	local awayScoreText = awayScore > 9 and awayScore or "  " .. awayScore
-	gfx.drawText("" .. awayScoreText, x - 7 + numOffsetX, y - 22)
-
-	gfx.setImageDrawMode(originalDrawMode)
-end
-
 function playdate.update()
-	updateGameState()
-	playdate.graphics.animation.blinker.updateAll()
 	playdate.timer.updateTimers()
 
+	updateGameState()
+	gfx.animation.blinker.updateAll()
+
 	gfx.clear()
 
 	if ball.x < BallOffscreen then
@@ -620,7 +594,8 @@ function playdate.update()
 		PlayerFrown:draw(runner.x, runner.y)
 	end
 
-	drawScoreboard()
+	gfx.setDrawOffset(0, 0)
+	drawScoreboard(0, Screen.H * 0.77, teams, outs)
 	announcer:draw(Center.x, 10)
 end
 
diff --git a/src/scoreboard.lua b/src/scoreboard.lua
new file mode 100644
index 0000000..db4eb2b
--- /dev/null
+++ b/src/scoreboard.lua
@@ -0,0 +1,41 @@
+local ScoreFont <const> = playdate.graphics.font.new("fonts/font-full-circle.pft")
+local OutBubbleRadius <const> = 5
+local ScoreboardMarginX <const> = 8
+local ScoreboardHeight = 55
+
+function drawScoreboard(x, y, teams, outs)
+	local gfx = playdate.graphics
+
+	local homeScore = teams.home.score
+	local awayScore = teams.away.score
+
+	local homeScoreText = "HOME " .. (homeScore > 9 and homeScore or "  " .. homeScore)
+	local awayScoreText = "AWAY " .. (awayScore > 9 and awayScore or "  " .. awayScore)
+
+	local rectWidth = (ScoreboardMarginX * 2) + ScoreFont:getTextWidth(homeScoreText)
+
+	gfx.setLineWidth(1)
+	gfx.setColor(gfx.kColorBlack)
+	gfx.fillRect(x, y, rectWidth, ScoreboardHeight)
+
+	local originalDrawMode = gfx.getImageDrawMode()
+	gfx.setImageDrawMode(gfx.kDrawModeInverted)
+
+	ScoreFont:drawText(homeScoreText, x + ScoreboardMarginX, y + 6)
+	ScoreFont:drawText(awayScoreText, x + ScoreboardMarginX, y + 22)
+
+	gfx.setImageDrawMode(originalDrawMode)
+
+	gfx.setColor(gfx.kColorWhite)
+
+	function circleParams(i)
+		return (x + ScoreboardMarginX + OutBubbleRadius) + (i * 2.5 * OutBubbleRadius), y + 46, OutBubbleRadius
+	end
+
+	for i = outs, 2 do
+		gfx.drawCircleAtPoint(circleParams(i))
+	end
+	for i = 0, (outs - 1) do
+		gfx.fillCircleAtPoint(circleParams(i))
+	end
+end
diff --git a/src/utils.lua b/src/utils.lua
index 6476029..d21b833 100644
--- a/src/utils.lua
+++ b/src/utils.lua
@@ -136,19 +136,24 @@ end
 local AnnouncementFont <const> = playdate.graphics.font.new("fonts/Roobert-20-Medium.pft")
 
 -- selene: allow(unscoped_variables)
-announcer = {}
+announcer = {
+	textQueue = {},
+}
 
 function announcer.say(self, text, durationMs)
-	self.text = text
-	durationMs = durationMs and durationMs or 3000
+	self.textQueue[#self.textQueue + 1] = text
+	-- Could cause some timing funk if messages are queued up asyncronously.
+	-- I.e. `:say("hello")` <1.5 seconds pass> `:say("hello")` would result
+	-- in the second message being displayed for 4.5 seconds instead of just 3.
+	durationMs = durationMs and durationMs or (3000 * #self.textQueue)
 	playdate.timer.new(durationMs, function()
-		self.text = nil
+		table.remove(self.textQueue, 1)
 	end)
 end
 
 function announcer.draw(self, x, y)
-	if not self.text then
+	if #self.textQueue == 0 then
 		return
 	end
-	AnnouncementFont:drawTextAligned(self.text, x, y, kTextAlignment.center)
+	AnnouncementFont:drawTextAligned(self.textQueue[1], x, y, kTextAlignment.center)
 end