diff --git a/src/ball.lua b/src/ball.lua index bf2ad2c..5bfd234 100644 --- a/src/ball.lua +++ b/src/ball.lua @@ -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 diff --git a/src/draw/box-score.lua b/src/draw/box-score.lua index 2fd8c72..49dbd2e 100644 --- a/src/draw/box-score.lua +++ b/src/draw/box-score.lua @@ -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)[] - -Statistics = {} - -local function newTeamInning() - return { - score = 0, - pitching = { - balls = 0, - strikes = 0, - }, - hits = {}, - } -end - ----@return table -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 = 70 local SmallFont = 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 diff --git a/src/main.lua b/src/main.lua index 16862a0..ddef83e 100644 --- a/src/main.lua +++ b/src/main.lua @@ -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")) diff --git a/src/statistics.lua b/src/statistics.lua new file mode 100644 index 0000000..fb61a2f --- /dev/null +++ b/src/statistics.lua @@ -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 +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)[] +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 diff --git a/src/test/mocks.lua b/src/test/mocks.lua index 8ec6658..b97746f 100644 --- a/src/test/mocks.lua +++ b/src/test/mocks.lua @@ -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, + }, }, } diff --git a/src/test/testBall.lua b/src/test/testBall.lua new file mode 100644 index 0000000..bf7e4fb --- /dev/null +++ b/src/test/testBall.lua @@ -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()) diff --git a/src/test/testMain.lua b/src/test/testMain.lua new file mode 100644 index 0000000..75a2725 --- /dev/null +++ b/src/test/testMain.lua @@ -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()) diff --git a/src/test/testStatistics.lua b/src/test/testStatistics.lua new file mode 100644 index 0000000..ef8ac55 --- /dev/null +++ b/src/test/testStatistics.lua @@ -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())