mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
258 lines
9.3 KiB
Lua
258 lines
9.3 KiB
Lua
-- Gen 2 clock setup: the InitClock screens and the RTC base they write.
|
|
--
|
|
-- The cart does not store "the time": InitClock stores the RTC reading at the
|
|
-- moment the player answered (wStartHour / wStartMinute / wStartDay) and every
|
|
-- later read goes through it. src/core/gen2/Clock.lua is that arithmetic and
|
|
-- src/ui/gen2/InitClock.lua is both screens that write it.
|
|
-- luajit tests/gen2_clock_test.lua
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 clock")
|
|
local check, eq = S.check, S.eq
|
|
|
|
love = require("tests.love_stub")
|
|
|
|
-- No font is loaded here (these are layout and arithmetic assertions), so
|
|
-- Font.encode would warn once per unknown glyph and bury a real failure.
|
|
require("src.core.Logger").warn = function() end
|
|
|
|
local Clock = require("src.core.gen2.Clock")
|
|
local InitClock = require("src.ui.gen2.InitClock")
|
|
|
|
-- A stub input the screen drives off, the same shape Input:wasPressed has.
|
|
local function fakeInput()
|
|
local pressed = {}
|
|
return {
|
|
press = function(self, button) pressed[button] = true end,
|
|
wasPressed = function(_self, button)
|
|
if pressed[button] then
|
|
pressed[button] = nil
|
|
return true
|
|
end
|
|
return false
|
|
end,
|
|
}
|
|
end
|
|
|
|
-- --------------------------------------------------------------- the offsets
|
|
do
|
|
local save = {}
|
|
check(not Clock.isSet(save), "a fresh save has no base")
|
|
eq(Clock.hour(save), math.floor(Clock.hostMinutes() / 60),
|
|
"so it reads the host clock straight through")
|
|
|
|
Clock.setTime(save, 10, 0)
|
|
check(Clock.isSet(save), "answering Oak writes the base")
|
|
eq(Clock.hour(save), 10, "and the clock reads back what was set")
|
|
eq(Clock.minute(save), 0, "minutes too")
|
|
|
|
Clock.setTime(save, 23, 45)
|
|
eq(Clock.hour(save), 23, "a second answer re-anchors it")
|
|
eq(Clock.minute(save), 45, "minutes and all")
|
|
|
|
-- The base is an OFFSET, not a frozen reading: the clock keeps running.
|
|
local base = save.rtc.startMinute
|
|
local moved = (Clock.hostMinutes() + 90 + base) % Clock.MINUTES_PER_DAY
|
|
eq(math.floor(moved / 60), 1,
|
|
"an hour and a half later it is 1:15, not still 23:45")
|
|
eq(moved % 60, 15, "minutes and all")
|
|
|
|
Clock.setTime(save, 0, 0)
|
|
eq(Clock.hour(save), 0, "midnight is hour 0")
|
|
Clock.setTime(save, 12, 30)
|
|
eq(Clock.hour(save), 12, "noon is hour 12")
|
|
end
|
|
|
|
do
|
|
local save = {}
|
|
eq(Clock.weekday(save), Clock.hostWeekday(),
|
|
"with no base the weekday is the host's")
|
|
for day = 0, 6 do
|
|
Clock.setWeekday(save, day)
|
|
eq(Clock.weekday(save), day, ("SetDayOfWeek pins day %d"):format(day))
|
|
end
|
|
eq(save.rtc.dayOfWeek, 6,
|
|
"and leaves wCurDay where the daily rollovers read it")
|
|
end
|
|
|
|
-- ------------------------------------------------------------ the hour screen
|
|
do
|
|
local save = {}
|
|
local input = fakeInput()
|
|
local done = {}
|
|
local screen = InitClock.new({ input = input }, {
|
|
save = save,
|
|
onDone = function(hour, minute) done = { hour = hour, minute = minute } end,
|
|
})
|
|
eq(screen.phase, "intro", "the screen opens on Oak waking up")
|
|
eq(screen.hour, 10, "with InitClock's own default of 10 AM")
|
|
eq(screen.minute, 0, "and no minutes")
|
|
|
|
-- OakTimeWokeUpText is two `para` pages, so A turns the page before it takes
|
|
-- the box down.
|
|
eq(#screen:pages(), 2, "Oak's opening is two pages")
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(screen.phase, "intro", "the first A turns the page")
|
|
check(screen:pageText():find("clock", 1, true) ~= nil,
|
|
"onto the one that asks about the clock")
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(screen.phase, "hour", "and the second opens the hour picker")
|
|
-- DisplayHourOClock is PrintHour + String_oclock, so the word rides along
|
|
eq(screen:display(), "DAY 10 o'clock", "which shows the hour DisplayHourOClock does")
|
|
|
|
input:press("up")
|
|
screen:update(0)
|
|
eq(screen.hour, 11, "up walks the hour forward")
|
|
input:press("down")
|
|
screen:update(0)
|
|
input:press("down")
|
|
screen:update(0)
|
|
eq(screen.hour, 9, "down walks it back")
|
|
-- .DecreaseThroughMidnight / .AdvanceThroughMidnight: both ends wrap.
|
|
screen.hour = 0
|
|
input:press("down")
|
|
screen:update(0)
|
|
eq(screen.hour, 23, "and midnight wraps to 11 PM")
|
|
input:press("up")
|
|
screen:update(0)
|
|
eq(screen.hour, 0, "and back again")
|
|
|
|
screen.hour = 7
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(screen.phase, "confirm-hour", "A confirms the hour")
|
|
check(screen:question():find("o'clock", 1, true) ~= nil,
|
|
"and the question reads it back")
|
|
-- NO drops back to the picker (`jr c, .loop`).
|
|
screen.yesNo = 2
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(screen.phase, "hour", "NO goes back to the picker")
|
|
input:press("a")
|
|
screen:update(0)
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(screen.phase, "minute", "YES moves on to the minutes")
|
|
|
|
input:press("up")
|
|
screen:update(0)
|
|
eq(screen.minute, 1, "up walks the minutes")
|
|
screen.minute = 0
|
|
input:press("down")
|
|
screen:update(0)
|
|
eq(screen.minute, 59, "and they wrap at the hour")
|
|
screen.minute = 30
|
|
eq(screen:display(), "30 min.", "DisplayMinutesWithMinString's own string")
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(screen.phase, "confirm-minute", "A confirms them")
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(screen.phase, "response", "and Oak answers with the time")
|
|
-- "MORN 7:30", not "7 AM:30": OakText_ResponseToSetTime is PrintHour (the
|
|
-- time-of-day word then the 1-12 hour) then ':' then two-digit minutes, so
|
|
-- the meridiem never appears and cannot land between the hour and them.
|
|
check(screen:question():find("MORN 7:30", 1, true) ~= nil,
|
|
"which is the pair the player just set")
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(done.hour, 7, "the screen hands the hour back")
|
|
eq(done.minute, 30, "and the minutes")
|
|
eq(Clock.hour(save), 7, "and the save now reads that hour")
|
|
eq(Clock.minute(save), 30, "and those minutes")
|
|
end
|
|
|
|
-- OakText_ResponseToSetTime's own ladder.
|
|
eq(InitClock.responseKey(2), "soDark", "before MORN_HOUR it is still dark")
|
|
eq(InitClock.responseKey(4), "overslept", "MORN_HOUR is 'I overslept'")
|
|
-- DAY_HOUR is 10 (constants/misc_constants.asm:38), and
|
|
-- OakText_ResponseToSetTime's `cp DAY_HOUR + 1 / jr c, .morn` keeps the hour
|
|
-- ITSELF in the plain "I overslept!" arm; "Yikes!" starts at 11. This read 9
|
|
-- when InitClock did, so both were an hour early together.
|
|
eq(InitClock.responseKey(10), "overslept", "and so is DAY_HOUR itself")
|
|
eq(InitClock.responseKey(11), "yikes", "past it Oak yikes")
|
|
eq(InitClock.responseKey(18), "soDark", "and NITE_HOUR is dark again")
|
|
|
|
-- PrintHour (engine/rtc/timeset.asm:672) is GetTimeOfDayString + PlaceString
|
|
-- and THEN the 1-12 hour, so the cart prints the time-of-day word ahead of the
|
|
-- number and no meridiem at all. These read "12 AM" / "1 PM" while InitClock
|
|
-- built the string that way, which is what let InitClock.timeString append
|
|
-- ":mm" to a meridiem and have Oak say "5 AM:30".
|
|
eq(InitClock.hourString(0), "NITE 12", "PrintHour shows midnight as NITE 12")
|
|
eq(InitClock.hourString(12), "DAY 12", "and noon as DAY 12")
|
|
eq(InitClock.hourString(13), "DAY 1", "and the afternoon on a 12-hour clock")
|
|
eq(InitClock.timeString(5, 30), "MORN 5:30",
|
|
"and OakText_ResponseToSetTime's line is PrintHour, ':', two-digit minutes")
|
|
|
|
-- --------------------------------------------------------- the weekday wheel
|
|
do
|
|
local save = {}
|
|
local input = fakeInput()
|
|
local picked
|
|
local screen = InitClock.new({ input = input }, {
|
|
mode = "day", save = save,
|
|
onDone = function(day) picked = day end,
|
|
})
|
|
eq(screen.phase, "day", "the wheel opens on its picker, with no preamble")
|
|
eq(screen.day, 0, "`xor a / ld [wTempDayOfWeek], a`: SUNDAY")
|
|
eq(screen:display(), "SUNDAY", "which is what the box shows")
|
|
for _ = 1, 2 do
|
|
input:press("up")
|
|
screen:update(0)
|
|
end
|
|
eq(screen:display(), "TUESDAY", "up walks the wheel forward")
|
|
input:press("down")
|
|
screen:update(0)
|
|
input:press("down")
|
|
screen:update(0)
|
|
input:press("down")
|
|
screen:update(0)
|
|
eq(screen:display(), "SATURDAY", "and it wraps past SUNDAY")
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(screen.phase, "confirm-day", "A confirms it")
|
|
input:press("b")
|
|
screen:update(0)
|
|
eq(screen.phase, "day", "B is NO and drops back to the wheel")
|
|
input:press("a")
|
|
screen:update(0)
|
|
input:press("a")
|
|
screen:update(0)
|
|
eq(picked, 6, "YES hands the day back")
|
|
eq(Clock.weekday(save), 6, "and the save reads it")
|
|
end
|
|
|
|
-- The driven path: a screen this new must not stall a scripted run, so it
|
|
-- walks itself to the end on its defaults.
|
|
do
|
|
local save = {}
|
|
local finished = false
|
|
local screen = InitClock.new({}, {
|
|
save = save, autoConfirm = true,
|
|
onDone = function() finished = true end,
|
|
})
|
|
for _ = 1, 20 do
|
|
if finished then break end
|
|
screen:update(0)
|
|
end
|
|
check(finished, "autoConfirm reaches the end on its own")
|
|
eq(Clock.hour(save), Clock.DEFAULT_HOUR, "taking InitClock's 10 AM default")
|
|
eq(Clock.minute(save), Clock.DEFAULT_MINUTE, "and no minutes")
|
|
end
|
|
|
|
-- Drawing must not throw with the stub canvas: the layout is transcribed from
|
|
-- hlcoord calls, so a bad coordinate is a crash rather than a wrong pixel.
|
|
do
|
|
local screen = InitClock.new({}, { save = {} })
|
|
for _, phase in ipairs({ "intro", "hour", "confirm-hour", "minute",
|
|
"confirm-minute", "response", "day", "confirm-day" }) do
|
|
screen.phase = phase
|
|
local ok, err = pcall(function() screen:drawPanel() end)
|
|
check(ok, ("phase %s draws (%s)"):format(phase, tostring(err)))
|
|
end
|
|
end
|
|
|
|
S.finish()
|