zx-kit 0.2.0 → 0.2.2
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 +202 -255
- package/dist/audio.d.ts +57 -3
- package/dist/audio.d.ts.map +1 -1
- package/dist/audio.js +56 -3
- package/dist/audio.js.map +1 -1
- package/dist/font.d.ts +22 -0
- package/dist/font.d.ts.map +1 -1
- package/dist/font.js +23 -0
- package/dist/font.js.map +1 -1
- package/dist/input.d.ts +42 -0
- package/dist/input.d.ts.map +1 -1
- package/dist/input.js +41 -0
- package/dist/input.js.map +1 -1
- package/dist/palette.d.ts +19 -0
- package/dist/palette.d.ts.map +1 -1
- package/dist/palette.js +11 -1
- package/dist/palette.js.map +1 -1
- package/dist/renderer.d.ts +97 -5
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +109 -10
- package/dist/renderer.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,356 +8,302 @@ Extracted from [Minefield](https://github.com/zrebec/minefield) — a ZX Spectru
|
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```json
|
|
14
|
-
"dependencies": {
|
|
15
|
-
"zx-kit": "file:../zx-kit"
|
|
16
|
-
}
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Then add a Vite alias so bundler resolution works (Vite doesn't transform `node_modules` by default):
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
// vite.config.ts
|
|
23
|
-
import { resolve } from 'path'
|
|
24
|
-
|
|
25
|
-
export default defineConfig({
|
|
26
|
-
resolve: {
|
|
27
|
-
alias: { 'zx-kit': resolve(__dirname, '../zx-kit/src/index.ts') },
|
|
28
|
-
},
|
|
29
|
-
})
|
|
11
|
+
```bash
|
|
12
|
+
npm install zx-kit
|
|
30
13
|
```
|
|
31
14
|
|
|
32
15
|
Import from the barrel:
|
|
33
16
|
|
|
34
17
|
```ts
|
|
35
|
-
import { C, CELL,
|
|
18
|
+
import { C, CELL, setupCanvas, initAudio, playPattern, initInput, tickMovement } from 'zx-kit'
|
|
36
19
|
```
|
|
37
20
|
|
|
21
|
+
No Vite alias or path mapping required — the package ships compiled JavaScript (`dist/`).
|
|
22
|
+
|
|
38
23
|
---
|
|
39
24
|
|
|
40
|
-
##
|
|
25
|
+
## Quick start
|
|
41
26
|
|
|
42
|
-
|
|
27
|
+
```ts
|
|
28
|
+
import { setupCanvas, C, CELL, drawText, initAudio, playPattern, initInput, tickMovement } from 'zx-kit'
|
|
43
29
|
|
|
44
|
-
|
|
30
|
+
// Canvas — one call replaces the manual boilerplate
|
|
31
|
+
const canvas = document.getElementById('game') as HTMLCanvasElement
|
|
32
|
+
const ctx = setupCanvas(canvas, 4) // scale=4, 256×192 game px → 1024×768 CSS px
|
|
45
33
|
|
|
46
|
-
|
|
34
|
+
// Input
|
|
35
|
+
initInput()
|
|
47
36
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
| `SCALE` | `number` | `4` | CSS pixel scale factor (1 game pixel = 4 CSS pixels) |
|
|
51
|
-
| `CELL` | `number` | `8` | Sprite / character grid size in game pixels |
|
|
52
|
-
| `C` | `object` | see below | All 15 Spectrum colors as `#RRGGBB` hex strings |
|
|
53
|
-
| `SpectrumColor` | `type` | union of all `C` values | TypeScript type for palette-safe color values |
|
|
37
|
+
// Audio (must be inside a user gesture)
|
|
38
|
+
window.addEventListener('keydown', () => initAudio(), { once: true })
|
|
54
39
|
|
|
55
|
-
|
|
40
|
+
// Game loop
|
|
41
|
+
function loop(dt: number) {
|
|
42
|
+
const dir = tickMovement(dt)
|
|
43
|
+
if (dir) movePlayer(dir)
|
|
56
44
|
|
|
57
|
-
|
|
45
|
+
drawText(ctx, 'SCORE:00000', 0, 0, C.B_WHITE, C.BLACK)
|
|
46
|
+
requestAnimationFrame(t => loop(t - lastT))
|
|
47
|
+
}
|
|
48
|
+
```
|
|
58
49
|
|
|
59
|
-
|
|
60
|
-
|-----|-----|-----------|
|
|
61
|
-
| `C.BLACK` | `#000000` | Black |
|
|
62
|
-
| `C.BLUE` | `#0000CD` | Dark blue |
|
|
63
|
-
| `C.RED` | `#CD0000` | Dark red |
|
|
64
|
-
| `C.MAGENTA` | `#CD00CD` | Dark magenta |
|
|
65
|
-
| `C.GREEN` | `#00CD00` | Dark green |
|
|
66
|
-
| `C.CYAN` | `#00CDCD` | Dark cyan |
|
|
67
|
-
| `C.YELLOW` | `#CDCD00` | Dark yellow |
|
|
68
|
-
| `C.WHITE` | `#CDCDCD` | Light grey |
|
|
50
|
+
---
|
|
69
51
|
|
|
70
|
-
|
|
52
|
+
## Modules
|
|
53
|
+
|
|
54
|
+
### `palette.ts` — ZX Spectrum color constants
|
|
71
55
|
|
|
72
|
-
|
|
73
|
-
|-----|-----|-----------|
|
|
74
|
-
| `C.B_BLACK` | `#000000` | Same as `BLACK` |
|
|
75
|
-
| `C.B_BLUE` | `#0000FF` | Bright blue |
|
|
76
|
-
| `C.B_RED` | `#FF0000` | Bright red |
|
|
77
|
-
| `C.B_MAGENTA` | `#FF00FF` | Bright magenta |
|
|
78
|
-
| `C.B_GREEN` | `#00FF00` | Bright green |
|
|
79
|
-
| `C.B_CYAN` | `#00FFFF` | Bright cyan |
|
|
80
|
-
| `C.B_YELLOW` | `#FFFF00` | Bright yellow |
|
|
81
|
-
| `C.B_WHITE` | `#FFFFFF` | Pure white |
|
|
56
|
+
#### Exports
|
|
82
57
|
|
|
83
|
-
|
|
58
|
+
| Export | Type | Description |
|
|
59
|
+
|--------|------|-------------|
|
|
60
|
+
| `SCALE` | `number` | CSS pixel scale factor (1 game pixel = 4 CSS pixels) |
|
|
61
|
+
| `CELL` | `number` | Sprite / character grid size: `8` game pixels |
|
|
62
|
+
| `C` | `object` | All 15 Spectrum colors as `#RRGGBB` hex strings |
|
|
63
|
+
| `SpectrumColor` | `type` | Union of every value in `C` — use for compile-time palette enforcement |
|
|
64
|
+
|
|
65
|
+
#### Color table
|
|
66
|
+
|
|
67
|
+
Normal brightness:
|
|
68
|
+
|
|
69
|
+
| Key | Hex |
|
|
70
|
+
|-----|-----|
|
|
71
|
+
| `C.BLACK` | `#000000` |
|
|
72
|
+
| `C.BLUE` | `#0000CD` |
|
|
73
|
+
| `C.RED` | `#CD0000` |
|
|
74
|
+
| `C.MAGENTA` | `#CD00CD` |
|
|
75
|
+
| `C.GREEN` | `#00CD00` |
|
|
76
|
+
| `C.CYAN` | `#00CDCD` |
|
|
77
|
+
| `C.YELLOW` | `#CDCD00` |
|
|
78
|
+
| `C.WHITE` | `#CDCDCD` |
|
|
84
79
|
|
|
85
|
-
|
|
86
|
-
import { C, CELL } from 'zx-kit'
|
|
80
|
+
Bright variants (`B_` prefix):
|
|
87
81
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
| Key | Hex |
|
|
83
|
+
|-----|-----|
|
|
84
|
+
| `C.B_BLACK` | `#000000` |
|
|
85
|
+
| `C.B_BLUE` | `#0000FF` |
|
|
86
|
+
| `C.B_RED` | `#FF0000` |
|
|
87
|
+
| `C.B_MAGENTA` | `#FF00FF` |
|
|
88
|
+
| `C.B_GREEN` | `#00FF00` |
|
|
89
|
+
| `C.B_CYAN` | `#00FFFF` |
|
|
90
|
+
| `C.B_YELLOW` | `#FFFF00` |
|
|
91
|
+
| `C.B_WHITE` | `#FFFFFF` |
|
|
91
92
|
|
|
92
93
|
---
|
|
93
94
|
|
|
94
95
|
### `font.ts` — ZX Spectrum ROM bitmap font
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
The font data is sourced from the original Spectrum ROM and must not be altered — any change breaks Spectrum authenticity.
|
|
97
|
+
96 printable characters (ASCII 32–127), each 8×8 pixels. Character 127 is a solid block █.
|
|
99
98
|
|
|
100
99
|
#### Exports
|
|
101
100
|
|
|
102
|
-
| Export |
|
|
103
|
-
|
|
104
|
-
| `FONT` | `Uint8Array` | Flat array: 96 chars × 8 bytes
|
|
105
|
-
| `getCharRow
|
|
101
|
+
| Export | Signature | Description |
|
|
102
|
+
|--------|-----------|-------------|
|
|
103
|
+
| `FONT` | `Uint8Array` | Flat array: 96 chars × 8 bytes. `FONT[(code-32)*8 + row]` = one row bitmap. |
|
|
104
|
+
| `getCharRow` | `(charCode, row) => number` | Bitmap byte for one row of a character (bit 7 = leftmost pixel). |
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
import { getCharRow } from 'zx-kit'
|
|
111
|
-
|
|
112
|
-
// Draw character 'A' (65) row by row
|
|
113
|
-
for (let row = 0; row < 8; row++) {
|
|
114
|
-
const byte = getCharRow(65, row)
|
|
115
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
116
|
-
if (byte & (0x80 >> bit)) {
|
|
117
|
-
ctx.fillRect(x + bit, y + row, 1, 1)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
In practice you won't need this directly — use `drawChar` / `drawText` from `renderer.ts` instead.
|
|
106
|
+
In practice use `drawChar` / `drawText` from `renderer.ts` — you rarely need `getCharRow` directly.
|
|
124
107
|
|
|
125
108
|
---
|
|
126
109
|
|
|
127
110
|
### `renderer.ts` — Canvas drawing primitives
|
|
128
111
|
|
|
129
|
-
All
|
|
112
|
+
All functions work in **game pixels**. At `SCALE=4` each game pixel maps to a 4×4 CSS pixel block. Call `setupCanvas` once at startup to configure the canvas correctly.
|
|
130
113
|
|
|
131
|
-
Every function follows the ZX Spectrum **ink / paper** model: each 8×8 cell has
|
|
114
|
+
Every draw function follows the ZX Spectrum **ink / paper** model: each 8×8 cell has one foreground (ink) and one background (paper) color.
|
|
132
115
|
|
|
133
|
-
####
|
|
116
|
+
#### `setupCanvas(canvas, scale, width?, height?): CanvasRenderingContext2D`
|
|
134
117
|
|
|
135
|
-
|
|
118
|
+
Initialises a canvas element for pixel-perfect scaled rendering. Sets canvas dimensions, applies CSS size, disables image smoothing, and calls `ctx.scale(scale, scale)` so all subsequent draw calls use game-pixel coordinates. **Replaces the manual canvas setup boilerplate.**
|
|
136
119
|
|
|
137
|
-
|
|
120
|
+
- `scale` — CSS pixels per game pixel (`4` = standard ZX Spectrum display)
|
|
121
|
+
- `width` — game pixels wide (default `256`)
|
|
122
|
+
- `height` — game pixels tall (default `192`)
|
|
138
123
|
|
|
139
124
|
```ts
|
|
140
|
-
|
|
125
|
+
const canvas = document.getElementById('game') as HTMLCanvasElement
|
|
126
|
+
const ctx = setupCanvas(canvas, 4) // 256×192 game px → 1024×768 CSS px
|
|
127
|
+
const ctx = setupCanvas(canvas, 4, 256, 208) // taller canvas for status rows
|
|
128
|
+
// ctx.imageSmoothingEnabled is already false — no need to set it manually
|
|
129
|
+
// all draw calls use game-pixel coordinates — ctx.scale() is already applied
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### `mirrorSprite(src): Uint8Array`
|
|
141
133
|
|
|
142
|
-
|
|
134
|
+
Flips an 8×8 sprite horizontally. Returns a new `Uint8Array`.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
export const PLAYER_RIGHT = new Uint8Array([...])
|
|
143
138
|
export const PLAYER_LEFT = mirrorSprite(PLAYER_RIGHT)
|
|
144
139
|
```
|
|
145
140
|
|
|
146
|
-
|
|
141
|
+
#### `drawSprite(ctx, sprite, x, y, ink, paper): void`
|
|
147
142
|
|
|
148
|
-
Draws an 8×8 sprite at game coordinates
|
|
149
|
-
|
|
150
|
-
| Parameter | Type | Description |
|
|
151
|
-
|-----------|------|-------------|
|
|
152
|
-
| `ctx` | `CanvasRenderingContext2D` | Target canvas context |
|
|
153
|
-
| `sprite` | `Uint8Array` | 8-byte sprite (one byte per row) |
|
|
154
|
-
| `x` | `number` | Left edge in game pixels |
|
|
155
|
-
| `y` | `number` | Top edge in game pixels |
|
|
156
|
-
| `ink` | `string` | Foreground color (Spectrum palette hex) |
|
|
157
|
-
| `paper` | `string` | Background color (Spectrum palette hex) |
|
|
143
|
+
Draws an 8×8 sprite at game coordinates. Always fills the background first.
|
|
158
144
|
|
|
159
145
|
```ts
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
drawSprite(ctx, MINE_SPRITE, col * 8, row * 8, C.B_RED, C.BLACK)
|
|
146
|
+
drawSprite(ctx, MINE_SPRITE, col * CELL, row * CELL, C.B_RED, C.BLACK)
|
|
163
147
|
```
|
|
164
148
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
Draws a single ASCII character at game coordinates using the ROM font. If `paper` is omitted, the background is not cleared (transparent background).
|
|
149
|
+
#### `drawChar(ctx, code, x, y, ink, paper?): void`
|
|
168
150
|
|
|
169
|
-
|
|
170
|
-
|-----------|------|-------------|
|
|
171
|
-
| `ctx` | `CanvasRenderingContext2D` | Target canvas context |
|
|
172
|
-
| `code` | `number` | ASCII character code (32–127) |
|
|
173
|
-
| `x` | `number` | Left edge in game pixels |
|
|
174
|
-
| `y` | `number` | Top edge in game pixels |
|
|
175
|
-
| `ink` | `string` | Foreground color |
|
|
176
|
-
| `paper?` | `string` | Optional background color |
|
|
151
|
+
Draws one ASCII character using the ROM font. Omit `paper` for transparent background.
|
|
177
152
|
|
|
178
153
|
```ts
|
|
179
|
-
import { drawChar, C } from 'zx-kit'
|
|
180
|
-
|
|
181
154
|
drawChar(ctx, 127, x, y, C.B_GREEN, C.BLACK) // solid block █
|
|
155
|
+
drawChar(ctx, 'A'.charCodeAt(0), x, y, C.B_WHITE)
|
|
182
156
|
```
|
|
183
157
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
Draws a string left-to-right starting at `(x, y)`, one character per `CELL`-wide slot.
|
|
158
|
+
#### `drawText(ctx, text, x, y, ink, paper?): void`
|
|
187
159
|
|
|
188
|
-
|
|
189
|
-
|-----------|------|-------------|
|
|
190
|
-
| `ctx` | `CanvasRenderingContext2D` | Target canvas context |
|
|
191
|
-
| `text` | `string` | ASCII string to render |
|
|
192
|
-
| `x` | `number` | Left edge in game pixels |
|
|
193
|
-
| `y` | `number` | Top edge in game pixels |
|
|
194
|
-
| `ink` | `string` | Foreground color |
|
|
195
|
-
| `paper?` | `string` | Optional background color |
|
|
160
|
+
Draws a string left-to-right, one character per `CELL`-wide slot.
|
|
196
161
|
|
|
197
162
|
```ts
|
|
198
|
-
import { drawText, C } from 'zx-kit'
|
|
199
|
-
|
|
200
163
|
drawText(ctx, 'SCORE:00000', 0, statusY, C.B_WHITE, C.BLACK)
|
|
201
164
|
```
|
|
202
165
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
Draws a string horizontally centered within a canvas of `cols` character columns. The `cols` parameter must match your game's grid width (e.g. 32 for a standard Spectrum layout).
|
|
166
|
+
#### `drawTextCentered(ctx, text, y, cols, ink, paper?): void`
|
|
206
167
|
|
|
207
|
-
|
|
208
|
-
|-----------|------|-------------|
|
|
209
|
-
| `ctx` | `CanvasRenderingContext2D` | Target canvas context |
|
|
210
|
-
| `text` | `string` | ASCII string to render |
|
|
211
|
-
| `y` | `number` | Top edge in game pixels |
|
|
212
|
-
| `cols` | `number` | Total character columns (canvas width ÷ CELL) |
|
|
213
|
-
| `ink` | `string` | Foreground color |
|
|
214
|
-
| `paper?` | `string` | Optional background color |
|
|
168
|
+
Centers a string within `cols` character columns. Bind `cols` once in a helper to avoid repetition.
|
|
215
169
|
|
|
216
170
|
```ts
|
|
217
|
-
|
|
171
|
+
// Bind once:
|
|
172
|
+
const centered = (ctx: CanvasRenderingContext2D, text: string, y: number, ink: string) =>
|
|
173
|
+
drawTextCentered(ctx, text, y, 32, ink)
|
|
218
174
|
|
|
219
|
-
|
|
175
|
+
centered(ctx, 'GAME OVER', y, C.B_RED)
|
|
220
176
|
```
|
|
221
177
|
|
|
222
|
-
|
|
178
|
+
#### `flashBorder(color, times, intervalMs, resetColor?): void`
|
|
179
|
+
|
|
180
|
+
Flashes `document.body.style.backgroundColor` between `color` and `resetColor`. Fire-and-forget — does not block. One flash = one `color → resetColor` cycle. Always resets to `resetColor` on completion (default `C.BLACK`).
|
|
223
181
|
|
|
224
182
|
```ts
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
183
|
+
flashBorder(C.B_RED, 3, 150) // explosion — 3 red flashes → black
|
|
184
|
+
flashBorder(C.B_GREEN, 2, 200) // level complete
|
|
185
|
+
flashBorder(C.B_CYAN, 2, 120, C.BLUE) // flash → reset to blue border
|
|
228
186
|
```
|
|
229
187
|
|
|
230
188
|
---
|
|
231
189
|
|
|
232
190
|
### `audio.ts` — Web Audio engine (ZX Spectrum style)
|
|
233
191
|
|
|
234
|
-
Wraps the Web Audio API
|
|
192
|
+
Wraps the Web Audio API for authentic 1-bit square-wave sound. All audio goes through a shared `AudioContext` and a single master `GainNode`.
|
|
235
193
|
|
|
236
|
-
**
|
|
194
|
+
**Browser autoplay policy:** `AudioContext` must be created inside a user gesture (click or keydown). Call `initAudio()` from an event handler.
|
|
237
195
|
|
|
238
|
-
####
|
|
196
|
+
#### `initAudio(volume?): void`
|
|
239
197
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
Creates the `AudioContext` and master `GainNode`. Idempotent — safe to call multiple times (subsequent calls are no-ops). Default volume: `0.3`.
|
|
198
|
+
Creates the `AudioContext` and master gain. Idempotent — safe to call multiple times.
|
|
243
199
|
|
|
244
200
|
```ts
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
document.addEventListener('keydown', () => initAudio(), { once: true })
|
|
201
|
+
window.addEventListener('keydown', () => initAudio(), { once: true })
|
|
202
|
+
window.addEventListener('click', () => initAudio(), { once: true })
|
|
248
203
|
```
|
|
249
204
|
|
|
250
|
-
|
|
205
|
+
#### `resumeAudio(): void`
|
|
251
206
|
|
|
252
|
-
Resumes a suspended `AudioContext`.
|
|
207
|
+
Resumes a suspended `AudioContext`. Call before scheduling audio in the game loop.
|
|
253
208
|
|
|
254
|
-
|
|
255
|
-
import { resumeAudio, beep, getAudioContext } from 'zx-kit'
|
|
209
|
+
#### `getAudioContext(): AudioContext | null`
|
|
256
210
|
|
|
257
|
-
|
|
258
|
-
if (ctx) {
|
|
259
|
-
resumeAudio()
|
|
260
|
-
beep(440, 80, ctx.currentTime)
|
|
261
|
-
}
|
|
262
|
-
```
|
|
211
|
+
Returns the current context, or `null` before `initAudio()`.
|
|
263
212
|
|
|
264
|
-
|
|
213
|
+
#### `getMasterGain(): GainNode | null`
|
|
265
214
|
|
|
266
|
-
Returns the
|
|
215
|
+
Returns the master gain node. Connect custom oscillators here to respect global volume.
|
|
267
216
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
Returns the master `GainNode`. Connect your own oscillators/gains to this node to respect the global volume level. Returns `null` before `initAudio()`.
|
|
217
|
+
#### `Note` interface
|
|
271
218
|
|
|
272
219
|
```ts
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
220
|
+
interface Note {
|
|
221
|
+
freq: number // Hz — use 0 for a rest (silence)
|
|
222
|
+
dur: number // ms — duration of note or rest
|
|
223
|
+
}
|
|
277
224
|
```
|
|
278
225
|
|
|
279
|
-
|
|
226
|
+
#### `playPattern(notes, startDelay?): void`
|
|
227
|
+
|
|
228
|
+
Schedules a sequence of notes. `freq: 0` = rest (advances time, no sound). `startDelay` delays the whole pattern in milliseconds.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
// Rising arpeggio
|
|
232
|
+
playPattern([
|
|
233
|
+
{ freq: 262, dur: 100 }, // C4
|
|
234
|
+
{ freq: 330, dur: 100 }, // E4
|
|
235
|
+
{ freq: 392, dur: 100 }, // G4
|
|
236
|
+
{ freq: 523, dur: 200 }, // C5
|
|
237
|
+
])
|
|
238
|
+
|
|
239
|
+
// With rests and a 200ms startup delay
|
|
240
|
+
playPattern([
|
|
241
|
+
{ freq: 523, dur: 120 }, // C5
|
|
242
|
+
{ freq: 0, dur: 40 }, // rest
|
|
243
|
+
{ freq: 784, dur: 200 }, // G5
|
|
244
|
+
], 200)
|
|
245
|
+
```
|
|
280
246
|
|
|
281
|
-
|
|
247
|
+
#### `beep(freq, durationMs, startTime): void`
|
|
282
248
|
|
|
283
|
-
|
|
284
|
-
|-----------|------|-------------|
|
|
285
|
-
| `freq` | `number` | Frequency in Hz |
|
|
286
|
-
| `durationMs` | `number` | Duration in milliseconds |
|
|
287
|
-
| `startTime` | `number` | `AudioContext.currentTime` offset to start at |
|
|
249
|
+
Schedules a single square-wave beep at an absolute `AudioContext.currentTime`. Use `playPattern` for sequences; use `beep` directly when you need algorithmic timing control.
|
|
288
250
|
|
|
289
251
|
```ts
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
resumeAudio()
|
|
295
|
-
const now = ctx.currentTime
|
|
296
|
-
// Two-pip warning: 880Hz beep twice with 60ms gap
|
|
297
|
-
beep(880, 80, now)
|
|
298
|
-
beep(880, 80, now + 0.14)
|
|
299
|
-
}
|
|
252
|
+
const audio = getAudioContext()!
|
|
253
|
+
resumeAudio()
|
|
254
|
+
beep(880, 80, audio.currentTime)
|
|
255
|
+
beep(880, 80, audio.currentTime + 0.14) // 140ms later
|
|
300
256
|
```
|
|
301
257
|
|
|
302
258
|
---
|
|
303
259
|
|
|
304
260
|
### `input.ts` — Keyboard input with key-repeat
|
|
305
261
|
|
|
306
|
-
Handles arrow
|
|
307
|
-
|
|
308
|
-
**Important:** Call `initInput()` once at startup (after DOM is ready) to attach `keydown`/`keyup` listeners. Then call `tickMovement(dt)` every frame.
|
|
309
|
-
|
|
310
|
-
#### Exports
|
|
262
|
+
Handles arrow-key movement with configurable key-repeat (immediate on first press, auto-repeat on hold) plus single-consume flags for action keys.
|
|
311
263
|
|
|
312
|
-
|
|
264
|
+
**Call `initInput()` once at startup.** Then call `tickMovement(dt)` every frame.
|
|
313
265
|
|
|
314
|
-
|
|
315
|
-
- `repeatDelay`: `150` ms — time before auto-repeat kicks in after initial press
|
|
316
|
-
- `repeatInterval`: `80` ms — time between repeats while key is held
|
|
266
|
+
#### `Direction` type
|
|
317
267
|
|
|
318
268
|
```ts
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
initInput(150, 80)
|
|
269
|
+
type Direction = 'up' | 'down' | 'left' | 'right'
|
|
322
270
|
```
|
|
323
271
|
|
|
324
|
-
|
|
272
|
+
#### `initInput(repeatDelay?, repeatInterval?): void`
|
|
325
273
|
|
|
326
|
-
|
|
274
|
+
Attaches `keydown`/`keyup` listeners. Keys: arrows = movement, `F` = flag, `P` = pause, `Ctrl+Shift+B` = debug.
|
|
327
275
|
|
|
328
276
|
```ts
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
function gameLoop(dtMs: number) {
|
|
332
|
-
const dir = tickMovement(dtMs)
|
|
333
|
-
if (dir) movePlayer(dir)
|
|
334
|
-
}
|
|
277
|
+
initInput() // 150ms delay, 80ms repeat
|
|
278
|
+
initInput(200, 60) // custom timing
|
|
335
279
|
```
|
|
336
280
|
|
|
337
|
-
|
|
281
|
+
#### `tickMovement(dtMs): Direction | null`
|
|
338
282
|
|
|
339
|
-
Returns
|
|
283
|
+
Returns the movement direction for this frame, or `null`. Call once per frame.
|
|
340
284
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
##### `consumePause(): boolean`
|
|
346
|
-
|
|
347
|
-
Returns `true` once if `P` key was pressed.
|
|
285
|
+
```ts
|
|
286
|
+
const dir = tickMovement(dt)
|
|
287
|
+
if (dir) movePlayer(dir)
|
|
288
|
+
```
|
|
348
289
|
|
|
349
|
-
|
|
290
|
+
#### Consume flags
|
|
350
291
|
|
|
351
|
-
|
|
292
|
+
| Function | Trigger | Use case |
|
|
293
|
+
|----------|---------|----------|
|
|
294
|
+
| `consumeFlag()` | `F` key | Flag / unflag a cell |
|
|
295
|
+
| `consumeDebug()` | `Ctrl+Shift+B` | Toggle debug mode |
|
|
296
|
+
| `consumePause()` | `P` key | Pause / unpause |
|
|
297
|
+
| `consumeAnyKey()` | Any key | Dismiss overlays, start game |
|
|
352
298
|
|
|
353
|
-
|
|
299
|
+
Each returns `true` once per press, then resets.
|
|
354
300
|
|
|
355
|
-
|
|
301
|
+
#### `isHeld(key): boolean`
|
|
356
302
|
|
|
357
|
-
|
|
303
|
+
Returns whether a key is currently held. Argument is `KeyboardEvent.key`.
|
|
358
304
|
|
|
359
305
|
```ts
|
|
360
|
-
|
|
306
|
+
if (isHeld('ArrowUp')) { ... }
|
|
361
307
|
```
|
|
362
308
|
|
|
363
309
|
---
|
|
@@ -366,46 +312,47 @@ export type Direction = 'up' | 'down' | 'left' | 'right'
|
|
|
366
312
|
|
|
367
313
|
```
|
|
368
314
|
zx-kit/
|
|
369
|
-
├── package.json #
|
|
370
|
-
├── tsconfig.json # strict,
|
|
315
|
+
├── package.json # exports: { ".": "./dist/index.js" }
|
|
316
|
+
├── tsconfig.json # strict, emits to dist/
|
|
371
317
|
├── README.md
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
318
|
+
├── src/ # TypeScript source
|
|
319
|
+
│ ├── index.ts # barrel — re-exports everything
|
|
320
|
+
│ ├── palette.ts # SCALE, CELL, C, SpectrumColor
|
|
321
|
+
│ ├── font.ts # FONT, getCharRow
|
|
322
|
+
│ ├── renderer.ts # setupCanvas, mirrorSprite, drawSprite, drawChar, drawText,
|
|
323
|
+
│ │ # drawTextCentered, flashBorder
|
|
324
|
+
│ ├── audio.ts # initAudio, resumeAudio, beep, playPattern, Note,
|
|
325
|
+
│ │ # getAudioContext, getMasterGain
|
|
326
|
+
│ └── input.ts # initInput, tickMovement, consumeFlag/Debug/Pause/AnyKey,
|
|
327
|
+
│ # isHeld, Direction
|
|
328
|
+
└── dist/ # compiled output (generated by npm run build)
|
|
329
|
+
├── index.js / .d.ts
|
|
330
|
+
└── ...
|
|
379
331
|
```
|
|
380
332
|
|
|
381
333
|
---
|
|
382
334
|
|
|
383
335
|
## Design principles
|
|
384
336
|
|
|
385
|
-
- **
|
|
337
|
+
- **Compiled distribution** — ships compiled JS + `.d.ts` in `dist/`. No bundler configuration needed in the consuming project.
|
|
386
338
|
- **No runtime dependencies** — only Web platform APIs (`CanvasRenderingContext2D`, `AudioContext`, `KeyboardEvent`).
|
|
387
339
|
- **Strict TypeScript** — `strict: true`, `noUnusedLocals`, `noUnusedParameters`. No `any`.
|
|
388
|
-
- **Singleton state** — `audio.ts` and `input.ts` hold module-level state. Suitable for single-game use; not suitable for
|
|
389
|
-
- **ZX Spectrum
|
|
340
|
+
- **Singleton state** — `audio.ts` and `input.ts` hold module-level state. Suitable for single-game use; not suitable for multiple independent game instances on the same page.
|
|
341
|
+
- **ZX Spectrum authenticity** — palette values, cell size, and font bytes are constants, not configuration. The library is deliberately opinionated.
|
|
390
342
|
|
|
391
343
|
---
|
|
392
344
|
|
|
393
|
-
##
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
| `FONT`, `getCharRow` | `font.ts` | Re-export from game's `font.ts` |
|
|
405
|
-
| `AudioContext` + `GainNode` init | `audio.ts` | Replace with `initAudio()` / `getAudioContext()` / `getMasterGain()` |
|
|
406
|
-
| `beep()` helper | `audio.ts` | Import directly; remove inline version |
|
|
407
|
-
| Arrow-key repeat logic | `input.ts` | Replace with `initInput()` + `tickMovement(dt)` |
|
|
408
|
-
| Flag / debug / pause key flags | `input.ts` | Use `consumeFlag()`, `consumeDebug()`, `consumePause()` |
|
|
345
|
+
## Local development
|
|
346
|
+
|
|
347
|
+
To work against a local checkout instead of the npm version:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
# In your game project
|
|
351
|
+
npm install ../zx-kit --prefer-online
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
> The `--prefer-online` flag ensures npm resolves from the local path without caching issues.
|
|
355
|
+
> After publishing a new version to npm, switch back with `npm install zx-kit@latest`.
|
|
409
356
|
|
|
410
357
|
---
|
|
411
358
|
|