mumuki-puzzle-runner 0.6.0 → 1.0.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 +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
|
/**
|