@moonbase-machines/player 0.10.0 → 0.12.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 -8
- package/bin/moonbase-play.mjs +18 -7
- package/disk/disk.js +5 -7
- package/dist/moonbase-player.js +16 -16
- package/dist/moonbase-player.js.map +4 -4
- package/engines/original.js +10 -8
- package/engines/scenes.js +29 -31
- package/index.js +2 -0
- package/package.json +6 -6
- package/play.js +59 -50
- package/provider.js +27 -0
- package/remake/README.md +138 -0
- package/settings.js +82 -0
- package/test/play.test.js +76 -2
package/README.md
CHANGED
|
@@ -3,18 +3,28 @@
|
|
|
3
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
4
|
|
|
5
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`)
|
|
7
|
-
Commodore's.
|
|
6
|
+
Commodore 64 (`@moonbase-machines/apple2e`, `c64`). It's the Original on the makers' ROMs, where the page gives
|
|
7
|
+
them (`roms`), and the Replica on Moonbase's own ROMs, which need nothing of Apple's or Commodore's.
|
|
8
8
|
- **Remake** (`way: 'remake'`): the game's scenes, lifted from its code and compiled to JavaScript, on the remake's
|
|
9
9
|
engine (`engines/scenes.js`, a title of `card` or `vector` scenes), or the title's own engine, a module in its folder.
|
|
10
10
|
|
|
11
|
+
[The four layers](../../docs/layers.md) explains how the Original, the Replica and the Remake differ, and what runs
|
|
12
|
+
each one.
|
|
13
|
+
|
|
11
14
|
The player makes its part of the page and owns it, the same for every title: the section (classes `player`, the way,
|
|
12
15
|
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
16
|
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`),
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
the saves (`saves.js`), the settings the player and its engine act on, each declared where it acts and shown only
|
|
18
|
+
there (`settings.js`), and what the page asks of it (`watch`, `claim`, `pace`, keys and a route). An engine plays
|
|
19
|
+
the game on the stage and returns its handle (`play.js` says what it may have). The page tells the player everything
|
|
20
|
+
and the player reads no address and keeps nothing itself: the page gives the title's disk, a virtual disk of files
|
|
21
|
+
(`disk/disk.js` `openDisk`), and the person's settings, a typed store (`settings.js`: a setting is a declared name,
|
|
22
|
+
type, default and owner; only a declared setting can be read or kept, its value checked). Both come from a
|
|
23
|
+
**provider** (`provider.js`), which chooses once a visit where files and settings live, the backend (this browser's,
|
|
24
|
+
IndexedDB; an account's, a site's own); a site makes one a page from a module of its own, giving its policy of
|
|
25
|
+
settings (hidden, fixed, or a value for the visit) and the player never chooses. Nothing in the package knows of an
|
|
26
|
+
address: a page that lets its address set anything reads it itself, each input by its type. The disk has its own
|
|
27
|
+
dialogs (`disk/dialogs.js`, in a modal window, `modal.js`). A game saved in one way loads in the other.
|
|
18
28
|
|
|
19
29
|
No title is named in it. A title is a folder of its own, kept with the title, which the page gives the player:
|
|
20
30
|
|
|
@@ -23,7 +33,8 @@ No title is named in it. A title is a folder of its own, kept with the title, wh
|
|
|
23
33
|
scenes call, by address, and what its engine needs beside them: `remake/cards.js` and `remake/vector.js` say what
|
|
24
34
|
each engine reads), the modules it brings (`modules`, its problems; `view`, its view's own drawing), its font's own
|
|
25
35
|
glyphs (`font`), its stylesheet (`style`);
|
|
26
|
-
- its scenes (`<name>.scene`) and `compiled/` (`moonbase-compile`).
|
|
36
|
+
- its scenes (`<name>.scene`) and `compiled/` (`moonbase-compile`); `remake/README.md` says what a scene is, how it
|
|
37
|
+
is compiled and run, and the two engines.
|
|
27
38
|
|
|
28
39
|
Beside it the game's data, `<name>.json` (its disks' files, its kind, and where it saves: `saves`).
|
|
29
40
|
|
|
@@ -33,8 +44,12 @@ Beside it the game's data, `<name>.json` (its disks' files, its kind, and where
|
|
|
33
44
|
<section id="player"></section>
|
|
34
45
|
<script type="module">
|
|
35
46
|
import { play } from '@moonbase-machines/player/play.js';
|
|
47
|
+
import { titleDisk } from '@moonbase-machines/player/disk/disk.js';
|
|
48
|
+
import { provider } from '@moonbase-machines/player/provider.js';
|
|
49
|
+
const { disk, settings } = await provider(); // (this browser's)
|
|
36
50
|
await play({ way: 'remake', slug: 'game', title: new URL('title/', location).href, data: new URL('game.json', location).href,
|
|
37
|
-
root: document.getElementById('player'),
|
|
51
|
+
root: document.getElementById('player'), disk: await disk(titleDisk('game')), settings,
|
|
52
|
+
words: { printer: { line: '', print: 'Print', tear: 'Tear off' } } });
|
|
38
53
|
</script>
|
|
39
54
|
```
|
|
40
55
|
|
package/bin/moonbase-play.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// A title played on this machine: its page at http://127.0.0.1:8002/, its Remake where it has one, and an Original for
|
|
3
|
-
// each game's data beside it (/?way=original, or with more than one /?game=<name>)
|
|
4
|
-
//
|
|
3
|
+
// each game's data beside it (/?way=original, or with more than one /?game=<name>); its disk and settings from the
|
|
4
|
+
// package's own provider (provider.js), this browser's, as the page has no site to give them; the page's own inputs
|
|
5
|
+
// its own, each checked: ?way and ?game one of the ways it has, ?keys=space,return keys to press at the start; each
|
|
6
|
+
// file sent with `Cache-Control: no-cache` so that a refresh asks for every module again. The folder holds
|
|
5
7
|
// the title's folder, title/ (title.js, its scenes, compiled/), if it is remade, and its games' data, <name>.json
|
|
6
8
|
// each, or for a title of its own engine data/; the @moonbase-machines packages are served from node_modules at
|
|
7
9
|
// /packages/, where the page's import map finds them. A folder with an index.html of its own is served as it is.
|
|
@@ -30,9 +32,10 @@ async function page() {
|
|
|
30
32
|
const games = await Promise.all(readdirSync(root).filter(f => f.endsWith('.json') && f !== 'package.json').sort((a, b) => (b === title?.slug + '.json') - (a === title?.slug + '.json') || a.slice(0, -5).localeCompare(b.slice(0, -5)))
|
|
31
33
|
.map(async f => ({ name: f.slice(0, -5), data: f, game: JSON.parse(await readFile(join(root, f), 'utf8')) })));
|
|
32
34
|
const slug = title?.slug ?? games[0]?.name, name = title?.name ?? games[0]?.game.title ?? slug;
|
|
33
|
-
// (the ways: the remake, its data the title's own or, for a title of its own engine, data/; an original for each
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
// (the ways: the remake, its data the title's own or, for a title of its own engine, data/; an original for each
|
|
36
|
+
// game, by the game's name where there are several; each with its address)
|
|
37
|
+
const ways = [...(title ? [{ label: 'Remake', search: '', way: 'remake', game: null, data: existsSync(join(root, slug + '.json')) ? slug + '.json' : 'data/' }] : []),
|
|
38
|
+
...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', game: games.length > 1 ? g.name : null, data: g.data }))];
|
|
36
39
|
const map = { imports: Object.fromEntries(PACKAGES.map(p => [`@moonbase-machines/${p}/`, `/packages/${p}/`])) };
|
|
37
40
|
return `<!doctype html>
|
|
38
41
|
<html lang="en">
|
|
@@ -52,11 +55,19 @@ ${ways.length > 1 ? `<nav>${ways.map(w => `<a href="/${w.search}">${esc(w.label)
|
|
|
52
55
|
</main>
|
|
53
56
|
<script type="module">
|
|
54
57
|
const ways = ${JSON.stringify(ways)};
|
|
55
|
-
const
|
|
58
|
+
const [{ play }, { titleDisk }, { provider }] = await Promise.all(['play.js', 'disk/disk.js', 'provider.js'].map(m => import('@moonbase-machines/player/' + m)));
|
|
59
|
+
// (the page's own inputs, each checked: the way or a game by its name, one of those it has, else the first; the keys
|
|
60
|
+
// to press, names of letters, digits and dashes; anything else refused and said)
|
|
61
|
+
const q = new URLSearchParams(location.search), refused = (n, what) => { console.warn('?' + n + '=' + q.get(n) + ' refused: not ' + what); return null; };
|
|
62
|
+
const one = (n, of) => !q.has(n) ? null : of.includes(q.get(n)) ? q.get(n) : refused(n, 'one of ' + of.join(', '));
|
|
63
|
+
const askedGame = one('game', ways.map(w => w.game).filter(Boolean)), askedWay = one('way', [...new Set(ways.map(w => w.way))]);
|
|
64
|
+
const way = ways.find(w => askedGame ? w.game === askedGame : askedWay ? w.way === askedWay : true) ?? ways[0];
|
|
56
65
|
for (const a of document.querySelectorAll('nav a')) if (a.search === way.search) a.setAttribute('aria-current', 'page');
|
|
57
|
-
const
|
|
66
|
+
const keys = !q.has('keys') ? undefined : /^[\\w-]+(,[\\w-]+)*$/.test(q.get('keys')) ? q.get('keys').split(',') : refused('keys', 'names separated by commas') ?? undefined;
|
|
58
67
|
const at = path => new URL(path, location.origin + '/').href;
|
|
68
|
+
const { disk, settings } = await provider();
|
|
59
69
|
await play({ way: way.way, slug: ${JSON.stringify(slug)}, title: ${title ? "at('title/')" : 'null'}, data: at(way.data), root: document.getElementById('player'),
|
|
70
|
+
disk: await disk(titleDisk(${JSON.stringify(slug)})), settings, keys,
|
|
60
71
|
words: { printer: { line: 'What the printer has printed.', print: 'Print', tear: 'Tear off' } } });
|
|
61
72
|
</script>
|
|
62
73
|
</body>
|
package/disk/disk.js
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
// @moonbase-machines/c64/drive.js's find), so a read is never waited for. A write changes the copy at once and is stored behind
|
|
9
9
|
// it, in order.
|
|
10
10
|
//
|
|
11
|
-
// const disk = await openDisk(name, { backend }) backend:
|
|
12
|
-
//
|
|
11
|
+
// const disk = await openDisk(name, { backend }) backend: where its files are kept, the provider's choice
|
|
12
|
+
// (provider.js), never the disk's: local() (local.js, this
|
|
13
|
+
// browser's) or memory() (memory.js)
|
|
13
14
|
// disk.list() [{ name, size, modified, meta }], newest first
|
|
14
15
|
// disk.read(name) its bytes, or null
|
|
15
16
|
// disk.write(name, bytes, meta) a promise, kept when stored
|
|
@@ -24,12 +25,9 @@
|
|
|
24
25
|
//
|
|
25
26
|
// The backend is where a disk's files are kept: { kept, where, load(disk), put(disk, file), delete(disk, name) },
|
|
26
27
|
// a file { name, bytes, meta, modified }. This browser's is local(); an account's will be the API's.
|
|
27
|
-
import { local } from './local.js';
|
|
28
|
-
import { memory } from './memory.js';
|
|
29
28
|
|
|
30
|
-
export async function openDisk(name, { backend }
|
|
31
|
-
|
|
32
|
-
if (!backend) { try { backend = await local(); } catch { backend = memory({ kept: false }); } }
|
|
29
|
+
export async function openDisk(name, { backend }) {
|
|
30
|
+
if (!backend) throw new Error('openDisk: a backend is the provider\'s to give (provider.js)');
|
|
33
31
|
const files = new Map((await backend.load(name)).map(f => [f.name, f]));
|
|
34
32
|
let queue = Promise.resolve();
|
|
35
33
|
// (each job stored in turn; one that fails is said (`failed`) and the next goes on, its own promise rejected for a
|