js-game-zero 0.0.0-stage → 0.3.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/LICENSE +22 -0
- package/README.md +138 -2
- package/dist/active-service.d.ts +21 -0
- package/dist/actor.d.ts +182 -0
- package/dist/animation.d.ts +61 -0
- package/dist/assets.d.ts +148 -0
- package/dist/audio.d.ts +150 -0
- package/dist/browser-asset-backends.d.ts +8 -0
- package/dist/browser.d.ts +10 -0
- package/dist/chunk-brk0rjcy.js +7846 -0
- package/dist/clock.d.ts +29 -0
- package/dist/color.d.ts +15 -0
- package/dist/events.d.ts +145 -0
- package/dist/friendly-names.d.ts +43 -0
- package/dist/geometry.d.ts +125 -0
- package/dist/helpers.d.ts +28 -0
- package/dist/host.d.ts +58 -0
- package/dist/host.js +435 -0
- package/dist/index.d.ts +340 -0
- package/dist/index.js +258 -0
- package/dist/input.d.ts +200 -0
- package/dist/inspection.d.ts +58 -0
- package/dist/jgz.js +8419 -0
- package/dist/node.d.ts +14 -0
- package/dist/node.js +145 -0
- package/dist/pixel-density.d.ts +23 -0
- package/dist/random.d.ts +31 -0
- package/dist/reference/actor.md +197 -0
- package/dist/reference/audio.md +128 -0
- package/dist/reference/clock.md +150 -0
- package/dist/reference/helpers.md +46 -0
- package/dist/reference/hooks.md +379 -0
- package/dist/reference/index.d.ts +2 -0
- package/dist/reference/index.js +39 -0
- package/dist/reference/loaders.md +164 -0
- package/dist/reference/rect.md +129 -0
- package/dist/reference/runtime.md +478 -0
- package/dist/reference/screen.md +313 -0
- package/dist/reference/storage.md +90 -0
- package/dist/rendering.d.ts +188 -0
- package/dist/runtime-contracts.d.ts +117 -0
- package/dist/runtime-diagnostics.d.ts +37 -0
- package/dist/runtime-persistence.d.ts +32 -0
- package/dist/runtime-source.d.ts +82 -0
- package/dist/runtime.d.ts +81 -0
- package/dist/source-scan.d.ts +49 -0
- package/dist/storage-path.d.ts +3 -0
- package/dist/storage.d.ts +88 -0
- package/dist/tone.d.ts +81 -0
- package/dist/touch-controls.d.ts +24 -0
- package/dist/touch-pointer.d.ts +11 -0
- package/dist/util.d.ts +62 -0
- package/package.json +69 -4
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# The `run()` Module API
|
|
2
|
+
|
|
3
|
+
Most learners never call `run()` directly — the sandbox and zero-boilerplate
|
|
4
|
+
examples call it for you, and you just write top-level `WIDTH`/`draw`/
|
|
5
|
+
`update`/hook definitions. This page is for import-mode code and for anyone
|
|
6
|
+
embedding the framework in a host page (a custom sandbox, a course platform,
|
|
7
|
+
an automated test).
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
function run(
|
|
11
|
+
moduleOrSource: GameModule | DefinedGame | string,
|
|
12
|
+
options?: RunOptions,
|
|
13
|
+
): GameHandle;
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Three entry styles
|
|
17
|
+
|
|
18
|
+
**Scoped import mode (recommended for applications)** — `defineGame()` gives
|
|
19
|
+
the game a typed set of services owned by that one run. Actors, assets,
|
|
20
|
+
storage, animation, input, and audio therefore cannot accidentally bind to a
|
|
21
|
+
different game on the same page:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { defineGame, run } from "js-game-zero";
|
|
25
|
+
|
|
26
|
+
run(
|
|
27
|
+
defineGame(({ Actor, screen, keyboard }) => {
|
|
28
|
+
const player = new Actor("player.svg");
|
|
29
|
+
return {
|
|
30
|
+
WIDTH: 320,
|
|
31
|
+
HEIGHT: 180,
|
|
32
|
+
update(dt) {
|
|
33
|
+
if (keyboard.right) player.x += 120 * dt;
|
|
34
|
+
},
|
|
35
|
+
draw() {
|
|
36
|
+
screen.clear();
|
|
37
|
+
player.draw();
|
|
38
|
+
},
|
|
39
|
+
onKeyDown(event) {
|
|
40
|
+
console.log(event.key);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The input hooks in a defined game receive one typed event object.
|
|
48
|
+
Zero-boilerplate and raw import hooks keep their Pygame Zero-style
|
|
49
|
+
parameter-name dispatch.
|
|
50
|
+
|
|
51
|
+
| Event | Fields |
|
|
52
|
+
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| `GameKeyEvent` (`onKeyDown`, `onKeyUp`) | `key`, `mod`, `unicode` and `repeat`, as [`hooks.md`](hooks.md#onkeydownkey-mod-unicode-repeat--onkeyupkey-mod-unicode-repeat) describes them. |
|
|
54
|
+
| `GameMouseEvent` (`onMouseDown`, `onMouseUp`, `onMouseMove`) | `pos`, `button` (the button pressed or released, or `null` for one with no `mouse` constant; `onMouseMove` reads `buttons` instead), `buttons` (a `Set` of held buttons) and `rel` (movement since the last event). |
|
|
55
|
+
|
|
56
|
+
The function given to `defineGame()` receives the run's own services:
|
|
57
|
+
`screen`, `clock`, `keyboard`, `mouse`, `images`, `sounds`, `music`, `fonts`,
|
|
58
|
+
`storage`, `tone`, `animate` and `exit`, the constants `keys` and `keymods`,
|
|
59
|
+
and the classes `Actor`, `Animation`, `Rect`, `ZRect`, `Screen` and
|
|
60
|
+
`Storage`. An `Actor` or `Animation` made from them belongs to that run.
|
|
61
|
+
|
|
62
|
+
Package-level `screen`, `clock`, `keyboard`, `images`, `sounds`, `music`, and
|
|
63
|
+
`storage` remain available for learner compatibility. They are convenience
|
|
64
|
+
proxies for a single active runtime; applications that can run more than one
|
|
65
|
+
game should prefer the services passed to `defineGame()`.
|
|
66
|
+
|
|
67
|
+
**Import mode** — pass a plain object of hooks and config:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import { run, screen, keyboard } from "js-game-zero";
|
|
71
|
+
|
|
72
|
+
run({
|
|
73
|
+
WIDTH: 320,
|
|
74
|
+
HEIGHT: 180,
|
|
75
|
+
draw() {
|
|
76
|
+
screen.fill("#10243d");
|
|
77
|
+
},
|
|
78
|
+
update(dt) {
|
|
79
|
+
/* ... */
|
|
80
|
+
},
|
|
81
|
+
onKeyDown(key) {
|
|
82
|
+
/* hooks receive values by parameter name */
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Global mode** — pass a source-code string, which runs with the framework's
|
|
88
|
+
globals (`Actor`, `screen`, `keyboard`, `WIDTH`, `draw`, ...) already in
|
|
89
|
+
scope, exactly like a zero-boilerplate sandbox project. This is how the
|
|
90
|
+
sandbox's preview pane runs the code from your editor.
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
run(`
|
|
94
|
+
WIDTH = 320;
|
|
95
|
+
HEIGHT = 180;
|
|
96
|
+
function draw() { screen.fill("navy"); }
|
|
97
|
+
`);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`handle.mode` tells you which one was used (`"import"` or `"global"`).
|
|
101
|
+
|
|
102
|
+
A global-mode game runs in its own scope rather than the page's. Reading a
|
|
103
|
+
name nothing defines throws a `ReferenceError` with a suggestion
|
|
104
|
+
(`scren is not defined. Did you mean screen?`); `typeof name` stays safe.
|
|
105
|
+
Browser APIs a game can't reach say what to use instead — for example,
|
|
106
|
+
`setTimeout` points at `clock.schedule(callback, seconds)`, and
|
|
107
|
+
`localStorage` at `storage`. Standard JavaScript built-ins such as `Math`,
|
|
108
|
+
`JSON`, `isNaN`, `Map` and `console` are available as usual.
|
|
109
|
+
|
|
110
|
+
Every library name has one spelling, in camelCase: `screen.draw.filledCircle`,
|
|
111
|
+
`actor.angleTo`, `rect.collidePoint`, `clock.scheduleInterval`, `onKeyDown`.
|
|
112
|
+
Constants stay upper case (`WIDTH`, `keys.SPACE`, `mouse.LEFT`). Another
|
|
113
|
+
spelling of a member, such as Pygame Zero's `actor.angle_to` or
|
|
114
|
+
`rect.colliderect`, throws where it's read, naming the real one
|
|
115
|
+
(`actor.angle_to doesn't exist. Did you mean actor.angleTo?`). In a
|
|
116
|
+
global-mode game, `screen`, `clock`, `music`, `tone`, `keys`, `keymods` and
|
|
117
|
+
`mouse` also throw with a suggestion for any member they don't have
|
|
118
|
+
(`screen.draw.cirlce doesn't exist. Did you mean screen.draw.circle?`), and
|
|
119
|
+
an undefined global suggests the real one
|
|
120
|
+
(`random_int is not defined. Did you mean randomInt?`).
|
|
121
|
+
The classes behind them (`Screen`, `Clock`, `Storage`) are globals too, so a
|
|
122
|
+
capital letter by mistake, as in `Screen.fill("navy")`, points at the service
|
|
123
|
+
instead (`Screen.fill doesn't exist. Did you mean screen.fill?`).
|
|
124
|
+
|
|
125
|
+
## `RunOptions`
|
|
126
|
+
|
|
127
|
+
| Option | Purpose |
|
|
128
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
129
|
+
| `canvas` | Draw into an existing `<canvas>` instead of creating one. |
|
|
130
|
+
| `container` | Where to mount a created canvas (default the page's `<body>`). Called from a script in `<head>`, before there's a `<body>`, the game starts at once and its canvas joins the `<body>` when the page has been read. |
|
|
131
|
+
| `autostart` | Default `true`. Set `false` to skip the automatic frame loop and drive frames yourself via `handle.step(dt)` — useful for deterministic tests. |
|
|
132
|
+
| `projectId` | Scopes storage keys and asset URLs, so multiple games on one page don't collide. |
|
|
133
|
+
| `assetBaseUrl` | Where asset URLs are rooted (default `/assets`). |
|
|
134
|
+
| `assetUrls` | The game's assets by path, each with its URL: `{ "images/cat.svg": "data:image/svg+xml;base64,…" }`. When set, assets load only from here, never from `assetBaseUrl`. See [assets without a server](loaders.md#assets-without-a-server). |
|
|
135
|
+
| `preload` | An array of asset paths (e.g. `["images/player.svg"]`) to start loading immediately, before any is referenced by code. |
|
|
136
|
+
| `preloadedAssets` | Pre-built `{ images, sounds, music, fonts }` asset maps to seed the cache synchronously — used by hosts that already have the bytes. `loadImages(assetUrls)` makes the `images` map; see [assets without a server](loaders.md#assets-without-a-server). |
|
|
137
|
+
| `storage` | A `Storage` instance or a raw `StorageAdapter` (`{ path, load(), save(text) }`) to use instead of the default `localStorage`-backed one. |
|
|
138
|
+
| `loaders` | Override one or more of the `images`/`sounds`/`music`/`fonts` loader instances. |
|
|
139
|
+
| `keyboardTarget` | The `EventTarget` that receives keyboard events (default `window`). |
|
|
140
|
+
| `mouseReleaseTarget` | The `EventTarget` where mouse releases (`mouseup`) are heard (default `window`, so a drag released outside the canvas still ends). A host driving a game itself passes the canvas, so its releases reach no other game. |
|
|
141
|
+
| `seed` | A string or number that makes the game's randomness repeatable: `Math.random` as a zero-boilerplate game sees it, and `randomInt` and `randomChoice`, follow one sequence for this seed, the sequence `jgz test --seed` uses. See [seeded runs](#seeded-runs). |
|
|
142
|
+
| `TOUCH_CONTROLS` | `true` for the full touch-control preset, `false` to disable it, or a button mapping with optional `show: "auto"` / `"always"`. See [touch controls](hooks.md#touch-controls). |
|
|
143
|
+
| `PIXEL_DENSITY` | Canvas pixels per logical pixel. Defaults to the device's pixel ratio, from 1 to 3. See [pixel density](#pixel-density). |
|
|
144
|
+
| `scheduler` | Override `requestAnimationFrame`/`cancelAnimationFrame` (for tests). |
|
|
145
|
+
| `diagnostics` | `(diagnostic) => void` — called for every diagnostic (see below). Without it, they go to the browser console. |
|
|
146
|
+
|
|
147
|
+
Any hook or `WIDTH`/`HEIGHT`/`PIXEL_DENSITY`/`TITLE`/`ICON`/`TOUCH_CONTROLS`
|
|
148
|
+
config can also be passed directly on `RunOptions`; if the same one is also present on the module
|
|
149
|
+
object, the option wins and a diagnostic warns about the duplicate.
|
|
150
|
+
|
|
151
|
+
## `GameHandle`
|
|
152
|
+
|
|
153
|
+
`run()` returns a handle for controlling the running game:
|
|
154
|
+
|
|
155
|
+
| Member | Purpose |
|
|
156
|
+
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| `canvas`, `screen`, `clock`, `keyboard`, `loaders`, `storage` | The live instances this run is using. |
|
|
158
|
+
| `module` | The final, merged hook/config object. |
|
|
159
|
+
| `diagnostics` | The diagnostics collected so far. |
|
|
160
|
+
| `state`, `failure` | Lifecycle state and the first fatal runtime failure, if one occurred. |
|
|
161
|
+
| `ready: Promise<void>` | Resolves once the preload gate is satisfied (see below). |
|
|
162
|
+
| `done: Promise<void>` | Resolves after final storage flushing and resource cleanup complete. |
|
|
163
|
+
| `pause()` / `resume()` | Stop/restart the frame loop without tearing anything down. |
|
|
164
|
+
| `step(dt)` | Manually advance one frame by `dt` seconds — only meaningful with `autostart: false`. |
|
|
165
|
+
| `stop()` | Detaches input listeners, flushes any pending storage save, and cancels the frame loop. |
|
|
166
|
+
| `dispose()` | Awaited form of cleanup; resolves only after pending persistence work is finished. |
|
|
167
|
+
| `restart(nextSource?)` | Awaits cleanup, then starts a fresh run with the same options. Resolves to the new run's handle; this one stays stopped. |
|
|
168
|
+
| `reload(nextSource?)` | Compatibility alias that starts synchronously and returns the new run's handle; prefer `restart()` when persistence ordering matters. |
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
const handle = run(gameModule, { canvas });
|
|
172
|
+
// later
|
|
173
|
+
handle.stop();
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## `exit()`
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
import { exit } from "js-game-zero";
|
|
180
|
+
exit(); // equivalent to calling .stop() on the currently active run() handle
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Also injected as a zero-boilerplate global, so game code can call `exit()`
|
|
184
|
+
directly to end itself.
|
|
185
|
+
|
|
186
|
+
## Preload gate
|
|
187
|
+
|
|
188
|
+
Each run tracks its own required image and font loads (Actor constructions,
|
|
189
|
+
`images.player` touches, and `preload` entries) and holds frames, with no
|
|
190
|
+
`update` and no `draw`, until those loads and storage initialization settle.
|
|
191
|
+
An image or font first asked for later, in a hook (a new `actor.image`, a first
|
|
192
|
+
`screen.blit`), holds the frames after it the same way until it has loaded,
|
|
193
|
+
and `handle.step()` does nothing meanwhile. So a host that steps a game in a
|
|
194
|
+
synchronous loop should hand it its images decoded, with
|
|
195
|
+
[`loadImages`](loaders.md#assets-without-a-server). A required image/font
|
|
196
|
+
failure rejects `handle.ready`, fails the handle, and performs cleanup. Sound and music loads
|
|
197
|
+
are recoverable: they report a warning but never freeze another run or stop
|
|
198
|
+
rendering. `handle.ready` lets hosts await startup explicitly instead of
|
|
199
|
+
polling.
|
|
200
|
+
|
|
201
|
+
## Pixel density
|
|
202
|
+
|
|
203
|
+
The canvas holds `WIDTH × HEIGHT × density` pixels, so a game is as sharp
|
|
204
|
+
as the screen it's on. Density is the device's `devicePixelRatio`, from 1
|
|
205
|
+
to 3, unless the game file or `RunOptions` sets `PIXEL_DENSITY`. A
|
|
206
|
+
`PIXEL_DENSITY` that isn't a positive number warns and uses the device's
|
|
207
|
+
ratio.
|
|
208
|
+
|
|
209
|
+
Game code works in logical pixels at any density; only reading the
|
|
210
|
+
canvas itself sees its real pixels:
|
|
211
|
+
|
|
212
|
+
| Logical pixels | Canvas pixels (logical × density) |
|
|
213
|
+
| -------------------------------------------------------- | --------------------------------- |
|
|
214
|
+
| `screen.width`, `screen.height`, `screen.bounds()` | `canvas.width`, `canvas.height` |
|
|
215
|
+
| Every drawing call, text, images and Actors | `getImageData()`, `toDataURL()` |
|
|
216
|
+
| Mouse and touch positions, Actor positions and hit tests | |
|
|
217
|
+
|
|
218
|
+
`screen.density` reports the density in use, so a host reading pixels back
|
|
219
|
+
multiplies logical coordinates by it.
|
|
220
|
+
|
|
221
|
+
**CSS size.** A denser canvas would show at its pixel size, so the runtime
|
|
222
|
+
gives it a CSS width of `WIDTH` px through a `:where(canvas[data-jgz-density])`
|
|
223
|
+
rule; its height follows the aspect ratio. The rule has no specificity, so
|
|
224
|
+
CSS the page sets on the canvas wins. A page that sizes the canvas should
|
|
225
|
+
set its width, or both width and height: a height alone keeps the rule's
|
|
226
|
+
width. The touch-control dock and the sandbox preview fit the canvas by its
|
|
227
|
+
logical size. At density 1 the canvas gets no rule and shows at its own
|
|
228
|
+
size.
|
|
229
|
+
|
|
230
|
+
**Changes.** Before each frame the runtime checks the canvas against the
|
|
231
|
+
screen:
|
|
232
|
+
|
|
233
|
+
- With no `PIXEL_DENSITY`, density follows `devicePixelRatio`, which
|
|
234
|
+
changes when the page is zoomed or moved to another display. The canvas
|
|
235
|
+
keeps what it shows, rescaled, for games that don't redraw everything.
|
|
236
|
+
- A host that sets `canvas.width` or `canvas.height` sets the logical size,
|
|
237
|
+
and the runtime multiplies it by the density.
|
|
238
|
+
- Setting a canvas's size resets its context, so the runtime restores the
|
|
239
|
+
density scale.
|
|
240
|
+
|
|
241
|
+
**Limit.** iOS Safari leaves a canvas over 16,777,216 pixels (4096²)
|
|
242
|
+
blank, so density comes down until the canvas fits, though never below 1
|
|
243
|
+
unless the game asks for less. A `PIXEL_DENSITY` that doesn't fit warns
|
|
244
|
+
and uses the most that does.
|
|
245
|
+
|
|
246
|
+
Raster images (PNG, JPEG) can't hold more detail than their own pixels. In
|
|
247
|
+
Chromium, SVG images draw from their vectors at the canvas's density, so an
|
|
248
|
+
SVG Actor stays sharp at any size.
|
|
249
|
+
|
|
250
|
+
## Package boundaries
|
|
251
|
+
|
|
252
|
+
Games import learner-facing values from `js-game-zero`. Embedders should
|
|
253
|
+
import loader factories, browser adapters, scheduler/diagnostic types, and
|
|
254
|
+
other integration APIs from `js-game-zero/host`; Node filesystem storage
|
|
255
|
+
lives at `js-game-zero/node`. Browser-safe advanced root imports remain
|
|
256
|
+
as deprecated compatibility aliases through 0.2.
|
|
257
|
+
|
|
258
|
+
Host tooling can also import `getGameInspector(handle)` from
|
|
259
|
+
`js-game-zero/host`. It returns a live, read-only binding inspector for a
|
|
260
|
+
running zero-boilerplate source game, or `null` for import-mode and disposed
|
|
261
|
+
handles (a failed game keeps its inspector, so a host can read the state at the
|
|
262
|
+
crash). This is the inspection seam the browser-backed `jgz` verification CLI
|
|
263
|
+
uses; it does not add globals or learner-facing runtime methods.
|
|
264
|
+
|
|
265
|
+
| Member | Purpose |
|
|
266
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
267
|
+
| `bindings()` | The captured non-function top-level identifiers, in source order. |
|
|
268
|
+
| `functions()` | The top-level functions. |
|
|
269
|
+
| `read(path)` | The live value at a binding or a [state path](#state-paths), as it is. |
|
|
270
|
+
| `inspect(path?)` | The value at a path as JSON, exactly as `jgz`'s `inspect` reports it (`serializeGameValue`); with no path, every binding's value by name. |
|
|
271
|
+
|
|
272
|
+
The same module exports `serializeGameValue(value)`, `parseStatePath(source)`,
|
|
273
|
+
`createSeededRandom(seed)` and `loadImages(assetUrls)` (see
|
|
274
|
+
[assets without a server](loaders.md#assets-without-a-server)).
|
|
275
|
+
|
|
276
|
+
The rest of `js-game-zero/host` is for building a host:
|
|
277
|
+
|
|
278
|
+
| Export | What it's for |
|
|
279
|
+
| ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
280
|
+
| `createImageLoader`, `createSoundLoader`, `createMusicLoader`, `createFontLoader` and `createAssetLoaders` | Loaders for `RunOptions.loaders`, each taking a `projectId` and an `assetBaseUrl` or `assetUrls`. |
|
|
281
|
+
| `createStorage({ adapter?, projectId?, path? })` and `createLocalStorageAdapter({ projectId?, path? })` | A `Storage`, or the `localStorage` adapter behind the default one, for `RunOptions.storage`. |
|
|
282
|
+
| `collectRuntimeDiagnostics(game, options?)` | The static [diagnostics](#diagnostics) `run()` would report for a game's source or module, without running it. |
|
|
283
|
+
| `layoutText(context, text, pos, options?)` and `fitTextboxFontSize(context, text, rect, options?)` | Where `screen.draw.text` and `screen.draw.textbox` would put text, and at what size. |
|
|
284
|
+
|
|
285
|
+
Its other exports (the input dispatcher, the `map…Event` functions,
|
|
286
|
+
`dispatchHook`, the audio and tone classes, `getScreen`/`setScreen`) are the
|
|
287
|
+
runtime's own parts, exported for tools and tests, and may change.
|
|
288
|
+
`js-game-zero/node` exports `createSandboxFileStorageAdapter({
|
|
289
|
+
projectRoot, path? })`, a `StorageAdapter` that keeps a project's storage in
|
|
290
|
+
a JSON file under `projectRoot` (`storage.json` by default).
|
|
291
|
+
|
|
292
|
+
This reference ships with `js-game-zero`, for a host that hands it to a
|
|
293
|
+
learner, a tool or a model. `js-game-zero/reference` exports `reference`,
|
|
294
|
+
every page's Markdown by its file name (`reference["actor.md"]`), and each
|
|
295
|
+
page is also a file at `js-game-zero/reference/<page>.md`.
|
|
296
|
+
|
|
297
|
+
### State paths
|
|
298
|
+
|
|
299
|
+
A path names a top-level binding, then `.name` for a property and `[n]` for an
|
|
300
|
+
index: `score`, `player.x`, `fish[0].y`. `parseStatePath("fish[0].y")` returns
|
|
301
|
+
`{ source: "fish[0].y", binding: "fish", segments: [0, "y"] }`, and throws a
|
|
302
|
+
`TypeError` for anything else, including `__proto__`, `prototype` and
|
|
303
|
+
`constructor`. `jgz`'s `inspect` takes the same paths.
|
|
304
|
+
|
|
305
|
+
### What a frame drew
|
|
306
|
+
|
|
307
|
+
A path that starts with `$drawn` reads what the game's last `draw()` drew,
|
|
308
|
+
in order, rather than one of its bindings, through `read`, `inspect` and
|
|
309
|
+
`jgz`'s `inspect` alike. Each thing drawn has its `kind` and what it is and
|
|
310
|
+
where, as the game gave them:
|
|
311
|
+
|
|
312
|
+
| `kind` | Fields | From |
|
|
313
|
+
| --------- | ----------------------------------------------------------------------------- | --------------------------------------------------------- |
|
|
314
|
+
| `fill` | `color`, and `to` for a gradient | `screen.fill` |
|
|
315
|
+
| `clear` | | `screen.clear` |
|
|
316
|
+
| `image` | `image` (its name), `x`, `y` (its top-left) | `screen.blit`, `screen.draw.image` |
|
|
317
|
+
| `actor` | `image`, `x`, `y` (its `pos`: its center, unless its `anchor` says otherwise) | an Actor's `draw()` |
|
|
318
|
+
| `circle` | `x`, `y`, `radius`, `color`, `filled` | `screen.circle`, `screen.draw.circle`, `filledCircle` |
|
|
319
|
+
| `rect` | `x`, `y`, `width`, `height`, `color`, `filled` | `screen.rect`, `screen.draw.rect`, `filledRect` |
|
|
320
|
+
| `polygon` | `x`, `y` (its first point), `points` (how many), `color`, `filled` | `screen.triangle`, `screen.draw.polygon`, `filledPolygon` |
|
|
321
|
+
| `line` | `x`, `y`, `x2`, `y2`, `color` | `screen.line`, `screen.draw.line` |
|
|
322
|
+
| `text` | `text`, `x`, `y`, `width`, `height` (its box), and `color` when it has one | `screen.text`, `screen.draw.text`, `screen.draw.textbox` |
|
|
323
|
+
|
|
324
|
+
`$drawn[0].kind`, `$drawn[2].image` and the like read single things. A frame
|
|
325
|
+
keeps its first 200 things; before the first frame `$drawn` is empty. So a
|
|
326
|
+
check that something is on the screen reads what the game drew rather than
|
|
327
|
+
its pixels: `{ kind: "actor", image: "robot.svg" }` is in `$drawn`.
|
|
328
|
+
|
|
329
|
+
### Inspected values
|
|
330
|
+
|
|
331
|
+
`serializeGameValue` turns a value into JSON: numbers, strings, booleans,
|
|
332
|
+
arrays and plain objects as they are (object keys sorted); anything else tagged
|
|
333
|
+
with `$type`:
|
|
334
|
+
|
|
335
|
+
```js
|
|
336
|
+
serializeGameValue(new Actor("cat", [40, 60]));
|
|
337
|
+
// { $type: "Actor", image: "cat", x: 40, y: 60, width, height, angle, …, properties: {} }
|
|
338
|
+
serializeGameValue(new Rect(1, 2, 3, 4));
|
|
339
|
+
// { $type: "Rect", left: 1, top: 2, right: 4, bottom: 6, width: 3, height: 4, center: [2.5, 4] }
|
|
340
|
+
serializeGameValue({ missing: undefined, big: Infinity });
|
|
341
|
+
// { big: { $type: "Number", value: "Infinity" }, missing: { $type: "Undefined" } }
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
An Actor's `properties` are the fields the game gave it (`cat.speed = 40` is
|
|
345
|
+
`properties.speed`); a `read` gives the Actor itself, so `cat.speed` there.
|
|
346
|
+
Sets, maps, dates and errors are tagged too; values deeper than six levels,
|
|
347
|
+
lists past 100 items and circular references come back as `Truncated` or
|
|
348
|
+
`Circular`.
|
|
349
|
+
|
|
350
|
+
### Seeded runs
|
|
351
|
+
|
|
352
|
+
`run(source, { seed })` gives the game a sequence of its own. A
|
|
353
|
+
zero-boilerplate game sees a `Math` whose `random` follows the seed (every
|
|
354
|
+
other member is the real `Math`'s), and `randomInt` and `randomChoice` draw
|
|
355
|
+
from it while the game runs, in its hooks and its top-level code alike. An
|
|
356
|
+
import-mode game's `randomInt` and `randomChoice` follow the seed in its hooks;
|
|
357
|
+
its `Math.random` is the page's. Nothing else changes: the page's own
|
|
358
|
+
`Math.random`, and any other game on the page, keep their randomness, and a
|
|
359
|
+
`restart()` starts the sequence again.
|
|
360
|
+
|
|
361
|
+
Up to nine plain digits are the number they spell (`42` and `"42"` are the same
|
|
362
|
+
seed); any other string is hashed (FNV-1a), and the sequence is Mulberry32 over
|
|
363
|
+
that 32-bit state. `jgz test --seed` passes its seed to the same option, so one
|
|
364
|
+
seed gives the same values in the CLI and in a host page;
|
|
365
|
+
`createSeededRandom(seed)` returns the sequence itself, for a host or a test to
|
|
366
|
+
predict. A seed that isn't a string or a finite number fails at startup.
|
|
367
|
+
|
|
368
|
+
### Driving a game from a host page
|
|
369
|
+
|
|
370
|
+
The script-tag build is `dist/jgz.js` in `js-game-zero` (`js-game-zero/iife`),
|
|
371
|
+
which jsDelivr also serves, at
|
|
372
|
+
`https://cdn.jsdelivr.net/npm/js-game-zero@0.3/dist/jgz.js`.
|
|
373
|
+
Its `window.JGZ` carries `loadImages`, `getGameInspector`,
|
|
374
|
+
`serializeGameValue`, `parseStatePath` and `createSeededRandom` beside the
|
|
375
|
+
learner-facing API, so a page can run a game it drives itself, the way the CLI
|
|
376
|
+
does, without a bundler:
|
|
377
|
+
|
|
378
|
+
```js
|
|
379
|
+
const canvas = document.createElement("canvas"); // never added to the page
|
|
380
|
+
const handle = JGZ.run(source, {
|
|
381
|
+
canvas,
|
|
382
|
+
autostart: false,
|
|
383
|
+
seed: "check-1",
|
|
384
|
+
keyboardTarget: canvas,
|
|
385
|
+
mouseReleaseTarget: canvas,
|
|
386
|
+
PIXEL_DENSITY: 1,
|
|
387
|
+
});
|
|
388
|
+
await handle.ready;
|
|
389
|
+
handle.step(0); // draws the first screen, as the CLI does
|
|
390
|
+
canvas.dispatchEvent(
|
|
391
|
+
new KeyboardEvent("keydown", { key: "ArrowRight", code: "ArrowRight" }),
|
|
392
|
+
);
|
|
393
|
+
for (let frame = 0; frame < 30; frame += 1) handle.step(1 / 60);
|
|
394
|
+
canvas.dispatchEvent(
|
|
395
|
+
new KeyboardEvent("keyup", { key: "ArrowRight", code: "ArrowRight" }),
|
|
396
|
+
);
|
|
397
|
+
canvas.dispatchEvent(
|
|
398
|
+
new MouseEvent("mousedown", {
|
|
399
|
+
clientX: 25,
|
|
400
|
+
clientY: 30,
|
|
401
|
+
button: 0,
|
|
402
|
+
buttons: 1,
|
|
403
|
+
}),
|
|
404
|
+
);
|
|
405
|
+
canvas.dispatchEvent(
|
|
406
|
+
new MouseEvent("mouseup", { clientX: 25, clientY: 30, button: 0 }),
|
|
407
|
+
);
|
|
408
|
+
JGZ.getGameInspector(handle).inspect("player.x");
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
- **Frames.** With `autostart: false`, nothing runs until the page calls
|
|
412
|
+
`handle.step(dt)`; each call is one `update(dt)` and one `draw()`. The CLI
|
|
413
|
+
steps once with `dt` 0 after `ready`, then one sixtieth of a second per frame,
|
|
414
|
+
so a page that does the same sees what `jgz test play` sees.
|
|
415
|
+
- **Keys.** Keyboard events are heard on `keyboardTarget` (default `window`):
|
|
416
|
+
`keydown` and `keyup`, read by `code` first (`ArrowRight`, `Space`, `KeyA`)
|
|
417
|
+
and then `key`. Passing the canvas keeps them from reaching other games.
|
|
418
|
+
- **Mouse and taps.** `mousedown`, `mousemove` and `wheel` are heard on the
|
|
419
|
+
canvas, `mouseup` on `mouseReleaseTarget` (default `window`). A position is
|
|
420
|
+
mapped from the canvas's displayed box onto the logical screen:
|
|
421
|
+
`clientX = box.left + x / screen.width * box.width`, and the same for `y`
|
|
422
|
+
(`handle.canvas.getBoundingClientRect()`, `handle.screen`). A canvas that
|
|
423
|
+
isn't on the page has an empty box, so its client coordinates are the
|
|
424
|
+
logical ones, as above. Touch arrives as `pointerdown`, `pointermove` and
|
|
425
|
+
`pointerup` with `pointerType: "touch"`; the CLI uses the mouse events.
|
|
426
|
+
|
|
427
|
+
## Diagnostics
|
|
428
|
+
|
|
429
|
+
`run()` performs lightweight static checks and reports them through the
|
|
430
|
+
`diagnostics` option (and collects them on `handle.diagnostics`) rather than
|
|
431
|
+
failing outright. A few mistakes stop the game before it starts instead:
|
|
432
|
+
`run()` reports a `runtime-error` in the `startup` phase and throws. The
|
|
433
|
+
main one is that **`draw()` must not declare any parameters** (a
|
|
434
|
+
`TypeError`, since mixing it up with `update(dt)` is a very common typo).
|
|
435
|
+
The others are a `TOUCH_CONTROLS` that isn't a valid mapping, a `seed` that
|
|
436
|
+
isn't a string or a finite number, an invalid `projectId` or `assetUrls`, and
|
|
437
|
+
a top-level statement that throws.
|
|
438
|
+
|
|
439
|
+
Everything else is a warning:
|
|
440
|
+
|
|
441
|
+
| `code` | Meaning |
|
|
442
|
+
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
443
|
+
| `hook-typo` | A top-level function is named nearly like a hook (e.g. `onKeyDwon`), or is a hook spelled another way (`on_key_down`), which is never called. |
|
|
444
|
+
| `constant-typo` | A top-level assignment is named nearly like a setting, such as `WIDTH` or `TITLE`. |
|
|
445
|
+
| `duplicate-config` | The same setting was given both on the module and in the run options. |
|
|
446
|
+
| `hook-arity` | `update` declares more than one parameter. |
|
|
447
|
+
| `hook-parameter` | An input hook parameter isn't one of its documented names; says what it receives. |
|
|
448
|
+
| `invalid-config` | `WIDTH`/`HEIGHT`/`PIXEL_DENSITY`/`ICON` isn't usable. |
|
|
449
|
+
| `asset-name` | A quoted string that looks like an asset filename doesn't match the naming grammar. |
|
|
450
|
+
| `runtime-error` | An uncaught error, or a failure loading/saving storage. |
|
|
451
|
+
|
|
452
|
+
The two typo checks only look at names the script _defines_ at top level
|
|
453
|
+
(`function drow()`, `const udpate = () => {}`, `WIDHT = 800`), never at names
|
|
454
|
+
it merely uses (`row`, `col`, `rows[y]`), and they stay quiet when the real
|
|
455
|
+
name is also defined. Short names get one edit of slack and longer ones two.
|
|
456
|
+
|
|
457
|
+
A `runtime-error` raised by the game's own code (a hook that threw, or a
|
|
458
|
+
top-level statement at startup) also carries `line`: the 1-based line in the
|
|
459
|
+
game source where the error was thrown, when the engine's stack trace names it.
|
|
460
|
+
A `hook-typo` or `constant-typo` warning carries the line that declares the
|
|
461
|
+
misspelled name, and `draw()` declaring parameters fails on the line that
|
|
462
|
+
declares it.
|
|
463
|
+
A syntax error carries one too: where the engine gives no position (V8 gives
|
|
464
|
+
none for compiled game code), it's the first line whose part of the source
|
|
465
|
+
fails the same way, so a missing `)` is placed on the line it's missing from,
|
|
466
|
+
and a block left open on the line it opens on. A name the game never defined
|
|
467
|
+
is placed the same way where the engine gives no position (JavaScriptCore): on
|
|
468
|
+
the one line that reads it, when only one does. `handle.failure.line` is the
|
|
469
|
+
same number. Errors thrown inside the library have no `line`.
|
|
470
|
+
|
|
471
|
+
The sandbox shows these under Messages, and marks a problem's line in the editor; import-mode hosts can pass
|
|
472
|
+
a `diagnostics` callback to log or display them however they like. Without
|
|
473
|
+
one, a run reports them to the browser console: a warning with
|
|
474
|
+
`console.warn` and an error with `console.error`, each starting
|
|
475
|
+
`js-game-zero:` and ending with its line, if it has one. A mistake that
|
|
476
|
+
makes `run()` itself throw isn't logged as well. A game that fails while
|
|
477
|
+
running removes a canvas it made, so on a page with no callback, the
|
|
478
|
+
console is where to find out why.
|