jigsawpuzzlegame 1.0.10 → 1.0.11

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.
package/favicon.ico ADDED
Binary file
package/game.css ADDED
@@ -0,0 +1,23 @@
1
+ /* Demo-specific styles for game.html */
2
+ /* Note: Library styles (.polypiece, .gameCanvas) are automatically injected by jigsaw-puzzle-game.js */
3
+
4
+ * {
5
+ margin: 0;
6
+ padding: 0;
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ body {
11
+ width: 100vw;
12
+ height: 100vh;
13
+ overflow: hidden;
14
+ background-color: #f0f0f0;
15
+ }
16
+
17
+ #puzzle-container {
18
+ width: 100%;
19
+ height: 100%;
20
+ position: relative;
21
+ background-color: #e8e8e8;
22
+ }
23
+
package/game.html ADDED
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link rel="icon" href="favicon.ico" type="image/x-icon">
7
+ <title>Jigsaw Puzzle Game</title>
8
+ <link rel="stylesheet" href="game.css">
9
+ </head>
10
+ <body>
11
+ <div id="puzzle-container"></div>
12
+ <script type="module" src="game.js"></script>
13
+ </body>
14
+ </html>
15
+
package/game.js ADDED
@@ -0,0 +1,28 @@
1
+ import { JigsawPuzzle } from './jigsaw-puzzle-game.js';
2
+
3
+ // Default image URL - you can change this to any image URL
4
+ const DEFAULT_IMAGE = 'https://assets.codepen.io/2574552/Mona_Lisa.jpg';
5
+
6
+ // Initialize the puzzle
7
+ const puzzle = new JigsawPuzzle('puzzle-container', {
8
+ image: DEFAULT_IMAGE,
9
+ numPieces: 25,
10
+ shapeType: 0,
11
+ allowRotation: true,
12
+ onReady: () => {
13
+ // Puzzle is ready (image loaded), start the game
14
+ console.log('Puzzle ready, starting game...');
15
+ puzzle.start();
16
+ },
17
+ onWin: () => {
18
+ console.log('Congratulations! Puzzle solved!');
19
+ alert('Congratulations! You solved the puzzle!');
20
+ },
21
+ onStart: () => {
22
+ console.log('Game started');
23
+ },
24
+ onStop: () => {
25
+ console.log('Game stopped');
26
+ }
27
+ });
28
+
package/index.html ADDED
@@ -0,0 +1,652 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link rel="icon" href="favicon.ico" type="image/x-icon">
7
+ <title>JigsawPuzzle Class - Usage Guide</title>
8
+ <style>
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
11
+ line-height: 1.6;
12
+ max-width: 1000px;
13
+ margin: 0 auto;
14
+ padding: 20px;
15
+ background-color: #f5f5f5;
16
+ }
17
+ h1 {
18
+ color: #333;
19
+ border-bottom: 3px solid #4CAF50;
20
+ padding-bottom: 10px;
21
+ }
22
+ h2 {
23
+ color: #555;
24
+ margin-top: 30px;
25
+ border-bottom: 2px solid #ddd;
26
+ padding-bottom: 5px;
27
+ }
28
+ h3 {
29
+ color: #666;
30
+ margin-top: 20px;
31
+ }
32
+ code {
33
+ background-color: #f4f4f4;
34
+ padding: 2px 6px;
35
+ border-radius: 3px;
36
+ font-family: 'Courier New', monospace;
37
+ font-size: 0.9em;
38
+ }
39
+ pre {
40
+ background-color: #2d2d2d;
41
+ color: #f8f8f2;
42
+ padding: 15px;
43
+ border-radius: 5px;
44
+ overflow-x: auto;
45
+ margin: 15px 0;
46
+ }
47
+ pre code {
48
+ background-color: transparent;
49
+ padding: 0;
50
+ color: inherit;
51
+ }
52
+ .option-table {
53
+ width: 100%;
54
+ border-collapse: collapse;
55
+ margin: 15px 0;
56
+ background-color: white;
57
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
58
+ }
59
+ .option-table th,
60
+ .option-table td {
61
+ padding: 12px;
62
+ text-align: left;
63
+ border-bottom: 1px solid #ddd;
64
+ }
65
+ .option-table th {
66
+ background-color: #4CAF50;
67
+ color: white;
68
+ font-weight: bold;
69
+ }
70
+ .option-table tr:hover {
71
+ background-color: #f9f9f9;
72
+ }
73
+ .method-section {
74
+ background-color: white;
75
+ padding: 15px;
76
+ margin: 15px 0;
77
+ border-left: 4px solid #4CAF50;
78
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
79
+ }
80
+ .example-box {
81
+ background-color: #e8f5e9;
82
+ border-left: 4px solid #4CAF50;
83
+ padding: 15px;
84
+ margin: 15px 0;
85
+ }
86
+ .warning-box {
87
+ background-color: #fff3cd;
88
+ border-left: 4px solid #ffc107;
89
+ padding: 15px;
90
+ margin: 15px 0;
91
+ }
92
+ ul {
93
+ padding-left: 20px;
94
+ }
95
+ li {
96
+ margin: 5px 0;
97
+ }
98
+ .button-container {
99
+ text-align: center;
100
+ margin-bottom: 30px;
101
+ }
102
+ .button-container a {
103
+ display: inline-block;
104
+ background-color: #4CAF50;
105
+ color: white;
106
+ padding: 12px 24px;
107
+ text-decoration: none;
108
+ border-radius: 5px;
109
+ font-weight: bold;
110
+ font-size: 16px;
111
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
112
+ margin: 5px;
113
+ min-width: 150px;
114
+ text-align: center;
115
+ box-sizing: border-box;
116
+ }
117
+ .button-container a:hover {
118
+ background-color: #45a049;
119
+ }
120
+ @media (max-width: 600px) {
121
+ body {
122
+ padding: 10px;
123
+ }
124
+ .button-container a {
125
+ display: block;
126
+ width: 100%;
127
+ max-width: 100%;
128
+ min-width: auto;
129
+ margin: 8px 0;
130
+ }
131
+ h1 {
132
+ font-size: 1.5em;
133
+ }
134
+ h2 {
135
+ font-size: 1.3em;
136
+ }
137
+ pre {
138
+ font-size: 0.85em;
139
+ padding: 10px;
140
+ }
141
+ .option-table th,
142
+ .option-table td {
143
+ padding: 8px;
144
+ font-size: 0.9em;
145
+ }
146
+ }
147
+ </style>
148
+ </head>
149
+ <body>
150
+ <div class="button-container">
151
+ <a href="game.html">Demo</a>
152
+ <a href="jigsaw-puzzle-game.js" download="jigsaw-puzzle-game.js">Download Source</a>
153
+ </div>
154
+
155
+ <h1>JigsawPuzzle Class - Usage Guide</h1>
156
+
157
+ <p>This guide explains how to use the <code>JigsawPuzzle</code> class to create interactive jigsaw puzzle games in your web application.</p>
158
+
159
+ <h2>1. Importing the Class</h2>
160
+
161
+ <p>The class is exported as an ES6 module. Import it in your JavaScript file:</p>
162
+
163
+ <pre><code>import { JigsawPuzzle } from './jigsaw-puzzle-game.js';</code></pre>
164
+
165
+ <p><strong>Note:</strong> Make sure your HTML file uses <code>type="module"</code> in the script tag:</p>
166
+
167
+ <pre><code>&lt;script type="module" src="your-script.js"&gt;&lt;/script&gt;</code></pre>
168
+
169
+ <h2>2. Basic Setup</h2>
170
+
171
+ <p>You need an HTML container element where the puzzle will be displayed:</p>
172
+
173
+ <pre><code>&lt;div id="puzzle-container"&gt;&lt;/div&gt;</code></pre>
174
+
175
+ <p>Then create a new puzzle instance:</p>
176
+
177
+ <pre><code>const puzzle = new JigsawPuzzle('puzzle-container', {
178
+ image: 'https://example.com/image.jpg',
179
+ numPieces: 20,
180
+ shapeType: 0,
181
+ allowRotation: false
182
+ });</code></pre>
183
+
184
+ <h2>3. Configuration Options</h2>
185
+
186
+ <p>The constructor takes two parameters:</p>
187
+ <ul>
188
+ <li><code>containerId</code> - String ID of the container element, or the DOM element itself</li>
189
+ <li><code>options</code> - Configuration object (see table below)</li>
190
+ </ul>
191
+
192
+ <table class="option-table">
193
+ <thead>
194
+ <tr>
195
+ <th>Option</th>
196
+ <th>Type</th>
197
+ <th>Default</th>
198
+ <th>Description</th>
199
+ </tr>
200
+ </thead>
201
+ <tbody>
202
+ <tr>
203
+ <td><code>image</code></td>
204
+ <td>string</td>
205
+ <td><code>null</code></td>
206
+ <td>URL or data URL of the image to use for the puzzle. Can be set later with <code>setImage()</code>. Ignored if <code>savedData</code> is provided.</td>
207
+ </tr>
208
+ <tr>
209
+ <td><code>savedData</code></td>
210
+ <td>string\|null</td>
211
+ <td><code>null</code></td>
212
+ <td>JSON string of saved game state:
213
+ <ul style="margin: 5px 0; padding-left: 20px;">
214
+ <li>Non-empty string: use this saved data</li>
215
+ <li>Empty string (<code>""</code>): load from localStorage</li>
216
+ <li>Not provided or <code>null</code>: use normal initialization with <code>image</code> and other parameters</li>
217
+ </ul>
218
+ </td>
219
+ </tr>
220
+ <tr>
221
+ <td><code>numPieces</code></td>
222
+ <td>number</td>
223
+ <td><code>20</code></td>
224
+ <td>Number of puzzle pieces (approximate - actual count depends on optimal grid layout). Ignored if <code>savedData</code> is provided.</td>
225
+ </tr>
226
+ <tr>
227
+ <td><code>shapeType</code></td>
228
+ <td>number</td>
229
+ <td><code>0</code></td>
230
+ <td>Shape type for puzzle pieces (0-3, ignored if <code>savedData</code> is provided):
231
+ <ul style="margin: 5px 0; padding-left: 20px;">
232
+ <li><code>0</code> - Classic jigsaw shape (curved tabs)</li>
233
+ <li><code>1</code> - Alternative shape 1</li>
234
+ <li><code>2</code> - Alternative shape 2</li>
235
+ <li><code>3</code> - Straight edges (rectangular pieces)</li>
236
+ </ul>
237
+ </td>
238
+ </tr>
239
+ <tr>
240
+ <td><code>allowRotation</code></td>
241
+ <td>boolean</td>
242
+ <td><code>false</code></td>
243
+ <td>Whether pieces can be rotated by clicking/tapping (90° increments). Ignored if <code>savedData</code> is provided.</td>
244
+ </tr>
245
+ <tr>
246
+ <td><code>onReady</code></td>
247
+ <td>function</td>
248
+ <td><code>null</code></td>
249
+ <td>Callback function called when the puzzle is ready (image loaded and displayed). Use this to start the game.</td>
250
+ </tr>
251
+ <tr>
252
+ <td><code>onWin</code></td>
253
+ <td>function</td>
254
+ <td><code>null</code></td>
255
+ <td>Callback function called when the puzzle is completed</td>
256
+ </tr>
257
+ <tr>
258
+ <td><code>onStart</code></td>
259
+ <td>function</td>
260
+ <td><code>null</code></td>
261
+ <td>Callback function called when a game starts</td>
262
+ </tr>
263
+ <tr>
264
+ <td><code>onStop</code></td>
265
+ <td>function</td>
266
+ <td><code>null</code></td>
267
+ <td>Callback function called when a game is stopped</td>
268
+ </tr>
269
+ </tbody>
270
+ </table>
271
+
272
+ <h2>4. Event Callbacks</h2>
273
+
274
+ <p>All callbacks are optional and receive no parameters:</p>
275
+
276
+ <div class="method-section">
277
+ <h3><code>onReady()</code></h3>
278
+ <p>Called when the puzzle image has loaded and the puzzle is ready to start. This is the perfect time to call <code>puzzle.start()</code>.</p>
279
+ <div class="example-box">
280
+ <strong>Example:</strong>
281
+ <pre><code>onReady: () => {
282
+ console.log('Puzzle ready!');
283
+ puzzle.start();
284
+ }</code></pre>
285
+ </div>
286
+ </div>
287
+
288
+ <div class="method-section">
289
+ <h3><code>onStart()</code></h3>
290
+ <p>Called when a game actually starts (pieces are being created and distributed).</p>
291
+ </div>
292
+
293
+ <div class="method-section">
294
+ <h3><code>onWin()</code></h3>
295
+ <p>Called when the puzzle is completed (all pieces connected and in correct position).</p>
296
+ <div class="example-box">
297
+ <strong>Example:</strong>
298
+ <pre><code>onWin: () => {
299
+ alert('Congratulations! You solved the puzzle!');
300
+ // Could show a victory screen, update score, etc.
301
+ }</code></pre>
302
+ </div>
303
+ </div>
304
+
305
+ <div class="method-section">
306
+ <h3><code>onStop()</code></h3>
307
+ <p>Called when a game is stopped (when <code>stop()</code> is called).</p>
308
+ </div>
309
+
310
+ <h2>5. Public Methods</h2>
311
+
312
+ <div class="method-section">
313
+ <h3><code>start()</code></h3>
314
+ <p>Starts a new game with the current settings. Creates the puzzle pieces and distributes them.</p>
315
+ <div class="example-box">
316
+ <strong>Example:</strong>
317
+ <pre><code>puzzle.start();</code></pre>
318
+ </div>
319
+ <div class="warning-box">
320
+ <strong>Important:</strong> Call this only after the puzzle is ready (use the <code>onReady</code> callback).
321
+ </div>
322
+ </div>
323
+
324
+ <div class="method-section">
325
+ <h3><code>stop()</code></h3>
326
+ <p>Stops the current game and returns to the image preview state.</p>
327
+ <pre><code>puzzle.stop();</code></pre>
328
+ </div>
329
+
330
+ <div class="method-section">
331
+ <h3><code>reset()</code></h3>
332
+ <p>Resets the puzzle to initial state. Reloads the current image and prepares for a new game. Use this to start a new game with the same instance.</p>
333
+ <pre><code>puzzle.reset();
334
+ // Then wait for onReady callback and call puzzle.start()</code></pre>
335
+ </div>
336
+
337
+ <div class="method-section">
338
+ <h3><code>setImage(imageUrl)</code></h3>
339
+ <p>Sets a new image for the puzzle.</p>
340
+ <p><strong>Parameters:</strong></p>
341
+ <ul>
342
+ <li><code>imageUrl</code> (string) - URL or data URL of the image</li>
343
+ </ul>
344
+ <div class="example-box">
345
+ <strong>Example:</strong>
346
+ <pre><code>puzzle.setImage('https://example.com/new-image.jpg');
347
+ // Image will reload, wait for onReady callback</code></pre>
348
+ </div>
349
+ </div>
350
+
351
+ <div class="method-section">
352
+ <h3><code>setOptions(newOptions)</code></h3>
353
+ <p>Updates puzzle options without creating a new instance.</p>
354
+ <p><strong>Parameters:</strong></p>
355
+ <ul>
356
+ <li><code>newOptions</code> (object) - Object with options to update (partial updates allowed)</li>
357
+ </ul>
358
+ <div class="example-box">
359
+ <strong>Example:</strong>
360
+ <pre><code>puzzle.setOptions({
361
+ numPieces: 50,
362
+ allowRotation: true,
363
+ shapeType: 1
364
+ });</code></pre>
365
+ </div>
366
+ </div>
367
+
368
+ <div class="method-section">
369
+ <h3><code>save([callback])</code></h3>
370
+ <p>Saves the current game state. Gets the state data, converts it to a JSON string, then either calls the callback with the string or saves to localStorage.</p>
371
+ <p><strong>Parameters:</strong></p>
372
+ <ul>
373
+ <li><code>callback</code> (function, optional) - Function that receives the saved data as JSON string. If not provided, saves to localStorage automatically.</li>
374
+ </ul>
375
+ <div class="example-box">
376
+ <strong>Examples:</strong>
377
+ <pre><code>// Save to localStorage (default)
378
+ puzzle.save();
379
+
380
+ // Save with custom callback
381
+ puzzle.save((savedData) => {
382
+ // savedData is a JSON string
383
+ localStorage.setItem('myPuzzleSave', savedData);
384
+ // Or send to server, download as file, etc.
385
+ });</code></pre>
386
+ </div>
387
+ </div>
388
+
389
+ <div class="method-section">
390
+ <h3>Loading Saved Games</h3>
391
+ <p>To load a saved game, create a new puzzle instance with the <code>savedData</code> option in the constructor. The <code>load()</code> method has been removed in favor of constructor-based loading.</p>
392
+ <div class="example-box">
393
+ <strong>Examples:</strong>
394
+ <pre><code>// Load from explicit string
395
+ const savedString = localStorage.getItem('myPuzzleSave');
396
+ const puzzle = new JigsawPuzzle('puzzle-container', {
397
+ savedData: savedString,
398
+ onReady: () => puzzle.start()
399
+ });
400
+
401
+ // Load from localStorage automatically (pass empty string)
402
+ const puzzle = new JigsawPuzzle('puzzle-container', {
403
+ savedData: "", // Empty string triggers localStorage lookup
404
+ onReady: () => puzzle.start()
405
+ });
406
+
407
+ // Create new puzzle (no saved data)
408
+ const puzzle = new JigsawPuzzle('puzzle-container', {
409
+ image: 'image.jpg',
410
+ numPieces: 25,
411
+ onReady: () => puzzle.start()
412
+ });</code></pre>
413
+ </div>
414
+ <div class="warning-box">
415
+ <strong>Note:</strong> When loading a saved game, you must create a new instance. Destroy the old instance first if needed.
416
+ </div>
417
+ </div>
418
+
419
+ <div class="method-section">
420
+ <h3><code>destroy()</code></h3>
421
+ <p>Completely destroys the puzzle instance, cleaning up all resources. Use this when you want to remove the puzzle and create a new one in the same container.</p>
422
+ <div class="warning-box">
423
+ <strong>Note:</strong> After calling <code>destroy()</code>, you must create a new instance. Event listeners remain attached to the container, but this is usually fine for creating new instances.
424
+ </div>
425
+ <div class="example-box">
426
+ <strong>Example:</strong>
427
+ <pre><code>puzzle.destroy();
428
+ puzzle = new JigsawPuzzle('puzzle-container', {
429
+ image: 'new-image.jpg',
430
+ numPieces: 30
431
+ });</code></pre>
432
+ </div>
433
+ </div>
434
+
435
+ <h2>6. Starting a Game</h2>
436
+
437
+ <p>Here's the recommended pattern for starting a game:</p>
438
+
439
+ <div class="example-box">
440
+ <strong>Complete Example:</strong>
441
+ <pre><code>import { JigsawPuzzle } from './jigsaw-puzzle-game.js';
442
+
443
+ const puzzle = new JigsawPuzzle('puzzle-container', {
444
+ image: 'https://example.com/image.jpg',
445
+ numPieces: 20,
446
+ shapeType: 0,
447
+ allowRotation: false,
448
+ onReady: () => {
449
+ // Puzzle is ready, start the game
450
+ puzzle.start();
451
+ },
452
+ onWin: () => {
453
+ console.log('Puzzle solved!');
454
+ alert('Congratulations!');
455
+ },
456
+ onStart: () => {
457
+ console.log('Game started');
458
+ }
459
+ });</code></pre>
460
+ </div>
461
+
462
+ <p><strong>Important:</strong> Always use the <code>onReady</code> callback to start the game. This ensures the image is loaded before starting.</p>
463
+
464
+ <h2>7. Starting a New Game</h2>
465
+
466
+ <p>There are several ways to start a new game:</p>
467
+
468
+ <h3>Option 1: Load Saved Game</h3>
469
+ <p>To load a previously saved game, create a new instance with the <code>savedData</code> option:</p>
470
+ <pre><code>// Load from explicit string
471
+ const savedString = localStorage.getItem('puzzleSave');
472
+ puzzle = new JigsawPuzzle('puzzle-container', {
473
+ savedData: savedString,
474
+ onReady: () => puzzle.start()
475
+ });
476
+
477
+ // Or load from localStorage automatically
478
+ puzzle = new JigsawPuzzle('puzzle-container', {
479
+ savedData: "", // Empty string triggers localStorage lookup
480
+ onReady: () => puzzle.start()
481
+ });</code></pre>
482
+
483
+ <h3>Option 2: Reset and Reuse (Same Instance)</h3>
484
+ <p>If you want to restart with the same image and settings:</p>
485
+ <pre><code>puzzle.reset();
486
+ // The puzzle will reload the image
487
+ // Wait for onReady callback, then:
488
+ puzzle.start();</code></pre>
489
+
490
+ <h3>Option 3: Change Image and Reset</h3>
491
+ <p>If you want to change the image:</p>
492
+ <pre><code>puzzle.setImage('new-image.jpg');
493
+ puzzle.reset();
494
+ // Wait for onReady callback, then:
495
+ puzzle.start();</code></pre>
496
+
497
+ <h3>Option 4: Change Settings, Image, and Reset</h3>
498
+ <p>If you want to change multiple settings:</p>
499
+ <pre><code>puzzle.setImage('new-image.jpg');
500
+ puzzle.setOptions({
501
+ numPieces: 50,
502
+ shapeType: 1,
503
+ allowRotation: true
504
+ });
505
+ puzzle.reset();
506
+ // Wait for onReady callback, then:
507
+ puzzle.start();</code></pre>
508
+
509
+ <h3>Option 5: Destroy and Create New (Clean Slate)</h3>
510
+ <p>For a completely fresh start with potentially different options:</p>
511
+ <pre><code>puzzle.destroy();
512
+ puzzle = new JigsawPuzzle('puzzle-container', {
513
+ image: 'completely-different-image.jpg',
514
+ numPieces: 30,
515
+ shapeType: 2,
516
+ allowRotation: true,
517
+ onReady: () => {
518
+ puzzle.start();
519
+ }
520
+ });</code></pre>
521
+
522
+ <h2>8. User Interactions</h2>
523
+
524
+ <p>The puzzle supports various user interactions:</p>
525
+ <ul>
526
+ <li><strong>Mouse/Touch:</strong> Click and drag pieces to move them</li>
527
+ <li><strong>Rotation:</strong> If <code>allowRotation</code> is enabled, quick click/tap rotates pieces 90°</li>
528
+ <li><strong>Piece Merging:</strong> When pieces are close and correctly aligned, they automatically merge</li>
529
+ <li><strong>Pan:</strong> Click and drag on empty space to pan all pieces</li>
530
+ <li><strong>Zoom:</strong> Mouse wheel to zoom in/out, or pinch gesture on touch devices</li>
531
+ <li><strong>Two-finger Zoom:</strong> Use two fingers on touch devices to zoom</li>
532
+ </ul>
533
+
534
+ <h2>9. Styling</h2>
535
+
536
+ <p>The puzzle container should have a defined size. Full-screen example:</p>
537
+ <pre><code>#puzzle-container {
538
+ width: 100vw;
539
+ height: 100vh;
540
+ position: relative;
541
+ }</code></pre>
542
+
543
+ <p>The puzzle adds these CSS classes you can style:</p>
544
+ <ul>
545
+ <li><code>.polypiece</code> - Individual puzzle pieces</li>
546
+ <li><code>.polypiece.moving</code> - Pieces during animation</li>
547
+ <li><code>.gameCanvas</code> - Reference image canvas (hidden during play)</li>
548
+ </ul>
549
+
550
+ <h2>10. Browser Compatibility</h2>
551
+
552
+ <p>The puzzle requires modern browser features:</p>
553
+ <ul>
554
+ <li>ES6 Modules support</li>
555
+ <li>Canvas API</li>
556
+ <li>Path2D API</li>
557
+ <li>Touch events (for mobile support)</li>
558
+ </ul>
559
+
560
+ <p>Works in all modern browsers (Chrome, Firefox, Safari, Edge).</p>
561
+
562
+ <h2>11. Common Patterns</h2>
563
+
564
+ <h3>Complete Game with Save/Load</h3>
565
+ <pre><code>let puzzle;
566
+
567
+ function startNewGame(imageUrl, numPieces) {
568
+ if (puzzle) puzzle.destroy();
569
+
570
+ puzzle = new JigsawPuzzle('puzzle-container', {
571
+ image: imageUrl,
572
+ numPieces: numPieces,
573
+ allowRotation: false,
574
+ onReady: () => puzzle.start(),
575
+ onWin: () => {
576
+ alert('You won!');
577
+ // Optionally save completion, show next puzzle, etc.
578
+ }
579
+ });
580
+ }
581
+
582
+ // Save game
583
+ function saveGame() {
584
+ puzzle.save((data) => {
585
+ localStorage.setItem('puzzleSave', data);
586
+ console.log('Game saved!');
587
+ });
588
+ }
589
+
590
+ // Load game - create new instance with saved data
591
+ function loadGame() {
592
+ const saved = localStorage.getItem('puzzleSave');
593
+ if (saved) {
594
+ if (puzzle) puzzle.destroy();
595
+ puzzle = new JigsawPuzzle('puzzle-container', {
596
+ savedData: saved,
597
+ onReady: () => puzzle.start(),
598
+ onWin: () => {
599
+ alert('You won!');
600
+ }
601
+ });
602
+ }
603
+ }</code></pre>
604
+
605
+ <h2>12. Troubleshooting</h2>
606
+
607
+ <div class="warning-box">
608
+ <h3>Puzzle doesn't start</h3>
609
+ <ul>
610
+ <li>Make sure you're calling <code>start()</code> in the <code>onReady</code> callback</li>
611
+ <li>Check that the image URL is valid and accessible (CORS issues if loading from different domain)</li>
612
+ <li>Verify the container element exists and has a size</li>
613
+ </ul>
614
+ </div>
615
+
616
+ <div class="warning-box">
617
+ <h3>Image doesn't load</h3>
618
+ <ul>
619
+ <li>Check browser console for CORS errors</li>
620
+ <li>Use data URLs or images from the same domain</li>
621
+ <li>Ensure the image URL is correct</li>
622
+ </ul>
623
+ </div>
624
+
625
+ <div class="warning-box">
626
+ <h3>Pieces don't merge</h3>
627
+ <ul>
628
+ <li>Make sure pieces are close enough (the puzzle calculates optimal distance)</li>
629
+ <li>Check that pieces are in the same rotation if rotation is enabled</li>
630
+ <li>Verify pieces are correctly aligned</li>
631
+ </ul>
632
+ </div>
633
+
634
+ <hr>
635
+ <p style="text-align: center; color: #888; margin-top: 40px;">
636
+ JigsawPuzzle Class Documentation | Version 1.0.8
637
+ </p>
638
+
639
+ <hr style="margin: 40px 0;">
640
+ <div style="background-color: #f9f9f9; padding: 20px; border-radius: 5px; margin: 40px 0;">
641
+ <h2 style="margin-top: 0;">License</h2>
642
+ <pre style="background-color: #f4f4f4; color: #333; padding: 15px; border-radius: 5px; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; font-family: 'Courier New', monospace; line-height: 1.6;">Copyright (c) 2026 by Dillon (https://codepen.io/Dillo/pen/QWKLYab) & Henrik Rasmussen (https://www.raketten.net)
643
+
644
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
645
+
646
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
647
+
648
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</pre>
649
+ </div>
650
+ </body>
651
+ </html>
652
+
@@ -1362,8 +1362,11 @@ export class JigsawPuzzle {
1362
1362
  onReady: options.onReady || null,
1363
1363
  onWin: options.onWin || null,
1364
1364
  onStart: options.onStart || null,
1365
- onStop: options.onStop || null
1365
+ onStop: options.onStop || null,
1366
+ onMerged: options.onMerged || null,
1367
+ onMoved: options.onMoved || null,
1366
1368
  };
1369
+ console.log('Options2:', this.options);
1367
1370
 
1368
1371
  // Create internal puzzle instance
1369
1372
  this.puzzle = new InternalPuzzle(container);
@@ -1631,6 +1634,7 @@ export class JigsawPuzzle {
1631
1634
  } else return;
1632
1635
 
1633
1636
  case 20:
1637
+ console.log('state 20');
1634
1638
  this.playing = true;
1635
1639
  if (this.options.onStart) this.options.onStart();
1636
1640
  this.puzzle.rotationAllowed = this.options.allowRotation;
@@ -1663,6 +1667,7 @@ export class JigsawPuzzle {
1663
1667
  break;
1664
1668
 
1665
1669
  case 25:
1670
+ console.log('state 25');
1666
1671
  this.puzzle.gameCanvas.style.display = "none";
1667
1672
  this.puzzle.polyPieces.forEach((pp) => {
1668
1673
  pp.canvas.classList.add("moving");
@@ -1671,12 +1676,14 @@ export class JigsawPuzzle {
1671
1676
  break;
1672
1677
 
1673
1678
  case 30:
1679
+ console.log('state 30');
1674
1680
  this.puzzle.optimInitial();
1675
1681
  setTimeout(() => this.events.push({ event: "finished" }), 1200);
1676
1682
  this.state = 35;
1677
1683
  break;
1678
1684
 
1679
1685
  case 35:
1686
+ console.log('state 35');
1680
1687
  if (!event || event.event !== "finished") return;
1681
1688
  this.puzzle.polyPieces.forEach((pp) => {
1682
1689
  pp.canvas.classList.remove("moving");
@@ -1686,6 +1693,7 @@ export class JigsawPuzzle {
1686
1693
 
1687
1694
  case 50:
1688
1695
  if (!event) return;
1696
+ console.log('state 50',event.event);
1689
1697
  if (event.event === "stop") {
1690
1698
  this.state = 10;
1691
1699
  return;
@@ -1726,6 +1734,7 @@ export class JigsawPuzzle {
1726
1734
 
1727
1735
  case 55:
1728
1736
  if (!event) return;
1737
+ console.log('state 55-123',event.event);
1729
1738
  if (event.event === "stop") {
1730
1739
  this.state = 10;
1731
1740
  return;
@@ -1766,9 +1775,11 @@ export class JigsawPuzzle {
1766
1775
  merged = true;
1767
1776
  if (pp.pieces.length > this.moving.pp.pieces.length) {
1768
1777
  pp.merge(this.moving.pp);
1778
+ if (this.options.onMerged) this.options.onMerged(pp, this.moving.pp);
1769
1779
  this.moving.pp = pp;
1770
- } else {
1780
+ } else {
1771
1781
  this.moving.pp.merge(pp);
1782
+ if (this.options.onMerged) this.options.onMerged(this.moving.pp, pp);
1772
1783
  }
1773
1784
  doneSomething = true;
1774
1785
  break;
@@ -1780,13 +1791,14 @@ export class JigsawPuzzle {
1780
1791
  this.moving.pp.selected = true;
1781
1792
  this.moving.pp.drawImage(true);
1782
1793
  this.moving.tInit = tStamp + 500;
1783
- this.state = 56;
1794
+ this.state = 56;
1784
1795
  break;
1785
1796
  }
1786
1797
  this.state = 50;
1787
1798
  if (this.puzzle.polyPieces.length === 1 && this.puzzle.polyPieces[0].rot === 0) {
1788
1799
  this.state = 60;
1789
1800
  }
1801
+ if (this.options.onMoved) this.options.onMoved(this.moving.pp);
1790
1802
  }
1791
1803
  break;
1792
1804
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jigsawpuzzlegame",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "A class for creating a jigsaw puzzle",
5
5
  "keywords": [
6
6
  "jigsaw",