tools/engine.js

67 lines · 2254 bytes

 1"use strict"
 2// Loads the real, unmodified 1MB game into a stubbed browser environment:
 3// sprites.js, data.js, then the inline engine script from index.html.
 4// Returns the vm context; engine globals (S, R, O, step, build, ...) live in it.
 5const fs = require("fs")
 6const path = require("path")
 7const vm = require("vm")
 8
 9function load(gameDir) {
10  const html = fs.readFileSync(path.join(gameDir, "index.html"), "utf8")
11  const dataSrc = fs.readFileSync(path.join(gameDir, "data.js"), "utf8")
12  const spriteSrc = fs.readFileSync(path.join(gameDir, "sprites.js"), "utf8")
13
14  // The engine is the inline <script> block that follows the data.js include.
15  const m = html.match(/<script src=data\.js><\/script>\s*<script>([\s\S]*?)<\/script>/)
16  if (!m) {
17    console.error("engine: could not find the inline engine <script> after the data.js include in index.html")
18    process.exit(2)
19  }
20  const engineSrc = m[1]
21
22  // Minimal inert DOM node — enough surface for the engine's load-time render().
23  function el() {
24    return {
25      dataset: {},
26      textContent: "",
27      innerHTML: "",
28      className: "",
29      hidden: false,
30      appendChild() {},
31      onclick: null,
32      classList: { toggle() {} },
33      insertAdjacentHTML() {},
34      insertAdjacentText() {},
35    }
36  }
37
38  const sandbox = {
39    document: {
40      getElementById: () => el(),
41      createElement: () => el(),
42    },
43    addEventListener() {},
44    localStorage: {
45      _mem: Object.create(null),
46      getItem(k) { return k in this._mem ? this._mem[k] : null },
47      setItem(k, v) { this._mem[k] = String(v) },
48      removeItem(k) { delete this._mem[k] },
49    },
50    window: { AudioContext: function () { throw new Error("SFX must not run headless") } },
51    console,
52  }
53  const ctx = vm.createContext(sandbox)
54
55  try {
56    vm.runInContext(spriteSrc, ctx, { filename: "1mb/sprites.js" })
57    vm.runInContext(dataSrc, ctx, { filename: "1mb/data.js" })
58    vm.runInContext(engineSrc, ctx, { filename: "1mb/index.html#engine" })
59  } catch (e) {
60    console.error("engine: game code failed to evaluate headless — improve the stubs in this harness, do not touch the game.")
61    console.error(e && e.stack || e)
62    process.exit(2)
63  }
64  return ctx
65}
66
67module.exports = { load }