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 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
  /**