feat(web): add browser build for TDK Pinball
Expose the existing Macroquad game as a static WASM website while preserving native desktop behavior. Native-only simulation/file-export code and the per-user filesystem save path are now separated from the browser build. The browser version uses a small WASM-only storage support crate and a Macroquad-compatible JavaScript plugin to persist the same JSON settings and high scores in localStorage. Browser audio decoding starts in an owned background coroutine so the game can render its original loading/attract screens while the embedded sounds finish loading. The checked-in web bundle contains the optimized WASM, centered black HTML shell, and build/serve instructions. Test Plan: - `just test` -- passed, 135 tests - `just clippy` -- passed - `cargo clippy --target wasm32-unknown-unknown -- -D warnings` -- passed - `cargo +nightly fmt --check` and web-storage format check -- passed - `just web-build` -- passed; packaged WASM matches the production artifact - Browser smoke test at `http://127.0.0.1:8000/` -- rendered the centered game, started gameplay, opened settings, and restored a changed language from browser storage in a fresh page with no runtime errors
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# TDK Pinball Machine web build
|
||||
|
||||
Build and serve the browser version from this directory with:
|
||||
|
||||
```sh
|
||||
just web-serve
|
||||
```
|
||||
|
||||
The game is compiled for `wasm32-unknown-unknown` and loaded into the centered
|
||||
black canvas by `index.html`. Browser settings and high scores are saved in
|
||||
`localStorage`; native builds continue to use their normal per-user save file.
|
||||
|
||||
The page uses Macroquad's official browser loader from the miniquad samples
|
||||
site. The web page must be served over HTTP rather than opened directly from a
|
||||
`file:` URL.
|
||||
@@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, viewport-fit=cover"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="Play the reconstructed 1995 TDK Pinball Machine in your browser."
|
||||
/>
|
||||
<title>TDK Pinball Machine 1.00</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#glcanvas {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #000;
|
||||
outline: none;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="glcanvas" tabindex="1" aria-label="TDK Pinball Machine"></canvas>
|
||||
<noscript>This game needs JavaScript enabled.</noscript>
|
||||
<script src="https://not-fl3.github.io/miniquad-samples/mq_js_bundle.js"></script>
|
||||
<script src="./storage.js"></script>
|
||||
<script>
|
||||
load("tdkpin-rs.wasm");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,69 @@
|
||||
"use strict";
|
||||
|
||||
(function registerStoragePlugin() {
|
||||
const storageKey = "tdkpin.save.v1";
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
let lastRevision = 0;
|
||||
|
||||
function browserStorage() {
|
||||
try {
|
||||
return window.localStorage;
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function sendSavedDataToRust() {
|
||||
wasm_exports.tdkpin_browser_storage_clear();
|
||||
const storage = browserStorage();
|
||||
let saved = null;
|
||||
try {
|
||||
saved = storage?.getItem(storageKey);
|
||||
} catch (_error) {
|
||||
saved = null;
|
||||
}
|
||||
if (saved !== null && saved !== undefined) {
|
||||
for (const byte of encoder.encode(saved)) {
|
||||
wasm_exports.tdkpin_browser_storage_push(byte);
|
||||
}
|
||||
}
|
||||
wasm_exports.tdkpin_browser_storage_finish();
|
||||
}
|
||||
|
||||
function flushRustSave() {
|
||||
const revision = wasm_exports.tdkpin_browser_storage_save_revision();
|
||||
if (revision === lastRevision) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bytes = new Uint8Array(
|
||||
wasm_exports.tdkpin_browser_storage_save_length(),
|
||||
);
|
||||
for (let index = 0; index < bytes.length; index += 1) {
|
||||
bytes[index] = wasm_exports.tdkpin_browser_storage_save_byte(index);
|
||||
}
|
||||
|
||||
const storage = browserStorage();
|
||||
try {
|
||||
storage?.setItem(storageKey, decoder.decode(bytes));
|
||||
} catch (_error) {
|
||||
// Private browsing or a full quota should not stop the game loop.
|
||||
}
|
||||
wasm_exports.tdkpin_browser_storage_save_ack();
|
||||
lastRevision = revision;
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
sendSavedDataToRust();
|
||||
lastRevision = wasm_exports.tdkpin_browser_storage_save_revision();
|
||||
window.setInterval(flushRustSave, 50);
|
||||
window.addEventListener("beforeunload", flushRustSave);
|
||||
}
|
||||
|
||||
miniquad_add_plugin({
|
||||
name: "tdkpin_storage",
|
||||
on_init: onInit,
|
||||
version: 1,
|
||||
});
|
||||
})();
|
||||
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user