@cubingcenter/scrambler 0.6.3 → 0.8.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 +17 -4
- package/dist/core/api.d.ts.map +1 -1
- package/dist/core/api.js +10 -2
- package/dist/puzzles/cube2/solver/browser-loader.d.ts +4 -7
- package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/search.js +3 -3
- package/dist/puzzles/cube2/solver/tables.d.ts +6 -0
- package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/tables.js +16 -2
- package/dist/puzzles/cube3/index.d.ts +1 -0
- package/dist/puzzles/cube3/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.js +104 -18
- package/dist/puzzles/cube3/scrambler.d.ts +16 -1
- package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube3/scrambler.js +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.js +48 -22
- package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist/puzzles/cube3/solver/index.d.ts +5 -3
- package/dist/puzzles/cube3/solver/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/index.js +2 -1
- package/dist/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist/puzzles/cube3/solver/phase1.d.ts +37 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +156 -15
- package/dist/puzzles/cube3/solver/phase2.d.ts +75 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +279 -68
- package/dist/puzzles/cube3/solver/search-utils.d.ts +1 -1
- package/dist/puzzles/cube3/solver/search-utils.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/symmetry.d.ts +24 -0
- package/dist/puzzles/cube3/solver/symmetry.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/symmetry.js +74 -0
- package/dist/puzzles/cube3/solver/tables.d.ts +6 -1
- package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/tables.js +14 -12
- package/dist/puzzles/cube3/validation.d.ts.map +1 -1
- package/dist/puzzles/cube3/validation.js +15 -7
- package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
- package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
- package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
- package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker.js +31 -0
- package/dist/puzzles/cube4/index.d.ts +10 -2
- package/dist/puzzles/cube4/index.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.js +6 -2
- package/dist/puzzles/cube4/moves.d.ts +5 -0
- package/dist/puzzles/cube4/moves.d.ts.map +1 -1
- package/dist/puzzles/cube4/moves.js +212 -11
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts +29 -1
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/browser-loader.js +227 -32
- package/dist/puzzles/cube4/reduction/center2.d.ts +44 -3
- package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/center2.js +152 -10
- package/dist/puzzles/cube4/reduction/edge3.d.ts +4 -0
- package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/edge3.js +23 -3
- package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
- package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/finish3.js +393 -0
- package/dist/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
- package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +52 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.js +220 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts +15 -1
- package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/node-loader.js +199 -25
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
- package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts +116 -7
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/phase3-search.js +458 -32
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
- package/dist/puzzles/cube4/reduction/solve.d.ts +172 -2
- package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/solve.js +1201 -180
- package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
- package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
- package/dist/puzzles/cube4/scramble-cache.js +150 -0
- package/dist/puzzles/cube4/scrambler.d.ts +201 -6
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +414 -176
- package/dist/puzzles/cube4/state.d.ts.map +1 -1
- package/dist/puzzles/cube4/state.js +4 -1
- package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
- package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-pool.js +405 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-protocol.js +58 -0
- package/dist/puzzles/cube4/worker.d.ts +26 -0
- package/dist/puzzles/cube4/worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker.js +49 -0
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/search.js +3 -6
- package/dist/puzzles/pyraminx/solver/tables.d.ts +6 -0
- package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/tables.js +17 -0
- package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
- package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
- package/dist/puzzles/shared/compressed-fetch.js +226 -31
- package/dist/puzzles/shared/load-once.d.ts +16 -0
- package/dist/puzzles/shared/load-once.d.ts.map +1 -0
- package/dist/puzzles/shared/load-once.js +11 -0
- package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
- package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist/puzzles/skewb/solver/search.js +3 -3
- package/dist/puzzles/skewb/solver/tables.d.ts +6 -0
- package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/tables.js +17 -0
- package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
- package/dist-cjs/core/api.js +10 -2
- package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/cube2/solver/search.js +2 -2
- package/dist-cjs/puzzles/cube2/solver/tables.js +17 -3
- package/dist-cjs/puzzles/cube3/moves.js +104 -18
- package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
- package/dist-cjs/puzzles/cube3/solver/browser-loader.js +48 -22
- package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist-cjs/puzzles/cube3/solver/index.js +8 -1
- package/dist-cjs/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist-cjs/puzzles/cube3/solver/phase1.js +157 -15
- package/dist-cjs/puzzles/cube3/solver/phase2.js +276 -65
- package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
- package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
- package/dist-cjs/puzzles/cube3/validation.js +15 -7
- package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
- package/dist-cjs/puzzles/cube4/index.js +23 -1
- package/dist-cjs/puzzles/cube4/moves.js +213 -11
- package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +227 -32
- package/dist-cjs/puzzles/cube4/reduction/center2.js +155 -10
- package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
- package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +232 -0
- package/dist-cjs/puzzles/cube4/reduction/node-loader.js +200 -25
- package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
- package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +460 -33
- package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
- package/dist-cjs/puzzles/cube4/reduction/solve.js +1203 -172
- package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
- package/dist-cjs/puzzles/cube4/scrambler.js +416 -174
- package/dist-cjs/puzzles/cube4/state.js +4 -1
- package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
- package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
- package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
- package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
- package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
- package/dist-cjs/puzzles/shared/load-once.js +14 -0
- package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
- package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
- package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
- package/docs/development.md +19 -7
- package/package.json +17 -7
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Web Worker pool for 4x4 random-state scrambles — the browser counterpart
|
|
4
|
+
* of `worker-pool.ts` with the same protocol, lazy-spawn lifecycle, FIFO
|
|
5
|
+
* queue, typed error propagation and per-thread table reuse.
|
|
6
|
+
*
|
|
7
|
+
* Unlike the Node pool (which locates its own worker file), the browser
|
|
8
|
+
* pool REQUIRES the caller to pass `workerUrl`, because only the caller's
|
|
9
|
+
* file can host the static `new URL("./browser-worker.js", import.meta.url)`
|
|
10
|
+
* expression that bundlers (Vite, Webpack 5, Parcel) statically analyse to
|
|
11
|
+
* emit a separate worker chunk:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* const pool = new Cube4BrowserWorkerPool({
|
|
15
|
+
* workerUrl: new URL("./browser-worker.js", import.meta.url)
|
|
16
|
+
* });
|
|
17
|
+
* const batch = await pool.generateBatch(8, { seed: "demo" });
|
|
18
|
+
* await pool.terminate();
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* This file contains no `import.meta` and no static Node imports, so it
|
|
22
|
+
* also compiles in the CommonJS build (unlike a hard-coded
|
|
23
|
+
* `new URL(..., import.meta.url)` default).
|
|
24
|
+
*
|
|
25
|
+
* Errors crossing the worker boundary arrive as `Error` instances with the
|
|
26
|
+
* original `name`, `message` and `code` preserved (`worker-protocol.ts`).
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.Cube4BrowserWorkerPool = exports.Cube4BrowserWorkerPoolClosedError = void 0;
|
|
30
|
+
exports.deriveBrowserBatchCube4Options = deriveBrowserBatchCube4Options;
|
|
31
|
+
const worker_protocol_js_1 = require("./worker-protocol.js");
|
|
32
|
+
/** Typed rejection used for post-terminate operations and shutdown itself. */
|
|
33
|
+
class Cube4BrowserWorkerPoolClosedError extends Error {
|
|
34
|
+
code = "cube4-browser-pool-closed";
|
|
35
|
+
constructor(action = "operation") {
|
|
36
|
+
super(`[cube4] browser worker pool is closed (${action} rejected).`);
|
|
37
|
+
this.name = "Cube4BrowserWorkerPoolClosedError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.Cube4BrowserWorkerPoolClosedError = Cube4BrowserWorkerPoolClosedError;
|
|
41
|
+
/**
|
|
42
|
+
* Batch seed derivation — identical to core `generateScrambles` and to
|
|
43
|
+
* `deriveBatchCube4Options` in the Node pool.
|
|
44
|
+
*/
|
|
45
|
+
function deriveBrowserBatchCube4Options(options, index, count) {
|
|
46
|
+
if (count === 1 || options.seed === undefined) {
|
|
47
|
+
return options;
|
|
48
|
+
}
|
|
49
|
+
return { ...options, seed: `${options.seed}:${index}` };
|
|
50
|
+
}
|
|
51
|
+
function defaultBrowserSize() {
|
|
52
|
+
if (typeof navigator !== "undefined" && navigator.hardwareConcurrency) {
|
|
53
|
+
return Math.max(1, navigator.hardwareConcurrency - 1);
|
|
54
|
+
}
|
|
55
|
+
return 4;
|
|
56
|
+
}
|
|
57
|
+
class Cube4BrowserWorkerPool {
|
|
58
|
+
workers = [];
|
|
59
|
+
queue = [];
|
|
60
|
+
workerUrl;
|
|
61
|
+
maxSize;
|
|
62
|
+
pumping = false;
|
|
63
|
+
terminatePromise;
|
|
64
|
+
nextId = 0;
|
|
65
|
+
terminated = false;
|
|
66
|
+
constructor(options) {
|
|
67
|
+
if (typeof Worker === "undefined") {
|
|
68
|
+
throw new Error("[cube4] Web Workers are not available in this environment; " +
|
|
69
|
+
"use generateCube4Scramble() directly for single-threaded generation.");
|
|
70
|
+
}
|
|
71
|
+
if (options.size !== undefined) {
|
|
72
|
+
if (!Number.isInteger(options.size) || options.size < 1) {
|
|
73
|
+
throw new RangeError(`[cube4] pool size must be an integer >= 1 (got ${options.size}).`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
this.workerUrl =
|
|
77
|
+
typeof options.workerUrl === "string" ? options.workerUrl : options.workerUrl.href;
|
|
78
|
+
this.maxSize = options.size ?? defaultBrowserSize();
|
|
79
|
+
}
|
|
80
|
+
/** Threads spawned so far (lazily grown; 0 before the first job). */
|
|
81
|
+
get workerCount() {
|
|
82
|
+
return this.workers.length;
|
|
83
|
+
}
|
|
84
|
+
get queuedCount() {
|
|
85
|
+
return this.queue.length;
|
|
86
|
+
}
|
|
87
|
+
get busyCount() {
|
|
88
|
+
return this.workers.reduce((n, rec) => n + (rec.task !== null ? 1 : 0), 0);
|
|
89
|
+
}
|
|
90
|
+
get closed() {
|
|
91
|
+
return this.terminated;
|
|
92
|
+
}
|
|
93
|
+
generate(options = {}) {
|
|
94
|
+
if (this.terminated) {
|
|
95
|
+
return Promise.reject(new Cube4BrowserWorkerPoolClosedError("generate"));
|
|
96
|
+
}
|
|
97
|
+
const id = this.nextId++;
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
this.queue.push({ id, options, resolve, reject });
|
|
100
|
+
this.pump();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
generateBatch(count, options = {}) {
|
|
104
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
105
|
+
return Promise.reject(new RangeError(`count must be a positive integer (got ${count}).`));
|
|
106
|
+
}
|
|
107
|
+
return Promise.all(Array.from({ length: count }, (_, index) => this.generate(deriveBrowserBatchCube4Options(options, index, count))));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Prompt shutdown: rejects queued/in-flight tasks and terminates every
|
|
111
|
+
* Web Worker synchronously (no threads to join in the browser).
|
|
112
|
+
* Idempotent.
|
|
113
|
+
*/
|
|
114
|
+
terminate() {
|
|
115
|
+
if (this.terminatePromise !== undefined)
|
|
116
|
+
return;
|
|
117
|
+
this.terminated = true;
|
|
118
|
+
const closed = () => new Cube4BrowserWorkerPoolClosedError();
|
|
119
|
+
for (const task of this.queue.splice(0))
|
|
120
|
+
task.reject(closed());
|
|
121
|
+
const records = this.workers.splice(0);
|
|
122
|
+
for (const rec of records) {
|
|
123
|
+
if (rec.task !== null) {
|
|
124
|
+
rec.task.reject(closed());
|
|
125
|
+
rec.task = null;
|
|
126
|
+
}
|
|
127
|
+
rec.worker.terminate();
|
|
128
|
+
}
|
|
129
|
+
this.terminatePromise = Promise.resolve();
|
|
130
|
+
}
|
|
131
|
+
// ── internals (same shape as the Node pool) ──────────────────────────
|
|
132
|
+
pump() {
|
|
133
|
+
if (this.pumping || this.terminated)
|
|
134
|
+
return;
|
|
135
|
+
this.pumping = true;
|
|
136
|
+
void this.runPump()
|
|
137
|
+
.catch(() => undefined)
|
|
138
|
+
.finally(() => {
|
|
139
|
+
this.pumping = false;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
async runPump() {
|
|
143
|
+
while (!this.terminated && this.queue.length > 0) {
|
|
144
|
+
const idle = this.workers.find((rec) => rec.task === null);
|
|
145
|
+
if (idle !== undefined) {
|
|
146
|
+
this.dispatch(idle, this.queue.shift());
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (this.workers.length < this.maxSize) {
|
|
150
|
+
this.spawnWorker();
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
spawnWorker() {
|
|
157
|
+
const worker = new Worker(this.workerUrl, { type: "module", name: "cubingcenter-444" });
|
|
158
|
+
const rec = { worker, task: null };
|
|
159
|
+
worker.onmessage = (event) => {
|
|
160
|
+
this.handleResponse(rec, event.data);
|
|
161
|
+
};
|
|
162
|
+
worker.onerror = (event) => {
|
|
163
|
+
this.handleWorkerGone(rec, event.error instanceof Error
|
|
164
|
+
? event.error
|
|
165
|
+
: new Error(event.message || "[cube4] browser worker failed."));
|
|
166
|
+
};
|
|
167
|
+
this.workers.push(rec);
|
|
168
|
+
return rec;
|
|
169
|
+
}
|
|
170
|
+
dispatch(rec, task) {
|
|
171
|
+
rec.task = task;
|
|
172
|
+
const message = { id: task.id, options: task.options };
|
|
173
|
+
try {
|
|
174
|
+
rec.worker.postMessage(message);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
rec.task = null;
|
|
178
|
+
task.reject(error);
|
|
179
|
+
this.pump();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
handleResponse(rec, response) {
|
|
183
|
+
const task = rec.task;
|
|
184
|
+
if (task === null || task.id !== response.id)
|
|
185
|
+
return;
|
|
186
|
+
rec.task = null;
|
|
187
|
+
if (this.terminated)
|
|
188
|
+
return;
|
|
189
|
+
if (response.ok)
|
|
190
|
+
task.resolve(response.result);
|
|
191
|
+
else
|
|
192
|
+
task.reject((0, worker_protocol_js_1.deserializeCube4Error)(response.error));
|
|
193
|
+
const next = this.queue.shift();
|
|
194
|
+
if (next !== undefined)
|
|
195
|
+
this.dispatch(rec, next);
|
|
196
|
+
this.pump();
|
|
197
|
+
}
|
|
198
|
+
handleWorkerGone(rec, error) {
|
|
199
|
+
const index = this.workers.indexOf(rec);
|
|
200
|
+
if (index === -1)
|
|
201
|
+
return;
|
|
202
|
+
this.workers.splice(index, 1);
|
|
203
|
+
const task = rec.task;
|
|
204
|
+
rec.task = null;
|
|
205
|
+
if (task !== null && !this.terminated)
|
|
206
|
+
task.reject(error);
|
|
207
|
+
if (!this.terminated)
|
|
208
|
+
this.pump();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.Cube4BrowserWorkerPool = Cube4BrowserWorkerPool;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// cube4 barrel exports
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.validateCube4State = exports.generateRandomCube4State = exports.FACE_MOVE_EFFECTS = exports.parseMove = exports.parseCube4Move = exports.parseCube4Algorithm = exports.formatCube4Algorithm = exports.simplifyCube4Algorithm = exports.invertCube4Algorithm = exports.invertCube4Move = exports.applyCube4Algorithm = exports.applyCube4Move = exports.deriveCube4StreamOptions = exports.createCube4ScrambleCache = exports.Cube4ScrambleCache = exports.deriveBrowserBatchCube4Options = exports.Cube4BrowserWorkerPoolClosedError = exports.Cube4BrowserWorkerPool = exports.closeDefaultCube4WorkerPool = exports.getDefaultCube4WorkerPool = exports.generateCube4ScrambleBatch = exports.deriveBatchCube4Options = exports.Cube4WorkerPoolClosedError = exports.Cube4WorkerPool = exports.generateCube4Attempt = exports.generateCube4Scramble = exports.cube4ScrambleGenerator = exports.Cube4Phase3CapError = exports.Cube4Phase2CapError = exports.Cube4FinishCapError = exports.areWingsPaired = exports.areCentersReduced = exports.isSolvedCube4State = exports.cube4StatesEqual = exports.cloneCube4State = exports.createSolvedCube4State = exports.CUBE4_OUTER_MOVES = exports.CUBE4_ALL_MOVES = exports.CUBE4_FACE_MOVES = exports.B = exports.L = exports.D = exports.F = exports.R = exports.U = exports.CENTERS_PER_FACE = exports.FACE_COUNT = exports.CENTER_COUNT = exports.WING_COUNT = exports.CORNER_COUNT = void 0;
|
|
7
|
+
exports.initializeScrambler = exports.getSupportedEvents = exports.getEventInfo = exports.generateScrambles = exports.generateScramble = exports.getCube4FaceOrder = exports.formatCube4Facelets = exports.toCube4Facelets = exports.isAllowedRandomCube4State = exports.isTrivialCube4State = exports.isOneMoveFromSolvedCube4State = void 0;
|
|
7
8
|
const index_js_1 = require("../../core/index.js");
|
|
8
9
|
const scrambler_js_1 = require("./scrambler.js");
|
|
9
10
|
(0, index_js_1.registerScrambleGenerator)(scrambler_js_1.cube4ScrambleGenerator);
|
|
@@ -29,14 +30,35 @@ Object.defineProperty(exports, "cube4StatesEqual", { enumerable: true, get: func
|
|
|
29
30
|
Object.defineProperty(exports, "isSolvedCube4State", { enumerable: true, get: function () { return state_js_1.isSolvedCube4State; } });
|
|
30
31
|
Object.defineProperty(exports, "areCentersReduced", { enumerable: true, get: function () { return state_js_1.areCentersReduced; } });
|
|
31
32
|
Object.defineProperty(exports, "areWingsPaired", { enumerable: true, get: function () { return state_js_1.areWingsPaired; } });
|
|
33
|
+
var solve_js_1 = require("./reduction/solve.js");
|
|
34
|
+
Object.defineProperty(exports, "Cube4FinishCapError", { enumerable: true, get: function () { return solve_js_1.Cube4FinishCapError; } });
|
|
35
|
+
Object.defineProperty(exports, "Cube4Phase2CapError", { enumerable: true, get: function () { return solve_js_1.Cube4Phase2CapError; } });
|
|
36
|
+
Object.defineProperty(exports, "Cube4Phase3CapError", { enumerable: true, get: function () { return solve_js_1.Cube4Phase3CapError; } });
|
|
32
37
|
var scrambler_js_2 = require("./scrambler.js");
|
|
33
38
|
Object.defineProperty(exports, "cube4ScrambleGenerator", { enumerable: true, get: function () { return scrambler_js_2.cube4ScrambleGenerator; } });
|
|
34
39
|
Object.defineProperty(exports, "generateCube4Scramble", { enumerable: true, get: function () { return scrambler_js_2.generateCube4Scramble; } });
|
|
40
|
+
Object.defineProperty(exports, "generateCube4Attempt", { enumerable: true, get: function () { return scrambler_js_2.generateCube4Attempt; } });
|
|
41
|
+
var worker_pool_js_1 = require("./worker-pool.js");
|
|
42
|
+
Object.defineProperty(exports, "Cube4WorkerPool", { enumerable: true, get: function () { return worker_pool_js_1.Cube4WorkerPool; } });
|
|
43
|
+
Object.defineProperty(exports, "Cube4WorkerPoolClosedError", { enumerable: true, get: function () { return worker_pool_js_1.Cube4WorkerPoolClosedError; } });
|
|
44
|
+
Object.defineProperty(exports, "deriveBatchCube4Options", { enumerable: true, get: function () { return worker_pool_js_1.deriveBatchCube4Options; } });
|
|
45
|
+
Object.defineProperty(exports, "generateCube4ScrambleBatch", { enumerable: true, get: function () { return worker_pool_js_1.generateCube4ScrambleBatch; } });
|
|
46
|
+
Object.defineProperty(exports, "getDefaultCube4WorkerPool", { enumerable: true, get: function () { return worker_pool_js_1.getDefaultCube4WorkerPool; } });
|
|
47
|
+
Object.defineProperty(exports, "closeDefaultCube4WorkerPool", { enumerable: true, get: function () { return worker_pool_js_1.closeDefaultCube4WorkerPool; } });
|
|
48
|
+
var browser_worker_pool_js_1 = require("./browser-worker-pool.js");
|
|
49
|
+
Object.defineProperty(exports, "Cube4BrowserWorkerPool", { enumerable: true, get: function () { return browser_worker_pool_js_1.Cube4BrowserWorkerPool; } });
|
|
50
|
+
Object.defineProperty(exports, "Cube4BrowserWorkerPoolClosedError", { enumerable: true, get: function () { return browser_worker_pool_js_1.Cube4BrowserWorkerPoolClosedError; } });
|
|
51
|
+
Object.defineProperty(exports, "deriveBrowserBatchCube4Options", { enumerable: true, get: function () { return browser_worker_pool_js_1.deriveBrowserBatchCube4Options; } });
|
|
52
|
+
var scramble_cache_js_1 = require("./scramble-cache.js");
|
|
53
|
+
Object.defineProperty(exports, "Cube4ScrambleCache", { enumerable: true, get: function () { return scramble_cache_js_1.Cube4ScrambleCache; } });
|
|
54
|
+
Object.defineProperty(exports, "createCube4ScrambleCache", { enumerable: true, get: function () { return scramble_cache_js_1.createCube4ScrambleCache; } });
|
|
55
|
+
Object.defineProperty(exports, "deriveCube4StreamOptions", { enumerable: true, get: function () { return scramble_cache_js_1.deriveCube4StreamOptions; } });
|
|
35
56
|
var moves_js_1 = require("./moves.js");
|
|
36
57
|
Object.defineProperty(exports, "applyCube4Move", { enumerable: true, get: function () { return moves_js_1.applyCube4Move; } });
|
|
37
58
|
Object.defineProperty(exports, "applyCube4Algorithm", { enumerable: true, get: function () { return moves_js_1.applyCube4Algorithm; } });
|
|
38
59
|
Object.defineProperty(exports, "invertCube4Move", { enumerable: true, get: function () { return moves_js_1.invertCube4Move; } });
|
|
39
60
|
Object.defineProperty(exports, "invertCube4Algorithm", { enumerable: true, get: function () { return moves_js_1.invertCube4Algorithm; } });
|
|
61
|
+
Object.defineProperty(exports, "simplifyCube4Algorithm", { enumerable: true, get: function () { return moves_js_1.simplifyCube4Algorithm; } });
|
|
40
62
|
Object.defineProperty(exports, "formatCube4Algorithm", { enumerable: true, get: function () { return moves_js_1.formatCube4Algorithm; } });
|
|
41
63
|
Object.defineProperty(exports, "parseCube4Algorithm", { enumerable: true, get: function () { return moves_js_1.parseCube4Algorithm; } });
|
|
42
64
|
Object.defineProperty(exports, "parseCube4Move", { enumerable: true, get: function () { return moves_js_1.parseCube4Move; } });
|
|
@@ -20,6 +20,7 @@ exports.invertCube4Algorithm = invertCube4Algorithm;
|
|
|
20
20
|
exports.formatCube4Algorithm = formatCube4Algorithm;
|
|
21
21
|
exports.parseCube4Algorithm = parseCube4Algorithm;
|
|
22
22
|
exports.parseCube4Move = parseCube4Move;
|
|
23
|
+
exports.simplifyCube4Algorithm = simplifyCube4Algorithm;
|
|
23
24
|
const types_js_1 = require("./types.js");
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
// Identity arrays
|
|
@@ -170,6 +171,32 @@ const FACE_MOVE_EFFECTS = {
|
|
|
170
171
|
};
|
|
171
172
|
exports.FACE_MOVE_EFFECTS = FACE_MOVE_EFFECTS;
|
|
172
173
|
// ---------------------------------------------------------------------------
|
|
174
|
+
// Precomposed total effect per move string ("R'" = R x3, "U2" = U x2),
|
|
175
|
+
// built once at module load. applyCube4Algorithm then costs one lookup +
|
|
176
|
+
// one pass per move: no per-move string parsing, no repeated
|
|
177
|
+
// quarter-turn passes (average ~2 passes collapse into exactly 1).
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
function composePower(face, turns) {
|
|
180
|
+
const quarter = FACE_MOVE_EFFECTS[face];
|
|
181
|
+
if (turns === 2)
|
|
182
|
+
return composeEffects(quarter, quarter);
|
|
183
|
+
if (turns === 3)
|
|
184
|
+
return composeEffects(composeEffects(quarter, quarter), quarter);
|
|
185
|
+
return quarter;
|
|
186
|
+
}
|
|
187
|
+
const COMPOSED_EFFECTS = Object.fromEntries(types_js_1.CUBE4_ALL_MOVES.map((move) => {
|
|
188
|
+
const { face, turns } = parseMove(move);
|
|
189
|
+
return [move, composePower(face, turns)];
|
|
190
|
+
}));
|
|
191
|
+
/** Total effect for one move string (precomposed fast path + parse fallback). */
|
|
192
|
+
function effectForMove(move) {
|
|
193
|
+
const hit = COMPOSED_EFFECTS[move];
|
|
194
|
+
if (hit !== undefined)
|
|
195
|
+
return hit;
|
|
196
|
+
const { face, turns } = parseMove(move);
|
|
197
|
+
return composePower(face, turns);
|
|
198
|
+
}
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
173
200
|
// Apply a single move to a state
|
|
174
201
|
// ---------------------------------------------------------------------------
|
|
175
202
|
function applyEffect(state, effect) {
|
|
@@ -204,20 +231,52 @@ function parseMove(move) {
|
|
|
204
231
|
return { face: face, suffix, turns };
|
|
205
232
|
}
|
|
206
233
|
function applyCube4Move(state, move) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
current = applyEffect(current, effect);
|
|
212
|
-
}
|
|
213
|
-
return current;
|
|
234
|
+
// Single-pass fast path through the precomposed total effect: one lookup
|
|
235
|
+
// + one pass, no string re-parsing, no repeated quarter-turn passes, no
|
|
236
|
+
// intermediate states.
|
|
237
|
+
return applyEffect(state, effectForMove(move));
|
|
214
238
|
}
|
|
215
239
|
function applyCube4Algorithm(state, moves) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
240
|
+
if (moves.length === 0)
|
|
241
|
+
return state;
|
|
242
|
+
const bufA = {
|
|
243
|
+
cp: new Uint8Array(state.cp),
|
|
244
|
+
co: new Uint8Array(state.co),
|
|
245
|
+
wp: new Uint8Array(state.wp),
|
|
246
|
+
centers: new Uint8Array(state.centers)
|
|
247
|
+
};
|
|
248
|
+
const bufB = {
|
|
249
|
+
cp: new Uint8Array(8),
|
|
250
|
+
co: new Uint8Array(8),
|
|
251
|
+
wp: new Uint8Array(24),
|
|
252
|
+
centers: new Uint8Array(24)
|
|
253
|
+
};
|
|
254
|
+
let cur = bufA;
|
|
255
|
+
let nxt = bufB;
|
|
256
|
+
for (let mIdx = 0; mIdx < moves.length; mIdx++) {
|
|
257
|
+
const move = moves[mIdx];
|
|
258
|
+
const eff = effectForMove(move);
|
|
259
|
+
for (let i = 0; i < 8; i++) {
|
|
260
|
+
const from = eff.cperm[i];
|
|
261
|
+
nxt.cp[i] = cur.cp[from];
|
|
262
|
+
nxt.co[i] = (cur.co[from] + eff.corient[i]) % 3;
|
|
263
|
+
}
|
|
264
|
+
for (let i = 0; i < 24; i++) {
|
|
265
|
+
nxt.wp[i] = cur.wp[eff.wperm[i]];
|
|
266
|
+
}
|
|
267
|
+
for (let i = 0; i < 24; i++) {
|
|
268
|
+
nxt.centers[i] = cur.centers[eff.ctperm[i]];
|
|
269
|
+
}
|
|
270
|
+
const tmp = cur;
|
|
271
|
+
cur = nxt;
|
|
272
|
+
nxt = tmp;
|
|
219
273
|
}
|
|
220
|
-
return
|
|
274
|
+
return {
|
|
275
|
+
cp: cur.cp,
|
|
276
|
+
co: cur.co,
|
|
277
|
+
wp: cur.wp,
|
|
278
|
+
centers: cur.centers
|
|
279
|
+
};
|
|
221
280
|
}
|
|
222
281
|
// ---------------------------------------------------------------------------
|
|
223
282
|
// Move inversion
|
|
@@ -249,3 +308,146 @@ function parseCube4Move(value) {
|
|
|
249
308
|
}
|
|
250
309
|
throw new Error(`Invalid 4x4 move: ${value}`);
|
|
251
310
|
}
|
|
311
|
+
function parse4Move(m) {
|
|
312
|
+
const match = m.match(/^([UDFBLR]w?)(2|')?$/);
|
|
313
|
+
if (!match)
|
|
314
|
+
throw new Error(`Invalid move format: ${m}`);
|
|
315
|
+
const base = match[1];
|
|
316
|
+
const suffix = match[2] ?? "";
|
|
317
|
+
const amount = suffix === "2" ? 2 : suffix === "'" ? 3 : 1;
|
|
318
|
+
const axis = (base[0] === "U" || base[0] === "D") ? 0 :
|
|
319
|
+
(base[0] === "R" || base[0] === "L") ? 1 : 2;
|
|
320
|
+
return { base, axis, amount };
|
|
321
|
+
}
|
|
322
|
+
const AXIS_FACES = [
|
|
323
|
+
["U", "D"],
|
|
324
|
+
["R", "L"],
|
|
325
|
+
["F", "B"]
|
|
326
|
+
];
|
|
327
|
+
const AXIS_TURN_OPTIONS = [
|
|
328
|
+
["2", 2],
|
|
329
|
+
["", 1],
|
|
330
|
+
["'", 3]
|
|
331
|
+
];
|
|
332
|
+
function axisVectorKey(vector) {
|
|
333
|
+
return vector[0] | (vector[1] << 2) | (vector[2] << 4) | (vector[3] << 6);
|
|
334
|
+
}
|
|
335
|
+
function applyMoveToAxisVector(vector, base, turns, out) {
|
|
336
|
+
out[0] = vector[0];
|
|
337
|
+
out[1] = vector[1];
|
|
338
|
+
out[2] = vector[2];
|
|
339
|
+
out[3] = vector[3];
|
|
340
|
+
const axis = base[0];
|
|
341
|
+
const [positive, negative] = AXIS_FACES.find(([p, n]) => p === axis || n === axis);
|
|
342
|
+
const isPositive = axis === positive;
|
|
343
|
+
const signedTurns = isPositive ? turns : 4 - turns;
|
|
344
|
+
const wide = base.endsWith("w");
|
|
345
|
+
if (wide) {
|
|
346
|
+
const firstLayer = isPositive ? 0 : 2;
|
|
347
|
+
out[firstLayer] = (out[firstLayer] + signedTurns) & 3;
|
|
348
|
+
out[firstLayer + 1] = (out[firstLayer + 1] + signedTurns) & 3;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
const layer = isPositive ? 0 : 3;
|
|
352
|
+
out[layer] = (out[layer] + signedTurns) & 3;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function buildAxisReductionTable(positiveFace, negativeFace) {
|
|
356
|
+
const bases = [positiveFace, negativeFace, `${positiveFace}w`, `${negativeFace}w`];
|
|
357
|
+
const paths = new Array(256);
|
|
358
|
+
const queue = new Uint8Array(256);
|
|
359
|
+
let head = 0;
|
|
360
|
+
let tail = 0;
|
|
361
|
+
paths[0] = [];
|
|
362
|
+
queue[tail++] = 0;
|
|
363
|
+
while (head < tail) {
|
|
364
|
+
const key = queue[head++];
|
|
365
|
+
const vector = [key & 3, (key >> 2) & 3, (key >> 4) & 3, (key >> 6) & 3];
|
|
366
|
+
const prefix = paths[key];
|
|
367
|
+
for (const base of bases) {
|
|
368
|
+
for (const [suffix, turns] of AXIS_TURN_OPTIONS) {
|
|
369
|
+
const nextVector = [0, 0, 0, 0];
|
|
370
|
+
applyMoveToAxisVector(vector, base, turns, nextVector);
|
|
371
|
+
const nextKey = axisVectorKey(nextVector);
|
|
372
|
+
if (paths[nextKey] !== undefined)
|
|
373
|
+
continue;
|
|
374
|
+
paths[nextKey] = [...prefix, `${base}${suffix}`];
|
|
375
|
+
queue[tail++] = nextKey;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (tail !== 256 || paths.some((path) => path === undefined)) {
|
|
380
|
+
throw new Error(`4x4 move simplifier: incomplete axis table for ${positiveFace}/${negativeFace}.`);
|
|
381
|
+
}
|
|
382
|
+
return paths;
|
|
383
|
+
}
|
|
384
|
+
const AXIS_REDUCTIONS = AXIS_FACES.map(([positive, negative]) => buildAxisReductionTable(positive, negative));
|
|
385
|
+
function encodeAxisRun(moves, axis) {
|
|
386
|
+
const vector = [0, 0, 0, 0];
|
|
387
|
+
const next = [0, 0, 0, 0];
|
|
388
|
+
for (const move of moves) {
|
|
389
|
+
if (move.axis === axis) {
|
|
390
|
+
applyMoveToAxisVector(vector, move.base, move.amount, next);
|
|
391
|
+
vector[0] = next[0];
|
|
392
|
+
vector[1] = next[1];
|
|
393
|
+
vector[2] = next[2];
|
|
394
|
+
vector[3] = next[3];
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return axisVectorKey(vector);
|
|
398
|
+
}
|
|
399
|
+
function addAxisKeys(a, b) {
|
|
400
|
+
let result = 0;
|
|
401
|
+
for (let layer = 0; layer < 4; layer++) {
|
|
402
|
+
const shift = layer * 2;
|
|
403
|
+
const amount = (((a >> shift) & 3) + ((b >> shift) & 3)) & 3;
|
|
404
|
+
result |= amount << shift;
|
|
405
|
+
}
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Simplifies a 4x4 algorithm by canceling inverse moves and merging redundant
|
|
410
|
+
* turns on the same face or across commuting parallel faces.
|
|
411
|
+
*/
|
|
412
|
+
function simplifyCube4Algorithm(moves) {
|
|
413
|
+
// Moves on parallel layers commute, and each maximal same-axis run is a
|
|
414
|
+
// four-coordinate vector of quarter turns (one coordinate per layer).
|
|
415
|
+
// A small exact shortest-path table maps that vector back to the fewest
|
|
416
|
+
// legal face/wide moves, including mixed identities such as Rw R' = the
|
|
417
|
+
// corresponding inner-slice turn. Different axes are never reordered.
|
|
418
|
+
const segments = [];
|
|
419
|
+
let run = [];
|
|
420
|
+
let runAxis = -1;
|
|
421
|
+
const flushRun = () => {
|
|
422
|
+
if (run.length === 0)
|
|
423
|
+
return;
|
|
424
|
+
const key = encodeAxisRun(run, runAxis);
|
|
425
|
+
if (key !== 0) {
|
|
426
|
+
const previous = segments[segments.length - 1];
|
|
427
|
+
if (previous?.axis === runAxis) {
|
|
428
|
+
const mergedKey = addAxisKeys(previous.key, key);
|
|
429
|
+
if (mergedKey === 0)
|
|
430
|
+
segments.pop();
|
|
431
|
+
else {
|
|
432
|
+
previous.key = mergedKey;
|
|
433
|
+
previous.moves = AXIS_REDUCTIONS[runAxis][mergedKey];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
segments.push({ axis: runAxis, key, moves: AXIS_REDUCTIONS[runAxis][key] });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
run = [];
|
|
441
|
+
runAxis = -1;
|
|
442
|
+
};
|
|
443
|
+
for (const move of moves) {
|
|
444
|
+
const parsed = parse4Move(move);
|
|
445
|
+
if (runAxis !== -1 && parsed.axis !== runAxis)
|
|
446
|
+
flushRun();
|
|
447
|
+
if (runAxis === -1)
|
|
448
|
+
runAxis = parsed.axis;
|
|
449
|
+
run.push(parsed);
|
|
450
|
+
}
|
|
451
|
+
flushRun();
|
|
452
|
+
return segments.flatMap((segment) => segment.moves);
|
|
453
|
+
}
|