@johnmorrisdotca/houseki 0.4.0 → 0.4.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/CHANGELOG.md +29 -1
- package/README.md +77 -95
- package/dist/colour-chains/challenge.d.ts +12 -1
- package/dist/colour-chains/challenge.js +12 -1
- package/dist/colour-chains/content-types.d.ts +112 -0
- package/dist/colour-chains/content.d.ts +140 -3
- package/dist/colour-chains/content.js +140 -3
- package/dist/colour-chains/engine.d.ts +85 -6
- package/dist/colour-chains/engine.js +85 -6
- package/dist/colour-chains/input.d.ts +97 -7
- package/dist/colour-chains/input.js +97 -7
- package/dist/colour-chains/persistence.d.ts +36 -3
- package/dist/colour-chains/persistence.js +36 -3
- package/dist/colour-chains/types.d.ts +291 -17
- package/dist/colour-chains/validation.d.ts +12 -1
- package/dist/colour-chains/validation.js +12 -1
- package/dist/falling-triplets/content-types.d.ts +69 -0
- package/dist/falling-triplets/content.d.ts +60 -3
- package/dist/falling-triplets/content.js +60 -3
- package/dist/falling-triplets/engine.d.ts +122 -10
- package/dist/falling-triplets/engine.js +122 -10
- package/dist/falling-triplets/input.d.ts +61 -5
- package/dist/falling-triplets/input.js +61 -5
- package/dist/falling-triplets/match.d.ts +26 -2
- package/dist/falling-triplets/match.js +26 -2
- package/dist/falling-triplets/random.d.ts +34 -3
- package/dist/falling-triplets/random.js +34 -3
- package/dist/falling-triplets/types.d.ts +227 -3
- package/dist/gem-swap/content.d.ts +116 -0
- package/dist/gem-swap/content.js +72 -0
- package/dist/gem-swap/engine.d.ts +124 -9
- package/dist/gem-swap/engine.js +124 -9
- package/dist/gem-swap/persistence.d.ts +60 -4
- package/dist/gem-swap/persistence.js +65 -7
- package/dist/gem-swap/practice.js +2 -2
- package/dist/gem-swap/types.d.ts +336 -3
- package/dist/magnetic-blocks/content-types.d.ts +56 -0
- package/dist/magnetic-blocks/content.d.ts +91 -5
- package/dist/magnetic-blocks/content.js +91 -5
- package/dist/magnetic-blocks/engine.d.ts +63 -5
- package/dist/magnetic-blocks/engine.js +63 -5
- package/dist/magnetic-blocks/persistence.d.ts +36 -3
- package/dist/magnetic-blocks/persistence.js +36 -3
- package/dist/magnetic-blocks/physics.d.ts +176 -6
- package/dist/magnetic-blocks/physics.js +176 -6
- package/dist/magnetic-blocks/types.d.ts +185 -1
- package/dist/magnetic-blocks/validation.d.ts +12 -0
- package/dist/magnetic-blocks/validation.js +12 -0
- package/dist/nature/attraction.d.ts +24 -2
- package/dist/nature/attraction.js +24 -2
- package/dist/nature/environment.d.ts +200 -7
- package/dist/nature/environment.js +88 -7
- package/dist/nature/marking.d.ts +13 -1
- package/dist/nature/marking.js +13 -1
- package/dist/nature/rebound.d.ts +30 -2
- package/dist/nature/rebound.js +30 -2
- package/dist/nature/state.d.ts +11 -0
- package/dist/nature/state.js +11 -0
- package/dist/nature/types.d.ts +194 -1
- package/dist/nature/validation.d.ts +12 -0
- package/dist/nature/validation.js +12 -0
- package/dist/stone-collapse/content-types.d.ts +69 -0
- package/dist/stone-collapse/content.d.ts +60 -3
- package/dist/stone-collapse/content.js +60 -3
- package/dist/stone-collapse/engine.d.ts +99 -8
- package/dist/stone-collapse/engine.js +99 -8
- package/dist/stone-collapse/persistence.d.ts +36 -3
- package/dist/stone-collapse/persistence.js +36 -3
- package/dist/stone-collapse/types.d.ts +220 -3
- package/dist/stone-collapse/validation.d.ts +12 -1
- package/dist/stone-collapse/validation.js +12 -1
- package/package.json +34 -26
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
export const HIDDEN_ROWS = 0;
|
|
2
|
-
|
|
2
|
+
/** The fixed 60 Hz ticks showing the magnetic-block match mark phase.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { MARK_TICKS } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
7
|
+
*
|
|
8
|
+
* console.log(MARK_TICKS);
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export const MARK_TICKS = 7;
|
|
12
|
+
/** Fixed 60 Hz ticks in the magnetic-block removal phase.
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { REMOVE_TICKS } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
16
|
+
* console.log(REMOVE_TICKS);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export const REMOVE_TICKS = 6;
|
|
20
|
+
/** Fixed 60 Hz ticks in the magnetic-block gravity phase.
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { GRAVITY_TICKS } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
24
|
+
* console.log(GRAVITY_TICKS);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const GRAVITY_TICKS = 9;
|
|
3
28
|
export const cell = (x, y, width) => y * width + x;
|
|
4
29
|
const BASE = [[0, 0], [1, 0], [1, 1], [0, 1]];
|
|
5
|
-
/** Returns stable-gem addresses after rotating the square block around its centre.
|
|
30
|
+
/** Returns stable-gem addresses after rotating the square block around its centre.
|
|
31
|
+
*
|
|
32
|
+
* @param block - The native active 2×2 block and its stable gem ids.
|
|
33
|
+
* @param width - The native board width in cells.
|
|
34
|
+
* @returns Returns stable-gem addresses after rotating the square block around its centre.
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { blockCells, createGame } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
38
|
+
*
|
|
39
|
+
* const state=createGame({seed:"docs"});
|
|
40
|
+
* console.log(blockCells(state.active!,state.settings.width).length);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
6
43
|
export function blockCells(block, width) {
|
|
7
44
|
return block.gems.map((gem, corner) => {
|
|
8
45
|
let [x, y] = BASE[corner];
|
|
@@ -11,18 +48,64 @@ export function blockCells(block, width) {
|
|
|
11
48
|
return { index: cell(block.x + x, block.y + y, width), gem };
|
|
12
49
|
});
|
|
13
50
|
}
|
|
51
|
+
/** Test whether the whole native block fits passable empty board cells.
|
|
52
|
+
*
|
|
53
|
+
* @param block - The native active 2×2 block and its stable gem ids.
|
|
54
|
+
* @param board - The row-major native board; null cells are empty or masked.
|
|
55
|
+
* @param width - The native board width in cells.
|
|
56
|
+
* @param height - The native board height in cells.
|
|
57
|
+
* @param mask - The row-major active-cell mask; false cells cannot hold gems.
|
|
58
|
+
* @returns Test whether the whole native block fits passable empty board cells.
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { canPlace, createGame } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
62
|
+
*
|
|
63
|
+
* const state=createGame({seed:"docs"});
|
|
64
|
+
* console.log(canPlace(state.active!,state.board,state.settings.width,state.settings.height,state.settings.mask));
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
14
67
|
export function canPlace(block, board, width, height, mask) {
|
|
15
68
|
if (block.x < 0 || block.x + 1 >= width || block.y < 0 || block.y + 1 >= height)
|
|
16
69
|
return false;
|
|
17
70
|
return blockCells(block, width).every(({ index }) => { const x = index % width, y = Math.floor(index / width); return x >= 0 && x < width && y >= 0 && y < height && mask[index] && !board[index]; });
|
|
18
71
|
}
|
|
72
|
+
/** Find the last legal block row before downward collision.
|
|
73
|
+
*
|
|
74
|
+
* @param block - The native active 2×2 block and its stable gem ids.
|
|
75
|
+
* @param board - The row-major native board; null cells are empty or masked.
|
|
76
|
+
* @param width - The native board width in cells.
|
|
77
|
+
* @param height - The native board height in cells.
|
|
78
|
+
* @param mask - The row-major active-cell mask; false cells cannot hold gems.
|
|
79
|
+
* @returns Find the last legal block row before downward collision.
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* import { createGame, landingY } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
83
|
+
*
|
|
84
|
+
* const state=createGame({seed:"docs"});
|
|
85
|
+
* console.log(landingY(state.active!,state.board,state.settings.width,state.settings.height,state.settings.mask));
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
19
88
|
export function landingY(block, board, width, height, mask) {
|
|
20
89
|
let y = block.y;
|
|
21
90
|
while (canPlace({ ...block, y: y + 1 }, board, width, height, mask))
|
|
22
91
|
y++;
|
|
23
92
|
return y;
|
|
24
93
|
}
|
|
25
|
-
/** Computes deterministic orthogonal colour components of at least four gems.
|
|
94
|
+
/** Computes deterministic orthogonal colour components of at least four gems.
|
|
95
|
+
*
|
|
96
|
+
* @param board - The row-major native board; null cells are empty or masked.
|
|
97
|
+
* @param width - The native board width in cells.
|
|
98
|
+
* @param height - The native board height in cells.
|
|
99
|
+
* @param mask - The row-major active-cell mask; false cells cannot hold gems.
|
|
100
|
+
* @returns Computes deterministic orthogonal colour components of at least four gems.
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* import { createGame, matchGroups } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
104
|
+
*
|
|
105
|
+
* const state=createGame({seed:"docs"});
|
|
106
|
+
* console.log(matchGroups(state.board,state.settings.width,state.settings.height,state.settings.mask).length);
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
26
109
|
export function matchGroups(board, width, height, mask) {
|
|
27
110
|
const seen = new Set(), matches = [];
|
|
28
111
|
for (let start = 0; start < board.length; start++) {
|
|
@@ -46,6 +129,20 @@ export function matchGroups(board, width, height, mask) {
|
|
|
46
129
|
return matches;
|
|
47
130
|
}
|
|
48
131
|
export function allMatchedCells(groups) { return [...new Set(groups.flat())].sort((a, b) => a - b); }
|
|
132
|
+
/** Remove the selected native cell indices and discard bonds whose endpoint gems were removed.
|
|
133
|
+
*
|
|
134
|
+
* @param board - The row-major native board; null cells are empty or masked.
|
|
135
|
+
* @param bonds - Native stable-id bonds connecting surviving gems.
|
|
136
|
+
* @param indices - Row-major cell indices selected for removal.
|
|
137
|
+
* @returns Remove the selected native cell indices and discard bonds whose endpoint gems were removed. Nested null values denote empty board cells, absent active pieces, unselected tools or outcomes not yet reached; optional undefined fields are absent until applicable.
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* import { createGame, removeIds } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
141
|
+
*
|
|
142
|
+
* const state=createGame({seed:"docs"});
|
|
143
|
+
* console.log(removeIds(state.board,state.bonds,[]).ids.length);
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
49
146
|
export function removeIds(board, bonds, indices) {
|
|
50
147
|
const removed = new Set(indices), ids = indices.flatMap(index => board[index] ? [board[index].id] : []), remaining = board.map((gem, index) => removed.has(index) ? null : gem), alive = new Set(remaining.flatMap(gem => gem ? [gem.id] : []));
|
|
51
148
|
return { board: Object.freeze(remaining), bonds: Object.freeze(bonds.filter(edge => alive.has(edge.a) && alive.has(edge.b))), ids: Object.freeze(ids) };
|
|
@@ -81,7 +178,22 @@ function connectedComponents(board, bonds) {
|
|
|
81
178
|
}
|
|
82
179
|
return groups;
|
|
83
180
|
}
|
|
84
|
-
/** Settles bonded connected components one cell per deterministic bottom/ID priority step.
|
|
181
|
+
/** Settles bonded connected components one cell per deterministic bottom/ID priority step.
|
|
182
|
+
*
|
|
183
|
+
* @param initial - The row-major starting board for gravity settlement.
|
|
184
|
+
* @param bonds - Native stable-id bonds connecting surviving gems.
|
|
185
|
+
* @param width - The native board width in cells.
|
|
186
|
+
* @param height - The native board height in cells.
|
|
187
|
+
* @param mask - The row-major active-cell mask; false cells cannot hold gems.
|
|
188
|
+
* @returns Settles bonded connected components one cell per deterministic bottom/ID priority step. Nested null values denote empty board cells, absent active pieces, unselected tools or outcomes not yet reached; optional undefined fields are absent until applicable.
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* import { calmGravity, createGame } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
192
|
+
*
|
|
193
|
+
* const state=createGame({seed:"docs"});
|
|
194
|
+
* console.log(calmGravity(state.board,state.bonds,state.settings.width,state.settings.height,state.settings.mask).length);
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
85
197
|
export function calmGravity(initial, bonds, width, height, mask) {
|
|
86
198
|
let board = [...initial], moved = true, guard = width * height * height;
|
|
87
199
|
while (moved && guard-- > 0) {
|
|
@@ -106,7 +218,21 @@ export function calmGravity(initial, bonds, width, height, mask) {
|
|
|
106
218
|
throw new RangeError('Calm gravity did not converge within its board-derived step bound');
|
|
107
219
|
return Object.freeze(board);
|
|
108
220
|
}
|
|
109
|
-
/** Magnetic floor removes all bonds and settles each column segment independently.
|
|
221
|
+
/** Magnetic floor removes all bonds and settles each column segment independently.
|
|
222
|
+
*
|
|
223
|
+
* @param initial - The row-major starting board for gravity settlement.
|
|
224
|
+
* @param width - The native board width in cells.
|
|
225
|
+
* @param height - The native board height in cells.
|
|
226
|
+
* @param mask - The row-major active-cell mask; false cells cannot hold gems.
|
|
227
|
+
* @returns Magnetic floor removes all bonds and settles each column segment independently. Nested null values denote empty board cells, absent active pieces, unselected tools or outcomes not yet reached; optional undefined fields are absent until applicable.
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* import { createGame, magneticGravity } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
231
|
+
*
|
|
232
|
+
* const state=createGame({seed:"docs"});
|
|
233
|
+
* console.log(magneticGravity(state.board,state.settings.width,state.settings.height,state.settings.mask).length);
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
110
236
|
export function magneticGravity(initial, width, height, mask) {
|
|
111
237
|
const board = [...initial];
|
|
112
238
|
for (let x = 0; x < width; x++) {
|
|
@@ -131,10 +257,42 @@ export function magneticGravity(initial, width, height, mask) {
|
|
|
131
257
|
}
|
|
132
258
|
return Object.freeze(board);
|
|
133
259
|
}
|
|
260
|
+
/** Settle surviving gems with the chosen floor rule and retain valid stable-id bonds.
|
|
261
|
+
*
|
|
262
|
+
* @param initial - The row-major starting board for gravity settlement.
|
|
263
|
+
* @param bonds - Native stable-id bonds connecting surviving gems.
|
|
264
|
+
* @param floor - The native calm or magnetic floor rule to apply.
|
|
265
|
+
* @param width - The native board width in cells.
|
|
266
|
+
* @param height - The native board height in cells.
|
|
267
|
+
* @param mask - The row-major active-cell mask; false cells cannot hold gems.
|
|
268
|
+
* @returns Settle surviving gems with the chosen floor rule and retain valid stable-id bonds. Nested null values denote empty board cells, absent active pieces, unselected tools or outcomes not yet reached; optional undefined fields are absent until applicable.
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* import { createGame, settle } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
272
|
+
*
|
|
273
|
+
* const state=createGame({seed:"docs"});
|
|
274
|
+
* console.log(settle(state.board,state.bonds,"calm",state.settings.width,state.settings.height,state.settings.mask).board.length);
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
134
277
|
export function settle(initial, bonds, floor, width, height, mask) {
|
|
135
278
|
return floor === 'magnetic' ? { board: magneticGravity(initial, width, height, mask), bonds: Object.freeze([]) } : { board: calmGravity(initial, bonds, width, height, mask), bonds };
|
|
136
279
|
}
|
|
137
|
-
/** Collects the occupied one-cell support layer under both block columns at first contact.
|
|
280
|
+
/** Collects the occupied one-cell support layer under both block columns at first contact.
|
|
281
|
+
*
|
|
282
|
+
* @param block - The native active 2×2 block and its stable gem ids.
|
|
283
|
+
* @param board - The row-major native board; null cells are empty or masked.
|
|
284
|
+
* @param width - The native board width in cells.
|
|
285
|
+
* @param height - The native board height in cells.
|
|
286
|
+
* @param mask - The row-major active-cell mask; false cells cannot hold gems.
|
|
287
|
+
* @returns Collects the occupied one-cell support layer under both block columns at first contact.
|
|
288
|
+
* @example
|
|
289
|
+
* ```ts
|
|
290
|
+
* import { createGame, impactSupportIds } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
291
|
+
*
|
|
292
|
+
* const state=createGame({seed:"docs"});
|
|
293
|
+
* console.log(impactSupportIds(state.active!,state.board,state.settings.width,state.settings.height,state.settings.mask).length);
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
138
296
|
export function impactSupportIds(block, board, width, height, mask) {
|
|
139
297
|
const byColumn = new Map();
|
|
140
298
|
for (const { index } of blockCells(block, width)) {
|
|
@@ -152,4 +310,16 @@ export function impactSupportIds(block, board, width, height, mask) {
|
|
|
152
310
|
}
|
|
153
311
|
return Object.freeze([...ids].sort((a, b) => a - b));
|
|
154
312
|
}
|
|
313
|
+
/** List the native stable-id bonds connecting adjacent gems in the active block.
|
|
314
|
+
*
|
|
315
|
+
* @param block - The native active 2×2 block and its stable gem ids.
|
|
316
|
+
* @returns List the native stable-id bonds connecting adjacent gems in the active block.
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* import { blockBonds, createGame } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
320
|
+
*
|
|
321
|
+
* const state=createGame({seed:"docs"});
|
|
322
|
+
* console.log(blockBonds(state.active!).length);
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
155
325
|
export function blockBonds(block) { return Object.freeze([[0, 1], [1, 2], [2, 3], [3, 0]].map(([a, b]) => { const ids = [block.gems[a].id, block.gems[b].id].sort((x, y) => x - y); return Object.freeze({ a: ids[0], b: ids[1] }); }).sort((a, b) => a.a - b.a || a.b - b.b)); }
|
|
@@ -1,6 +1,35 @@
|
|
|
1
|
-
/** A standard gem colour used by a magnetic block.
|
|
1
|
+
/** A standard gem colour used by a magnetic block.
|
|
2
|
+
*
|
|
3
|
+
* @example
|
|
4
|
+
* ```ts
|
|
5
|
+
* import type { Colour } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
6
|
+
*
|
|
7
|
+
* const value: Colour = "red";
|
|
8
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
2
11
|
export type Colour = 'red' | 'blue' | 'green' | 'gold' | 'purple' | 'teal';
|
|
12
|
+
/** The native calm or magnetic floor used when settling blocks.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import type { Floor } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
17
|
+
*
|
|
18
|
+
* const value: Floor = "calm";
|
|
19
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
3
22
|
export type Floor = 'calm' | 'magnetic';
|
|
23
|
+
/** A fixed, periodic or authored schedule selecting the floor for committed block placements.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import type { FloorSchedule } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
28
|
+
*
|
|
29
|
+
* const value: FloorSchedule = {kind:"fixed",floor:"calm"};
|
|
30
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
4
33
|
export type FloorSchedule = {
|
|
5
34
|
readonly kind: 'frequent';
|
|
6
35
|
} | {
|
|
@@ -18,16 +47,70 @@ export type FloorSchedule = {
|
|
|
18
47
|
readonly magneticPlacements: readonly number[];
|
|
19
48
|
readonly defaultFloor?: Floor;
|
|
20
49
|
};
|
|
50
|
+
/** The native game mode; authored challenges have fixed setup and queue rules.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import type { Mode } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
55
|
+
*
|
|
56
|
+
* const value: Mode = "relaxed";
|
|
57
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
21
60
|
export type Mode = 'relaxed' | 'arcade';
|
|
61
|
+
/** The native falling, resolution, pause and ending phases.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import { createGame } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
66
|
+
* import type { Phase } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
67
|
+
*
|
|
68
|
+
* const state=createGame({seed:"docs"});
|
|
69
|
+
* const value: Phase = state.phase;
|
|
70
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
22
73
|
export type Phase = 'falling' | 'clear-mark' | 'clear-remove' | 'gravity' | 'paused' | 'won' | 'lost' | 'finished';
|
|
74
|
+
/** A native gem with a stable run-local id and colour.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import type { Gem } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
79
|
+
*
|
|
80
|
+
* const value: Gem = {id:1,colour:"red"};
|
|
81
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
23
84
|
export interface Gem {
|
|
24
85
|
readonly id: number;
|
|
25
86
|
readonly colour: Colour;
|
|
26
87
|
}
|
|
88
|
+
/** An undirected stable-id connection between two gems in a magnetic block.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* import type { Bond } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
93
|
+
*
|
|
94
|
+
* const value: Bond = {a:1,b:2};
|
|
95
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
27
98
|
export interface Bond {
|
|
28
99
|
readonly a: number;
|
|
29
100
|
readonly b: number;
|
|
30
101
|
}
|
|
102
|
+
/** The native active 2×2 block, orientation and fixed-tick descent/lock counters.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* import { createGame } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
107
|
+
* import type { Block } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
108
|
+
*
|
|
109
|
+
* const state=createGame({seed:"docs"});
|
|
110
|
+
* const value: Block = state.active!;
|
|
111
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
31
114
|
export interface Block {
|
|
32
115
|
readonly x: number;
|
|
33
116
|
readonly y: number;
|
|
@@ -38,12 +121,32 @@ export interface Block {
|
|
|
38
121
|
readonly lockResets: number;
|
|
39
122
|
readonly lockStarted: boolean;
|
|
40
123
|
}
|
|
124
|
+
/** The native authored goal; its discriminant selects the required collection or clearing condition.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* import type { Goal } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
129
|
+
*
|
|
130
|
+
* const value: Goal = {kind:"clear-all"};
|
|
131
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
41
134
|
export type Goal = {
|
|
42
135
|
readonly kind: 'clear-all';
|
|
43
136
|
} | {
|
|
44
137
|
readonly kind: 'clear-targets';
|
|
45
138
|
readonly targetIds: readonly number[];
|
|
46
139
|
};
|
|
140
|
+
/** Creation settings for this native engine; dimensions, mode, seed and optional mechanics are validated together.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* import type { CreateOptions } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
145
|
+
*
|
|
146
|
+
* const value: CreateOptions = {seed:"docs"};
|
|
147
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
47
150
|
export interface CreateOptions {
|
|
48
151
|
readonly mode?: Mode;
|
|
49
152
|
readonly width?: number;
|
|
@@ -60,6 +163,18 @@ export interface CreateOptions {
|
|
|
60
163
|
readonly magneticImpact?: boolean;
|
|
61
164
|
readonly goal?: Goal;
|
|
62
165
|
}
|
|
166
|
+
/** Validated settings fixed at native game creation.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* import { createGame } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
171
|
+
* import type { Settings } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
172
|
+
*
|
|
173
|
+
* const state=createGame({seed:"docs"});
|
|
174
|
+
* const value: Settings = state.settings;
|
|
175
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
63
178
|
export interface Settings {
|
|
64
179
|
readonly mode: Mode;
|
|
65
180
|
readonly width: number;
|
|
@@ -76,6 +191,18 @@ export interface Settings {
|
|
|
76
191
|
readonly initialQueue?: readonly (readonly [Colour, Colour, Colour, Colour])[];
|
|
77
192
|
readonly goal?: Goal;
|
|
78
193
|
}
|
|
194
|
+
/** The immutable authoritative native game state, including its board, random state and recording.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* import { createGame } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
199
|
+
* import type { GameState } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
200
|
+
*
|
|
201
|
+
* const state=createGame({seed:"docs"});
|
|
202
|
+
* const value: GameState = state;
|
|
203
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
79
206
|
export interface GameState {
|
|
80
207
|
readonly game: 'magnetic-blocks';
|
|
81
208
|
readonly rules: 'magnetic-blocks-1';
|
|
@@ -105,6 +232,17 @@ export interface GameState {
|
|
|
105
232
|
readonly recording: readonly RecordedAction[];
|
|
106
233
|
readonly reason?: string;
|
|
107
234
|
}
|
|
235
|
+
/** A native player action accepted or rejected according to the current phase and rules.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* import type { Action } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
240
|
+
*
|
|
241
|
+
* const action: Action={kind:"left"};
|
|
242
|
+
* const value: Action = action;
|
|
243
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
108
246
|
export type Action = {
|
|
109
247
|
readonly kind: 'left';
|
|
110
248
|
} | {
|
|
@@ -129,21 +267,67 @@ export type Action = {
|
|
|
129
267
|
} | {
|
|
130
268
|
readonly kind: 'cancel-floor-override';
|
|
131
269
|
};
|
|
270
|
+
/** A native action recording with the logical position needed for deterministic replay.
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```ts
|
|
274
|
+
* import type { Action, RecordedAction } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
275
|
+
*
|
|
276
|
+
* const action: Action={kind:"left"};
|
|
277
|
+
* const value: RecordedAction = {tick:0,ordinal:0,action};
|
|
278
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
132
281
|
export interface RecordedAction {
|
|
133
282
|
readonly tick: number;
|
|
134
283
|
readonly ordinal: number;
|
|
135
284
|
readonly action: Action;
|
|
136
285
|
}
|
|
286
|
+
/** An event emitted by one native transition; type selects the event’s additional fields.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```ts
|
|
290
|
+
* import type { GameEvent } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
291
|
+
*
|
|
292
|
+
* const value: GameEvent = {type:"host-example"};
|
|
293
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
137
296
|
export interface GameEvent {
|
|
138
297
|
readonly type: string;
|
|
139
298
|
readonly [key: string]: unknown;
|
|
140
299
|
}
|
|
300
|
+
/** The resulting native state, emitted events and acceptance decision; rejected actions preserve the input.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```ts
|
|
304
|
+
* import { applyAction, createGame } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
305
|
+
* import type { Action, Transition } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
306
|
+
*
|
|
307
|
+
* const state=createGame({seed:"docs"});
|
|
308
|
+
* const action: Action={kind:"left"};
|
|
309
|
+
* const value: Transition = applyAction(state,action);
|
|
310
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
141
313
|
export interface Transition {
|
|
142
314
|
readonly state: GameState;
|
|
143
315
|
readonly events: readonly GameEvent[];
|
|
144
316
|
readonly accepted: boolean;
|
|
145
317
|
readonly reason?: string;
|
|
146
318
|
}
|
|
319
|
+
/** A compact native status derived from the authoritative state for a host’s labels.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* import { createGame, statusOf } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
324
|
+
* import type { GameStatus } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
325
|
+
*
|
|
326
|
+
* const state=createGame({seed:"docs"});
|
|
327
|
+
* const value: GameStatus = statusOf(state);
|
|
328
|
+
* console.log(value == null ? value : typeof value === "object" ? Object.keys(value).slice(0,6) : value);
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
147
331
|
export interface GameStatus {
|
|
148
332
|
readonly phase: Phase;
|
|
149
333
|
readonly score: number;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import type { Bond, Colour, CreateOptions, FloorSchedule, Gem, Goal, Mode } from './types.js';
|
|
2
|
+
/** A typed native magnetic-block setup validation error.
|
|
3
|
+
*
|
|
4
|
+
* @param code - The native stable error code used by callers to distinguish setup or replay failures.
|
|
5
|
+
* @param message - The human-readable diagnostic accompanying the native code.
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { MagneticBlocksOptionsError } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
9
|
+
*
|
|
10
|
+
* const error=new MagneticBlocksOptionsError("docs-example","Invalid example setup");
|
|
11
|
+
* console.log(error.name,error.code,error.message);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
2
14
|
export declare class MagneticBlocksOptionsError extends RangeError {
|
|
3
15
|
readonly code: string;
|
|
4
16
|
constructor(code: string, message: string);
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { calmGravity } from './physics.js';
|
|
2
|
+
/** A typed native magnetic-block setup validation error.
|
|
3
|
+
*
|
|
4
|
+
* @param code - The native stable error code used by callers to distinguish setup or replay failures.
|
|
5
|
+
* @param message - The human-readable diagnostic accompanying the native code.
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { MagneticBlocksOptionsError } from "@johnmorrisdotca/houseki/magnetic-blocks";
|
|
9
|
+
*
|
|
10
|
+
* const error=new MagneticBlocksOptionsError("docs-example","Invalid example setup");
|
|
11
|
+
* console.log(error.name,error.code,error.message);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
2
14
|
export class MagneticBlocksOptionsError extends RangeError {
|
|
3
15
|
code;
|
|
4
16
|
constructor(code, message) { super(message); this.name = 'MagneticBlocksOptionsError'; this.code = code; }
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import type { MagneticPreview, NatureState, NatureTransition } from './types.js';
|
|
2
|
-
/** Preview one bounded deterministic attraction pulse without mutating the state.
|
|
2
|
+
/** Preview one bounded deterministic attraction pulse without mutating the state.
|
|
3
|
+
*
|
|
4
|
+
* @param state - The immutable native state to read or transition; the input is retained.
|
|
5
|
+
* @returns Preview one bounded deterministic attraction pulse without mutating the state.
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { createNatureState, previewMagneticPulse } from "@johnmorrisdotca/houseki/nature";
|
|
9
|
+
*
|
|
10
|
+
* const state=createNatureState({width:4,height:4,seed:"docs"});
|
|
11
|
+
* console.log(previewMagneticPulse(state).moves.length);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
3
14
|
export declare function previewMagneticPulse(state: NatureState): MagneticPreview;
|
|
4
|
-
/** Apply one attraction pulse. Disabled nature rules preserve the exact state reference and consume no random state.
|
|
15
|
+
/** Apply one attraction pulse. Disabled nature rules preserve the exact state reference and consume no random state.
|
|
16
|
+
*
|
|
17
|
+
* @param state - The immutable native state to read or transition; the input is retained.
|
|
18
|
+
* @returns Apply one attraction pulse. Disabled nature rules preserve the exact state reference and consume no random state.
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { applyMagneticPulse, createNatureState } from "@johnmorrisdotca/houseki/nature";
|
|
22
|
+
*
|
|
23
|
+
* const state=createNatureState({width:4,height:4,seed:"docs"});
|
|
24
|
+
* console.log(applyMagneticPulse(state).accepted);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
5
27
|
export declare function applyMagneticPulse(state: NatureState): NatureTransition;
|
|
@@ -116,7 +116,18 @@ function movePair(board, state, pair) {
|
|
|
116
116
|
const a = coordinates(aIndex, state.width), b = coordinates(bIndex, state.width);
|
|
117
117
|
return deepFreeze({ ids, axis: pair.axis, emptyGap: pair.gap, midpoint: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }, ...(pair.a.anchored || pair.b.anchored ? { anchoredId: pair.a.anchored ? pair.a.id : pair.b.id } : {}), moves: localMoves });
|
|
118
118
|
}
|
|
119
|
-
/** Preview one bounded deterministic attraction pulse without mutating the state.
|
|
119
|
+
/** Preview one bounded deterministic attraction pulse without mutating the state.
|
|
120
|
+
*
|
|
121
|
+
* @param state - The immutable native state to read or transition; the input is retained.
|
|
122
|
+
* @returns Preview one bounded deterministic attraction pulse without mutating the state.
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* import { createNatureState, previewMagneticPulse } from "@johnmorrisdotca/houseki/nature";
|
|
126
|
+
*
|
|
127
|
+
* const state=createNatureState({width:4,height:4,seed:"docs"});
|
|
128
|
+
* console.log(previewMagneticPulse(state).moves.length);
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
120
131
|
export function previewMagneticPulse(state) {
|
|
121
132
|
validateState(state);
|
|
122
133
|
if (!state.rules.magneticEnabled)
|
|
@@ -126,7 +137,18 @@ export function previewMagneticPulse(state) {
|
|
|
126
137
|
const moves = pairs.flatMap(pair => pair.moves);
|
|
127
138
|
return deepFreeze({ pairs, moves, cells: board });
|
|
128
139
|
}
|
|
129
|
-
/** Apply one attraction pulse. Disabled nature rules preserve the exact state reference and consume no random state.
|
|
140
|
+
/** Apply one attraction pulse. Disabled nature rules preserve the exact state reference and consume no random state.
|
|
141
|
+
*
|
|
142
|
+
* @param state - The immutable native state to read or transition; the input is retained.
|
|
143
|
+
* @returns Apply one attraction pulse. Disabled nature rules preserve the exact state reference and consume no random state.
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* import { applyMagneticPulse, createNatureState } from "@johnmorrisdotca/houseki/nature";
|
|
147
|
+
*
|
|
148
|
+
* const state=createNatureState({width:4,height:4,seed:"docs"});
|
|
149
|
+
* console.log(applyMagneticPulse(state).accepted);
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
130
152
|
export function applyMagneticPulse(state) {
|
|
131
153
|
const preview = previewMagneticPulse(state);
|
|
132
154
|
if (!state.rules.magneticEnabled || preview.moves.length === 0)
|