mumuki-puzzle-runner 0.1.2 → 0.6.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.
- checksums.yaml +4 -4
- data/lib/public/css/muzzle-editor.css +25 -1
- data/lib/public/js/muzzle-editor.js +35 -16
- data/lib/public/js/muzzle.js +242 -56
- data/lib/public/vendor/headbreaker.d.ts +245 -67
- data/lib/public/vendor/headbreaker.js +1 -1
- data/lib/public/vendor/headbreaker.js.map +1 -1
- data/lib/version_hook.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20cbf33422b40662d79aa0662f45011525a7260454eccfb1f64e5f0a4cdab780
|
4
|
+
data.tar.gz: e84cc1ba0a61dffe0be0ccb9bd26840ff5e9de7dc3b2d1ecff7a9010ac1a36ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac96c26344814c63d596c1cb012781b18799eb683603ec738beea160ace4e677dd650926cd2720a3e4fb85e712d19a432137040233be99a7a69534823c82d655
|
7
|
+
data.tar.gz: c56d817d8240de55674cad90192c59f823da755ce9d3eabdf8a4f373cf84be5c22b1e15fdad64c1c5ff0f40770d9102c11fcd65674621910a07bbdccd95ed065
|
@@ -1,4 +1,28 @@
|
|
1
1
|
.mu-kids-state-image img {
|
2
|
-
|
2
|
+
height: 100%;
|
3
|
+
width: auto;
|
3
4
|
padding: 30px;
|
4
5
|
}
|
6
|
+
|
7
|
+
.mu-kids-state.mu-state-initial {
|
8
|
+
width: 100%;
|
9
|
+
}
|
10
|
+
|
11
|
+
.mu-kids-exercise-workspace.muzzle-simple .mu-kids-submit-button {
|
12
|
+
display: none;
|
13
|
+
}
|
14
|
+
|
15
|
+
.mu-kids-exercise-workspace.muzzle-simple .mu-kids-blocks {
|
16
|
+
overflow: hidden;
|
17
|
+
}
|
18
|
+
|
19
|
+
.mu-kids-exercise-workspace.muzzle-simple .mu-kids-blocks:after {
|
20
|
+
content: ' ';
|
21
|
+
box-shadow: inset 0 0 30px 30px #FFFFFF;
|
22
|
+
position: absolute;
|
23
|
+
top: 0;
|
24
|
+
left: 0;
|
25
|
+
right: 0;
|
26
|
+
bottom: 0;
|
27
|
+
pointer-events: none;
|
28
|
+
}
|
@@ -1,5 +1,13 @@
|
|
1
1
|
// @ts-nocheck
|
2
2
|
$(() => {
|
3
|
+
function register(event, callback) {
|
4
|
+
const _event = Muzzle[event];
|
5
|
+
Muzzle[event] = (...args) => {
|
6
|
+
callback(...args);
|
7
|
+
_event(...args);
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
3
11
|
// ================
|
4
12
|
// Muzzle rendering
|
5
13
|
// ================
|
@@ -27,40 +35,51 @@ $(() => {
|
|
27
35
|
getContent() { return { name: "client_result[status]", value: Muzzle.clientResultStatus }; }
|
28
36
|
})
|
29
37
|
|
30
|
-
//
|
38
|
+
// Required to actually bind Muzzle's submit to
|
31
39
|
// mumuki's solution processing
|
32
|
-
|
33
|
-
Muzzle.onSubmit = (submission) => {
|
34
|
-
console.log(`submitting ${submission}`)
|
40
|
+
register('onSubmit', (submission) => {
|
35
41
|
mumuki.submission.processSolution(submission);
|
36
|
-
|
37
|
-
}
|
42
|
+
});
|
38
43
|
|
39
44
|
// ===========
|
40
45
|
// Kids config
|
41
46
|
// ===========
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
48
|
+
mumuki.kids.registerStateScaler(($state, fullMargin) => {
|
49
|
+
const $image = $state.find('img');
|
50
|
+
|
51
|
+
$image.css('transform', 'scale(1)');
|
52
|
+
const width = ($state.width() - fullMargin) / $image.width();
|
53
|
+
const height = ($state.height() - fullMargin) / $image.height();
|
54
|
+
$image.css('transform', 'scale(' + Math.min(width, height) + ')');
|
47
55
|
});
|
56
|
+
|
48
57
|
mumuki.kids.registerBlocksAreaScaler(($blocks) => {
|
49
|
-
|
50
|
-
|
58
|
+
const maxHeight = $('.mu-kids-exercise').height() - $('.mu-kids-exercise-description').height();
|
59
|
+
Muzzle.scale($blocks.width(), Math.min($blocks.height(), maxHeight));
|
60
|
+
});
|
61
|
+
|
62
|
+
Muzzle.manualScale = true;
|
63
|
+
|
64
|
+
// ====================
|
65
|
+
// Submit button hiding
|
66
|
+
// ====================
|
67
|
+
|
68
|
+
register('onReady', () => {
|
69
|
+
if (Muzzle.simple) {
|
70
|
+
$('.mu-kids-exercise-workspace').addClass('muzzle-simple');
|
71
|
+
}
|
51
72
|
});
|
52
73
|
|
53
74
|
// ==============
|
54
75
|
// Assets loading
|
55
76
|
// ==============
|
56
77
|
|
57
|
-
|
58
|
-
Muzzle.onReady = () => {
|
78
|
+
register('onReady', () => {
|
59
79
|
mumuki.assetsLoadedFor('editor');
|
60
80
|
// although layout assets
|
61
81
|
// are actually loaded before this script, puzzle runner is not aimed
|
62
82
|
// to be used without a custom editor
|
63
83
|
mumuki.assetsLoadedFor('layout');
|
64
|
-
|
65
|
-
};
|
84
|
+
});
|
66
85
|
});
|
data/lib/public/js/muzzle.js
CHANGED
@@ -73,6 +73,14 @@ class MuzzleCanvas {
|
|
73
73
|
*/
|
74
74
|
this.canvasHeight = 600;
|
75
75
|
|
76
|
+
/**
|
77
|
+
* Wether canvas shoud **not** be resized.
|
78
|
+
* Default is `false`
|
79
|
+
*
|
80
|
+
* @type {boolean}
|
81
|
+
*/
|
82
|
+
this.fixedDimensions = false;
|
83
|
+
|
76
84
|
/**
|
77
85
|
* Size of fill. Set null for perfect-match
|
78
86
|
*
|
@@ -85,7 +93,7 @@ class MuzzleCanvas {
|
|
85
93
|
*
|
86
94
|
* @type {number}
|
87
95
|
*/
|
88
|
-
this.strokeWidth =
|
96
|
+
this.strokeWidth = 3;
|
89
97
|
|
90
98
|
/**
|
91
99
|
* Piece size
|
@@ -95,11 +103,22 @@ class MuzzleCanvas {
|
|
95
103
|
this.pieceSize = 100;
|
96
104
|
|
97
105
|
/**
|
98
|
-
*
|
106
|
+
* The x:y aspect ratio of the piece. Set null for automatic
|
107
|
+
* aspectRatio
|
108
|
+
*
|
109
|
+
* @type {number}
|
110
|
+
*/
|
111
|
+
this.aspectRatio = null;
|
112
|
+
|
113
|
+
/**
|
114
|
+
* If the images should be adjusted vertically instead of horizontally
|
115
|
+
* to puzzle dimensions. `false` by default
|
99
116
|
*
|
100
117
|
* @type {boolean}
|
101
118
|
*/
|
102
|
-
this.
|
119
|
+
this.fitImagesVertically = false;
|
120
|
+
|
121
|
+
this.manualScale = false;
|
103
122
|
|
104
123
|
/**
|
105
124
|
* Callback that will be executed
|
@@ -117,7 +136,28 @@ class MuzzleCanvas {
|
|
117
136
|
*
|
118
137
|
* @type {string}
|
119
138
|
*/
|
120
|
-
this.previousSolutionContent = null
|
139
|
+
this.previousSolutionContent = null;
|
140
|
+
|
141
|
+
/**
|
142
|
+
* Whether the current puzzle can be solved in very few tries.
|
143
|
+
*
|
144
|
+
* Set null for automatic configuration of this property. Basic puzzles will be considered
|
145
|
+
* basic and match puzzles will be considered non-basic.
|
146
|
+
*
|
147
|
+
* @type {boolean}
|
148
|
+
*/
|
149
|
+
this.simple = null;
|
150
|
+
|
151
|
+
this.spiky = false;
|
152
|
+
|
153
|
+
/**
|
154
|
+
* The reference insert axis, used at rounded outline to compute insert internal and external diameters
|
155
|
+
* Set null for default computation of axis - no axis reference for basic boards
|
156
|
+
* and vertical axis for match
|
157
|
+
*
|
158
|
+
* @type {Axis}
|
159
|
+
* */
|
160
|
+
this.referenceInsertAxis = null;
|
121
161
|
|
122
162
|
/**
|
123
163
|
* Callback to be executed when submitting puzzle.
|
@@ -139,18 +179,59 @@ class MuzzleCanvas {
|
|
139
179
|
this.onValid = () => {};
|
140
180
|
}
|
141
181
|
|
182
|
+
|
183
|
+
|
142
184
|
/**
|
143
185
|
*/
|
144
186
|
get baseConfig() {
|
145
|
-
return {
|
187
|
+
return Object.assign({
|
146
188
|
width: this.canvasWidth,
|
147
189
|
height: this.canvasHeight,
|
148
|
-
pieceSize: this.
|
149
|
-
proximity: this.
|
150
|
-
borderFill: this.borderFill === null ? this.pieceSize / 10 : this.borderFill,
|
190
|
+
pieceSize: this.adjustedPieceSize,
|
191
|
+
proximity: Math.min(this.adjustedPieceSize.x, this.adjustedPieceSize.y) / 5,
|
151
192
|
strokeWidth: this.strokeWidth,
|
152
193
|
lineSoftness: 0.18
|
153
|
-
};
|
194
|
+
}, this.outlineConfig);
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
*/
|
199
|
+
get outlineConfig() {
|
200
|
+
if (this.spiky) {
|
201
|
+
return {
|
202
|
+
borderFill: this.borderFill === null ? headbreaker.Vector.divide(this.adjustedPieceSize, 10) : this.borderFill,
|
203
|
+
}
|
204
|
+
} else {
|
205
|
+
return {
|
206
|
+
borderFill: 0,
|
207
|
+
outline: new headbreaker.outline.Rounded({
|
208
|
+
bezelize: true,
|
209
|
+
insertDepth: 3/5,
|
210
|
+
bezelDepth: 9/10,
|
211
|
+
referenceInsertAxis: this.referenceInsertAxis
|
212
|
+
}),
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
/**
|
218
|
+
* The piece size, adjusted to the aspect ratio
|
219
|
+
*
|
220
|
+
* @returns {Vector}
|
221
|
+
*/
|
222
|
+
get adjustedPieceSize() {
|
223
|
+
if (!this._adjustedPieceSize) {
|
224
|
+
const aspectRatio = this.aspectRatio || 1;
|
225
|
+
this._adjustedPieceSize = headbreaker.vector(this.pieceSize / aspectRatio, this.pieceSize);
|
226
|
+
}
|
227
|
+
return this._adjustedPieceSize;
|
228
|
+
}
|
229
|
+
|
230
|
+
/**
|
231
|
+
* @type {Axis}
|
232
|
+
*/
|
233
|
+
get imageAdjustmentAxis() {
|
234
|
+
return this.fitImagesVertically ? headbreaker.Vertical : headbreaker.Horizontal;
|
154
235
|
}
|
155
236
|
|
156
237
|
/**
|
@@ -195,15 +276,25 @@ class MuzzleCanvas {
|
|
195
276
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
196
277
|
*/
|
197
278
|
async basic(x, y, imagePath) {
|
279
|
+
if (!this.aspectRatio) {
|
280
|
+
this.aspectRatio = x / y;
|
281
|
+
}
|
282
|
+
|
283
|
+
if (this.simple === null) {
|
284
|
+
this.simple = true;
|
285
|
+
}
|
286
|
+
|
198
287
|
/**
|
199
288
|
* @todo take all container size
|
200
289
|
**/
|
201
290
|
const image = await this._loadImage(imagePath);
|
202
291
|
/** @type {Canvas} */
|
203
292
|
// @ts-ignore
|
204
|
-
const canvas = this._createCanvas(image);
|
293
|
+
const canvas = this._createCanvas({ image: image });
|
294
|
+
canvas.adjustImagesToPuzzle(this.imageAdjustmentAxis);
|
205
295
|
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y });
|
206
296
|
this._attachBasicValidator(canvas);
|
297
|
+
canvas.shuffleGrid(0.8);
|
207
298
|
this._configCanvas(canvas);
|
208
299
|
canvas.onValid(() => {
|
209
300
|
setTimeout(() => {
|
@@ -225,7 +316,7 @@ class MuzzleCanvas {
|
|
225
316
|
const count = imagePaths.length;
|
226
317
|
const images = await Promise.all(imagePaths.map(imagePath => this._loadImage(imagePath)));
|
227
318
|
|
228
|
-
const canvas = this._createCanvas(
|
319
|
+
const canvas = this._createCanvas();
|
229
320
|
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y * count });
|
230
321
|
|
231
322
|
// todo validate
|
@@ -243,30 +334,70 @@ class MuzzleCanvas {
|
|
243
334
|
* @param {string[]} rightUrls must be of the same size of lefts
|
244
335
|
* @param {string[]} leftOddUrls
|
245
336
|
* @param {string[]} rightOddUrls
|
337
|
+
* @param {number?} rightWidthRatio a multiplicator to apply to the right piece's width
|
246
338
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
247
339
|
*/
|
248
|
-
async match(leftUrls, rightUrls, leftOddUrls = [], rightOddUrls = []) {
|
340
|
+
async match(leftUrls, rightUrls, leftOddUrls = [], rightOddUrls = [], rightWidthRatio = 1) {
|
341
|
+
if (!this.referenceInsertAxis) {
|
342
|
+
this.referenceInsertAxis = headbreaker.Vertical;
|
343
|
+
}
|
344
|
+
|
249
345
|
/** @private @type {(Promise<Template>)[]} */
|
250
346
|
const templatePromises = [];
|
251
|
-
|
252
|
-
|
347
|
+
|
348
|
+
const rightSize = headbreaker.diameter(headbreaker.Vector.multiply(this.adjustedPieceSize, headbreaker.vector(rightWidthRatio, 1)));
|
349
|
+
|
350
|
+
const pushTemplate = (path, options) =>
|
351
|
+
templatePromises.push(this._createMatchTemplate(path, options));
|
352
|
+
|
353
|
+
const pushLeftTemplate = (index, path, options) =>
|
354
|
+
pushTemplate(path, {
|
355
|
+
left: true,
|
356
|
+
targetPosition: headbreaker.Vector.multiply(this.pieceSize, headbreaker.vector(1, index)),
|
357
|
+
...options
|
358
|
+
});
|
359
|
+
|
360
|
+
const pushRightTemplate = (index, path, options) =>
|
361
|
+
pushTemplate(path, {
|
362
|
+
size: rightSize,
|
363
|
+
targetPosition: headbreaker.Vector.multiply(this.pieceSize, headbreaker.vector(2, index)),
|
364
|
+
...options
|
365
|
+
});
|
253
366
|
|
254
367
|
const last = leftUrls.length - 1;
|
255
368
|
for (let i = 0; i <= last; i++) {
|
256
369
|
const leftId = `l${i}`;
|
257
370
|
const rightId = `r${i}`;
|
258
371
|
|
259
|
-
|
260
|
-
|
372
|
+
pushLeftTemplate(i + 1, leftUrls[i], {
|
373
|
+
id: leftId,
|
374
|
+
rightTargetId: rightId
|
375
|
+
});
|
376
|
+
pushRightTemplate(i + 1, rightUrls[i], {
|
377
|
+
id: rightId
|
378
|
+
});
|
261
379
|
}
|
262
380
|
|
263
|
-
leftOddUrls.forEach((it, i) =>
|
264
|
-
|
265
|
-
|
381
|
+
leftOddUrls.forEach((it, i) =>
|
382
|
+
pushLeftTemplate(i + leftUrls.length, it, {
|
383
|
+
id: `lo${i}`,
|
384
|
+
odd: true
|
385
|
+
})
|
386
|
+
);
|
387
|
+
rightOddUrls.forEach((it, i) =>
|
388
|
+
pushRightTemplate(i + rightUrls.length, it, {
|
389
|
+
id: `ro${i}`,
|
390
|
+
odd: true
|
391
|
+
})
|
392
|
+
);
|
393
|
+
|
394
|
+
// + Math.max(leftOddUrls.length, rightOddUrls.length)
|
266
395
|
const templates = await Promise.all(templatePromises);
|
267
396
|
/** @type {Canvas} */
|
268
|
-
const canvas = this._createCanvas();
|
397
|
+
const canvas = this._createCanvas({ maxPiecesCount: {x: 2, y: leftUrls.length} });
|
398
|
+
canvas.adjustImagesToPiece(this.imageAdjustmentAxis);
|
269
399
|
templates.forEach(it => canvas.sketchPiece(it));
|
400
|
+
canvas.shuffleColumns(0.8);
|
270
401
|
this._attachMatchValidator(canvas);
|
271
402
|
this._configCanvas(canvas);
|
272
403
|
return canvas;
|
@@ -283,19 +414,11 @@ class MuzzleCanvas {
|
|
283
414
|
|
284
415
|
/**
|
285
416
|
* @private
|
286
|
-
* @param {
|
417
|
+
* @param {any} config
|
287
418
|
* @return {Canvas}
|
288
419
|
*/
|
289
|
-
_createCanvas(
|
290
|
-
return new headbreaker.Canvas(this.canvasId, this.
|
291
|
-
}
|
292
|
-
|
293
|
-
/**
|
294
|
-
* @private
|
295
|
-
* @param {HTMLImageElement} image
|
296
|
-
*/
|
297
|
-
_canvasConfig(image) {
|
298
|
-
return Object.assign({ image }, this.baseConfig);
|
420
|
+
_createCanvas(config = {}) {
|
421
|
+
return new headbreaker.Canvas(this.canvasId, Object.assign(config, this.baseConfig));
|
299
422
|
}
|
300
423
|
|
301
424
|
/**
|
@@ -339,37 +462,17 @@ class MuzzleCanvas {
|
|
339
462
|
* @param {object} options
|
340
463
|
* @returns {Promise<object>}
|
341
464
|
*/
|
342
|
-
_createMatchTemplate(imagePath, {id, left = false, rightTargetId = null, odd = false}) {
|
465
|
+
_createMatchTemplate(imagePath, {id, left = false, targetPosition = null, rightTargetId = null, odd = false, size = null}) {
|
343
466
|
const structure = left ? 'T-N-' : `N-S-`;
|
344
|
-
|
345
467
|
return this._loadImage(imagePath).then((image) => {
|
346
|
-
const scale = this._imageScale(image);
|
347
|
-
const offset = this.baseConfig.borderFill / scale;
|
348
468
|
return {
|
469
|
+
...(size ? {size} : {}),
|
349
470
|
structure,
|
350
|
-
metadata: {
|
351
|
-
id,
|
352
|
-
left,
|
353
|
-
odd,
|
354
|
-
rightTargetId,
|
355
|
-
image: {
|
356
|
-
scale,
|
357
|
-
content: image,
|
358
|
-
offset: { x: offset, y: offset }
|
359
|
-
}
|
360
|
-
}
|
471
|
+
metadata: { id, left, odd, rightTargetId, image, targetPosition }
|
361
472
|
}
|
362
473
|
});
|
363
474
|
}
|
364
475
|
|
365
|
-
/**
|
366
|
-
* @private
|
367
|
-
* @param {HTMLImageElement} image
|
368
|
-
*/
|
369
|
-
_imageScale(image) {
|
370
|
-
return this.scaleImageWidthToFit ? this.pieceSize / image.width : 1;
|
371
|
-
}
|
372
|
-
|
373
476
|
/**
|
374
477
|
* @private
|
375
478
|
* @param {Canvas} canvas
|
@@ -379,15 +482,86 @@ class MuzzleCanvas {
|
|
379
482
|
this._canvas.onValid(() => {
|
380
483
|
setTimeout(() => this.onValid(), 0);
|
381
484
|
});
|
485
|
+
this._setUpScaler();
|
382
486
|
this.ready();
|
383
487
|
}
|
384
488
|
|
489
|
+
_setUpScaler() {
|
490
|
+
if (this.manualScale) return;
|
491
|
+
|
492
|
+
['resize', 'load'].forEach((event) => {
|
493
|
+
window.addEventListener(event, () => {
|
494
|
+
var container = document.getElementById(this.canvasId);
|
495
|
+
this.scale(container.offsetWidth, container.scrollHeight);
|
496
|
+
});
|
497
|
+
});
|
498
|
+
}
|
499
|
+
|
500
|
+
/**
|
501
|
+
* Scales the canvas to the given width and height
|
502
|
+
*
|
503
|
+
* @param {number} width
|
504
|
+
* @param {number} height
|
505
|
+
*/
|
506
|
+
scale(width, height) {
|
507
|
+
if (this.fixedDimensions || !this.canvas) return;
|
508
|
+
const factor = this.optimalScaleFactor(width, height);
|
509
|
+
this.canvas.resize(width, height);
|
510
|
+
this.canvas.scale(factor);
|
511
|
+
this.canvas.redraw();
|
512
|
+
this.focus();
|
513
|
+
}
|
514
|
+
|
515
|
+
/**
|
516
|
+
* Focuses the stage around the canvas center
|
517
|
+
*/
|
518
|
+
focus() {
|
519
|
+
const stage = this.canvas['__konvaLayer__'].getStage();
|
520
|
+
|
521
|
+
const area = headbreaker.Vector.divide(headbreaker.vector(stage.width(), stage.height()), stage.scaleX());
|
522
|
+
const realDiameter = (() => {
|
523
|
+
const [xs, ys] = this.coordinates;
|
524
|
+
|
525
|
+
const minX = Math.min(...xs);
|
526
|
+
const minY = Math.min(...ys);
|
527
|
+
|
528
|
+
const maxX = Math.max(...xs);
|
529
|
+
const maxY = Math.max(...ys);
|
530
|
+
|
531
|
+
return headbreaker.Vector.plus(headbreaker.vector(maxX - minX, maxY - minY), this.canvas.puzzle.pieceDiameter);
|
532
|
+
})();
|
533
|
+
const diff = headbreaker.Vector.minus(area, realDiameter);
|
534
|
+
const semi = headbreaker.Vector.divide(diff, -2);
|
535
|
+
|
536
|
+
stage.setOffset(semi);
|
537
|
+
stage.draw();
|
538
|
+
}
|
539
|
+
|
540
|
+
/**
|
541
|
+
* @private
|
542
|
+
*/
|
543
|
+
get coordinates() {
|
544
|
+
const points = this.canvas.puzzle.points;
|
545
|
+
return [points.map(([x, _y]) => x), points.map(([_x, y]) => y)];
|
546
|
+
}
|
547
|
+
|
548
|
+
/**
|
549
|
+
* @private
|
550
|
+
* @param {number} width
|
551
|
+
* @param {number} height
|
552
|
+
*/
|
553
|
+
optimalScaleFactor(width, height) {
|
554
|
+
const factors = headbreaker.Vector.divide(headbreaker.vector(width, height), this.canvas.puzzleDiameter);
|
555
|
+
return Math.min(factors.x, factors.y) / 1.75;
|
556
|
+
}
|
557
|
+
|
385
558
|
/**
|
386
559
|
* Mark Muzzle as ready, loading previous solution
|
387
560
|
* and drawing the canvas
|
388
561
|
*/
|
389
562
|
ready() {
|
390
563
|
this.loadPreviousSolution();
|
564
|
+
this.resetCoordinates();
|
391
565
|
this.draw();
|
392
566
|
this.onReady();
|
393
567
|
}
|
@@ -413,6 +587,7 @@ class MuzzleCanvas {
|
|
413
587
|
*/
|
414
588
|
loadSolution(solution) {
|
415
589
|
this.canvas.puzzle.relocateTo(solution.positions);
|
590
|
+
this.canvas.puzzle.autoconnect();
|
416
591
|
}
|
417
592
|
|
418
593
|
/**
|
@@ -426,11 +601,20 @@ class MuzzleCanvas {
|
|
426
601
|
} catch (e) {
|
427
602
|
console.warn("Ignoring unparseabe editor value");
|
428
603
|
}
|
429
|
-
} else {
|
430
|
-
this.canvas.shuffle(0.8);
|
431
604
|
}
|
432
605
|
}
|
433
606
|
|
607
|
+
/**
|
608
|
+
* Translates the pieces so that
|
609
|
+
* they start at canvas' coordinates origin
|
610
|
+
*/
|
611
|
+
resetCoordinates() {
|
612
|
+
const [xs, ys] = this.coordinates;
|
613
|
+
const minX = Math.min(...xs);
|
614
|
+
const minY = Math.min(...ys);
|
615
|
+
this.canvas.puzzle.translate(-minX, -minY);
|
616
|
+
}
|
617
|
+
|
434
618
|
// ==========
|
435
619
|
// Submitting
|
436
620
|
// ==========
|
@@ -491,3 +675,5 @@ const Muzzle = new class extends MuzzleCanvas {
|
|
491
675
|
return muzzle;
|
492
676
|
}
|
493
677
|
}
|
678
|
+
|
679
|
+
window['Muzzle'] = Muzzle;
|