@moonbase-machines/player 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -20
- package/bin/moonbase-play.mjs +4 -4
- package/disk/disk.js +9 -4
- package/disk/local.js +2 -2
- package/dist/moonbase-player.js +17 -18
- package/dist/moonbase-player.js.map +4 -4
- package/engines/original.js +153 -0
- package/engines/scenes.js +215 -0
- package/index.js +8 -5
- package/keys.js +28 -0
- package/package.json +6 -6
- package/play.js +186 -0
- package/player.css +10 -9
- package/remake/card.js +4 -3
- package/remake/load.js +12 -3
- package/saves.js +22 -0
- package/test/play.test.js +78 -0
- package/original.js +0 -200
- package/remake/main.js +0 -51
- package/remake/scenes.js +0 -243
package/README.md
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
# @moonbase-machines/player
|
|
2
2
|
|
|
3
|
-
The player for Mark Wharton's titles of 1985 to 1995,
|
|
4
|
-
|
|
5
|
-
- **Original** (`original.js`): the game's own disk, untouched, on Moonbase's Apple IIe or
|
|
6
|
-
(`@moonbase-machines/apple2e`, `c64`), on Moonbase's own ROMs, which need nothing of Apple's or
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
The player for Mark Wharton's titles of 1985 to 1995, one way in for every title and every way to play it (`play.js`):
|
|
4
|
+
|
|
5
|
+
- **Original** (`way: 'original'`, `engines/original.js`): the game's own disk, untouched, on Moonbase's Apple IIe or
|
|
6
|
+
Commodore 64 (`@moonbase-machines/apple2e`, `c64`), on Moonbase's own ROMs, which need nothing of Apple's or
|
|
7
|
+
Commodore's.
|
|
8
|
+
- **Remake** (`way: 'remake'`): the game's scenes, lifted from its code and compiled to JavaScript, on the remake's
|
|
9
|
+
engine (`engines/scenes.js`, a title of `card` or `vector` scenes), or the title's own engine, a module in its folder.
|
|
10
|
+
|
|
11
|
+
The player makes its part of the page and owns it, the same for every title: the section (classes `player`, the way,
|
|
12
|
+
the title's slug), the stage sized a whole number of the device's pixels to each of the screen's dots, the options
|
|
13
|
+
line and the status, the keys (one listener, each machine's codes, `keys.js`), the sound, a Commodore 64's printer,
|
|
14
|
+
the saves (`saves.js`), and what the page asks of it (`watch`, `claim`, `pace`, a route). An engine plays the game on
|
|
15
|
+
the stage and returns its handle (`play.js` says what it may have). What a player saves goes on **the player's disk**
|
|
16
|
+
(`disk/`): a title's own disk of files in this browser (IndexedDB), with its own dialogs (`disk/dialogs.js`, in a modal
|
|
17
|
+
window, `modal.js`); the player opens it, on the backend the page gives. A game saved in one way loads in the other.
|
|
15
18
|
|
|
16
19
|
No title is named in it. A title is a folder of its own, kept with the title, which the page gives the player:
|
|
17
20
|
|
|
18
|
-
- `title.js`, what the title is: its engine (`'card'`, `'vector'` or a module of its own), its
|
|
19
|
-
|
|
20
|
-
`remake/cards.js` and `remake/vector.js` say what
|
|
21
|
-
problems; `view`, its view's own drawing), its font's own
|
|
21
|
+
- `title.js`, what the title is: its engine (`'card'`, `'vector'` or a module of its own), its screen where it is not
|
|
22
|
+
its machine's (`screen`, in its own dots), its scripts and the one it starts with, its `map` (the routines its
|
|
23
|
+
scenes call, by address, and what its engine needs beside them: `remake/cards.js` and `remake/vector.js` say what
|
|
24
|
+
each engine reads), the modules it brings (`modules`, its problems; `view`, its view's own drawing), its font's own
|
|
25
|
+
glyphs (`font`), its stylesheet (`style`);
|
|
22
26
|
- its scenes (`<name>.scene`) and `compiled/` (`moonbase-compile`).
|
|
23
27
|
|
|
24
28
|
Beside it the game's data, `<name>.json` (its disks' files, its kind, and where it saves: `saves`).
|
|
@@ -28,10 +32,9 @@ Beside it the game's data, `<name>.json` (its disks' files, its kind, and where
|
|
|
28
32
|
<script type="importmap">{"imports": {"@moonbase-machines/player/": "/packages/player/", "@moonbase-machines/apple2e/": "/packages/apple2e/", "@moonbase-machines/c64/": "/packages/c64/", "@moonbase-machines/charset/": "/packages/charset/", "@moonbase-machines/mos6502/": "/packages/mos6502/", "@moonbase-machines/vector/": "/packages/vector/"}}</script>
|
|
29
33
|
<section id="player"></section>
|
|
30
34
|
<script type="module">
|
|
31
|
-
import { play } from '@moonbase-machines/player/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
root: document.getElementById('player'), disk: await openDisk('title/game'), words: { printer: { line: '', print: 'Print', tear: 'Tear off' } } });
|
|
35
|
+
import { play } from '@moonbase-machines/player/play.js';
|
|
36
|
+
await play({ way: 'remake', slug: 'game', title: new URL('title/', location).href, data: new URL('game.json', location).href,
|
|
37
|
+
root: document.getElementById('player'), words: { printer: { line: '', print: 'Print', tear: 'Tear off' } } });
|
|
35
38
|
</script>
|
|
36
39
|
```
|
|
37
40
|
|
package/bin/moonbase-play.mjs
CHANGED
|
@@ -31,8 +31,8 @@ async function page() {
|
|
|
31
31
|
.map(async f => ({ name: f.slice(0, -5), data: f, game: JSON.parse(await readFile(join(root, f), 'utf8')) })));
|
|
32
32
|
const slug = title?.slug ?? games[0]?.name, name = title?.name ?? games[0]?.game.title ?? slug;
|
|
33
33
|
// (the ways: the remake, its data the title's own or, for a title of its own engine, data/; an original for each game)
|
|
34
|
-
const ways = [...(title ? [{ label: 'Remake', search: '',
|
|
35
|
-
...games.map((g, i) => ({ label: games.length > 1 ? g.game.title : 'Original', search: !title && !i ? '' : games.length > 1 ? `?game=${g.name}` : '?way=original',
|
|
34
|
+
const ways = [...(title ? [{ label: 'Remake', search: '', way: 'remake', data: existsSync(join(root, slug + '.json')) ? slug + '.json' : 'data/' }] : []),
|
|
35
|
+
...games.map((g, i) => ({ label: games.length > 1 ? g.game.title : 'Original', search: !title && !i ? '' : games.length > 1 ? `?game=${g.name}` : '?way=original', way: 'original', data: g.data }))];
|
|
36
36
|
const map = { imports: Object.fromEntries(PACKAGES.map(p => [`@moonbase-machines/${p}/`, `/packages/${p}/`])) };
|
|
37
37
|
return `<!doctype html>
|
|
38
38
|
<html lang="en">
|
|
@@ -54,9 +54,9 @@ ${ways.length > 1 ? `<nav>${ways.map(w => `<a href="/${w.search}">${esc(w.label)
|
|
|
54
54
|
const ways = ${JSON.stringify(ways)};
|
|
55
55
|
const way = ways.find(w => w.search === location.search) ?? ways[0];
|
|
56
56
|
for (const a of document.querySelectorAll('nav a')) if (a.search === way.search) a.setAttribute('aria-current', 'page');
|
|
57
|
-
const
|
|
57
|
+
const { play } = await import('@moonbase-machines/player/play.js');
|
|
58
58
|
const at = path => new URL(path, location.origin + '/').href;
|
|
59
|
-
await play({ title: at('title/'), data: at(way.data), root: document.getElementById('player'),
|
|
59
|
+
await play({ way: way.way, slug: ${JSON.stringify(slug)}, title: ${title ? "at('title/')" : 'null'}, data: at(way.data), root: document.getElementById('player'),
|
|
60
60
|
words: { printer: { line: 'What the printer has printed.', print: 'Print', tear: 'Tear off' } } });
|
|
61
61
|
</script>
|
|
62
62
|
</body>
|
package/disk/disk.js
CHANGED
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
// disk.write(name, bytes, meta) a promise, kept when stored
|
|
16
16
|
// disk.remove(name) a promise
|
|
17
17
|
// disk.written() a promise: every write so far stored
|
|
18
|
+
// disk.failed = err => ... told of a write or remove the backend could not store (the queue goes on)
|
|
18
19
|
// disk.name, disk.kept the disk's name; whether its files outlast the page
|
|
20
|
+
// disk.where where they are kept, as the backend says ('this browser'), for the dialogs
|
|
19
21
|
//
|
|
20
22
|
// A disk's name says whose it is, so a title and a program can never share one: titleDisk(slug), a title's
|
|
21
23
|
// player's, 'title/zoopak'; programDisk(name), one of the site's own programs', 'program/basic'.
|
|
22
24
|
//
|
|
23
|
-
// The backend is where a disk's files are kept: { kept, load(disk), put(disk, file), delete(disk, name) },
|
|
25
|
+
// The backend is where a disk's files are kept: { kept, where, load(disk), put(disk, file), delete(disk, name) },
|
|
24
26
|
// a file { name, bytes, meta, modified }. This browser's is local(); an account's will be the API's.
|
|
25
27
|
import { local } from './local.js';
|
|
26
28
|
import { memory } from './memory.js';
|
|
@@ -30,10 +32,12 @@ export async function openDisk(name, { backend } = {}) {
|
|
|
30
32
|
if (!backend) { try { backend = await local(); } catch { backend = memory({ kept: false }); } }
|
|
31
33
|
const files = new Map((await backend.load(name)).map(f => [f.name, f]));
|
|
32
34
|
let queue = Promise.resolve();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
// (each job stored in turn; one that fails is said (`failed`) and the next goes on, its own promise rejected for a
|
|
36
|
+
// caller that waits for it)
|
|
37
|
+
const later = job => { const p = queue.then(job); queue = p.catch(err => disk.failed?.(err)); p.catch(() => {}); return p; };
|
|
38
|
+
const disk = {
|
|
35
39
|
name,
|
|
36
|
-
kept: backend.kept,
|
|
40
|
+
kept: backend.kept, where: backend.where ?? null, failed: null,
|
|
37
41
|
list: () => [...files.values()].sort((a, b) => b.modified - a.modified).map(({ name, bytes, meta, modified }) => ({ name, size: bytes.length, modified, meta })),
|
|
38
42
|
read: n => files.get(n)?.bytes ?? null,
|
|
39
43
|
write(n, bytes, meta = {}) {
|
|
@@ -47,6 +51,7 @@ export async function openDisk(name, { backend } = {}) {
|
|
|
47
51
|
},
|
|
48
52
|
written: () => queue,
|
|
49
53
|
};
|
|
54
|
+
return disk;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
export const titleDisk = slug => `title/${slug}`;
|
package/disk/local.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// keyed by its account, its disk and its name. The account is `local`, this browser's own, until there
|
|
3
3
|
// are accounts; theirs will be kept by the API, behind the same calls.
|
|
4
4
|
//
|
|
5
|
-
// await local({ account }) a backend: { kept, load(disk), put(disk, file), delete(disk, name) }
|
|
5
|
+
// await local({ account }) a backend: { kept, where, load(disk), put(disk, file), delete(disk, name) }
|
|
6
6
|
// (named here once, for a tool that puts a file on a disk too)
|
|
7
7
|
export const DB = 'moonbase', VERSION = 1, STORE = 'files', KEY = ['account', 'disk', 'name'], ACCOUNT = 'local';
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ export async function local({ account = ACCOUNT } = {}) {
|
|
|
18
18
|
const db = await opened();
|
|
19
19
|
const store = mode => db.transaction(STORE, mode).objectStore(STORE);
|
|
20
20
|
return {
|
|
21
|
-
kept: true,
|
|
21
|
+
kept: true, where: 'this browser',
|
|
22
22
|
// (the disk's files: those keyed from [account, disk] up to the next disk's)
|
|
23
23
|
load: async disk => (await asked(store('readonly').getAll(IDBKeyRange.bound([account, disk], [account, disk, []]))))
|
|
24
24
|
.map(({ name, bytes, meta, modified }) => ({ name, bytes, meta, modified })),
|