Add testBall.lua, testMain.lua, and testStatistics.lua

testMain.lua is really just a does-this-big-harness-work check right now, but it does work!
Extract statistics.lua for testing
Consolidate BoxScore ALL into draw/box-score.lua
This commit is contained in:
Sage Vaillancourt 2025-02-24 13:33:34 -05:00
parent 48a9854653
commit b44756ff57
8 changed files with 175 additions and 65 deletions

View File

@ -85,3 +85,8 @@ function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScal
self.floatAnimator:reset(flyTimeMs)
end
end
-- luacheck: ignore
if not playdate or playdate.TEST_MODE then
return Ball
end

View File

@ -1,61 +1,3 @@
---@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? Some other stats
-- * Scroll left and right through games that go into extra innings
-- * Scroll up and down through other stats.
-- + Balls and strikes
-- + Batting average
-- + Farthest hit ball
local MarginY <const> = 70
local SmallFont <const> = playdate.graphics.font.new("fonts/font-full-circle.pft")
@ -101,6 +43,20 @@ local function drawInning(x, inningNumber, homeScore, awayScore)
ScoreFont:drawTextAligned(homeScore, x, AwayY, gfx.kAlignRight)
end
---@class BoxScore
---@field stats Statistics
---@field private targetY number
BoxScore = {}
---@param stats Statistics
---@return BoxScore
function BoxScore.new(stats)
return setmetatable({
stats = stats,
targetY = 0,
}, { __index = BoxScore })
end
function BoxScore:drawBoxScore()
local inningStart = 4 + (AwayWidth * 1.5)
local widthAndMarg = InningDrawWidth + 4

View File

@ -38,6 +38,7 @@ import 'fielding.lua'
import 'graphics.lua'
import 'npc.lua'
import 'pitching.lua'
import 'statistics.lua'
import 'draw/box-score.lua'
import 'draw/fans.lua'
@ -242,13 +243,10 @@ function Game:pitcherIsReady()
end
function Game:checkForGameOver()
local homeScore, awayScore = utils.totalScores(self.state.stats)
local isFinalInning = self.state.inning >= self.settings.finalInning
local gameOver = isFinalInning and self.state.battingTeam == "home" and awayScore ~= homeScore
gameOver = gameOver or self.state.battingTeam == "away" and isFinalInning and homeScore > awayScore
Fielding.celebrate()
if gameOver then
local state = self.state
if state.stats:gameIsOver(state.inning, self.settings.finalInning, state.battingTeam) then
self.announcer:say("THAT'S THE BALL GAME!")
playdate.timer.new(3000, function()
transitionTo(BoxScore.new(self.state.stats))
@ -727,6 +725,11 @@ function Game:update()
end
end
-- luacheck: ignore
if not playdate or playdate.TEST_MODE then
return Game
end
playdate.display.setRefreshRate(50)
gfx.setBackgroundColor(gfx.kColorWhite)
playdate.setMenuImage(gfx.image.new("images/game/menu-image.png"))

60
src/statistics.lua Normal file
View File

@ -0,0 +1,60 @@
-- TODO? Some other stats
-- * Scroll left and right through games that go into extra innings
-- * Scroll up and down through other stats.
-- + Balls and strikes
-- + Batting average
-- + Farthest hit ball
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
---@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 = {}
---@return Statistics
function Statistics.new()
return setmetatable({
innings = { newInning() },
}, { __index = Statistics })
end
function Statistics:pushInning()
self.innings[#self.innings + 1] = newInning()
end
---@param inning number
---@param finalInning number
---@param battingTeam TeamId
---@return boolean gameOver
function Statistics:gameIsOver(inning, finalInning, battingTeam)
local homeScore, awayScore = utils.totalScores(self)
local isFinalInning = inning >= finalInning
local gameOver = isFinalInning and battingTeam == "home" and awayScore ~= homeScore
gameOver = gameOver or battingTeam == "away" and isFinalInning and homeScore > awayScore
return gameOver
end
-- luacheck: ignore
if not playdate or playdate.TEST_MODE then
return Statistics
end

View File

@ -2,7 +2,9 @@ utils = require("utils")
local currentTimeMs = 0
local mockPlaydate = {
local mockPlaydate = {}
mockPlaydate = {
TEST_MODE = true,
skipMs = function(skip)
currentTimeMs = currentTimeMs + skip
@ -11,13 +13,21 @@ local mockPlaydate = {
currentTimeMs = currentTimeMs + 1
return currentTimeMs
end,
easingFunctions = {},
timer = {
lastTimer = {
mockCompletion = function()
error("No lastTimer set!")
end,
},
new = function(_, callback)
return {
local timer = {
mockCompletion = function()
callback()
end,
}
mockPlaydate.timer.lastTimer = timer
return timer
end,
},
graphics = {
@ -38,6 +48,21 @@ local mockPlaydate = {
return {}
end,
},
image = {
new = function()
return {}
end,
},
},
sound = {
sampleplayer = {
new = function()
return {
play = function() end,
setFinishCallback = function() end,
}
end,
},
},
}

14
src/test/testBall.lua Normal file
View File

@ -0,0 +1,14 @@
require("test/setup")
local Ball = require("ball")
function testMarkUncatchable()
local ball = Ball.new(playdate.graphics.animator)
luaunit.assertIsTrue(ball.catchable, "Ball should start catchable")
ball:markUncatchable()
luaunit.assertIsFalse(ball.catchable, "Ball should not be catchable immediately after mark")
playdate.timer.lastTimer.mockCompletion()
luaunit.assertIsTrue(ball.catchable, "Ball should return to catchability after its timer expires")
end
os.exit(luaunit.LuaUnit.run())

28
src/test/testMain.lua Normal file
View File

@ -0,0 +1,28 @@
require("test/setup")
require("draw/panner")
function string.starts(str, start)
return string.sub(str, 1, str.len(start)) == start
end
import = function(target)
if string.starts(target, "CoreLibs") or string.starts(target, "draw/") then
return
end
-- Remove .lua
require(target:sub(1, #target - 4))
end
local Game = require("main")
local settings = {
homeTeamSpriteGroup = {},
awayTeamSpriteGroup = {},
}
function testStandaloneInit()
-- Harness should be fleshed-out enough to init without error.
Game.new(settings, announcer)
end
os.exit(luaunit.LuaUnit.run())

View File

@ -0,0 +1,19 @@
require("test/setup")
local Statistics = require("statistics")
function testReportGameOver()
---@type Statistics
local stats = Statistics.new()
stats.innings[1].home.score = 0
stats.innings[1].away.score = 0
luaunit.assertIsFalse(stats:gameIsOver(9, 9, "home"), "Tie games should not report a game over")
stats.innings[1].home.score = 1
luaunit.assertIsTrue(stats:gameIsOver(9, 9, "home"), "Team in lead should report a game over")
stats.innings[1].home.score = 1
luaunit.assertIsFalse(stats:gameIsOver(1, 9, "home"), "Should not game over with innings left")
end
os.exit(luaunit.LuaUnit.run())