minimojs 1.0.0-alpha.4 → 1.0.0-alpha.40

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.
Files changed (71) hide show
  1. package/README.md +58 -20
  2. package/dist/internal/AnimationSystem.js +345 -0
  3. package/dist/internal/AssetSystem.d.ts +6 -0
  4. package/dist/internal/AssetSystem.js +281 -0
  5. package/dist/internal/BackgroundSystem.d.ts +1 -0
  6. package/dist/internal/BackgroundSystem.js +26 -0
  7. package/dist/internal/CanvasSystem.d.ts +1 -0
  8. package/dist/internal/CanvasSystem.js +51 -0
  9. package/dist/internal/DesignNamingCheck.d.ts +1 -0
  10. package/dist/internal/DesignNamingCheck.js +7 -0
  11. package/dist/internal/ExplosionSystem.d.ts +1 -0
  12. package/dist/internal/ExplosionSystem.js +540 -0
  13. package/dist/internal/InputSystem.d.ts +1 -0
  14. package/dist/internal/InputSystem.js +292 -0
  15. package/dist/internal/LoopSystem.d.ts +1 -0
  16. package/dist/internal/LoopSystem.js +61 -0
  17. package/dist/internal/PhysicsSystem.d.ts +1 -0
  18. package/dist/internal/PhysicsSystem.js +174 -0
  19. package/dist/internal/RenderSystem.d.ts +1 -0
  20. package/dist/internal/RenderSystem.js +1021 -0
  21. package/dist/internal/SoundSystem.d.ts +39 -0
  22. package/dist/internal/SoundSystem.js +292 -0
  23. package/dist/internal/SpriteSystem.d.ts +1 -0
  24. package/dist/internal/SpriteSystem.js +32 -0
  25. package/dist/internal/TextSystem.d.ts +1 -0
  26. package/dist/internal/TextSystem.js +16 -0
  27. package/dist/internal/TimerSystem.d.ts +1 -0
  28. package/dist/internal/TimerSystem.js +43 -0
  29. package/dist/internal/TrailSystem.d.ts +1 -0
  30. package/dist/internal/TrailSystem.js +116 -0
  31. package/dist/internal/TransitionSystem.d.ts +1 -0
  32. package/dist/internal/TransitionSystem.js +74 -0
  33. package/dist/internal/arcade-racer/ArcadeRacerCollisionSystem.d.ts +1 -0
  34. package/dist/internal/arcade-racer/ArcadeRacerCollisionSystem.js +198 -0
  35. package/dist/internal/arcade-racer/ArcadeRacerLaneSystem.d.ts +1 -0
  36. package/dist/internal/arcade-racer/ArcadeRacerLaneSystem.js +120 -0
  37. package/dist/internal/arcade-racer/ArcadeRacerRenderSystem.d.ts +1 -0
  38. package/dist/internal/arcade-racer/ArcadeRacerRenderSystem.js +599 -0
  39. package/dist/internal/arcade-racer/ArcadeRacerRoadSprite.d.ts +1 -0
  40. package/dist/internal/arcade-racer/ArcadeRacerRoadSprite.js +13 -0
  41. package/dist/internal/arcade-racer/ArcadeRacerTrackSystem.d.ts +1 -0
  42. package/dist/internal/arcade-racer/ArcadeRacerTrackSystem.js +447 -0
  43. package/dist/minimo-ambient.d.ts +299 -0
  44. package/dist/minimo-ambient.js +1445 -0
  45. package/dist/minimo-arcaderacer.d.ts +1431 -0
  46. package/dist/minimo-arcaderacer.js +2060 -0
  47. package/dist/minimo-cards.d.ts +359 -0
  48. package/dist/minimo-cards.js +819 -0
  49. package/dist/minimo-dice.d.ts +206 -0
  50. package/dist/minimo-dice.js +719 -0
  51. package/dist/minimo-fire.d.ts +282 -0
  52. package/dist/minimo-fire.js +930 -0
  53. package/dist/minimo-format.d.ts +632 -0
  54. package/dist/minimo-format.js +669 -0
  55. package/dist/minimo-leaderboard.d.ts +199 -0
  56. package/dist/minimo-leaderboard.js +566 -0
  57. package/dist/minimo-multiplayer.d.ts +374 -0
  58. package/dist/minimo-multiplayer.js +1310 -0
  59. package/dist/minimo.d.ts +1690 -233
  60. package/dist/minimo.js +2587 -892
  61. package/package.json +10 -3
  62. package/dist/animations.js +0 -30
  63. package/dist/audio.js +0 -17
  64. package/dist/game.js +0 -1105
  65. package/dist/input.js +0 -185
  66. package/dist/internal-types.js +0 -4
  67. package/dist/physics.js +0 -10
  68. package/dist/render.js +0 -75
  69. package/dist/sprite.js +0 -149
  70. package/dist/timers.js +0 -23
  71. /package/dist/{pointer-info.js → internal/AnimationSystem.d.ts} +0 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Images and sounds, loaded by key. Both kinds share one queue, one progress
