-- Space Ace (ReadySoft 1989, WHDLoad v1.3): a demo player, not an arena bot. -- -- Space Ace is a laserdisc game: every scene is a clip, and the game checks -- for one move at fixed moments of it. The moves are in the game's own -- table, so this bot reads the answer from RAM instead of carrying the -- walkthrough. Found under playbooks/spaceace/spaceace.hdf (AROS ROM, 2 MB -- chip, 8 MB fast so WHDLoad preloads the four disks): -- -- $158.w the stick as the game reads it each frame (JOY1DAT and fire) -- $160.w the moves pressed since the last check, OR-ed: fire 1, down 2, -- right 4, up $200, left $400. A second press before the check -- makes it $606, a wrong move. Cleared by every check. -- $15a.l frames since the last press; a right move scores only if -- pressed fewer than 90 frames before its check -- $326.w the part of the game (1..4; 4 is Borf) -- $267.b the scene within the part -- $269.b the moment within the scene, one step about every 7 frames -- $380e per part, a word offset (from $380e) to the part's checks: -- 6-byte entries {scene, moment, move.w, outcome.b, pad.b}, ended -- by scene 0. moment >= $80 means "this move at any moment is -- fatal"; otherwise the move must be in $160 when $269 reaches -- the moment, or the clip of outcome (a death) plays -- (the check is at $18a8 in the game's code). -- -- The bot presses each expected move LEAD moments before its check, once -- per visit to the scene (a death replays it), for HOLD frames, so it is -- one clean press. local PART, SCENE, MOMENT, PENDING = 0x326, 0x267, 0x269, 0x160 local TABLE = 0x380e local LEAD, HOLD = 1, 3 local BITS = { [0x1] = 16, [0x2] = 2, [0x4] = 8, [0x200] = 1, [0x400] = 4 } -- what the bot says as it presses (say(): the watcher's panel) local WORDS = { [16] = "fire!", [2] = "moving down", [8] = "moving right", [1] = "moving up", [4] = "moving left" } local say = say or function() end local checks = {} -- per part: list of {scene, moment, move} local function load_part(part) if checks[part] then return checks[part] end local off = ram.u16(TABLE + (part - 1) * 2) if not off then return nil end if off >= 0x8000 then off = off - 0x10000 end local a, list = TABLE + off, {} for k = 0, 63 do local scene = ram.u8(a + 6 * k) if scene == 0 then break end local moment = ram.u8(a + 6 * k + 1) local move = ram.u16(a + 6 * k + 2) if moment < 0x80 and BITS[move] then list[#list + 1] = { scene, moment, move } end end checks[part] = list return list end local done = {} -- "part:scene:moment" pressed on this visit local was_scene, was_moment, was_part local press_left, press_bits = 0, 0 local stats = { presses = 0 } function init(port, players) print(string.format("spaceace bot on port %d", port)) end function frame(n) local part, scene, moment = ram.u16(PART), ram.u8(SCENE), ram.u8(MOMENT) if not part or part < 1 or part > 4 then return 0 end -- a new visit to a scene (or a replay after a death): forget its presses if scene ~= was_scene or part ~= was_part or (was_moment and moment < was_moment) then done = {} end was_scene, was_moment, was_part = scene, moment, part if press_left > 0 then press_left = press_left - 1 return press_bits end local list = load_part(part) if not list then return 0 end -- the next check of this scene not yet passed local best for _, c in ipairs(list) do if c[1] == scene and c[2] >= moment and (not best or c[2] < best[2]) then local key = part .. ":" .. scene .. ":" .. c[2] if not done[key] then best = c end end end if best and best[2] - moment <= LEAD and ram.u16(PENDING) == 0 then done[part .. ":" .. scene .. ":" .. best[2]] = true press_bits, press_left = BITS[best[3]], HOLD - 1 stats.presses = stats.presses + 1 say(string.format("%s (part %d, scene %d, move %d)", WORDS[press_bits], part, scene, stats.presses)) return press_bits end return 0 end