120 lines
5.3 KiB
Text
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
|