3
+ * count, one failure report, and one namespace of keys, so a game's loading
4
+ * screen and its retry cover every file it loads.
5
+ *
6
+ * @internal
7
+ */
8
+ export class AssetSystem {
9
+ constructor(_soundLoader = null) {
10
+ this._soundLoader = _soundLoader;
11
+ /** @internal */
12
+ this._queuedImages = new Map();
13
+ /** @internal */
14
+ this._loadedImages = new Map();
15
+ /** @internal */
16
+ this._imageSources = new Map();
17
+ /** @internal */
18
+ this._imageVersions = new Map();
19
+ /**
20
+ * The asset file that declared each key, as the object passed to
21
+ * `loadAssets`. Keys are unique in the whole game, so a key belongs to
22
+ * exactly one asset file, or to `loadImage` and `createTexture`.
23
+ *
24
+ * @internal
25
+ */
26
+ this._assetFileByKey = new Map();
27
+ /** @internal */
28
+ this._registeredAssetFiles = new WeakSet();
29
+ /** @internal */
30
+ this._queuedSounds = new Map();
31
+ /** @internal */
32
+ this._loadedSounds = new Map();
33
+ /** @internal */
34
+ this._soundSources = new Map();
35
+ }
36
+ queueImage(key, src) {
37
+ const safeKey = key.trim();
38
+ const safeSrc = src.trim();
39
+ if (safeKey.length === 0) {
40
+ throw new Error("MinimoJS: Image keys must be non-empty strings.");
41
+ }
42
+ if (safeSrc.length === 0) {
43
+ throw new Error("MinimoJS: Image sources must be non-empty strings.");
44
+ }
45
+ this.refuseAssetFileKey(safeKey, "loadImage()");
46
+ this.refuseSoundKey(safeKey, "loadImage()");
47
+ this.enqueueImage(safeKey, safeSrc);
48
+ }
49
+ queueSound(key, src) {
50
+ const safeKey = key.trim();
51
+ const safeSrc = src.trim();
52
+ if (safeKey.length === 0) {
53
+ throw new Error("MinimoJS: Sound keys must be non-empty strings.");
54
+ }
55
+ if (safeSrc.length === 0) {
56
+ throw new Error("MinimoJS: Sound sources must be non-empty strings.");
57
+ }
58
+ this.refuseAssetFileKey(safeKey, "loadSound()");
59
+ if (this._imageSources.has(safeKey) || this._loadedImages.has(safeKey)) {
60
+ throw new Error(`MinimoJS: loadSound() cannot use the key "${safeKey}", which an image uses. Keys must be unique in the whole game.`);
61
+ }
62
+ this.enqueueSound(safeKey, safeSrc);
63
+ }
64
+ /**
65
+ * Registers a parsed asset file and queues its images. `source` is the
66
+ * object the game passed, which identifies the file, so loading the same
67
+ * file again, as a scene's `onPreload()` does on every entry, only queues
68
+ * images that are not loaded.
69
+ *
70
+ * Every key is checked before anything is queued, so a refused file
71
+ * registers nothing. Returns `true` the first time a file is registered.
72
+ */
73
+ registerAssetFile(file, source) {
74
+ for (const entry of file.assets) {
75
+ const owner = this._assetFileByKey.get(entry.key);
76
+ if (owner === undefined) {
77
+ if (this._imageSources.has(entry.key) || this._loadedImages.has(entry.key)) {
78
+ throw new Error(`MinimoJS: Asset key "${entry.key}" is already registered with loadImage() or createTexture(). Keys must be unique in the whole game.`);
79
+ }
80
+ if (this._soundSources.has(entry.key)) {
81
+ throw new Error(`MinimoJS: Asset key "${entry.key}" is already registered with loadSound(). Keys must be unique in the whole game.`);
82
+ }
83
+ }
84
+ else if (owner !== source) {
85
+ throw new Error(`MinimoJS: Asset key "${entry.key}" is declared by two asset files. Keys must be unique in the whole game.`);
86
+ }
87
+ }
88
+ for (const entry of file.assets) {
89
+ this._assetFileByKey.set(entry.key, source);
90
+ if (entry.type === "image") {
91
+ this.enqueueImage(entry.key, entry.src.trim());
92
+ }
93
+ else if (entry.type === "sound") {
94
+ this.enqueueSound(entry.key, entry.src.trim());
95
+ }
96
+ }
97
+ const isFirstRegistration = !this._registeredAssetFiles.has(source);
98
+ this._registeredAssetFiles.add(source);
99
+ return isFirstRegistration;
100
+ }
101
+ /** @internal */
102
+ enqueueImage(safeKey, safeSrc) {
103
+ const existingSrc = this._imageSources.get(safeKey);
104
+ if (existingSrc !== undefined && existingSrc !== safeSrc) {
105
+ throw new Error(`MinimoJS: Image key "${safeKey}" is already registered with a different source.`);
106
+ }
107
+ this._imageSources.set(safeKey, safeSrc);
108
+ this._queuedImages.set(safeKey, safeSrc);
109
+ }
110
+ /** @internal */
111
+ enqueueSound(safeKey, safeSrc) {
112
+ const existingSrc = this._soundSources.get(safeKey);
113
+ if (existingSrc !== undefined && existingSrc !== safeSrc) {
114
+ throw new Error(`MinimoJS: Sound key "${safeKey}" is already registered with a different source.`);
115
+ }
116
+ this._soundSources.set(safeKey, safeSrc);
117
+ if (!this._loadedSounds.has(safeKey)) {
118
+ this._queuedSounds.set(safeKey, safeSrc);
119
+ }
120
+ }
121
+ createTexture(key, width, height, painter) {
122
+ const safeKey = key.trim();
123
+ if (safeKey.length === 0) {
124
+ throw new Error("MinimoJS: Texture keys must be non-empty strings.");
125
+ }
126
+ if (typeof painter !== "function") {
127
+ throw new Error("MinimoJS: createTexture() requires a painter function.");
128
+ }
129
+ this.refuseAssetFileKey(safeKey, "createTexture()");
130
+ this.refuseSoundKey(safeKey, "createTexture()");
131
+ const canvas = document.createElement("canvas");
132
+ canvas.width = Math.max(1, Math.round(Number.isFinite(width) ? width : 1));
133
+ canvas.height = Math.max(1, Math.round(Number.isFinite(height) ? height : 1));
134
+ const ctx = canvas.getContext("2d");
135
+ if (!ctx) {
136
+ throw new Error("MinimoJS: Could not acquire a texture rendering context.");
137
+ }
138
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
139
+ painter(ctx, canvas);
140
+ this._queuedImages.delete(safeKey);
141
+ this._imageSources.delete(safeKey);
142
+ this._loadedImages.set(safeKey, canvas);
143
+ this.bumpImageVersion(safeKey);
144
+ return canvas;
145
+ }
146
+ getImage(key) {
147
+ return this._loadedImages.get(key);
148
+ }
149
+ getImageVersion(key) {
150
+ return this._imageVersions.get(key) ?? 0;
151
+ }
152
+ hasImage(key) {
153
+ return this._loadedImages.has(key);
154
+ }
155
+ /** Returns a decoded sound, as the sound loader returned it, if it is loaded. */
156
+ getSound(key) {
157
+ return this._loadedSounds.get(key);
158
+ }
159
+ hasSound(key) {
160
+ return this._loadedSounds.has(key);
161
+ }
162
+ /** Counts the queued images and sounds that are not loaded yet. */
163
+ getQueuedAssetCount() {
164
+ let total = 0;
165
+ for (const key of this._queuedImages.keys()) {
166
+ if (!this._loadedImages.has(key)) {
167
+ total++;
168
+ }
169
+ }
170
+ for (const key of this._queuedSounds.keys()) {
171
+ if (!this._loadedSounds.has(key)) {
172
+ total++;
173
+ }
174
+ }
175
+ return total;
176
+ }
177
+ /**
178
+ * Loads every queued image and sound that is not loaded yet, settling all
179
+ * of them before returning instead of stopping at the first failure.
180
+ *
181
+ * Loaded files leave the queue; failed ones stay in it, so a retry loads
182
+ * only them. Returns the keys that failed, empty when all loaded.
183
+ */
184
+ async loadQueuedAssets(onProgress) {
185
+ const images = [...this._queuedImages.entries()].filter(([key]) => !this._loadedImages.has(key));
186
+ const sounds = [...this._queuedSounds.entries()].filter(([key]) => !this._loadedSounds.has(key));
187
+ const total = images.length + sounds.length;
188
+ let settled = 0;
189
+ const failed = [];
190
+ onProgress?.(0, total, null);
191
+ if (total === 0) {
192
+ this._queuedImages.clear();
193
+ this._queuedSounds.clear();
194
+ return failed;
195
+ }
196
+ const settle = (key) => {
197
+ settled += 1;
198
+ onProgress?.(settled, total, key);
199
+ };
200
+ await Promise.all([
201
+ ...images.map(async ([key, src]) => {
202
+ try {
203
+ const image = await this.loadImage(src);
204
+ this._loadedImages.set(key, image);
205
+ this.bumpImageVersion(key);
206
+ this._queuedImages.delete(key);
207
+ }
208
+ catch {
209
+ failed.push(key);
210
+ }
211
+ settle(key);
212
+ }),
213
+ ...sounds.map(async ([key, src]) => {
214
+ try {
215
+ if (!this._soundLoader) {
216
+ throw new Error("MinimoJS: This game cannot load sounds.");
217
+ }
218
+ this._loadedSounds.set(key, await this._soundLoader(src));
219
+ this._queuedSounds.delete(key);
220
+ }
221
+ catch {
222
+ failed.push(key);
223
+ }
224
+ settle(key);
225
+ }),
226
+ ]);
227
+ for (const key of [...this._queuedImages.keys()]) {
228
+ if (this._loadedImages.has(key)) {
229
+ this._queuedImages.delete(key);
230
+ }
231
+ }
232
+ return failed;
233
+ }
234
+ /** Returns the source registered for an image or sound key, if any. */
235
+ getAssetSource(key) {
236
+ return this._imageSources.get(key) ?? this._soundSources.get(key);
237
+ }
238
+ /**
239
+ * Drops queued images and sounds that were not loaded, so a later load does
240
+ * not pick them up. Their keys may be registered again.
241
+ */
242
+ discardQueuedAssets() {
243
+ this._queuedImages.clear();
244
+ this._queuedSounds.clear();
245
+ }
246
+ /** @internal */
247
+ refuseSoundKey(safeKey, caller) {
248
+ if (this._soundSources.has(safeKey)) {
249
+ throw new Error(`MinimoJS: ${caller} cannot use the key "${safeKey}", which a sound uses. Keys must be unique in the whole game.`);
250
+ }
251
+ }
252
+ /** @internal */
253
+ refuseAssetFileKey(safeKey, caller) {
254
+ if (this._assetFileByKey.has(safeKey)) {
255
+ throw new Error(`MinimoJS: ${caller} cannot use the key "${safeKey}", which an asset file declares. Keys must be unique in the whole game.`);
256
+ }
257
+ }
258
+ /**
259
+ * Loads one image with a CORS request, as `fetch` loads sounds, so an image
260
+ * from another origin never taints the canvas and the game can still read
261
+ * its pixels or export it. A host that does not answer with CORS fails the
262
+ * image instead.
263
+ *
264
+ * @internal
265
+ */
266
+ loadImage(src) {
267
+ return new Promise((resolve, reject) => {
268
+ const image = new Image();
269
+ image.crossOrigin = "anonymous";
270
+ image.onload = () => resolve(image);
271
+ image.onerror = () => {
272
+ reject(new Error(`MinimoJS: Failed to load image "${src}".`));
273
+ };
274
+ image.src = src;
275
+ });
276
+ }
277
+ /** @internal */
278
+ bumpImageVersion(key) {
279
+ this._imageVersions.set(key, (this._imageVersions.get(key) ?? 0) + 1);
280
+ }
281
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ /** @internal */
2
+ export class BackgroundSystem {
3
+ constructor() {
4
+ /** @internal */
5
+ this._layers = [];
6
+ }
7
+ add(layer) {
8
+ this._layers.push(layer);
9
+ return layer;
10
+ }
11
+ destroyLayer(layer) {
12
+ const index = this._layers.indexOf(layer);
13
+ if (index !== -1) {
14
+ this._layers.splice(index, 1);
15
+ }
16
+ }
17
+ getLayers() {
18
+ return [...this._layers];
19
+ }
20
+ getMutableLayers() {
21
+ return this._layers;
22
+ }
23
+ clearAll() {
24
+ this._layers = [];
25
+ }
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ /** @internal */
2
+ export class CanvasSystem {
3
+ constructor(/** @internal */ canvas) {
4
+ this.canvas = canvas;
5
+ /** @internal */
6
+ this.onResize = () => this.applyResponsiveCanvasLayout();
7
+ /** @internal */
8
+ this.onViewportChange = () => this.applyResponsiveCanvasLayout();
9
+ }
10
+ initialize() {
11
+ const mountCanvas = () => {
12
+ if (document.body && !this.canvas.isConnected) {
13
+ document.body.appendChild(this.canvas);
14
+ }
15
+ this.applyResponsiveCanvasLayout();
16
+ };
17
+ if (document.body) {
18
+ mountCanvas();
19
+ }
20
+ else {
21
+ window.addEventListener("DOMContentLoaded", mountCanvas, { once: true });
22
+ }
23
+ window.addEventListener("resize", this.onResize);
24
+ window.visualViewport?.addEventListener("resize", this.onViewportChange);
25
+ window.visualViewport?.addEventListener("scroll", this.onViewportChange);
26
+ }
27
+ /** @internal */
28
+ applyResponsiveCanvasLayout() {
29
+ if (!document.body)
30
+ return;
31
+ const viewportW = Math.max(1, Math.floor(window.visualViewport?.width ??
32
+ window.innerWidth ??
33
+ document.documentElement.clientWidth ??
34
+ 1));
35
+ const viewportH = Math.max(1, Math.floor(window.visualViewport?.height ??
36
+ window.innerHeight ??
37
+ document.documentElement.clientHeight ??
38
+ 1));
39
+ document.body.style.margin = "0";
40
+ document.body.style.height = `${viewportH}px`;
41
+ document.body.style.minHeight = `${viewportH}px`;
42
+ document.body.style.display = "grid";
43
+ document.body.style.placeItems = "center";
44
+ document.body.style.touchAction = "manipulation";
45
+ document.body.style.overflow = "hidden";
46
+ const scale = Math.min(viewportW / this.canvas.width, viewportH / this.canvas.height);
47
+ const safeScale = Number.isFinite(scale) && scale > 0 ? scale : 1;
48
+ this.canvas.style.width = `${Math.floor(this.canvas.width * safeScale)}px`;
49
+ this.canvas.style.height = `${Math.floor(this.canvas.height * safeScale)}px`;
50
+ }
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ // A compile-time check, with no runtime code, that every property of a design
2
+ // object, except `id` and `type`, exists on its runtime class under the same
3
+ // name and with a compatible type (X-002). Designs use the runtime's names so
4
+ // agents learn one vocabulary for code and files. The build fails when a
5
+ // property is misnamed; a property that must be named differently would be
6
+ // declared here as an explicit exception.
7
+ export {};
@@ -0,0 +1 @@
1
+ export {};