mumuki-puzzle-runner 0.6.0 → 1.0.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 +27 -3
- data/lib/public/js/muzzle-editor.js +8 -11
- data/lib/public/js/muzzle.js +138 -50
- data/lib/public/vendor/headbreaker.d.ts +17 -1
- 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: f021b6da535ba17552c88c6e273d6dcd0aab75e02637420d6859864dac6c31d6
|
4
|
+
data.tar.gz: 173cdc9a8f6dbe9b5e83c2c684168db5c2b05c9bbc0c9ae5bce2016c346ffe20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3263f1c2fd1398d9d159f714cbc0c38308375cde0620bd22f50c21d08a42e17c584bcfc405db4398cca548e69f8dacabee11071e0ae8b26bc51ea795e93483e9
|
7
|
+
data.tar.gz: 9419c14f400ae387ead448c2a68ec5063d371c00d92e4247c10c5ac366cc5c7a92b82e98d79bac435326be53633e0455596f9f2caa9f6f31deb0e829a9c2e1e1
|
@@ -1,3 +1,11 @@
|
|
1
|
+
|
2
|
+
/*
|
3
|
+
* ============
|
4
|
+
* Initial size
|
5
|
+
* ============
|
6
|
+
*/
|
7
|
+
|
8
|
+
|
1
9
|
.mu-kids-state-image img {
|
2
10
|
height: 100%;
|
3
11
|
width: auto;
|
@@ -8,15 +16,31 @@
|
|
8
16
|
width: 100%;
|
9
17
|
}
|
10
18
|
|
19
|
+
/*
|
20
|
+
* ====================
|
21
|
+
* Submit button hiding
|
22
|
+
* ====================
|
23
|
+
*/
|
24
|
+
|
11
25
|
.mu-kids-exercise-workspace.muzzle-simple .mu-kids-submit-button {
|
12
26
|
display: none;
|
13
27
|
}
|
14
28
|
|
15
|
-
|
29
|
+
/*
|
30
|
+
* ===========
|
31
|
+
* Blur effect
|
32
|
+
* ===========
|
33
|
+
*/
|
34
|
+
|
35
|
+
.mu-kids-exercise-workspace.mu-full-workspace .mu-kids-submit-button {
|
36
|
+
margin: 0 30px 30px;
|
37
|
+
}
|
38
|
+
|
39
|
+
.mu-kids-exercise-workspace .mu-kids-blocks {
|
16
40
|
overflow: hidden;
|
17
41
|
}
|
18
42
|
|
19
|
-
.mu-kids-exercise-workspace
|
43
|
+
.mu-kids-exercise-workspace .mu-kids-blocks:after {
|
20
44
|
content: ' ';
|
21
45
|
box-shadow: inset 0 0 30px 30px #FFFFFF;
|
22
46
|
position: absolute;
|
@@ -25,4 +49,4 @@
|
|
25
49
|
right: 0;
|
26
50
|
bottom: 0;
|
27
51
|
pointer-events: none;
|
28
|
-
|
52
|
+
}
|
@@ -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
|
@@ -37,7 +30,7 @@ $(() => {
|
|
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
|
|
@@ -47,6 +40,7 @@ $(() => {
|
|
47
40
|
|
48
41
|
mumuki.kids.registerStateScaler(($state, fullMargin) => {
|
49
42
|
const $image = $state.find('img');
|
43
|
+
if (!$image.length) return;
|
50
44
|
|
51
45
|
$image.css('transform', 'scale(1)');
|
52
46
|
const width = ($state.width() - fullMargin) / $image.width();
|
@@ -55,8 +49,9 @@ $(() => {
|
|
55
49
|
});
|
56
50
|
|
57
51
|
mumuki.kids.registerBlocksAreaScaler(($blocks) => {
|
52
|
+
console.debug("Scaler fired");
|
58
53
|
const maxHeight = $('.mu-kids-exercise').height() - $('.mu-kids-exercise-description').height();
|
59
|
-
Muzzle.scale($blocks.width(), Math.min($blocks.height(), maxHeight));
|
54
|
+
Muzzle.run(() => Muzzle.scale($blocks.width(), Math.min($blocks.height(), maxHeight)));
|
60
55
|
});
|
61
56
|
|
62
57
|
Muzzle.manualScale = true;
|
@@ -65,7 +60,7 @@ $(() => {
|
|
65
60
|
// Submit button hiding
|
66
61
|
// ====================
|
67
62
|
|
68
|
-
register('onReady', () => {
|
63
|
+
Muzzle.register('onReady', () => {
|
69
64
|
if (Muzzle.simple) {
|
70
65
|
$('.mu-kids-exercise-workspace').addClass('muzzle-simple');
|
71
66
|
}
|
@@ -75,7 +70,9 @@ $(() => {
|
|
75
70
|
// Assets loading
|
76
71
|
// ==============
|
77
72
|
|
78
|
-
register('onReady', () => {
|
73
|
+
Muzzle.register('onReady', () => {
|
74
|
+
console.debug("Muzzle is ready");
|
75
|
+
|
79
76
|
mumuki.assetsLoadedFor('editor');
|
80
77
|
// although layout assets
|
81
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,
|
@@ -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,7 +157,7 @@ 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
|
*/
|
@@ -152,6 +167,7 @@ class MuzzleCanvas {
|
|
152
167
|
|
153
168
|
/**
|
154
169
|
* The reference insert axis, used at rounded outline to compute insert internal and external diameters
|
170
|
+
*
|
155
171
|
* Set null for default computation of axis - no axis reference for basic boards
|
156
172
|
* and vertical axis for match
|
157
173
|
*
|
@@ -177,20 +193,29 @@ class MuzzleCanvas {
|
|
177
193
|
* property with any code you need the be called here
|
178
194
|
*/
|
179
195
|
this.onValid = () => {};
|
180
|
-
}
|
181
196
|
|
197
|
+
/**
|
198
|
+
* @private
|
199
|
+
*/
|
200
|
+
this._ready = false;
|
201
|
+
}
|
182
202
|
|
203
|
+
get painter() {
|
204
|
+
return new MuzzlePainter();
|
205
|
+
}
|
183
206
|
|
184
207
|
/**
|
185
208
|
*/
|
186
209
|
get baseConfig() {
|
187
210
|
return Object.assign({
|
211
|
+
preventOffstageDrag: true,
|
188
212
|
width: this.canvasWidth,
|
189
213
|
height: this.canvasHeight,
|
190
214
|
pieceSize: this.adjustedPieceSize,
|
191
215
|
proximity: Math.min(this.adjustedPieceSize.x, this.adjustedPieceSize.y) / 5,
|
192
216
|
strokeWidth: this.strokeWidth,
|
193
|
-
lineSoftness: 0.18
|
217
|
+
lineSoftness: 0.18,
|
218
|
+
painter: this.painter
|
194
219
|
}, this.outlineConfig);
|
195
220
|
}
|
196
221
|
|
@@ -221,8 +246,8 @@ class MuzzleCanvas {
|
|
221
246
|
*/
|
222
247
|
get adjustedPieceSize() {
|
223
248
|
if (!this._adjustedPieceSize) {
|
224
|
-
const aspectRatio = this.
|
225
|
-
this._adjustedPieceSize = headbreaker.vector(this.pieceSize
|
249
|
+
const aspectRatio = this.effectiveAspectRatio;
|
250
|
+
this._adjustedPieceSize = headbreaker.vector(this.pieceSize * aspectRatio, this.pieceSize);
|
226
251
|
}
|
227
252
|
return this._adjustedPieceSize;
|
228
253
|
}
|
@@ -234,6 +259,15 @@ class MuzzleCanvas {
|
|
234
259
|
return this.fitImagesVertically ? headbreaker.Vertical : headbreaker.Horizontal;
|
235
260
|
}
|
236
261
|
|
262
|
+
/**
|
263
|
+
* The configured aspect ratio, or 1
|
264
|
+
*
|
265
|
+
* @type {number}
|
266
|
+
*/
|
267
|
+
get effectiveAspectRatio() {
|
268
|
+
return this.aspectRatio || 1;
|
269
|
+
}
|
270
|
+
|
237
271
|
/**
|
238
272
|
* The currently active canvas, or null if
|
239
273
|
* it has not yet initialized
|
@@ -247,7 +281,7 @@ class MuzzleCanvas {
|
|
247
281
|
/**
|
248
282
|
* Draws the - previusly built - current canvas.
|
249
283
|
*
|
250
|
-
* Prefer
|
284
|
+
* Prefer `this.currentCanvas.redraw()` when performing
|
251
285
|
* small updates to the pieces.
|
252
286
|
*/
|
253
287
|
draw() {
|
@@ -276,13 +310,9 @@ class MuzzleCanvas {
|
|
276
310
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
277
311
|
*/
|
278
312
|
async basic(x, y, imagePath) {
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
if (this.simple === null) {
|
284
|
-
this.simple = true;
|
285
|
-
}
|
313
|
+
this._config('aspectRatio', y / x);
|
314
|
+
this._config('simple', true);
|
315
|
+
this._config('shuffler', Muzzle.Shuffler.grid);
|
286
316
|
|
287
317
|
/**
|
288
318
|
* @todo take all container size
|
@@ -294,7 +324,6 @@ class MuzzleCanvas {
|
|
294
324
|
canvas.adjustImagesToPuzzle(this.imageAdjustmentAxis);
|
295
325
|
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y });
|
296
326
|
this._attachBasicValidator(canvas);
|
297
|
-
canvas.shuffleGrid(0.8);
|
298
327
|
this._configCanvas(canvas);
|
299
328
|
canvas.onValid(() => {
|
300
329
|
setTimeout(() => {
|
@@ -307,45 +336,50 @@ class MuzzleCanvas {
|
|
307
336
|
}
|
308
337
|
|
309
338
|
/**
|
310
|
-
*
|
311
|
-
*
|
312
|
-
*
|
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
|
+
*
|
313
350
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
314
351
|
*/
|
315
|
-
async
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
const canvas = this._createCanvas();
|
320
|
-
canvas.autogenerate({ horizontalPiecesCount: x, verticalPiecesCount: y * count });
|
321
|
-
|
322
|
-
// todo validate
|
323
|
-
// todo set images
|
324
|
-
|
325
|
-
this._configCanvas(canvas);
|
326
|
-
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});
|
327
355
|
}
|
328
356
|
|
329
357
|
/**
|
330
|
-
*
|
331
|
-
* 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.
|
332
361
|
*
|
333
362
|
* @param {string[]} leftUrls
|
334
363
|
* @param {string[]} rightUrls must be of the same size of lefts
|
335
|
-
* @param {
|
336
|
-
* @param {string[]}
|
337
|
-
* @param {
|
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
|
338
368
|
* @returns {Promise<Canvas>} the promise of the built canvas
|
339
369
|
*/
|
340
|
-
async match(leftUrls, rightUrls, leftOddUrls = [], rightOddUrls = [],
|
341
|
-
|
342
|
-
|
343
|
-
|
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);
|
344
377
|
|
345
378
|
/** @private @type {(Promise<Template>)[]} */
|
346
379
|
const templatePromises = [];
|
347
380
|
|
348
|
-
const rightSize = headbreaker.diameter(
|
381
|
+
const rightSize = headbreaker.diameter(
|
382
|
+
headbreaker.Vector.multiply(this.adjustedPieceSize, headbreaker.vector(rightWidthRatio, 1)));
|
349
383
|
|
350
384
|
const pushTemplate = (path, options) =>
|
351
385
|
templatePromises.push(this._createMatchTemplate(path, options));
|
@@ -397,7 +431,6 @@ class MuzzleCanvas {
|
|
397
431
|
const canvas = this._createCanvas({ maxPiecesCount: {x: 2, y: leftUrls.length} });
|
398
432
|
canvas.adjustImagesToPiece(this.imageAdjustmentAxis);
|
399
433
|
templates.forEach(it => canvas.sketchPiece(it));
|
400
|
-
canvas.shuffleColumns(0.8);
|
401
434
|
this._attachMatchValidator(canvas);
|
402
435
|
this._configCanvas(canvas);
|
403
436
|
return canvas;
|
@@ -479,6 +512,7 @@ class MuzzleCanvas {
|
|
479
512
|
*/
|
480
513
|
_configCanvas(canvas) {
|
481
514
|
this._canvas = canvas;
|
515
|
+
this._canvas.shuffleWith(0.8, this.shuffler);
|
482
516
|
this._canvas.onValid(() => {
|
483
517
|
setTimeout(() => this.onValid(), 0);
|
484
518
|
});
|
@@ -491,6 +525,7 @@ class MuzzleCanvas {
|
|
491
525
|
|
492
526
|
['resize', 'load'].forEach((event) => {
|
493
527
|
window.addEventListener(event, () => {
|
528
|
+
console.debug("Scaler event fired:", event);
|
494
529
|
var container = document.getElementById(this.canvasId);
|
495
530
|
this.scale(container.offsetWidth, container.scrollHeight);
|
496
531
|
});
|
@@ -505,6 +540,8 @@ class MuzzleCanvas {
|
|
505
540
|
*/
|
506
541
|
scale(width, height) {
|
507
542
|
if (this.fixedDimensions || !this.canvas) return;
|
543
|
+
|
544
|
+
console.debug("Scaling:", {width, height})
|
508
545
|
const factor = this.optimalScaleFactor(width, height);
|
509
546
|
this.canvas.resize(width, height);
|
510
547
|
this.canvas.scale(factor);
|
@@ -528,7 +565,7 @@ class MuzzleCanvas {
|
|
528
565
|
const maxX = Math.max(...xs);
|
529
566
|
const maxY = Math.max(...ys);
|
530
567
|
|
531
|
-
return headbreaker.
|
568
|
+
return headbreaker.vector(maxX - minX, maxY - minY);
|
532
569
|
})();
|
533
570
|
const diff = headbreaker.Vector.minus(area, realDiameter);
|
534
571
|
const semi = headbreaker.Vector.divide(diff, -2);
|
@@ -563,9 +600,14 @@ class MuzzleCanvas {
|
|
563
600
|
this.loadPreviousSolution();
|
564
601
|
this.resetCoordinates();
|
565
602
|
this.draw();
|
603
|
+
this._ready = true;
|
566
604
|
this.onReady();
|
567
605
|
}
|
568
606
|
|
607
|
+
isReady() {
|
608
|
+
return this._ready;
|
609
|
+
}
|
610
|
+
|
569
611
|
// ===========
|
570
612
|
// Persistence
|
571
613
|
// ===========
|
@@ -654,12 +696,58 @@ class MuzzleCanvas {
|
|
654
696
|
};
|
655
697
|
}
|
656
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
|
+
}
|
657
743
|
}
|
658
744
|
|
659
745
|
const Muzzle = new class extends MuzzleCanvas {
|
660
746
|
constructor() {
|
661
747
|
super();
|
662
748
|
this.aux = {};
|
749
|
+
|
750
|
+
this.Shuffler = headbreaker.Shuffler;
|
663
751
|
}
|
664
752
|
|
665
753
|
/**
|