117 lines
4.7 KiB
HTML
117 lines
4.7 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>MRPCI — Monster Robot Party Creative Interpreter</title>
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><text y='14' font-size='14'>🎨</text></svg>" />
|
||
<link rel="stylesheet" href="arcade.css" />
|
||
<style>
|
||
html, body { margin: 0; height: 100%; background: #05070b; overflow: hidden; }
|
||
/* miniquad sizes its drawing buffer from the canvas's *CSS* box, so the
|
||
canvas needs real dimensions — `max-width` inside a centring grid
|
||
resolves to zero and the engine renders a 0x0 frame. Give it the
|
||
whole area below the arcade bar, like MRPGI does. */
|
||
canvas {
|
||
position: absolute; inset: 34px 0 24px 0;
|
||
width: 100%; height: calc(100% - 58px);
|
||
display: block; outline: none; image-rendering: pixelated; background: #0b0d11;
|
||
}
|
||
body.arcade-standalone-page canvas { inset: 0 0 24px 0; height: calc(100% - 24px); }
|
||
#boot {
|
||
position: absolute; inset: 34px 0 0 0; display: grid; place-items: center;
|
||
background: #05070b; color: #8296aa; z-index: 20;
|
||
font: 12px/1 ui-monospace, Menlo, monospace; letter-spacing: 0.16em;
|
||
text-transform: uppercase; transition: opacity 400ms ease; pointer-events: none;
|
||
}
|
||
#keys {
|
||
position: fixed; bottom: 0; left: 0; right: 0; z-index: 30;
|
||
padding: 4px 10px; background: rgba(6,10,16,0.86); color: #6f8296;
|
||
border-top: 1px solid rgba(120,190,255,0.18);
|
||
font: 11px/1.5 ui-monospace, Menlo, monospace;
|
||
}
|
||
#keys b { color: #9fb4c8; font-weight: 600; }
|
||
</style>
|
||
</head>
|
||
<body class="arcade-inset">
|
||
<canvas id="glcanvas" tabindex="1"></canvas>
|
||
<div id="boot">starting the interpreter…</div>
|
||
<div id="keys">
|
||
<b>click</b> verb at pixel · <b>right-click</b> cycle verb · <b>F1–F4</b> walk/look/do/talk ·
|
||
<b>I</b> inventory · <b>Enter</b> type a command · <b>F5/F7</b> save/restore · <b>M</b> mute · <b>N</b> scanlines
|
||
</div>
|
||
|
||
<script src="mq_js_bundle.js"></script>
|
||
<script>
|
||
// miniquad resolves the engine's undefined imports at runtime; register
|
||
// before the wasm loads or the instantiation fails.
|
||
let onSaveOut = null
|
||
miniquad_add_plugin({
|
||
register_plugin: (imports) => {
|
||
imports.env.mrpci_save_out = (ptr, len) => {
|
||
const json = new TextDecoder().decode(new Uint8Array(wasm_memory.buffer, ptr, len))
|
||
if (onSaveOut) onSaveOut(json)
|
||
}
|
||
},
|
||
version: '1',
|
||
name: 'mrpci_bridge',
|
||
})
|
||
|
||
/** Copy a string into the engine and call an exported entry point. */
|
||
function handOver(fn, json) {
|
||
const bytes = new TextEncoder().encode(json)
|
||
const ptr = wasm_exports.mrpci_alloc(bytes.length)
|
||
new Uint8Array(wasm_memory.buffer, ptr, bytes.length).set(bytes)
|
||
fn(ptr, bytes.length)
|
||
}
|
||
|
||
/** Hand a WorldBundle JSON to the running engine. */
|
||
function sendBundleToEngine(json) {
|
||
handOver(wasm_exports.mrpci_world_in, json)
|
||
}
|
||
|
||
/**
|
||
* The engine emits its progress a frame after being asked, so a save
|
||
* is a round trip, not a return value — hence the promise. The timeout
|
||
* matters: if the engine is wedged, the menu should say so rather than
|
||
* hang on a button press forever.
|
||
*/
|
||
function captureSave() {
|
||
return new Promise((resolve) => {
|
||
if (!wasm_exports?.mrpci_request_save) return resolve(null)
|
||
const timer = setTimeout(() => { onSaveOut = null; resolve(null) }, 2000)
|
||
onSaveOut = (json) => {
|
||
clearTimeout(timer)
|
||
onSaveOut = null
|
||
resolve(json)
|
||
}
|
||
wasm_exports.mrpci_request_save()
|
||
})
|
||
}
|
||
|
||
load('mrpci.wasm')
|
||
|
||
const engineReady = () => typeof wasm_exports !== 'undefined' && !!wasm_exports
|
||
const t = setInterval(() => {
|
||
if (!engineReady()) return
|
||
document.getElementById('boot').style.opacity = '0'
|
||
clearInterval(t)
|
||
}, 200)
|
||
</script>
|
||
<script src="picker.js"></script>
|
||
<script>
|
||
// The arcade picker is optional chrome: a standalone deploy without
|
||
// picker.js still boots the baked game.
|
||
if (typeof MRPArcade !== 'undefined') MRPArcade.init({
|
||
engine: 'mrpci',
|
||
ready: () => typeof wasm_exports !== 'undefined' && !!wasm_exports,
|
||
load: (json) => sendBundleToEngine(json),
|
||
saves: {
|
||
capture: () => captureSave(),
|
||
restore: (json) => handOver(wasm_exports.mrpci_save_in, json),
|
||
},
|
||
})
|
||
</script>
|
||
</body>
|
||
</html>
|