zx-kit 0.30.0 → 0.31.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 +61 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/monoscreen.d.ts +82 -0
- package/dist/monoscreen.d.ts.map +1 -0
- package/dist/monoscreen.js +105 -0
- package/dist/monoscreen.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **A Speccy-flavoured fantasy toolkit for tiny TypeScript browser games.**
|
|
4
4
|
> Inspired by the ZX Spectrum — not an emulator, not a hardware clone.
|
|
5
5
|
|
|
6
|
-
Spectrum-palette canvas rendering. ROM bitmap font. AY-3-8912 three-channel audio. Beeper SFX. Tile maps. Free-roaming sprites. Collision detection. Saves. Camera. Scene manager. Particle pool. Dithered lighting. Offscreen layer cache. Authentic attribute clash. Zero dependencies. TypeScript-first.
|
|
6
|
+
Spectrum-palette canvas rendering. ROM bitmap font. AY-3-8912 three-channel audio. Beeper SFX. Tile maps. Free-roaming sprites. Collision detection. Saves. Camera. Scene manager. Particle pool. Dithered lighting. Offscreen layer cache. Authentic attribute clash. Monochrome playfield. Zero dependencies. TypeScript-first.
|
|
7
7
|
|
|
8
8
|
[](https://www.npmjs.com/package/zx-kit)
|
|
9
9
|
[](LICENSE)
|
|
@@ -29,6 +29,7 @@ zx-kit captures that aesthetic in TypeScript. You get the Spectrum's palette, RO
|
|
|
29
29
|
- **Tile map engine** — scrollable maps, O(1) id-index, smart seasonal background swapping, solid-tile collision queries
|
|
30
30
|
- **Offscreen layer cache** — render a static or rarely-changing layer (tile map, CRT overlay) once to an offscreen canvas and blit it each frame; `dirty`-flag invalidation turns thousands of per-pixel `fillRect`s into a single `drawImage`
|
|
31
31
|
- **Authentic attribute clash (opt-in)** — a 32×24 cell ink/paper screen that reproduces the real Spectrum colour bleed when a sprite and the background share an 8×8 cell; resolved to one `putImageData`/frame. Off by default, on when you want it
|
|
32
|
+
- **Monochrome playfield (opt-in)** — the classic anti-clash trick: render the action area in a single ink + paper at its own size, keep the colour in the HUD around it. Everything inside becomes a clean two-colour silhouette — no clash, ever
|
|
32
33
|
- **Free-roaming sprites** — position, velocity, gravity, `flipX` caching, transparent or opaque background
|
|
33
34
|
- **Three-tier collision** — AABB overlap tests, generic rect-vs-tile wall resolution (any sprite size), and pixel-precise mask overlap with O(pixels) sorted-merge intersection — no allocations per frame
|
|
34
35
|
- **Keyboard and gamepad input** — configurable key-repeat, transparent gamepad polling, single-consume action flags, instant state reset on phase transitions
|
|
@@ -593,6 +594,7 @@ requestAnimationFrame(loop)
|
|
|
593
594
|
| [`lighting.ts`](#lightingts--dithered-cave-darkness) | Dithered cave darkness: pre-baked level tiles + dirty-cell buffer, one blit/frame (no per-frame putImageData) |
|
|
594
595
|
| [`cache.ts`](#cachets--offscreen-layer-cache) | Offscreen layer cache: render a static layer once, blit each frame, `dirty`-flag invalidation |
|
|
595
596
|
| [`attrscreen.ts`](#attrscreents--attribute-clash-opt-in) | Opt-in authentic ZX colour clash: 1-bit pixels + 32×24 per-cell ink/paper, one `putImageData`/frame |
|
|
597
|
+
| [`monoscreen.ts`](#monoscreents--monochrome-playfield) | Opt-in monochrome playfield (own size): 1-bit mask + one ink/paper, blitted at an offset — clash-proof |
|
|
596
598
|
| [`music.ts`](#musicts--note-name-ay-music) | Write AY music by note name (`A5`, `C#4`) and loop it for background tracks |
|
|
597
599
|
|
|
598
600
|
---
|
|
@@ -2853,6 +2855,64 @@ flushAttrScreen(ctx, scr)
|
|
|
2853
2855
|
|
|
2854
2856
|
---
|
|
2855
2857
|
|
|
2858
|
+
## `monoscreen.ts` — Monochrome Playfield
|
|
2859
|
+
|
|
2860
|
+
The surest cure for attribute clash is to not have it: render the action area in a
|
|
2861
|
+
**single ink + paper** and keep the colour in a separate HUD/border. Light Force,
|
|
2862
|
+
Bobby Bearing, Highway Encounter and Head Over Heels all did exactly this.
|
|
2863
|
+
|
|
2864
|
+
A `MonoScreen` is a **1-bit foreground mask of its own size** (smaller than the
|
|
2865
|
+
canvas) plus two colours. Everything drawn into it collapses to ink (lit pixels)
|
|
2866
|
+
or paper — a white sprite, a green tile and a cyan hero all become the same ink, so
|
|
2867
|
+
they can never clash. Draw the colourful HUD normally *outside* the region;
|
|
2868
|
+
`flushMonoScreen` blits the playfield at an offset.
|
|
2869
|
+
|
|
2870
|
+
Versus `attrscreen` (authentic *per-cell* clash): mono is one ink/paper for the
|
|
2871
|
+
**whole** region — simpler, cheaper, and the right tool for a clean retro look
|
|
2872
|
+
rather than the colour-bleed artefact. Flush is one `putImageData` + `drawImage`.
|
|
2873
|
+
Little-endian; headless-safe.
|
|
2874
|
+
|
|
2875
|
+
### `createMonoScreen(width, height, ink, paper): MonoScreen`
|
|
2876
|
+
|
|
2877
|
+
Creates a playfield of its own pixel size (not the canvas). `ink`/`paper` are
|
|
2878
|
+
mutable — recolour the whole playfield any time (e.g. per biome). Throws on a
|
|
2879
|
+
non-positive size.
|
|
2880
|
+
|
|
2881
|
+
### `clearMonoScreen(scr)`
|
|
2882
|
+
|
|
2883
|
+
Resets the mask to all-paper. Call at the start of each frame.
|
|
2884
|
+
|
|
2885
|
+
### `drawMonoBitmap(scr, bitmap, x, y)`
|
|
2886
|
+
|
|
2887
|
+
Draws a monochrome `Bitmap`'s lit pixels as foreground (clipped). Clear pixels are
|
|
2888
|
+
left untouched, so paper and earlier draws show through.
|
|
2889
|
+
|
|
2890
|
+
### `fillMono(scr, x, y, w, h)`
|
|
2891
|
+
|
|
2892
|
+
Sets a filled foreground rectangle (clipped) — a 1px-wide rect is a thread, rail or
|
|
2893
|
+
laser; a block is a platform.
|
|
2894
|
+
|
|
2895
|
+
### `flushMonoScreen(ctx, scr, dx?, dy?)`
|
|
2896
|
+
|
|
2897
|
+
Resolves the mask (ink where lit, paper elsewhere) and blits the region at canvas
|
|
2898
|
+
offset `(dx, dy)` under the current transform. Headless: fills `scr.rgba`, skips
|
|
2899
|
+
the blit.
|
|
2900
|
+
|
|
2901
|
+
```ts
|
|
2902
|
+
const play = createMonoScreen(256, 160, C.BLACK, C.B_CYAN) // playfield, once
|
|
2903
|
+
// each frame, in playfield space:
|
|
2904
|
+
clearMonoScreen(play)
|
|
2905
|
+
drawMonoBitmap(play, tileBmp, tx, ty)
|
|
2906
|
+
drawMonoBitmap(play, heroBmp, hx, hy)
|
|
2907
|
+
fillMono(play, threadX, threadY, 1, len)
|
|
2908
|
+
flushMonoScreen(ctx, play, 0, 16) // playfield below a 16px colour HUD
|
|
2909
|
+
```
|
|
2910
|
+
|
|
2911
|
+
> Tip: it carries its own `ink`/`paper`, so a one-line swap reskins the whole
|
|
2912
|
+
> playfield — a cheap "biome" tint or a damage flash.
|
|
2913
|
+
|
|
2914
|
+
---
|
|
2915
|
+
|
|
2856
2916
|
## Architecture
|
|
2857
2917
|
|
|
2858
2918
|
### Module structure
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module monoscreen
|
|
3
|
+
*
|
|
4
|
+
* **Monochrome playfield — the classic ZX anti-clash trick.** The surest way to
|
|
5
|
+
* avoid attribute clash is to have no clash at all: render the action area in a
|
|
6
|
+
* single ink + paper, and keep the colour in a separate HUD/border around it.
|
|
7
|
+
* Light Force, Bobby Bearing, Highway Encounter and Head Over Heels all did this.
|
|
8
|
+
*
|
|
9
|
+
* A {@link MonoScreen} is a **1-bit foreground mask of any size** (its own,
|
|
10
|
+
* smaller than the canvas) plus two colours. Everything drawn into it is reduced
|
|
11
|
+
* to ink (lit pixels) or paper (everything else) — a white sprite, a green tile
|
|
12
|
+
* and a cyan rabbit all become the same ink, so they can never clash. Draw your
|
|
13
|
+
* colourful HUD normally *outside* the region; {@link flushMonoScreen} blits the
|
|
14
|
+
* playfield at a given offset.
|
|
15
|
+
*
|
|
16
|
+
* Unlike {@link "attrscreen" | attrscreen} (authentic *per-cell* clash), this is
|
|
17
|
+
* one ink/paper for the **whole** region — simpler, cheaper, and the right tool
|
|
18
|
+
* when you want a clean retro look rather than the colour-bleed artefact.
|
|
19
|
+
*
|
|
20
|
+
* Fast flush: the mask is resolved into one reused `ImageData` and uploaded with
|
|
21
|
+
* a single `putImageData` + `drawImage` — never per-pixel `fillRect`. Little-endian
|
|
22
|
+
* (every browser). Headless-safe: the resolve runs, the blit is skipped.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const play = createMonoScreen(256, 160, C.BLACK, C.B_CYAN) // playfield, once
|
|
27
|
+
* // each frame, in playfield space:
|
|
28
|
+
* clearMonoScreen(play)
|
|
29
|
+
* drawMonoBitmap(play, tileBmp, tx, ty) // tiles → ink
|
|
30
|
+
* drawMonoBitmap(play, rabbitBmp, rx, ry) // sprite → ink (silhouette)
|
|
31
|
+
* fillMono(play, threadX, threadY, 1, len) // a thin line (e.g. a spider thread)
|
|
32
|
+
* flushMonoScreen(ctx, play, 0, 16) // blit the playfield below a 16px HUD
|
|
33
|
+
* // …draw the colourful HUD normally, outside the region…
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
import type { SpectrumColor } from './palette.js';
|
|
37
|
+
import type { Bitmap } from './renderer.js';
|
|
38
|
+
/** A monochrome region: a 1-bit foreground mask plus an ink/paper colour pair. */
|
|
39
|
+
export interface MonoScreen {
|
|
40
|
+
readonly width: number;
|
|
41
|
+
readonly height: number;
|
|
42
|
+
/** Foreground colour (lit pixels). Mutable — recolour the playfield any time. */
|
|
43
|
+
ink: SpectrumColor;
|
|
44
|
+
/** Background colour (everything else). Mutable. */
|
|
45
|
+
paper: SpectrumColor;
|
|
46
|
+
/** One byte per pixel — `1` = ink/foreground, `0` = paper. Row-major, `width*height`. */
|
|
47
|
+
readonly pixels: Uint8Array;
|
|
48
|
+
/** Resolved RGBA buffer (`width*height*4`), filled by {@link flushMonoScreen}. */
|
|
49
|
+
readonly rgba: Uint8ClampedArray;
|
|
50
|
+
/** Reusable `ImageData` backing {@link rgba} (`null` when headless). */
|
|
51
|
+
readonly image: ImageData | null;
|
|
52
|
+
/** Offscreen canvas the resolved buffer is uploaded to (`null` when headless). */
|
|
53
|
+
readonly canvas: HTMLCanvasElement | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Creates a monochrome playfield of the given pixel size — **its own size, not
|
|
57
|
+
* the canvas**. Pick a region smaller than the canvas and keep the HUD colourful
|
|
58
|
+
* around it. `ink`/`paper` are mutable. Create once; reuse across frames. Throws
|
|
59
|
+
* on a non-positive size.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createMonoScreen(width: number, height: number, ink: SpectrumColor, paper: SpectrumColor): MonoScreen;
|
|
62
|
+
/** Resets the playfield to all-paper for a new frame. Call before drawing. */
|
|
63
|
+
export declare function clearMonoScreen(scr: MonoScreen): void;
|
|
64
|
+
/**
|
|
65
|
+
* Draws a monochrome {@link Bitmap}'s lit pixels as foreground (ink) at `(x, y)`
|
|
66
|
+
* (rounded; may be off-screen — clipped). Clear pixels are left untouched, so the
|
|
67
|
+
* paper (and anything drawn earlier) shows through.
|
|
68
|
+
*/
|
|
69
|
+
export declare function drawMonoBitmap(scr: MonoScreen, bitmap: Bitmap, x: number, y: number): void;
|
|
70
|
+
/**
|
|
71
|
+
* Sets a filled foreground rectangle (clipped) — handy for thin lines (a 1px-wide
|
|
72
|
+
* rect is a spider thread, a ladder rail, a laser) or solid blocks.
|
|
73
|
+
*/
|
|
74
|
+
export declare function fillMono(scr: MonoScreen, x: number, y: number, w: number, h: number): void;
|
|
75
|
+
/**
|
|
76
|
+
* Resolves the mask into RGBA (ink where lit, paper elsewhere) and blits the
|
|
77
|
+
* region at canvas offset `(dx, dy)` with one `putImageData` + `drawImage` — under
|
|
78
|
+
* the current transform, so `setupCanvas`'s `×scale` is honoured. Headless: fills
|
|
79
|
+
* {@link MonoScreen.rgba} and skips the blit.
|
|
80
|
+
*/
|
|
81
|
+
export declare function flushMonoScreen(ctx: CanvasRenderingContext2D, scr: MonoScreen, dx?: number, dy?: number): void;
|
|
82
|
+
//# sourceMappingURL=monoscreen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monoscreen.d.ts","sourceRoot":"","sources":["../src/monoscreen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C,kFAAkF;AAClF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,iFAAiF;IACjF,GAAG,EAAE,aAAa,CAAA;IAClB,oDAAoD;IACpD,KAAK,EAAE,aAAa,CAAA;IACpB,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAChC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAChC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAA;CAC1C;AAUD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,aAAa,EAClB,KAAK,EAAE,aAAa,GACnB,UAAU,CAkBZ;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAsB1F;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAS1F;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,wBAAwB,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,SAAI,EAAE,EAAE,SAAI,GAAG,IAAI,CAcpG"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** Packs a `#RRGGBB` colour into a little-endian RGBA word (opaque). */
|
|
2
|
+
function hexToU32(hex) {
|
|
3
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
4
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
5
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
6
|
+
return ((255 << 24) | (b << 16) | (g << 8) | r) >>> 0;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates a monochrome playfield of the given pixel size — **its own size, not
|
|
10
|
+
* the canvas**. Pick a region smaller than the canvas and keep the HUD colourful
|
|
11
|
+
* around it. `ink`/`paper` are mutable. Create once; reuse across frames. Throws
|
|
12
|
+
* on a non-positive size.
|
|
13
|
+
*/
|
|
14
|
+
export function createMonoScreen(width, height, ink, paper) {
|
|
15
|
+
if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
|
|
16
|
+
throw new Error(`createMonoScreen: width and height must be positive, got ${width}×${height}`);
|
|
17
|
+
}
|
|
18
|
+
let canvas = null;
|
|
19
|
+
let image = null;
|
|
20
|
+
if (typeof document !== 'undefined') {
|
|
21
|
+
canvas = document.createElement('canvas');
|
|
22
|
+
canvas.width = width;
|
|
23
|
+
canvas.height = height;
|
|
24
|
+
const ctx = canvas.getContext('2d');
|
|
25
|
+
if (ctx) {
|
|
26
|
+
ctx.imageSmoothingEnabled = false;
|
|
27
|
+
image = ctx.createImageData(width, height);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const rgba = image ? image.data : new Uint8ClampedArray(width * height * 4);
|
|
31
|
+
return { width, height, ink, paper, pixels: new Uint8Array(width * height), rgba, image, canvas };
|
|
32
|
+
}
|
|
33
|
+
/** Resets the playfield to all-paper for a new frame. Call before drawing. */
|
|
34
|
+
export function clearMonoScreen(scr) {
|
|
35
|
+
scr.pixels.fill(0);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Draws a monochrome {@link Bitmap}'s lit pixels as foreground (ink) at `(x, y)`
|
|
39
|
+
* (rounded; may be off-screen — clipped). Clear pixels are left untouched, so the
|
|
40
|
+
* paper (and anything drawn earlier) shows through.
|
|
41
|
+
*/
|
|
42
|
+
export function drawMonoBitmap(scr, bitmap, x, y) {
|
|
43
|
+
const { data, width: bw, height: bh } = bitmap;
|
|
44
|
+
const bytesPerRow = bw >> 3;
|
|
45
|
+
const ox = Math.round(x);
|
|
46
|
+
const oy = Math.round(y);
|
|
47
|
+
for (let row = 0; row < bh; row++) {
|
|
48
|
+
const py = oy + row;
|
|
49
|
+
if (py < 0 || py >= scr.height)
|
|
50
|
+
continue;
|
|
51
|
+
const rowByteBase = row * bytesPerRow;
|
|
52
|
+
const rowPixBase = py * scr.width;
|
|
53
|
+
for (let byteIdx = 0; byteIdx < bytesPerRow; byteIdx++) {
|
|
54
|
+
const byte = data[rowByteBase + byteIdx];
|
|
55
|
+
if (!byte)
|
|
56
|
+
continue;
|
|
57
|
+
const colBase = byteIdx << 3;
|
|
58
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
59
|
+
if (!(byte & (0x80 >> bit)))
|
|
60
|
+
continue;
|
|
61
|
+
const px = ox + colBase + bit;
|
|
62
|
+
if (px < 0 || px >= scr.width)
|
|
63
|
+
continue;
|
|
64
|
+
scr.pixels[rowPixBase + px] = 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Sets a filled foreground rectangle (clipped) — handy for thin lines (a 1px-wide
|
|
71
|
+
* rect is a spider thread, a ladder rail, a laser) or solid blocks.
|
|
72
|
+
*/
|
|
73
|
+
export function fillMono(scr, x, y, w, h) {
|
|
74
|
+
const x0 = Math.max(0, Math.round(x));
|
|
75
|
+
const y0 = Math.max(0, Math.round(y));
|
|
76
|
+
const x1 = Math.min(scr.width, Math.round(x) + w);
|
|
77
|
+
const y1 = Math.min(scr.height, Math.round(y) + h);
|
|
78
|
+
for (let yy = y0; yy < y1; yy++) {
|
|
79
|
+
const base = yy * scr.width;
|
|
80
|
+
for (let xx = x0; xx < x1; xx++)
|
|
81
|
+
scr.pixels[base + xx] = 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolves the mask into RGBA (ink where lit, paper elsewhere) and blits the
|
|
86
|
+
* region at canvas offset `(dx, dy)` with one `putImageData` + `drawImage` — under
|
|
87
|
+
* the current transform, so `setupCanvas`'s `×scale` is honoured. Headless: fills
|
|
88
|
+
* {@link MonoScreen.rgba} and skips the blit.
|
|
89
|
+
*/
|
|
90
|
+
export function flushMonoScreen(ctx, scr, dx = 0, dy = 0) {
|
|
91
|
+
const inkU32 = hexToU32(scr.ink);
|
|
92
|
+
const paperU32 = hexToU32(scr.paper);
|
|
93
|
+
const out = new Uint32Array(scr.rgba.buffer);
|
|
94
|
+
const px = scr.pixels;
|
|
95
|
+
for (let i = 0; i < px.length; i++)
|
|
96
|
+
out[i] = px[i] ? inkU32 : paperU32;
|
|
97
|
+
if (scr.canvas && scr.image) {
|
|
98
|
+
const offctx = scr.canvas.getContext('2d');
|
|
99
|
+
if (offctx) {
|
|
100
|
+
offctx.putImageData(scr.image, 0, 0);
|
|
101
|
+
ctx.drawImage(scr.canvas, dx, dy);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=monoscreen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monoscreen.js","sourceRoot":"","sources":["../src/monoscreen.ts"],"names":[],"mappings":"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"}
|