mumuki-puzzle-runner 0.1.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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;
|