singe/testScripts/ports/videolandDrive.singe
2026-09-22 18:32:07 -05:00

120 lines
5.3 KiB
Text

-- Drives Adventures in Videoland: Rollercoaster (the original or its port) from what the game
-- shows, the same way for both. The driver file defines what it takes: a view() of the game's
-- state and videolandKey(keysym, scancode) to press a key; this file decides. The trace lines
-- it prints are what compare.sh diffs.
--
-- The policy: a name at the name prompt, any key where the game waits for one, and the
-- walkthrough's lines typed one character a frame whenever the game waits for a line; with
-- PORT_PERFECT=1 the walkthrough is the solution alone, otherwise it opens with the unknown
-- words, the inventory play, and two of the deaths (the restaurant's meal and the coaster's
-- car) before solving the game from the start again.
VIDEOLAND_FRAMES = tonumber(os.getenv("PORT_FRAMES")) or 60000
local perfect = (os.getenv("PORT_PERFECT") == "1")
-- The solution: the coins, the bear from the gallery, the uniform from the closet, the toolkit,
-- the book past the guard, the bear to the dancer for the ticket, the radio from the booth, the
-- batteries out of the bear, the jammer made and powered, and the tower.
local SOLUTION = { "E", "TAKE COINS", "W", "W", "S", "SHOOT", "N", "N", "E", "TAKE UNIFORM", "WEAR UNIFORM", "W", "S", "W", "TAKE TOOLKIT",
"E", "E", "S", "S", "TAKE BOOK", "READ BOOK", "N", "N", "E", "S", "YES", "S", "TAKE TICKET", "W",
"E", "E", "PLAY", "RADIO", "OPEN BEAR", "TAKE BATTERIES", "MAKE JAMMER", "PUT BATTERIES", "IN JAMMER",
"W", "W", "W", "W", "S", "S", "JAM" }
-- The detours: words the game does not know, the inventory, a meal that ends the game and a
-- yes to play again, a walk to the top of the coaster and the car, and a yes again.
local DETOURS = { "DANCE", "HELP", "I", "LOOK", "TAKE ALL", "FIND BATTERIES", "KILL SABOTEUR", "N", "MAYBE", "YES", "HUH", "YES",
"E", "TAKE COINS", "DROP COINS", "TAKE COINS", "PUT COINS", "DOWN", "TAKE COINS", "GO NORTH", "W", "W", "N",
"EXAMINE BOX", "BREAK DOOR", "VISIT DANCER", "READ TICKET", "S", "W", "N", "N", "YES" }
local lines = {}
local at = 1
local typing = nil -- The line being typed, and how much of it is in.
local typed = 0
local settle = 0 -- Frames to wait before the next line.
local frames = 0
local pressedFor = nil -- The wait for any key already answered, by what follows it.
local lastLine = nil
if not perfect then
for _, line in ipairs(DETOURS) do
lines[#lines + 1] = line
end
end
for _, line in ipairs(SOLUTION) do
lines[#lines + 1] = line
end
table.insert(lines, 1, "SCOTT")
-- The states that wait for a typed line (the game's numbers): the name, the restaurant, the
-- tent, the parser, the play-again, the prize, and the put-where.
local WAITING = { [2] = true, [10] = true, [11] = true, [15] = true, [17] = true, [20] = true, [23] = true }
function videolandTraceSeek(frame)
debugPrint(string.format("TRACE seek %d", frame))
end
-- view: { state, next, room, turns, keyboard, keyhit, delay, still, endFrame, frame, prints,
-- last (the newest line of the screen), input }
function videolandDrive(view)
frames = frames + 1
local line = string.format("TRACE state %s next %s room %s turns %s kb %s hit %s delay %s still %s end %s prints %s last %s",
tostring(view.state), tostring(view.next), tostring(view.room), tostring(view.turns), tostring(view.keyboard), tostring(view.keyhit),
tostring(view.delay), tostring(view.still), tostring(view.endFrame), tostring(view.prints), tostring(view.last))
if line ~= lastLine then
debugPrint(line .. " disc " .. tostring(view.frame))
lastLine = line
end
if frames >= VIDEOLAND_FRAMES then
debugPrint("TRACE end after " .. frames .. " frames")
singeQuit()
return
end
-- Any key where the game waits for one, once per wait (two waits follow each other at the
-- story's end, told apart by what comes next). A full stop: the keyboard is still on for
-- the frame the wait begins in, and a letter or a space pressed then would go into the line.
if view.keyhit then
if pressedFor ~= view.next then
pressedFor = view.next
videolandKey(46, SCANCODE.PERIOD.value)
end
return
end
pressedFor = nil
if typing then
-- The line in, a character a frame, then the return key.
typed = typed + 1
if typed <= string.len(typing) then
local c = typing:sub(typed, typed)
if c == " " then
videolandKey(32, SCANCODE.SPACE.value)
else
videolandKey(string.byte(string.lower(c)), SCANCODE[c].value)
end
else
videolandKey(13, SCANCODE.RETURN.value)
typing = nil
settle = 3
end
return
end
if settle > 0 then
settle = settle - 1
return
end
if os.getenv("PORT_DEBUG") == "1" and (frames % 100 == 0) then
debugPrint(string.format("DRIVE probe kb %s input [%s] next %s at %s line %s typing %s settle %s", tostring(view.keyboard), tostring(view.input), tostring(view.next), tostring(at), tostring(lines[at]), tostring(typing), tostring(settle)))
end
-- (Not while a delay runs: the keyboard is on through the bomb's, and a line typed then
-- is taken before the play-again prompt and leaves the game in no state at all.)
if view.keyboard and (view.delay == 0) and (view.input == "") and WAITING[view.next] and lines[at] then
typing = lines[at]
typed = 0
at = at + 1
debugPrint("DRIVE line " .. typing)
end
end