mumuki-puzzle-runner 0.4.0 → 1.0.2
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 +45 -2
- data/lib/public/js/muzzle-editor.js +16 -14
- data/lib/public/js/muzzle.js +247 -59
- data/lib/public/vendor/headbreaker.d.ts +39 -7
- 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: babd3a8ed60a9345983363242f5e1fc1bee0bb36a471d136c314ba5fba8c4ed1
|
4
|
+
data.tar.gz: 62d411ca5bfeca771b9b78e113be587c72fdbea23d811e91635cae4a9c703e9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5baff4c0cdac71f1d0038d3cced0c45fd852be5e6712cb470cdb601981d01dbf0f230bea99c07cca85d914bbcff2a84a042ee853f18ed94f7e1583559142861f
|
7
|
+
data.tar.gz: fa0b70e5c7ce9529c28fda9bb9b1f464c62bcc2f768fa4bd082fe2193684267e2c3e239361c7a4645b18bbbf7f9ab27dd374be0978711e53634e6858d83135a7
|
@@ -1,3 +1,19 @@
|
|
1
|
+
|
2
|
+
/*
|
3
|
+
* ============
|
4
|
+
* Initial size
|
5
|
+
* ============
|
6
|
+
*/
|
7
|
+
|
8
|
+
.mu-exercise-content {
|
9
|
+
border-radius: 10px;
|
10
|
+
border: 1px solid #dddddd;
|
11
|
+
}
|
12
|
+
|
13
|
+
.mu-kids-exercise-workspace.muzzle-simple .mu-kids-submit-button {
|
14
|
+
display: none;
|
15
|
+
}
|
16
|
+
|
1
17
|
.mu-kids-state-image img {
|
2
18
|
height: 100%;
|
3
19
|
width: auto;
|
@@ -8,6 +24,33 @@
|
|
8
24
|
width: 100%;
|
9
25
|
}
|
10
26
|
|
11
|
-
|
12
|
-
|
27
|
+
/*
|
28
|
+
* ====================
|
29
|
+
* Submit button hiding
|
30
|
+
* ====================
|
31
|
+
*/
|
32
|
+
|
33
|
+
/*
|
34
|
+
* ===========
|
35
|
+
* Blur effect
|
36
|
+
* ===========
|
37
|
+
*/
|
38
|
+
|
39
|
+
.mu-kids-exercise-workspace.mu-full-workspace .mu-kids-submit-button {
|
40
|
+
margin: 0 30px 30px;
|
41
|
+
}
|
42
|
+
|
43
|
+
.mu-kids-exercise-workspace .mu-kids-blocks {
|
44
|
+
overflow: hidden;
|
45
|
+
}
|
46
|
+
|
47
|
+
.mu-kids-exercise-workspace .mu-kids-blocks:after {
|
48
|
+
content: ' ';
|
49
|
+
box-shadow: inset 0 0 30px 30px #FFFFFF;
|
50
|
+
position: absolute;
|
51
|
+
top: 0;
|
52
|
+
left: 0;
|
53
|
+
right: 0;
|
54
|
+
bottom: 0;
|
55
|
+
pointer-events: none;
|
13
56
|
}
|
@@ -1,12 +1,5 @@
|
|
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
5
|
// Muzzle rendering
|
@@ -35,9 +28,9 @@ $(() => {
|
|
35
28
|
getContent() { return { name: "client_result[status]", value: Muzzle.clientResultStatus }; }
|
36
29
|
})
|
37
30
|
|
38
|
-
//
|
31
|
+
// Required to actually bind Muzzle's submit to
|
39
32
|
// mumuki's solution processing
|
40
|
-
register('onSubmit', (submission) => {
|
33
|
+
Muzzle.register('onSubmit', (submission) => {
|
41
34
|
mumuki.submission.processSolution(submission);
|
42
35
|
});
|
43
36
|
|
@@ -45,13 +38,20 @@ $(() => {
|
|
45
38
|
// Kids config
|
46
39
|
// ===========
|
47
40
|
|
48
|
-
mumuki.kids.registerStateScaler(($state, fullMargin
|
49
|
-
|
41
|
+
mumuki.kids.registerStateScaler(($state, fullMargin) => {
|
42
|
+
const $image = $state.find('img');
|
43
|
+
if (!$image.length) return;
|
44
|
+
|
45
|
+
$image.css('transform', 'scale(1)');
|
46
|
+
const width = ($state.width() - fullMargin) / $image.width();
|
47
|
+
const height = ($state.height() - fullMargin) / $image.height();
|
48
|
+
$image.css('transform', 'scale(' + Math.min(width, height) + ')');
|
50
49
|
});
|
51
50
|
|
52
51
|
mumuki.kids.registerBlocksAreaScaler(($blocks) => {
|
52
|
+
console.debug("Scaler fired");
|
53
53
|
const maxHeight = $('.mu-kids-exercise').height() - $('.mu-kids-exercise-description').height();
|
54
|
-
Muzzle.scale($blocks.width(), Math.min($blocks.height(), maxHeight));
|
54
|
+
Muzzle.run(() => Muzzle.scale($blocks.width(), Math.min($blocks.height(), maxHeight)));
|
55
55
|
});
|
56
56
|
|
57
57
|
Muzzle.manualScale = true;
|
@@ -60,7 +60,7 @@ $(() => {
|
|
60
60
|
// Submit button hiding
|
61
61
|
// ====================
|
62
62
|
|
63
|
-
register('onReady', () => {
|
63
|
+
Muzzle.register('onReady', () => {
|
64
64
|
if (Muzzle.simple) {
|
65
65
|
$('.mu-kids-exercise-workspace').addClass('muzzle-simple');
|
66
66
|
}
|
@@ -70,7 +70,9 @@ $(() => {
|
|
70
70
|
// Assets loading
|
71
71
|
// ==============
|
72
72
|
|
73
|
-
register('onReady', () => {
|
73
|
+
Muzzle.register('onReady', () => {
|
74
|
+
console.debug("Muzzle is ready");
|
75
|
+
|
74
76
|
mumuki.assetsLoadedFor('editor');
|
75
77
|
// although layout assets
|
76
78
|
// are actually loaded before this script, puzzle runner is not aimed
|
data/lib/public/js/muzzle.js
CHANGED
@@ -1,9 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* @typedef {object} PieceConfig
|
3
|
-
* @property {string} imagePath
|
4
|
-
* @property {string} structure
|
5
|
-
*/
|
6
|
-
|
7
1
|
/**
|
8
2
|
* @typedef {number[]} Point
|
9
3
|
*/
|
@@ -14,6 +8,14 @@
|
|
14
8
|
*/
|
15
9
|
|
16
10
|
|
11
|
+
class MuzzlePainter extends headbreaker.painters.Konva {
|
12
|
+
_newLine(options) {
|
13
|
+
|
14
|
+
const line = super._newLine(options);
|
15
|
+
line.strokeScaleEnabled(false);
|
16
|
+
return line;
|
17
|
+
}
|
18
|
+
}
|
17
19
|
|
18
20
|
/**
|
19
21
|
* Facade for referencing and creating a global puzzle canvas,
|
@@ -93,7 +95,7 @@ class MuzzleCanvas {
|
|
93
95
|
*
|
94
96
|
* @type {number}
|
95
97
|
*/
|
96
|
-
this.strokeWidth =
|
98
|
+
this.strokeWidth = 3;
|
97
99
|
|
98
100
|
/**
|
99
101
|
* Piece size
|
@@ -103,7 +105,7 @@ class MuzzleCanvas {
|
|
103
105
|
this.pieceSize = 100;
|
104
106
|
|
105
107
|
/**
|
106
|
-
* The x:y aspect ratio of the piece. Set null for automatic
|
108
|
+
* The `x:y` aspect ratio of the piece. Set null for automatic
|
107
109
|
* aspectRatio
|
108
110
|
*
|
109
111
|
* @type {number}
|
@@ -112,14 +114,27 @@ class MuzzleCanvas {
|
|
112
114
|
|
113
115
|
/**
|
114
116
|
* If the images should be adjusted vertically instead of horizontally
|
115
|
-
* to puzzle dimensions.
|
117
|
+
* to puzzle dimensions.
|
118
|
+
*
|
119
|
+
* Set null for automatic fit.
|
116
120
|
*
|
117
121
|
* @type {boolean}
|
118
122
|
*/
|
119
|
-
this.fitImagesVertically =
|
123
|
+
this.fitImagesVertically = null;
|
120
124
|
|
125
|
+
/**
|
126
|
+
* Wether the scaling should ignore the scaler
|
127
|
+
* rise events
|
128
|
+
*/
|
121
129
|
this.manualScale = false;
|
122
130
|
|
131
|
+
/**
|
132
|
+
* The canvas shuffler.
|
133
|
+
*
|
134
|
+
* Set it null to automatic shuffling algorithm selection.
|
135
|
+
*/
|
136
|
+
this.shuffler = null;
|
137
|
+
|
123
138
|
/**
|
124
139
|
* Callback that will be executed
|
125
140
|
* when muzzle has fully loaded and rendered its first
|
@@ -142,12 +157,24 @@ class MuzzleCanvas {
|
|
142
157
|
* Whether the current puzzle can be solved in very few tries.
|
143
158
|
*
|
144
159
|
* Set null for automatic configuration of this property. Basic puzzles will be considered
|
145
|
-
* basic and match puzzles will be considered non-
|
160
|
+
* basic and match puzzles will be considered non-simple.
|
146
161
|
*
|
147
162
|
* @type {boolean}
|
148
163
|
*/
|
149
164
|
this.simple = null;
|
150
165
|
|
166
|
+
this.spiky = false;
|
167
|
+
|
168
|
+
/**
|
169
|
+
* The reference insert axis, used at rounded outline to compute insert internal and external diameters
|
170
|
+
*
|
171
|
+
* Set null for default computation of axis - no axis reference for basic boards
|
172
|
+
* and vertical axis for match
|
173
|
+
*
|
174
|
+
* @type {Axis}
|
175
|
+
* */
|
176
|
+
this.referenceInsertAxis = null;
|
177
|
+
|
151
178
|
/**
|
152
179
|
* Callback to be executed when submitting puzzle.
|
153
180
|
*
|
@@ -166,32 +193,81 @@ class MuzzleCanvas {
|
|
166
193
|
* property with any code you need the be called here
|
167
194
|
*/
|
168
195
|
this.onValid = () => {};
|
196
|
+
|
197
|
+
/**
|
198
|
+
* @private
|
199
|
+
*/
|
200
|
+
this._ready = false;
|
201
|
+
}
|
202
|
+
|
203
|
+
get painter() {
|
204
|
+
return new MuzzlePainter();
|
169
205
|
}
|
170
206
|
|
171
207
|
/**
|
172
208
|
*/
|
173
209
|
get baseConfig() {
|
174
|
-
|
175
|
-
|
176
|
-
return {
|
210
|
+
return Object.assign({
|
211
|
+
preventOffstageDrag: true,
|
177
212
|
width: this.canvasWidth,
|
178
213
|
height: this.canvasHeight,
|
179
|
-
pieceSize:
|
180
|
-
proximity: Math.min(
|
181
|
-
borderFill: this.borderFill === null ? headbreaker.Vector.divide(pieceSize, 10) : this.borderFill,
|
214
|
+
pieceSize: this.adjustedPieceSize,
|
215
|
+
proximity: Math.min(this.adjustedPieceSize.x, this.adjustedPieceSize.y) / 5,
|
182
216
|
strokeWidth: this.strokeWidth,
|
183
|
-
lineSoftness: 0.18
|
184
|
-
|
217
|
+
lineSoftness: 0.18,
|
218
|
+
painter: this.painter
|
219
|
+
}, this.outlineConfig);
|
220
|
+
}
|
221
|
+
|
222
|
+
/**
|
223
|
+
*/
|
224
|
+
get outlineConfig() {
|
225
|
+
if (this.spiky) {
|
226
|
+
return {
|
227
|
+
borderFill: this.borderFill === null ? headbreaker.Vector.divide(this.adjustedPieceSize, 10) : this.borderFill,
|
228
|
+
}
|
229
|
+
} else {
|
230
|
+
return {
|
231
|
+
borderFill: 0,
|
232
|
+
outline: new headbreaker.outline.Rounded({
|
233
|
+
bezelize: true,
|
234
|
+
insertDepth: 3/5,
|
235
|
+
bezelDepth: 9/10,
|
236
|
+
referenceInsertAxis: this.referenceInsertAxis
|
237
|
+
}),
|
238
|
+
}
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
/**
|
243
|
+
* The piece size, adjusted to the aspect ratio
|
244
|
+
*
|
245
|
+
* @returns {Vector}
|
246
|
+
*/
|
247
|
+
get adjustedPieceSize() {
|
248
|
+
if (!this._adjustedPieceSize) {
|
249
|
+
const aspectRatio = this.effectiveAspectRatio;
|
250
|
+
this._adjustedPieceSize = headbreaker.vector(this.pieceSize * aspectRatio, this.pieceSize);
|
251
|
+
}
|
252
|
+
return this._adjustedPieceSize;
|
185
253
|
}
|
186
254
|
|
187
255
|
/**
|
188
256
|
* @type {Axis}
|
189
257
|
*/
|
190
258
|
get imageAdjustmentAxis() {
|
191
|
-
console.log(this.fitImagesVertically)
|
192
259
|
return this.fitImagesVertically ? headbreaker.Vertical : headbreaker.Horizontal;
|
193
260
|
}
|
194
261
|
|
262
|
+
/**
|
263
|
+
* The configured aspect ratio, or 1
|
264
|
+
*
|
265
|
+
* @type {number}
|
266
|
+
*/
|
267
|
+
get effectiveAspectRatio() {
|
268
|
+
return this.aspectRatio || 1;
|
269
|
+
}
|
270
|
+
|
195
271
|
/**
|
196
272
|
* The currently active canvas, or null if
|
197
273
|
* it has not yet initialized
|
@@ -205,7 +281,7 @@ class MuzzleCanvas {
|
|
205
281
|
/**
|
206
282
|
* Draws the - previusly built - current canvas.
|
207
283
|
*
|
208
|
-
* Prefer
|
284
|
+
* Prefer `this.currentCanvas.redraw()` when performing
|
209
285
|
* small updates to the pieces.
|
210
286
|
*/
|
211
287
|
draw() {
|
@@ -234,13 +310,9 @@ class MuzzleCanvas {
|
|
234
310
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
235
311
|
*/
|
236
312
|
async basic(x, y, imagePath) {
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
if (this.simple === null) {
|
242
|
-
this.simple = true;
|
243
|
-
}
|
313
|
+
this._config('aspectRatio', y / x);
|
314
|
+
this._config('simple', true);
|
315
|
+
this._config('shuffler', Muzzle.Shuffler.grid);
|
244
316
|
|
245
317
|
/**
|
246
318
|
* @todo take all container size
|
@@ -252,7 +324,6 @@ class MuzzleCanvas {
|
|
252
324
|
canvas.adjustImagesToPuzzle(this.imageAdjustmentAxis);
|
253
325
|
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y });
|
254
326
|
this._attachBasicValidator(canvas);
|
255
|
-
canvas.shuffleGrid(0.8);
|
256
327
|
this._configCanvas(canvas);
|
257
328
|
canvas.onValid(() => {
|
258
329
|
setTimeout(() => {
|
@@ -265,52 +336,94 @@ class MuzzleCanvas {
|
|
265
336
|
}
|
266
337
|
|
267
338
|
/**
|
268
|
-
*
|
269
|
-
*
|
270
|
-
*
|
339
|
+
* Creates a choose puzzle, where a single right piece must match the single left piece,
|
340
|
+
* choosing the latter from a bunch of other left odd pieces. By default, `Muzzle.Shuffler.line` shuffling is used.
|
341
|
+
*
|
342
|
+
* This is a particular case of a match puzzle with line
|
343
|
+
*
|
344
|
+
* @param {string} leftUrl the url of the left piece
|
345
|
+
* @param {string} rightUrl the url of the right piece
|
346
|
+
* @param {string[]} leftOddUrls the urls of the off left urls
|
347
|
+
* @param {number} [rightAspectRatio] the `x:y` ratio of the right pieces, that override the general `aspectRatio` of the puzzle.
|
348
|
+
* Use null to have the same aspect ratio as left pieces
|
349
|
+
*
|
271
350
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
272
351
|
*/
|
273
|
-
async
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
const canvas = this._createCanvas();
|
278
|
-
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y * count });
|
279
|
-
|
280
|
-
// todo validate
|
281
|
-
// todo set images
|
282
|
-
|
283
|
-
this._configCanvas(canvas);
|
284
|
-
return canvas;
|
352
|
+
async choose(leftUrl, rightUrl, leftOddUrls, rightAspectRatio = null) {
|
353
|
+
this._config('shuffler', Muzzle.Shuffler.line);
|
354
|
+
return this.match([leftUrl], [rightUrl], {leftOddUrls, rightAspectRatio});
|
285
355
|
}
|
286
356
|
|
287
357
|
/**
|
288
|
-
*
|
289
|
-
* with optional odd left and right pieces that don't match
|
358
|
+
* Creates a match puzzle, where left pieces are matched against right pieces,
|
359
|
+
* with optional odd left and right pieces that don't match. By default, `Muzzle.Shuffler.columns`
|
360
|
+
* shuffling is used.
|
290
361
|
*
|
291
362
|
* @param {string[]} leftUrls
|
292
363
|
* @param {string[]} rightUrls must be of the same size of lefts
|
293
|
-
* @param {
|
294
|
-
* @param {string[]}
|
364
|
+
* @param {object} [options]
|
365
|
+
* @param {string[]} [options.leftOddUrls]
|
366
|
+
* @param {string[]} [options.rightOddUrls]
|
367
|
+
* @param {number?} [options.rightAspectRatio] the aspect ratio of the right pieces. Use null to have the same aspect ratio as left pieces
|
295
368
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
296
369
|
*/
|
297
|
-
async match(leftUrls, rightUrls, leftOddUrls = [], rightOddUrls = []) {
|
370
|
+
async match(leftUrls, rightUrls, {leftOddUrls = [], rightOddUrls = [], rightAspectRatio = this.effectiveAspectRatio} = {}) {
|
371
|
+
const rightWidthRatio = rightAspectRatio / this.effectiveAspectRatio;
|
372
|
+
|
373
|
+
this._config('simple', false);
|
374
|
+
this._config('shuffler', Muzzle.Shuffler.columns);
|
375
|
+
this._config('fitImagesVertically', rightWidthRatio > 1);
|
376
|
+
this._config('referenceInsertAxis', headbreaker.Vertical);
|
377
|
+
|
298
378
|
/** @private @type {(Promise<Template>)[]} */
|
299
379
|
const templatePromises = [];
|
300
|
-
|
301
|
-
|
380
|
+
|
381
|
+
const rightSize = headbreaker.diameter(
|
382
|
+
headbreaker.Vector.multiply(this.adjustedPieceSize, headbreaker.vector(rightWidthRatio, 1)));
|
383
|
+
|
384
|
+
const pushTemplate = (path, options) =>
|
385
|
+
templatePromises.push(this._createMatchTemplate(path, options));
|
386
|
+
|
387
|
+
const pushLeftTemplate = (index, path, options) =>
|
388
|
+
pushTemplate(path, {
|
389
|
+
left: true,
|
390
|
+
targetPosition: headbreaker.Vector.multiply(this.pieceSize, headbreaker.vector(1, index)),
|
391
|
+
...options
|
392
|
+
});
|
393
|
+
|
394
|
+
const pushRightTemplate = (index, path, options) =>
|
395
|
+
pushTemplate(path, {
|
396
|
+
size: rightSize,
|
397
|
+
targetPosition: headbreaker.Vector.multiply(this.pieceSize, headbreaker.vector(2, index)),
|
398
|
+
...options
|
399
|
+
});
|
302
400
|
|
303
401
|
const last = leftUrls.length - 1;
|
304
402
|
for (let i = 0; i <= last; i++) {
|
305
403
|
const leftId = `l${i}`;
|
306
404
|
const rightId = `r${i}`;
|
307
405
|
|
308
|
-
|
309
|
-
|
406
|
+
pushLeftTemplate(i + 1, leftUrls[i], {
|
407
|
+
id: leftId,
|
408
|
+
rightTargetId: rightId
|
409
|
+
});
|
410
|
+
pushRightTemplate(i + 1, rightUrls[i], {
|
411
|
+
id: rightId
|
412
|
+
});
|
310
413
|
}
|
311
414
|
|
312
|
-
leftOddUrls.forEach((it, i) =>
|
313
|
-
|
415
|
+
leftOddUrls.forEach((it, i) =>
|
416
|
+
pushLeftTemplate(i + leftUrls.length, it, {
|
417
|
+
id: `lo${i}`,
|
418
|
+
odd: true
|
419
|
+
})
|
420
|
+
);
|
421
|
+
rightOddUrls.forEach((it, i) =>
|
422
|
+
pushRightTemplate(i + rightUrls.length, it, {
|
423
|
+
id: `ro${i}`,
|
424
|
+
odd: true
|
425
|
+
})
|
426
|
+
);
|
314
427
|
|
315
428
|
// + Math.max(leftOddUrls.length, rightOddUrls.length)
|
316
429
|
const templates = await Promise.all(templatePromises);
|
@@ -318,7 +431,6 @@ class MuzzleCanvas {
|
|
318
431
|
const canvas = this._createCanvas({ maxPiecesCount: {x: 2, y: leftUrls.length} });
|
319
432
|
canvas.adjustImagesToPiece(this.imageAdjustmentAxis);
|
320
433
|
templates.forEach(it => canvas.sketchPiece(it));
|
321
|
-
canvas.shuffleColumns(0.8);
|
322
434
|
this._attachMatchValidator(canvas);
|
323
435
|
this._configCanvas(canvas);
|
324
436
|
return canvas;
|
@@ -383,11 +495,11 @@ class MuzzleCanvas {
|
|
383
495
|
* @param {object} options
|
384
496
|
* @returns {Promise<object>}
|
385
497
|
*/
|
386
|
-
_createMatchTemplate(imagePath, {id, left = false, targetPosition = null, rightTargetId = null, odd = false}) {
|
498
|
+
_createMatchTemplate(imagePath, {id, left = false, targetPosition = null, rightTargetId = null, odd = false, size = null}) {
|
387
499
|
const structure = left ? 'T-N-' : `N-S-`;
|
388
|
-
|
389
500
|
return this._loadImage(imagePath).then((image) => {
|
390
501
|
return {
|
502
|
+
...(size ? {size} : {}),
|
391
503
|
structure,
|
392
504
|
metadata: { id, left, odd, rightTargetId, image, targetPosition }
|
393
505
|
}
|
@@ -400,6 +512,7 @@ class MuzzleCanvas {
|
|
400
512
|
*/
|
401
513
|
_configCanvas(canvas) {
|
402
514
|
this._canvas = canvas;
|
515
|
+
this._canvas.shuffleWith(0.8, this.shuffler);
|
403
516
|
this._canvas.onValid(() => {
|
404
517
|
setTimeout(() => this.onValid(), 0);
|
405
518
|
});
|
@@ -412,14 +525,23 @@ class MuzzleCanvas {
|
|
412
525
|
|
413
526
|
['resize', 'load'].forEach((event) => {
|
414
527
|
window.addEventListener(event, () => {
|
528
|
+
console.debug("Scaler event fired:", event);
|
415
529
|
var container = document.getElementById(this.canvasId);
|
416
530
|
this.scale(container.offsetWidth, container.scrollHeight);
|
417
531
|
});
|
418
532
|
});
|
419
533
|
}
|
420
534
|
|
535
|
+
/**
|
536
|
+
* Scales the canvas to the given width and height
|
537
|
+
*
|
538
|
+
* @param {number} width
|
539
|
+
* @param {number} height
|
540
|
+
*/
|
421
541
|
scale(width, height) {
|
422
542
|
if (this.fixedDimensions || !this.canvas) return;
|
543
|
+
|
544
|
+
console.debug("Scaling:", {width, height})
|
423
545
|
const factor = this.optimalScaleFactor(width, height);
|
424
546
|
this.canvas.resize(width, height);
|
425
547
|
this.canvas.scale(factor);
|
@@ -427,6 +549,9 @@ class MuzzleCanvas {
|
|
427
549
|
this.focus();
|
428
550
|
}
|
429
551
|
|
552
|
+
/**
|
553
|
+
* Focuses the stage around the canvas center
|
554
|
+
*/
|
430
555
|
focus() {
|
431
556
|
const stage = this.canvas['__konvaLayer__'].getStage();
|
432
557
|
|
@@ -440,7 +565,7 @@ class MuzzleCanvas {
|
|
440
565
|
const maxX = Math.max(...xs);
|
441
566
|
const maxY = Math.max(...ys);
|
442
567
|
|
443
|
-
return headbreaker.
|
568
|
+
return headbreaker.vector(maxX - minX, maxY - minY);
|
444
569
|
})();
|
445
570
|
const diff = headbreaker.Vector.minus(area, realDiameter);
|
446
571
|
const semi = headbreaker.Vector.divide(diff, -2);
|
@@ -449,11 +574,19 @@ class MuzzleCanvas {
|
|
449
574
|
stage.draw();
|
450
575
|
}
|
451
576
|
|
577
|
+
/**
|
578
|
+
* @private
|
579
|
+
*/
|
452
580
|
get coordinates() {
|
453
581
|
const points = this.canvas.puzzle.points;
|
454
582
|
return [points.map(([x, _y]) => x), points.map(([_x, y]) => y)];
|
455
583
|
}
|
456
584
|
|
585
|
+
/**
|
586
|
+
* @private
|
587
|
+
* @param {number} width
|
588
|
+
* @param {number} height
|
589
|
+
*/
|
457
590
|
optimalScaleFactor(width, height) {
|
458
591
|
const factors = headbreaker.Vector.divide(headbreaker.vector(width, height), this.canvas.puzzleDiameter);
|
459
592
|
return Math.min(factors.x, factors.y) / 1.75;
|
@@ -467,9 +600,14 @@ class MuzzleCanvas {
|
|
467
600
|
this.loadPreviousSolution();
|
468
601
|
this.resetCoordinates();
|
469
602
|
this.draw();
|
603
|
+
this._ready = true;
|
470
604
|
this.onReady();
|
471
605
|
}
|
472
606
|
|
607
|
+
isReady() {
|
608
|
+
return this._ready;
|
609
|
+
}
|
610
|
+
|
473
611
|
// ===========
|
474
612
|
// Persistence
|
475
613
|
// ===========
|
@@ -508,6 +646,10 @@ class MuzzleCanvas {
|
|
508
646
|
}
|
509
647
|
}
|
510
648
|
|
649
|
+
/**
|
650
|
+
* Translates the pieces so that
|
651
|
+
* they start at canvas' coordinates origin
|
652
|
+
*/
|
511
653
|
resetCoordinates() {
|
512
654
|
const [xs, ys] = this.coordinates;
|
513
655
|
const minX = Math.min(...xs);
|
@@ -554,12 +696,58 @@ class MuzzleCanvas {
|
|
554
696
|
};
|
555
697
|
}
|
556
698
|
|
699
|
+
/**
|
700
|
+
* @param {string} key
|
701
|
+
* @param {any} value
|
702
|
+
*/
|
703
|
+
_config(key, value) {
|
704
|
+
const current = this[key];
|
705
|
+
console.debug("Setting config: ", [key, value])
|
706
|
+
|
707
|
+
if (current === null) {
|
708
|
+
this[key] = value;
|
709
|
+
}
|
710
|
+
}
|
711
|
+
|
712
|
+
// ==============
|
713
|
+
// Event handling
|
714
|
+
// ==============
|
715
|
+
|
716
|
+
|
717
|
+
/**
|
718
|
+
* Registers an event handler
|
719
|
+
*
|
720
|
+
* @param {string} event
|
721
|
+
* @param {(...args: any) => void} callback
|
722
|
+
*/
|
723
|
+
register(event, callback) {
|
724
|
+
const _event = this[event];
|
725
|
+
this[event] = (...args) => {
|
726
|
+
callback(...args);
|
727
|
+
_event(...args);
|
728
|
+
}
|
729
|
+
}
|
730
|
+
|
731
|
+
/**
|
732
|
+
* Runs the given action if muzzle is ready,
|
733
|
+
* queueing it otherwise
|
734
|
+
* @param {() => void} callback
|
735
|
+
*/
|
736
|
+
run(callback) {
|
737
|
+
if (this.isReady()) {
|
738
|
+
callback();
|
739
|
+
} else {
|
740
|
+
this.register('onReady', callback);
|
741
|
+
}
|
742
|
+
}
|
557
743
|
}
|
558
744
|
|
559
745
|
const Muzzle = new class extends MuzzleCanvas {
|
560
746
|
constructor() {
|
561
747
|
super();
|
562
748
|
this.aux = {};
|
749
|
+
|
750
|
+
this.Shuffler = headbreaker.Shuffler;
|
563
751
|
}
|
564
752
|
|
565
753
|
/**
|