@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 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`), on Moonbase's own ROMs, which need nothing of Apple's or
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`), 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.
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'), words: { printer: { line: '', print: 'Print', tear: 'Tear off' } } });
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
 
@@ -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>), on the browser's own player's
4
- // disk; each file sent with `Cache-Control: no-cache` so that a refresh asks for every module again. The folder holds
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 game)
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 }))];
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 way = ways.find(w => w.search === location.search) ?? ways[0];
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 { play } = await import('@moonbase-machines/player/play.js');
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: local() (local.js, the default)
12
- // or memory() (memory.js)
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
- // (a browser that keeps nothing, as a private window may: the disk in memory, and said so)
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