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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20cbf33422b40662d79aa0662f45011525a7260454eccfb1f64e5f0a4cdab780
4
- data.tar.gz: e84cc1ba0a61dffe0be0ccb9bd26840ff5e9de7dc3b2d1ecff7a9010ac1a36ef
3
+ metadata.gz: f021b6da535ba17552c88c6e273d6dcd0aab75e02637420d6859864dac6c31d6
4
+ data.tar.gz: 173cdc9a8f6dbe9b5e83c2c684168db5c2b05c9bbc0c9ae5bce2016c346ffe20
5
5
  SHA512:
6
- metadata.gz: ac96c26344814c63d596c1cb012781b18799eb683603ec738beea160ace4e677dd650926cd2720a3e4fb85e712d19a432137040233be99a7a69534823c82d655
7
- data.tar.gz: c56d817d8240de55674cad90192c59f823da755ce9d3eabdf8a4f373cf84be5c22b1e15fdad64c1c5ff0f40770d9102c11fcd65674621910a07bbdccd95ed065
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
- .mu-kids-exercise-workspace.muzzle-simple .mu-kids-blocks {
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.muzzle-simple .mu-kids-blocks:after {
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
@@ -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. `false` by default
117
+ * to puzzle dimensions.
118
+ *
119
+ * Set null for automatic fit.
116
120
  *
117
121
  * @type {boolean}
118
122
  */
119
- this.fitImagesVertically = false;
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-basic.
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.aspectRatio || 1;
225
- this._adjustedPieceSize = headbreaker.vector(this.pieceSize / aspectRatio, 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 {@code this.currentCanvas.redraw()} when performing
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
- if (!this.aspectRatio) {
280
- this.aspectRatio = x / y;
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
- * @param {number} x
311
- * @param {number} y
312
- * @param {string[]} [imagePaths]
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 multi(x, y, imagePaths) {
316
- const count = imagePaths.length;
317
- const images = await Promise.all(imagePaths.map(imagePath => this._loadImage(imagePath)));
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
- * Craates a match puzzle, where left pieces are matched against right pieces,
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 {string[]} leftOddUrls
336
- * @param {string[]} rightOddUrls
337
- * @param {number?} rightWidthRatio a multiplicator to apply to the right piece's width
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 = [], rightWidthRatio = 1) {
341
- if (!this.referenceInsertAxis) {
342
- this.referenceInsertAxis = headbreaker.Vertical;
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(headbreaker.Vector.multiply(this.adjustedPieceSize, headbreaker.vector(rightWidthRatio, 1)));
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.Vector.plus(headbreaker.vector(maxX - minX, maxY - minY), this.canvas.puzzle.pieceDiameter);
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
  /**