diff --git a/Games/Jigsaw_Puzzle/Readme.md b/Games/Jigsaw_Puzzle/Readme.md
new file mode 100644
index 0000000000..315381d415
--- /dev/null
+++ b/Games/Jigsaw_Puzzle/Readme.md
@@ -0,0 +1,35 @@
+# Classic Jigsaw Puzzle Game
+
+Welcome to the Classic Jigsaw Puzzle Game! This game allows you to upload any image and transform it into a jigsaw puzzle with varying difficulty levels. Whether you're looking for a quick challenge or a more intricate puzzle to solve, this game provides an enjoyable and interactive experience.
+
+## Game Features
+
+- **Image Upload:** Upload your own images to create personalized puzzles.
+- **Piece Selection:** Choose from 12, 25, 50, 100, or 200 pieces for different difficulty levels.
+- **Interactive Gameplay:** Drag and drop pieces to solve the puzzle.
+- **User-Friendly Interface:** Designed for a smooth and enjoyable user experience.
+- **Responsive Design:** Compatible with different devices, ensuring a great experience on both desktop and mobile.
+
+## Rules
+
+1. **Objective:** The goal is to assemble the puzzle pieces to form the complete image.
+2. **Piece Selection:** Select the number of pieces you want for your puzzle. The more pieces, the more challenging the puzzle.
+3. **Gameplay:** Drag and drop the pieces to their correct positions. Pieces will snap into place when they are correctly aligned.
+
+## How to Play
+
+1. **Upload Image:** Click on the "Upload Image" button to select an image from your device.
+2. **Select Pieces:** Choose the number of pieces for your puzzle (12, 25, 50, 100, 200).
+3. **Start Puzzle:** The image will be broken into pieces, and you can start solving the puzzle by dragging and dropping the pieces.
+4. **Solving:** Arrange the pieces to form the complete image. Pieces will snap into place when correctly positioned.
+5. **Complete Puzzle:** Once all pieces are correctly placed, the puzzle is complete, and you can admire your work!
+
+## Screenshot
+
+![image](/assets/images/Jigsaw_Puzzle_SS.png)
+
+## Enjoy the Game!
+
+We hope you have fun playing the Classic Jigsaw Puzzle Game. If you have any feedback or suggestions, feel free to share them with us. Happy puzzling!
+
+
diff --git a/Games/Jigsaw_Puzzle/index.html b/Games/Jigsaw_Puzzle/index.html
new file mode 100644
index 0000000000..bc5092d432
--- /dev/null
+++ b/Games/Jigsaw_Puzzle/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Games/Jigsaw_Puzzle/script.js b/Games/Jigsaw_Puzzle/script.js
new file mode 100644
index 0000000000..41d1fe3abb
--- /dev/null
+++ b/Games/Jigsaw_Puzzle/script.js
@@ -0,0 +1,1259 @@
+"use strict";
+
+let puzzle, autoStart;
+
+const mhypot = Math.hypot,
+ mrandom = Math.random,
+ mmax = Math.max,
+ mmin = Math.min,
+ mround = Math.round,
+ mfloor = Math.floor,
+ msqrt = Math.sqrt,
+ mabs = Math.abs;
+
+function isMiniature() {
+ return location.pathname.includes('/fullcpgrid/');
+}
+
+
+function alea(min, max) {
+ if (typeof max == 'undefined') return min * mrandom();
+ return min + (max - min) * mrandom();
+}
+
+
+function intAlea(min, max) {
+ if (typeof max == 'undefined') {
+ max = min; min = 0;
+ }
+ return mfloor(min + (max - min) * mrandom());
+}
+
+
+
+function arrayShuffle(array) {
+ let k1, temp;
+ for (let k = array.length - 1; k >= 1; --k) {
+ k1 = intAlea(0, k + 1);
+ temp = array[k];
+ array[k] = array[k1];
+ array[k1] = temp;
+ }
+ return array
+}
+
+class Point {
+ constructor(x, y) {
+ this.x = Number(x);
+ this.y = Number(y);
+ }
+ copy() {
+ return new Point(this.x, this.y);
+ }
+
+ distance(otherPoint) {
+ return mhypot(this.x - otherPoint.x, this.y - otherPoint.y);
+ }
+}
+
+class Segment {
+ constructor(p1, p2) {
+ this.p1 = new Point(p1.x, p1.y);
+ this.p2 = new Point(p2.x, p2.y);
+ }
+ dx() {
+ return this.p2.x - this.p1.x;
+ }
+ dy() {
+ return this.p2.y - this.p1.y;
+ }
+ length() {
+ return mhypot(this.dx(), this.dy());
+ }
+
+ pointOnRelative(coeff) {
+ let dx = this.dx();
+ let dy = this.dy();
+ return new Point(this.p1.x + coeff * dx, this.p1.y + coeff * dy);
+ }
+}
+
+class Side {
+ constructor() {
+ this.type = "";
+ this.points = [];
+ }
+
+ reversed() {
+ const ns = new Side();
+ ns.type = this.type;
+ ns.points = this.points.slice().reverse();
+ return ns;
+ }
+
+
+ scale(puzzle) {
+
+ const coefx = puzzle.scalex;
+ const coefy = puzzle.scaley;
+ this.scaledPoints = this.points.map(p => new Point(p.x * coefx, p.y * coefy));
+
+ }
+
+ drawPath(ctx, shiftx, shifty, withoutMoveTo) {
+
+ if (!withoutMoveTo) {
+ ctx.moveTo(this.scaledPoints[0].x + shiftx, this.scaledPoints[0].y + shifty);
+ }
+ if (this.type == "d") {
+ ctx.lineTo(this.scaledPoints[1].x + shiftx, this.scaledPoints[1].y + shifty);
+ } else {
+ for (let k = 1; k < this.scaledPoints.length - 1; k += 3) {
+ ctx.bezierCurveTo(this.scaledPoints[k].x + shiftx, this.scaledPoints[k].y + shifty,
+ this.scaledPoints[k + 1].x + shiftx, this.scaledPoints[k + 1].y + shifty,
+ this.scaledPoints[k + 2].x + shiftx, this.scaledPoints[k + 2].y + shifty);
+ }
+ }
+
+ }
+}
+
+
+function twist0(side, ca, cb) {
+
+ const seg0 = new Segment(side.points[0], side.points[1]);
+ const dxh = seg0.dx();
+ const dyh = seg0.dy();
+
+ const seg1 = new Segment(ca, cb);
+ const mid0 = seg0.pointOnRelative(0.5);
+ const mid1 = seg1.pointOnRelative(0.5);
+
+ const segMid = new Segment(mid0, mid1);
+ const dxv = segMid.dx();
+ const dyv = segMid.dy();
+
+ const scalex = alea(0.8, 1);
+ const scaley = alea(0.9, 1);
+ const mid = alea(0.45, 0.55);
+
+ const pa = pointAt(mid - 1 / 12 * scalex, 1 / 12 * scaley);
+ const pb = pointAt(mid - 2 / 12 * scalex, 3 / 12 * scaley);
+ const pc = pointAt(mid, 4 / 12 * scaley);
+ const pd = pointAt(mid + 2 / 12 * scalex, 3 / 12 * scaley);
+ const pe = pointAt(mid + 1 / 12 * scalex, 1 / 12 * scaley);
+
+ side.points = [seg0.p1,
+ new Point(seg0.p1.x + 5 / 12 * dxh * 0.52,
+ seg0.p1.y + 5 / 12 * dyh * 0.52),
+ new Point(pa.x - 1 / 12 * dxv * 0.72,
+ pa.y - 1 / 12 * dyv * 0.72),
+ pa,
+ new Point(pa.x + 1 / 12 * dxv * 0.72,
+ pa.y + 1 / 12 * dyv * 0.72),
+
+ new Point(pb.x - 1 / 12 * dxv * 0.92,
+ pb.y - 1 / 12 * dyv * 0.92),
+ pb,
+ new Point(pb.x + 1 / 12 * dxv * 0.52,
+ pb.y + 1 / 12 * dyv * 0.52),
+ new Point(pc.x - 2 / 12 * dxh * 0.40,
+ pc.y - 2 / 12 * dyh * 0.40),
+ pc,
+ new Point(pc.x + 2 / 12 * dxh * 0.40,
+ pc.y + 2 / 12 * dyh * 0.40),
+ new Point(pd.x + 1 / 12 * dxv * 0.52,
+ pd.y + 1 / 12 * dyv * 0.52),
+ pd,
+ new Point(pd.x - 1 / 12 * dxv * 0.92,
+ pd.y - 1 / 12 * dyv * 0.92),
+ new Point(pe.x + 1 / 12 * dxv * 0.72,
+ pe.y + 1 / 12 * dyv * 0.72),
+ pe,
+ new Point(pe.x - 1 / 12 * dxv * 0.72,
+ pe.y - 1 / 12 * dyv * 0.72),
+ new Point(seg0.p2.x - 5 / 12 * dxh * 0.52,
+ seg0.p2.y - 5 / 12 * dyh * 0.52),
+ seg0.p2];
+ side.type = "z";
+
+ function pointAt(coeffh, coeffv) {
+ return new Point(seg0.p1.x + coeffh * dxh + coeffv * dxv,
+ seg0.p1.y + coeffh * dyh + coeffv * dyv)
+ }
+
+}
+
+
+
+function twist1(side, ca, cb) {
+
+ const seg0 = new Segment(side.points[0], side.points[1]);
+ const dxh = seg0.dx();
+ const dyh = seg0.dy();
+
+ const seg1 = new Segment(ca, cb);
+ const mid0 = seg0.pointOnRelative(0.5);
+ const mid1 = seg1.pointOnRelative(0.5);
+
+ const segMid = new Segment(mid0, mid1);
+ const dxv = segMid.dx();
+ const dyv = segMid.dy();
+
+ const pa = pointAt(alea(0.3, 0.35), alea(-0.05, 0.05));
+ const pb = pointAt(alea(0.45, 0.55), alea(0.2, 0.3));
+ const pc = pointAt(alea(0.65, 0.78), alea(-0.05, 0.05));
+
+ side.points = [seg0.p1,
+ seg0.p1, pa, pa,
+ pa, pb, pb,
+ pb, pc, pc,
+ pc, seg0.p2, seg0.p2];
+ side.type = "z";
+
+ function pointAt(coeffh, coeffv) {
+ return new Point(seg0.p1.x + coeffh * dxh + coeffv * dxv,
+ seg0.p1.y + coeffh * dyh + coeffv * dyv)
+ }
+
+}
+
+
+
+function twist2(side, ca, cb) {
+
+ const seg0 = new Segment(side.points[0], side.points[1]);
+ const dxh = seg0.dx();
+ const dyh = seg0.dy();
+
+ const seg1 = new Segment(ca, cb);
+ const mid0 = seg0.pointOnRelative(0.5);
+ const mid1 = seg1.pointOnRelative(0.5);
+
+ const segMid = new Segment(mid0, mid1);
+ const dxv = segMid.dx();
+ const dyv = segMid.dy();
+
+ const hmid = alea(0.45, 0.55);
+ const vmid = alea(0.4, 0.5)
+ const pc = pointAt(hmid, vmid);
+ let sega = new Segment(seg0.p1, pc);
+
+ const pb = sega.pointOnRelative(2 / 3);
+ sega = new Segment(seg0.p2, pc);
+ const pd = sega.pointOnRelative(2 / 3);
+
+ side.points = [seg0.p1, pb, pd, seg0.p2];
+ side.type = "z";
+
+ function pointAt(coeffh, coeffv) {
+ return new Point(seg0.p1.x + coeffh * dxh + coeffv * dxv,
+ seg0.p1.y + coeffh * dyh + coeffv * dyv)
+ }
+
+}
+
+
+function twist3(side, ca, cb) {
+
+ side.points = [side.points[0], side.points[1]];
+
+}
+
+class Piece {
+ constructor(kx, ky) {
+ this.ts = new Side();
+ this.rs = new Side();
+ this.bs = new Side();
+ this.ls = new Side();
+ this.kx = kx;
+ this.ky = ky;
+ }
+
+ scale(puzzle) {
+ this.ts.scale(puzzle);
+ this.rs.scale(puzzle);
+ this.bs.scale(puzzle);
+ this.ls.scale(puzzle);
+ }
+}
+
+class PolyPiece {
+
+ constructor(initialPiece, puzzle) {
+ this.pckxmin = initialPiece.kx;
+ this.pckxmax = initialPiece.kx + 1;
+ this.pckymin = initialPiece.ky;
+ this.pckymax = initialPiece.ky + 1;
+ this.pieces = [initialPiece];
+ this.puzzle = puzzle;
+ this.listLoops();
+
+ this.canvas = document.createElement('CANVAS');
+ puzzle.container.appendChild(this.canvas);
+ this.canvas.classList.add('polypiece');
+ this.ctx = this.canvas.getContext("2d");
+ }
+
+
+ merge(otherPoly) {
+
+ const orgpckxmin = this.pckxmin;
+ const orgpckymin = this.pckymin;
+
+ const kOther = this.puzzle.polyPieces.indexOf(otherPoly);
+ this.puzzle.polyPieces.splice(kOther, 1);
+
+ this.puzzle.container.removeChild(otherPoly.canvas);
+
+ for (let k = 0; k < otherPoly.pieces.length; ++k) {
+ this.pieces.push(otherPoly.pieces[k]);
+
+ if (otherPoly.pieces[k].kx < this.pckxmin) this.pckxmin = otherPoly.pieces[k].kx;
+ if (otherPoly.pieces[k].kx + 1 > this.pckxmax) this.pckxmax = otherPoly.pieces[k].kx + 1;
+ if (otherPoly.pieces[k].ky < this.pckymin) this.pckymin = otherPoly.pieces[k].ky;
+ if (otherPoly.pieces[k].ky + 1 > this.pckymax) this.pckymax = otherPoly.pieces[k].ky + 1;
+ }
+
+
+ this.pieces.sort(function (p1, p2) {
+ if (p1.ky < p2.ky) return -1;
+ if (p1.ky > p2.ky) return 1;
+ if (p1.kx < p2.kx) return -1;
+ if (p1.kx > p2.kx) return 1;
+ return 0;
+ });
+
+
+ this.listLoops();
+
+ this.drawImage();
+ this.moveTo(this.x + this.puzzle.scalex * (this.pckxmin - orgpckxmin),
+ this.y + this.puzzle.scaley * (this.pckymin - orgpckymin));
+
+ this.puzzle.evaluateZIndex();
+ }
+
+
+ ifNear(otherPoly) {
+
+ let p1, p2;
+ let puzzle = this.puzzle;
+
+
+ let x = this.x - puzzle.scalex * this.pckxmin;
+ let y = this.y - puzzle.scaley * this.pckymin;
+
+ let ppx = otherPoly.x - puzzle.scalex * otherPoly.pckxmin;
+ let ppy = otherPoly.y - puzzle.scaley * otherPoly.pckymin;
+ if (mhypot(x - ppx, y - ppy) >= puzzle.dConnect) return false;
+
+
+ for (let k = this.pieces.length - 1; k >= 0; --k) {
+ p1 = this.pieces[k];
+ for (let ko = otherPoly.pieces.length - 1; ko >= 0; --ko) {
+ p2 = otherPoly.pieces[ko];
+ if (p1.kx == p2.kx && mabs(p1.ky - p2.ky) == 1) return true;
+ if (p1.ky == p2.ky && mabs(p1.kx - p2.kx) == 1) return true;
+ }
+ }
+
+
+
+ return false;
+
+ }
+
+
+ listLoops() {
+
+ const that = this;
+ function edgeIsCommon(kx, ky, edge) {
+ let k;
+ switch (edge) {
+ case 0: ky--; break;
+ case 1: kx++; break;
+ case 2: ky++; break;
+ case 3: kx--; break;
+ }
+ for (k = 0; k < that.pieces.length; k++) {
+ if (kx == that.pieces[k].kx && ky == that.pieces[k].ky) return true;
+ }
+ return false;
+ }
+
+
+ function edgeIsInTbEdges(kx, ky, edge) {
+ let k;
+ for (k = 0; k < tbEdges.length; k++) {
+ if (kx == tbEdges[k].kx && ky == tbEdges[k].ky && edge == tbEdges[k].edge) return k; // found it
+ }
+ return false;
+ }
+
+
+ let tbLoops = [];
+ let tbEdges = [];
+ let k;
+ let kEdge;
+ let lp;
+ let currEdge;
+ let tries;
+ let edgeNumber;
+ let potNext;
+
+
+ let tbTries = [
+ [
+ { dkx: 0, dky: 0, edge: 1 },
+ { dkx: 1, dky: 0, edge: 0 },
+ { dkx: 1, dky: -1, edge: 3 }
+ ],
+ [
+ { dkx: 0, dky: 0, edge: 2 },
+ { dkx: 0, dky: 1, edge: 1 },
+ { dkx: 1, dky: 1, edge: 0 }
+ ],
+ [
+ { dkx: 0, dky: 0, edge: 3 },
+ { dkx: - 1, dky: 0, edge: 2 },
+ { dkx: - 1, dky: 1, edge: 1 }
+ ],
+ [
+ { dkx: 0, dky: 0, edge: 0 },
+ { dkx: 0, dky: - 1, edge: 3 },
+ { dkx: - 1, dky: - 1, edge: 2 }
+ ],
+ ];
+
+
+ for (k = 0; k < this.pieces.length; k++) {
+ for (kEdge = 0; kEdge < 4; kEdge++) {
+ if (!edgeIsCommon(this.pieces[k].kx, this.pieces[k].ky, kEdge))
+ tbEdges.push({ kx: this.pieces[k].kx, ky: this.pieces[k].ky, edge: kEdge, kp: k })
+ }
+ }
+
+ while (tbEdges.length > 0) {
+ lp = [];
+ currEdge = tbEdges[0];
+ lp.push(currEdge);
+ tbEdges.splice(0, 1);
+ do {
+ for (tries = 0; tries < 3; tries++) {
+ potNext = tbTries[currEdge.edge][tries];
+ edgeNumber = edgeIsInTbEdges(currEdge.kx + potNext.dkx, currEdge.ky + potNext.dky, potNext.edge);
+ if (edgeNumber === false) continue;
+ currEdge = tbEdges[edgeNumber];
+ lp.push(currEdge);
+ tbEdges.splice(edgeNumber, 1);
+ break;
+ }
+ if (edgeNumber === false) break;
+ } while (1);
+ tbLoops.push(lp);
+ }
+
+
+ this.tbLoops = tbLoops.map(loop => loop.map(edge => {
+ let cell = this.pieces[edge.kp];
+ if (edge.edge == 0) return cell.ts;
+ if (edge.edge == 1) return cell.rs;
+ if (edge.edge == 2) return cell.bs;
+ return cell.ls;
+ }));
+ }
+
+
+ drawPath(ctx, shiftx, shifty) {
+
+
+ this.tbLoops.forEach(loop => {
+ let without = false;
+ loop.forEach(side => {
+ side.drawPath(ctx, shiftx, shifty, without);
+ without = true;
+ });
+ ctx.closePath();
+ });
+
+ }
+
+
+ drawImage() {
+
+ puzzle = this.puzzle;
+ this.nx = this.pckxmax - this.pckxmin + 1;
+ this.ny = this.pckymax - this.pckymin + 1;
+ this.canvas.width = this.nx * puzzle.scalex;
+ this.canvas.height = this.ny * puzzle.scaley;
+
+ this.offsx = (this.pckxmin - 0.5) * puzzle.scalex;
+ this.offsy = (this.pckymin - 0.5) * puzzle.scaley;
+
+ this.path = new Path2D();
+ this.drawPath(this.path, -this.offsx, -this.offsy);
+
+
+ this.ctx.fillStyle = 'none';
+ this.ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
+ this.ctx.shadowBlur = 4;
+ this.ctx.shadowOffsetX = 4;
+ this.ctx.shadowOffsetY = 4;
+ this.ctx.fill(this.path);
+ this.ctx.shadowColor = 'rgba(0, 0, 0, 0)';
+
+ this.pieces.forEach((pp, kk) => {
+
+ this.ctx.save();
+
+ const path = new Path2D();
+ const shiftx = -this.offsx;
+ const shifty = -this.offsy;
+ pp.ts.drawPath(path, shiftx, shifty, false);
+ pp.rs.drawPath(path, shiftx, shifty, true);
+ pp.bs.drawPath(path, shiftx, shifty, true);
+ pp.ls.drawPath(path, shiftx, shifty, true);
+ path.closePath();
+
+ this.ctx.clip(path);
+
+ const srcx = pp.kx ? ((pp.kx - 0.5) * puzzle.scalex) : 0;
+ const srcy = pp.ky ? ((pp.ky - 0.5) * puzzle.scaley) : 0;
+
+ const destx = (pp.kx ? 0 : puzzle.scalex / 2) + (pp.kx - this.pckxmin) * puzzle.scalex;
+ const desty = (pp.ky ? 0 : puzzle.scaley / 2) + (pp.ky - this.pckymin) * puzzle.scaley;
+
+ let w = 2 * puzzle.scalex;
+ let h = 2 * puzzle.scaley;
+ if (srcx + w > puzzle.gameCanvas.width) w = puzzle.gameCanvas.width - srcx;
+ if (srcy + h > puzzle.gameCanvas.height) h = puzzle.gameCanvas.height - srcy;
+
+ this.ctx.drawImage(puzzle.gameCanvas, srcx, srcy, w, h,
+ destx, desty, w, h);
+
+ this.ctx.translate(puzzle.embossThickness / 2, -puzzle.embossThickness / 2);
+ this.ctx.lineWidth = puzzle.embossThickness;
+ this.ctx.strokeStyle = "rgba(0, 0, 0, 0.35)";
+ this.ctx.stroke(path);
+
+ this.ctx.translate(-puzzle.embossThickness, puzzle.embossThickness);
+ this.ctx.strokeStyle = "rgba(255, 255, 255, 0.35)";
+ this.ctx.stroke(path);
+ this.ctx.restore();
+ });
+
+ }
+
+ moveTo(x, y) {
+
+ this.x = x;
+ this.y = y;
+ this.canvas.style.left = x + 'px';
+ this.canvas.style.top = y + 'px';
+ }
+
+ moveToInitialPlace() {
+ const puzzle = this.puzzle;
+ this.moveTo(puzzle.offsx + (this.pckxmin - 0.5) * puzzle.scalex,
+ puzzle.offsy + (this.pckymin - 0.5) * puzzle.scaley);
+ }
+
+}
+
+class Puzzle {
+
+
+ constructor(params) {
+
+ this.autoStart = false;
+
+ this.container = (typeof params.container == "string") ?
+ document.getElementById(params.container) :
+ params.container;
+
+
+ this.container.addEventListener("mousedown", event => {
+ event.preventDefault();
+ events.push({ event: 'touch', position: this.relativeMouseCoordinates(event) });
+ });
+ this.container.addEventListener("touchstart", event => {
+ event.preventDefault();
+ if (event.touches.length != 1) return;
+ let ev = event.touches[0];
+ events.push({ event: 'touch', position: this.relativeMouseCoordinates(ev) });
+ }, { passive: false });
+
+ this.container.addEventListener("mouseup", event => {
+ event.preventDefault();
+ handleLeave();
+ });
+ this.container.addEventListener("touchend", handleLeave);
+ this.container.addEventListener("touchleave", handleLeave);
+ this.container.addEventListener("touchcancel", handleLeave);
+
+ this.container.addEventListener("mousemove", event => {
+ event.preventDefault();
+
+ if (events.length && events[events.length - 1].event == "move") events.pop();
+ events.push({ event: 'move', position: this.relativeMouseCoordinates(event) })
+ });
+ this.container.addEventListener("touchmove", event => {
+ event.preventDefault();
+ if (event.touches.length != 1) return;
+ let ev = event.touches[0];
+ if (events.length && events[events.length - 1].event == "move") events.pop();
+ events.push({ event: 'move', position: this.relativeMouseCoordinates(ev) });
+ }, { passive: false });
+
+
+ this.gameCanvas = document.createElement('CANVAS');
+ this.container.appendChild(this.gameCanvas)
+
+ this.srcImage = new Image();
+ this.imageLoaded = false;
+ this.srcImage.addEventListener("load", () => imageLoaded(this));
+
+ function handleLeave() {
+ events.push({ event: 'leave' }); //
+ }
+
+ }
+
+ getContainerSize() {
+ let styl = window.getComputedStyle(this.container);
+
+ this.contWidth = parseFloat(styl.width);
+ this.contHeight = parseFloat(styl.height);
+ }
+
+ create() {
+
+ this.container.innerHTML = "";
+
+ this.getContainerSize();
+ this.computenxAndny();
+
+ this.relativeHeight = (this.srcImage.naturalHeight / this.ny) / (this.srcImage.naturalWidth / this.nx);
+
+ this.defineShapes({ coeffDecentr: 0.12, twistf: [twist0, twist1, twist2, twist3][document.getElementById("shape").value - 1] });
+
+ this.polyPieces = [];
+ this.pieces.forEach(row => row.forEach(piece => {
+ this.polyPieces.push(new PolyPiece(piece, this));
+ }));
+
+ arrayShuffle(this.polyPieces);
+ this.evaluateZIndex();
+
+ }
+
+
+
+ computenxAndny() {
+
+ let kx, ky, width = this.srcImage.naturalWidth, height = this.srcImage.naturalHeight, npieces = this.nbPieces;
+ let err, errmin = 1e9;
+ let ncv, nch;
+
+ let nHPieces = mround(msqrt(npieces * width / height));
+ let nVPieces = mround(npieces / nHPieces);
+
+
+ for (ky = 0; ky < 5; ky++) {
+ ncv = nVPieces + ky - 2;
+ for (kx = 0; kx < 5; kx++) {
+ nch = nHPieces + kx - 2;
+ err = nch * height / ncv / width;
+ err = (err + 1 / err) - 2;
+ err += mabs(1 - nch * ncv / npieces);
+
+ if (err < errmin) {
+ errmin = err;
+ this.nx = nch;
+ this.ny = ncv;
+ }
+ }
+ }
+
+ }
+
+
+ defineShapes(shapeDesc) {
+
+
+ let { coeffDecentr, twistf } = shapeDesc;
+
+ const corners = [];
+ const nx = this.nx, ny = this.ny;
+ let np;
+
+ for (let ky = 0; ky <= ny; ++ky) {
+ corners[ky] = [];
+ for (let kx = 0; kx <= nx; ++kx) {
+ corners[ky][kx] = new Point(kx + alea(-coeffDecentr, coeffDecentr),
+ ky + alea(-coeffDecentr, coeffDecentr));
+ if (kx == 0) corners[ky][kx].x = 0;
+ if (kx == nx) corners[ky][kx].x = nx;
+ if (ky == 0) corners[ky][kx].y = 0;
+ if (ky == ny) corners[ky][kx].y = ny;
+ }
+ }
+
+ this.pieces = [];
+ for (let ky = 0; ky < ny; ++ky) {
+ this.pieces[ky] = [];
+ for (let kx = 0; kx < nx; ++kx) {
+ this.pieces[ky][kx] = np = new Piece(kx, ky);
+ if (ky == 0) {
+ np.ts.points = [corners[ky][kx], corners[ky][kx + 1]];
+ np.ts.type = "d";
+ } else {
+ np.ts = this.pieces[ky - 1][kx].bs.reversed();
+ }
+ np.rs.points = [corners[ky][kx + 1], corners[ky + 1][kx + 1]];
+ np.rs.type = "d";
+ if (kx < nx - 1) {
+ if (intAlea(2))
+ twistf(np.rs, corners[ky][kx], corners[ky + 1][kx]);
+ else
+ twistf(np.rs, corners[ky][kx + 2], corners[ky + 1][kx + 2]);
+ }
+ if (kx == 0) {
+ np.ls.points = [corners[ky + 1][kx], corners[ky][kx]];
+ np.ls.type = "d";
+ } else {
+ np.ls = this.pieces[ky][kx - 1].rs.reversed()
+ }
+ np.bs.points = [corners[ky + 1][kx + 1], corners[ky + 1][kx]];
+ np.bs.type = "d";
+ if (ky < ny - 1) {
+ if (intAlea(2))
+ twistf(np.bs, corners[ky][kx + 1], corners[ky][kx]);
+ else
+ twistf(np.bs, corners[ky + 2][kx + 1], corners[ky + 2][kx]);
+ }
+ }
+ }
+
+ }
+
+
+ scale() {
+
+
+ const maxWidth = 0.95 * this.contWidth;
+ const maxHeight = 0.95 * this.contHeight;
+
+ this.gameHeight = maxHeight;
+ this.gameWidth = this.gameHeight * this.srcImage.naturalWidth / this.srcImage.naturalHeight;
+
+ if (this.gameWidth > maxWidth) {
+ this.gameWidth = maxWidth;
+ this.gameHeight = this.gameWidth * this.srcImage.naturalHeight / this.srcImage.naturalWidth;
+ }
+
+ this.gameCanvas.width = this.gameWidth;
+ this.gameCanvas.height = this.gameHeight;
+ this.gameCtx = this.gameCanvas.getContext("2d");
+ this.gameCtx.drawImage(this.srcImage, 0, 0, this.gameWidth, this.gameHeight);
+
+ this.gameCanvas.classList.add("gameCanvas");
+ this.gameCanvas.style.zIndex = 500;
+
+ this.scalex = this.gameWidth / this.nx;
+ this.scaley = this.gameHeight / this.ny;
+
+ this.pieces.forEach(row => {
+ row.forEach(piece => piece.scale(this));
+ });
+
+ this.offsx = (this.contWidth - this.gameWidth) / 2;
+ this.offsy = (this.contHeight - this.gameHeight) / 2;
+
+
+ this.dConnect = mmax(10, mmin(this.scalex, this.scaley) / 10);
+
+ this.embossThickness = mmin(2 + this.scalex / 200 * (5 - 2), 5);
+
+ }
+
+ relativeMouseCoordinates(event) {
+
+
+ const br = this.container.getBoundingClientRect();
+ return {
+ x: event.clientX - br.x,
+ y: event.clientY - br.y
+ };
+ }
+
+ limitRectangle(rect) {
+
+
+ rect.x0 = mmin(mmax(rect.x0, -this.scalex / 2), this.contWidth - 1.5 * this.scalex);
+ rect.x1 = mmin(mmax(rect.x1, -this.scalex / 2), this.contWidth - 1.5 * this.scalex);
+ rect.y0 = mmin(mmax(rect.y0, -this.scaley / 2), this.contHeight - 1.5 * this.scaley);
+ rect.y1 = mmin(mmax(rect.y1, -this.scaley / 2), this.contHeight - 1.5 * this.scaley);
+ }
+
+ spreadInRectangle(rect) {
+ this.limitRectangle(rect);
+ this.polyPieces.forEach(pp =>
+ pp.moveTo(alea(rect.x0, rect.x1), alea(rect.y0, rect.y1))
+ );
+ }
+
+ spreadSetInRectangle(set, rect) {
+ this.limitRectangle(rect);
+ set.forEach(pp =>
+ pp.moveTo(alea(rect.x0, rect.x1), alea(rect.y0, rect.y1))
+ );
+ }
+
+ optimInitial() {
+
+ const minx = -this.scalex / 2;
+ const miny = -this.scaley / 2;
+ const maxx = this.contWidth - 1.5 * this.scalex;
+ const maxy = this.contHeight - 1.5 * this.scaley;
+
+ let freex = this.contWidth - this.gameWidth;
+ let freey = this.contHeight - this.gameHeight;
+
+ let where = [0, 0, 0, 0];
+ let rects = [];
+ if (freex > 1.5 * this.scalex) {
+ where[1] = 1;
+ rects[1] = {
+ x0: this.gameWidth - 0.5 * this.scalex,
+ x1: maxx,
+ y0: miny, y1: maxy
+ };
+ }
+ if (freex > 3 * this.scalex) {
+ where[3] = 1;
+ rects[3] = {
+ x0: minx,
+ x1: freex / 2 - 1.5 * this.scalex,
+ y0: miny, y1: maxy
+ };
+ rects[1].x0 = this.contWidth - freex / 2 - 0.5 * this.scalex;
+ }
+ if (freey > 1.5 * this.scaley) {
+ where[2] = 1;
+ rects[2] = {
+ x0: minx, x1: maxx,
+ y0: this.gameHeight - 0.5 * this.scaley,
+ y1: this.contHeight - 1.5 * this.scaley
+ };
+ }
+ if (freey > 3 * this.scaley) {
+ where[0] = 1;
+ rects[0] = {
+ x0: minx, x1: maxx,
+ y0: miny,
+ y1: freey / 2 - 1.5 * this.scaley
+ };
+ rects[2].y0 = this.contHeight - freey / 2 - 0.5 * this.scaley;
+ }
+ if (where.reduce((sum, a) => sum + a) < 2) {
+
+ if (freex - freey > 0.2 * this.scalex || where[1]) {
+
+ this.spreadInRectangle({
+ x0: this.gameWidth - this.scalex / 2,
+ x1: maxx,
+ y0: miny,
+ y1: maxy
+ });
+ } else if (freey - freex > 0.2 * this.scalex || where[2]) {
+
+ this.spreadInRectangle({
+ x0: minx,
+ x1: maxx,
+ y0: this.gameHeight - this.scaley / 2,
+ y1: maxy
+ });
+ } else {
+ if (this.gameWidth > this.gameHeight) {
+
+ this.spreadInRectangle({
+ x0: minx,
+ x1: maxx,
+ y0: this.gameHeight - this.scaley / 2,
+ y1: maxy
+ });
+
+ } else {
+ this.spreadInRectangle({
+ x0: this.gameWidth - this.scalex / 2,
+ x1: maxx,
+ y0: miny,
+ y1: maxy
+ });
+ }
+ }
+ return;
+ }
+
+ let nrects = [];
+ rects.forEach(rect => {
+ nrects.push(rect);
+ });
+ let k0 = 0
+ const npTot = this.nx * this.ny;
+ for (let k = 0; k < nrects.length; ++k) {
+ let k1 = mround((k + 1) / nrects.length * npTot);
+ this.spreadSetInRectangle(this.polyPieces.slice(k0, k1), nrects[k]);
+ k0 = k1;
+ }
+ arrayShuffle(this.polyPieces);
+ this.evaluateZIndex();
+
+ }
+
+ evaluateZIndex() {
+
+
+ for (let k = this.polyPieces.length - 1; k > 0; --k) {
+ if (this.polyPieces[k].pieces.length > this.polyPieces[k - 1].pieces.length) {
+
+ [this.polyPieces[k], this.polyPieces[k - 1]] = [this.polyPieces[k - 1], this.polyPieces[k]];
+ }
+ }
+
+ this.polyPieces.forEach((pp, k) => {
+ pp.canvas.style.zIndex = k + 10;
+ });
+ this.zIndexSup = this.polyPieces.length + 10;
+ }
+}
+
+
+let loadFile;
+{
+
+ let options;
+
+ let elFile = document.createElement('input');
+ elFile.setAttribute('type', 'file');
+ elFile.style.display = 'none';
+ elFile.addEventListener("change", getFile);
+
+ function getFile() {
+
+ if (this.files.length == 0) {
+
+ return;
+ }
+ let file = this.files[0];
+ let reader = new FileReader();
+
+ reader.addEventListener('load', () => {
+ puzzle.srcImage.src = reader.result;
+ });
+ reader.readAsDataURL(this.files[0]);
+
+ }
+
+ loadFile = function (ooptions) {
+ elFile.setAttribute("accept", "image/*");
+ elFile.value = null;
+ elFile.click();
+
+ }
+}
+
+function loadInitialFile() {
+ puzzle.srcImage.src = "/assets/images/JigsawPuzzle.jpg";
+}
+
+function imageLoaded(puzzle) {
+ events.push({ event: "srcImageLoaded" });
+ puzzle.imageLoaded = true;
+
+}
+
+function fitImage(img, width, height) {
+
+
+ let wn = img.naturalWidth;
+ let hn = img.naturalHeight;
+ let w = width;
+ let h = w * hn / wn;
+ if (h > height) {
+ h = height;
+ w = h * wn / hn;
+ }
+ img.style.position = "absolute";
+ img.style.width = w + "px";
+ img.style.height = h + "px";
+ img.style.top = "50%";
+ img.style.left = "50%";
+ img.style.transform = "translate(-50%,-50%)";
+}
+
+let animate;
+let events = [];
+
+{
+ let state = 0;
+ let moving;
+ let tmpImage;
+
+ animate = function () {
+ requestAnimationFrame(animate);
+
+ let event;
+ if (events.length) event = events.shift();
+ if (event && event.event == "reset") state = 0;
+ if (event && event.event == "srcImageLoaded") state = 0;
+
+ if (event && event.event == "resize") {
+
+ puzzle.prevWidth = puzzle.contWidth;
+ puzzle.prevHeight = puzzle.contHeight;
+ puzzle.getContainerSize();
+ if (state == 15 || state > 60) {
+ puzzle.getContainerSize();
+ fitImage(tmpImage, puzzle.contWidth * 0.95, puzzle.contHeight * 0.95);
+ }
+ else if (state >= 25) {
+ puzzle.prevGameWidth = puzzle.gameWidth;
+ puzzle.prevGameHeight = puzzle.gameHeight;
+ puzzle.scale();
+ let reScale = puzzle.contWidth / puzzle.prevWidth;
+ puzzle.polyPieces.forEach(pp => {
+
+ let nx = puzzle.contWidth / 2 - (puzzle.prevWidth / 2 - pp.x) * reScale;
+ let ny = puzzle.contHeight / 2 - (puzzle.prevHeight / 2 - pp.y) * reScale;
+
+ nx = mmin(mmax(nx, -puzzle.scalex / 2), puzzle.contWidth - 1.5 * puzzle.scalex);
+ ny = mmin(mmax(ny, -puzzle.scaley / 2), puzzle.contHeight - 1.5 * puzzle.scaley);
+
+ pp.moveTo(nx, ny);
+ pp.drawImage();
+
+ });
+ }
+
+ return;
+ }
+
+ switch (state) {
+
+ case 0:
+ state = 10;
+ break;
+
+ case 10:
+ if (!puzzle.imageLoaded) return;
+
+ puzzle.container.innerHTML = "";
+ tmpImage = document.createElement("img");
+ tmpImage.src = puzzle.srcImage.src;
+ puzzle.getContainerSize();
+ fitImage(tmpImage, puzzle.contWidth * 0.95, puzzle.contHeight * 0.95);
+ tmpImage.style.boxShadow = "4px 4px 4px rgba(0, 0, 0, 0.5)";
+ puzzle.container.appendChild(tmpImage);
+ state = 15;
+ break;
+
+
+ case 15:
+ if (autoStart) event = { event: "nbpieces", nbpieces: 12 };
+ autoStart = false;
+ if (!event) return;
+ if (event.event == "nbpieces") {
+ puzzle.nbPieces = event.nbpieces;
+ state = 20;
+ } else if (event.event == "srcImageLoaded") {
+ state = 10;
+ return;
+ } else return;
+
+ case 20:
+ menu.close();
+ puzzle.create();
+ puzzle.scale();
+ puzzle.polyPieces.forEach(pp => {
+ pp.drawImage();
+ pp.moveToInitialPlace();
+ });
+ puzzle.gameCanvas.style.top = puzzle.offsy + "px";
+ puzzle.gameCanvas.style.left = puzzle.offsx + "px";
+ puzzle.gameCanvas.style.display = "block";
+ state = 25;
+ break;
+
+ case 25:
+ puzzle.gameCanvas.style.display = "none";
+ puzzle.polyPieces.forEach(pp => {
+ pp.canvas.classList.add("moving");
+ });
+ state = 30;
+ break;
+
+ case 30:
+ puzzle.optimInitial();
+
+
+ setTimeout(() => events.push({ event: "finished" }), 1200);
+ state = 35;
+ break;
+
+ case 35:
+ if (!event || event.event != "finished") return;
+ puzzle.polyPieces.forEach(pp => {
+ pp.canvas.classList.remove("moving");
+ });
+
+ state = 50;
+
+ break;
+
+ case 50:
+ if (!event) return;
+ if (event.event == "nbpieces") {
+ puzzle.nbPieces = event.nbpieces;
+ state = 20;
+ return;
+ }
+ if (event.event != "touch") return;
+ moving = {
+ xMouseInit: event.position.x,
+ yMouseInit: event.position.y
+ }
+
+
+ for (let k = puzzle.polyPieces.length - 1; k >= 0; --k) {
+ let pp = puzzle.polyPieces[k];
+ if (pp.ctx.isPointInPath(pp.path, event.position.x - pp.x, event.position.y - pp.y)) {
+ moving.pp = pp;
+ moving.ppXInit = pp.x;
+ moving.ppYInit = pp.y;
+
+ puzzle.polyPieces.splice(k, 1);
+ puzzle.polyPieces.push(pp);
+ pp.canvas.style.zIndex = puzzle.zIndexSup;
+ state = 55;
+ return;
+ }
+
+ }
+ break;
+
+ case 55:
+ if (!event) return;
+ switch (event.event) {
+ case "move":
+ moving.pp.moveTo(event.position.x - moving.xMouseInit + moving.ppXInit,
+ event.position.y - moving.yMouseInit + moving.ppYInit);
+ break;
+ case "leave":
+
+ let doneSomething;
+ do {
+ doneSomething = false;
+ for (let k = puzzle.polyPieces.length - 1; k >= 0; --k) {
+ let pp = puzzle.polyPieces[k];
+ if (pp == moving.pp) continue;
+ if (moving.pp.ifNear(pp)) {
+
+ if (pp.pieces.length > moving.pp.pieces.length) {
+ pp.merge(moving.pp);
+ moving.pp = pp;
+ } else {
+ moving.pp.merge(pp);
+ }
+ doneSomething = true;
+ break;
+ }
+ }
+
+ } while (doneSomething);
+
+ puzzle.evaluateZIndex();
+ state = 50;
+ if (puzzle.polyPieces.length == 1) state = 60; // won!
+ return;
+ }
+
+ break;
+
+ case 60:
+ puzzle.container.innerHTML = "";
+ puzzle.getContainerSize();
+ fitImage(tmpImage, puzzle.contWidth * 0.95, puzzle.contHeight * 0.95);
+ tmpImage.style.boxShadow = "4px 4px 4px rgba(0, 0, 0, 0.5)";
+
+ tmpImage.style.left = (puzzle.polyPieces[0].x + puzzle.scalex / 2 + puzzle.gameWidth / 2) / puzzle.contWidth * 100 + "%";
+ tmpImage.style.top = (puzzle.polyPieces[0].y + puzzle.scaley / 2 + puzzle.gameHeight / 2) / puzzle.contHeight * 100 + "%";
+
+ tmpImage.classList.add("moving");
+ setTimeout(() => tmpImage.style.top = tmpImage.style.left = "50%", 0);
+ puzzle.container.appendChild(tmpImage);
+ state = 65;
+ menu.open();
+
+ case 65:
+ if (event && event.event == "nbpieces") {
+ puzzle.nbPieces = event.nbpieces;
+ state = 20;
+ return;
+ }
+ break;
+
+ case 9999: break;
+ default:
+ let st = state;
+ state = 9999;
+ throw ("oops, unknown state " + st);
+ }
+ }
+}
+
+let menu = (function () {
+ let menu = { items: [] };
+ document.querySelectorAll("#menu li").forEach(menuEl => {
+ let kItem = menu.items.length;
+ let item = { element: menuEl, kItem: kItem };
+ menu.items[kItem] = item;
+
+ });
+
+ menu.open = function () {
+ menu.items.forEach(item => item.element.style.display = "block");
+ menu.opened = true;
+ }
+ menu.close = function () {
+ menu.items.forEach((item, k) => {
+ if (k > 0) item.element.style.display = "none";
+ });
+ menu.opened = false;
+ }
+ menu.items[0].element.addEventListener("click", () => {
+ if (menu.opened) menu.close(); else menu.open()
+ });
+ menu.items[1].element.addEventListener("click", loadInitialFile);
+ menu.items[2].element.addEventListener("click", loadFile);
+ menu.items[3].element.addEventListener("click", () => { });
+ for (let k = 4; k < menu.items.length; ++k) {
+ menu.items[k].element.addEventListener("click", () => events.push({ event: "nbpieces", nbpieces: [12, 25, 50, 100, 200][k - 4] }));
+ }
+ return menu;
+})();
+
+menu.close();
+
+window.addEventListener("resize", event => {
+ if (events.length && events[events.length - 1].event == "resize") return;;
+ events.push({ event: "resize" });
+});
+
+puzzle = new Puzzle({ container: "forPuzzle" });
+autoStart = isMiniature();
+
+loadInitialFile();
+requestAnimationFrame(animate);
diff --git a/Games/Jigsaw_Puzzle/style.css b/Games/Jigsaw_Puzzle/style.css
new file mode 100644
index 0000000000..a385c165d0
--- /dev/null
+++ b/Games/Jigsaw_Puzzle/style.css
@@ -0,0 +1,58 @@
+body {
+ font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif;
+ background-color: #fff;
+ margin: 0;
+ padding: 0;
+ border-width: 0;
+ cursor: pointer;
+ }
+
+ #menu {
+ position: relative;
+ list-style-type: none;
+ padding-left: 5px;
+ z-index: 1000;
+ /* 1 */
+ display: inline-block;
+ text-align: center;
+ }
+
+ #menu li {
+ margin: 2px;
+ padding: 4px 10px;
+ border-radius: 5px;
+ background-color: #ffd580;
+ }
+
+ #menu li:hover {
+ background-color: #ffDD60;
+ }
+
+ #forPuzzle {
+ position: absolute;
+ width: 95vw;
+ height: 95vh;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: #ffffdd;
+ overflow: hidden;
+ }
+
+ .polypiece {
+ display: block;
+ overflow: hidden;
+ position: absolute;
+ }
+
+ .moving {
+ transition-property: top, left;
+ transition-duration: 1s;
+ transition-timing-function: linear;
+ }
+
+ .gameCanvas {
+ display: none;
+ overflow: hidden;
+ position: absolute;
+ }
diff --git a/README.md b/README.md
index 615b55181d..96c64efc1f 100644
--- a/README.md
+++ b/README.md
@@ -823,6 +823,8 @@ This repository also provides one such platforms where contributers come over an
|[Math_Race_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Math_Race_Game)|
| [Bunny is Lost](https://github.com/kunjgit/GameZone/tree/main/Games/Bunny_is_Lost)|
|[Steam_Punk](https://github.com/kunjgit/GameZone/tree/main/Games/Steam_Punk)|
+|[Tower Defence Game](https://github.com/Will2Jacks/GameZoneForked/tree/Task/Games/Tower_Defence_Game)|
+| [Jigsaw_Puzzle](https://github.com/kunjgit/GameZone/tree/main/Games/Jigsaw_Puzzle) |
| [Mathematics Escape Room](https://github.com/kunjgit/GameZone/tree/main/Games/MathematicsEscapeRoom) |
|[Tower Defence Game](https://github.com/Will2Jacks/GameZoneForked/tree/Task/Games/Tower_Defence_Game) | | [Ant Smasher Game](https://github.com/kunjgit/GameZone/tree/main/Games/Ant_Smasher)|
|[Mario Matching Game](https://github.com/ananyag309/GameZone_ggsoc/tree/main/Games/MarioMatchingGame)|
diff --git a/assets/images/JigsawPuzzle.jpg b/assets/images/JigsawPuzzle.jpg
new file mode 100644
index 0000000000..8bee34f48a
Binary files /dev/null and b/assets/images/JigsawPuzzle.jpg differ
diff --git a/assets/images/Jigsaw_Puzzle_SS.png b/assets/images/Jigsaw_Puzzle_SS.png
new file mode 100644
index 0000000000..a968b1851f
Binary files /dev/null and b/assets/images/Jigsaw_Puzzle_SS.png differ