/*********************************************************************************** JigsawPuzzle.js Initializes a puzzle and canvas and handles logic behind creating and updating the puzzle using puzzle, piece, and edge classes. Event handlers are in Events.js file. ***********************************************************************************/ // canvas variables var canvas; var backCanvas; var ctx; var backCtx; var puzzle; // picks puzzle image and initializes puzzle var puzzleImg = new Image(); // random between 0-9 var pickImg = Math.floor(Math.random() * 9); if (pickImg == 0) { imgPath = 'img/puzzle0.jpg'; } else if (pickImg == 1) { imgPath = 'img/puzzle1.jpg'; } else if (pickImg == 2) { imgPath = 'img/puzzle2.jpg'; } else if (pickImg == 3) { imgPath = 'img/puzzle3.jpg'; } else if (pickImg == 4) { imgPath = 'img/puzzle4.jpg'; } else if (pickImg == 5) { imgPath = 'img/puzzle5.jpg'; } else if (pickImg == 6) { imgPath = 'img/puzzle6.jpg'; } else if (pickImg == 7) { imgPath = 'img/puzzle7.jpg'; } else if (pickImg == 8) { imgPath = 'img/puzzle8.jpg'; } puzzleImg.src = imgPath; puzzleImg.onload = function () { initialize(); }; // have separate image for background ghost image so opacity can be easily modified var ghostImg = new Image(); ghostImg.src = imgPath; /*********************************************************************** initialize - Initializes a puzzle including size, canvas info, event setup and an initial drawing of puzzle on canvas. ************************************************************************/ function initialize() { console.log("Entering - initialize"); // initialize canvas info canvas = document.getElementById('front canvas'); backCanvas = document.createElement('canvas'); initCanvasSize(); ctx = canvas.getContext('2d'); backCtx = backCanvas.getContext('2d'); setupEvents(); let piece = []; // initialize piece info let numPieces = puzzle.numPieces; for (let i = 0; i < numPieces; i++) { // top left of piece in solved position let finalX = puzzle.pieceW * (i % puzzle.numCols) + puzzle.x; let finalY = puzzle.pieceH * (Math.floor(i / puzzle.numCols)) + puzzle.y; // pick random starting location (avoiding going off edge of canvas) let shuffleX = Math.random() * (canvas.width - puzzle.pieceW); let shuffleY = Math.random() * (canvas.height - puzzle.pieceH); // don't allow starting location (of midpoint of piece) to be on the puzzle while (coordOnPuzzle(shuffleX, shuffleY)) { shuffleX = Math.random() * (canvas.width - puzzle.pieceW) + (.5 * puzzle.pieceW); shuffleY = Math.random() * (canvas.height - puzzle.pieceH) + (.5 * puzzle.pieceH); } shuffleX -= (.5 * puzzle.pieceW); shuffleY -= (.5 * puzzle.pieceH); let left = determineEdgeType(i, 'left'); let right = determineEdgeType(i, 'right'); let top = determineEdgeType(i, 'top'); let bottom = determineEdgeType(i, 'bottom'); // random between 0-3 let orientation = Math.floor(Math.random() * 4); piece[i] = new Piece(i, shuffleX, shuffleY, finalX, finalY, left, right, top, bottom, orientation); //[i].printInfo(); (puzzle.pieces).push(piece[i]); determineEdgeInfo(piece[i], 'left'); determineEdgeInfo(piece[i], 'right'); determineEdgeInfo(piece[i], 'top'); determineEdgeInfo(piece[i], 'bottom'); //console.log("Piece " + i + " TOP: KeyLoc:" + piece[i].top.keyLoc + " keySize:" + piece[i].top.keySize + " curve:" + piece[i].top.curveStartX + " " + piece[i].top.curveStartY + " " + piece[i].top.curveEndX + " " + piece[i].top.curveEndY); //console.log("Piece " + i + " RIGHT: KeyLoc:" + piece[i].right.keyLoc + " keySize:" + piece[i].right.keySize + " curve:" + piece[i].right.curveStartX + " " + piece[i].right.curveStartY + " " + piece[i].right.curveEndX + " " + piece[i].right.curveEndY); //console.log("Piece " + i + " BOTTOM: KeyLoc:" + piece[i].bottom.keyLoc + " keySize:" + piece[i].bottom.keySize + " curve:" + piece[i].bottom.curveStartX + " " + piece[i].bottom.curveStartY + " " + piece[i].bottom.curveEndX + " " + piece[i].bottom.curveEndY); //console.log("Piece " + i + " LEFT: KeyLoc:" + piece[i].left.keyLoc + " keySize:" + piece[i].left.keySize + " curve:" + piece[i].left.curveStartX + " " + piece[i].left.curveStartY + " " + piece[i].left.curveEndX + " " + piece[i].left.curveEndY); } redrawOnFrontCanvas(); // initialize timer startTime = Math.floor(Date.now() / 1000); updateTime(); }; /*********************************************************************** coordOnPuzzle - Checks if a given coordinate is within the bounds of the current puzzle. Parameters: coordinate to look at Returns: boolean value of whether coordinate is on puzzle or not ************************************************************************/ function coordOnPuzzle(x, y) { if (x > puzzle.x && x < (puzzle.x + puzzle.width) && y > puzzle.y && y < (puzzle.y + puzzle.height)) { return true; } return false; }; /*********************************************************************** newPuzzleSize - Handles change in the number of rows or columns in the puzzle. Determines new piece info, sets up events, and draws the puzzle. ************************************************************************/ function newPuzzleSize() { console.log("Entering - newPuzzleSize"); setupEvents(); let pieces = []; puzzle.pieces = []; puzzle.numPieces = puzzle.numRows * puzzle.numCols; puzzle.pieceW = puzzle.width / puzzle.numCols; puzzle.pieceH = puzzle.height / puzzle.numRows; // initialize info for each piece let numPieces = puzzle.numPieces; for (let i = 0; i < numPieces; i++) { // top left solved coordinate let finalX = puzzle.pieceW * (i % puzzle.numCols) + puzzle.x; let finalY = puzzle.pieceH * (Math.floor(i / puzzle.numCols)) + puzzle.y; let shuffleX = Math.random() * (canvas.width - puzzle.pieceW); let shuffleY = Math.random() * (canvas.height - puzzle.pieceH); while (coordOnPuzzle(shuffleX, shuffleY)) { shuffleX = Math.random() * (canvas.width - puzzle.pieceW) + (.5 * puzzle.pieceW); shuffleY = Math.random() * (canvas.height - puzzle.pieceH) + (.5 * puzzle.pieceH); } shuffleX -= (.5 * puzzle.pieceW); shuffleY -= (.5 * puzzle.pieceH); let left = determineEdgeType(i, 'left'); let right = determineEdgeType(i, 'right'); let top = determineEdgeType(i, 'top'); let bottom = determineEdgeType(i, 'bottom'); // random between 0-3 let orientation = Math.floor(Math.random() * 4); pieces[i] = new Piece(i, shuffleX, shuffleY, finalX, finalY, left, right, top, bottom, orientation); //pieces[i].printInfo(); (puzzle.pieces).push(pieces[i]); determineEdgeInfo(pieces[i], 'left'); determineEdgeInfo(pieces[i], 'right'); determineEdgeInfo(pieces[i], 'top'); determineEdgeInfo(pieces[i], 'bottom'); } puzzle.drawPriority = []; // start with priority in order of indeces for (let i = 0; i < puzzle.numPieces; i++) { puzzle.drawPriority[i] = i; } // only working if I call it twice... ??? redrawOnFrontCanvas(); redrawOnFrontCanvas(); }; /*********************************************************************** initCanvasSize - Finds the initial dimensions for the canvas and puzzle based on target dimensions and puzzle image dimensions ************************************************************************/ function initCanvasSize() { console.log("initCanvasSize"); let targetW = window.innerWidth * .65; let targetH = window.innerHeight * .65; // allow puzzle to take up more vertical space for portrait images if (puzzleImg.width < puzzleImg.height) { targetH = window.innerHeight * .8; } let idealW = puzzleImg.width; let idealH = puzzleImg.height; let sizes = calculateAspectRatio(idealW, idealH, targetW, targetH); canvas.width = window.innerWidth; canvas.height = window.innerHeight - 75; // -1 otherwise outer rectangle sometimes doesn't show up let puzzleW = sizes[0]; let puzzleH = sizes[1] - 1; // set back canvas info the same as front canvas backCanvas.width = canvas.width; backCanvas.height = canvas.height; backCanvas.style.left = "0px"; backCanvas.style.top = "0px"; backCanvas.style.position = "absolute"; let puzzleX = canvas.width / 2 - puzzleW / 2; let puzzleY = canvas.height / 2 - puzzleH / 2; console.log("puzzle width: " + puzzleW + " puzzle height: " + puzzleH); console.log("canvas width: " + canvas.width + " canvas height: " + canvas.height); puzzle = new Puzzle(puzzleW, puzzleH, puzzleX, puzzleY, 3, 4, puzzleImg); }; /*********************************************************************** resizeCanvas - Modifies canvas dimensions (used when screen is resized) but keeps the puzzle dimensions the same. ************************************************************************/ function resizeCanvas() { console.log("Entering - resize canvas"); //console.log("canvas w & h " + canvas.width + " " + canvas.height); //console.log("doc width/height " + window.screen.availWidth + " " + window.screen.availHeight); // save current canvas dimensions to calculate puzzle image location on canvas let oldWidth = canvas.width; let oldHeight = canvas.height; canvas.width = window.screen.availWidth; canvas.height = window.screen.availHeight - 75; backCanvas.width = canvas.width; backCanvas.height = canvas.height; // center puzzle frame on screen let oldPuzzleY = puzzle.y; puzzle.y = canvas.height * .5 - puzzle.height * .5; let yDiff = puzzle.y - oldPuzzleY; let oldPuzzleX = puzzle.x; puzzle.x = canvas.width * .5 - puzzle.width * .5; let xDiff = puzzle.x - oldPuzzleX; for (let i = 0; i < puzzle.numPieces; i++) { puzzle.pieces[i].y = puzzle.pieces[i].y + yDiff; puzzle.pieces[i].x = puzzle.pieces[i].x + xDiff; puzzle.pieces[i].correctY = puzzle.pieces[i].correctY + yDiff; puzzle.pieces[i].correctX = puzzle.pieces[i].correctX + xDiff; } redrawOnFrontCanvas(); }; /*********************************************************************** Idea modified from puzzle here: http://www.custarddoughnuts.co.uk/article/2017/3/9/javascript-jigsaw-puzzle calculateAspectRatio - Given ideal dimensions and max dimensions, finds the best possible dimensions for the puzzle. Parameters: idealW/idealH - ideal dimensions if perfect ratio, parentW/parentH - max dimensions Returns: dimensions of puzzle ************************************************************************/ function calculateAspectRatio(idealW, idealH, parentW, parentH) { let aspect = Math.floor((parentH / idealH) * idealW); let cWidth = Math.min(idealW, parentW); let cHeight = Math.min(idealH, parentH); let w = Math.min(parentW, aspect); let h = (w / idealW) * idealH; return ([w, h]); }; // for debugging purposes function drawGrid() { // horizontal lines for (let j = 0; j < canvas.height; j += 10) { ctx.beginPath(); ctx.moveTo(0, j); ctx.lineTo(0, canvas.width); ctx.stroke(); ctx.closePath(); } // draw vertical lines across canvas for (let i = 0; i < canvas.width; i += 10) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, canvas.height); ctx.stroke(); ctx.closePath(); } }; /*********************************************************************** determineEdgeType - Decides whether an edge is an outer edge, in, or out, taking into consideration whether the edge has already been defined. Parameters: index of piece the edge belongs to, side Returns: type of edge ************************************************************************/ function determineEdgeType(index, side) { if (isOutsideEdge(index, side)) { return 'edge'; } else { // random for bottom and right, as these have not been defined // special cases > first column (also define left edge) and first row (also define top edge) (both type 'edge') if (side == 'bottom' || side == 'right') { // random - 0 or 1 var rand = Math.floor(Math.random() * 2); if (rand == 0) { return 'in'; } else { return 'out'; } } else if (side == 'left') { return oppositeType(puzzle.pieces[index - 1].right.type); } else if (side == 'top') { return oppositeType(puzzle.pieces[index - puzzle.numCols].bottom.type); } } }; /*********************************************************************** determineEdgeInfo - Determines edge information by picking random values between two percentages. Parameters: index of piece the edge belongs to, side ************************************************************************/ function determineEdgeInfo(piece, side) { let index = piece.index; if (isOutsideEdge(index, side)) { return; } else { // random for bottom and right, as these have not been defined if (side == 'bottom' || side == 'right') { // keyLoc random between 35-65% // keySize random between 15-25 // curveX: 5-25 curveY: 15-25 if (side == 'bottom') { piece.bottom.keyLoc = (Math.random() * 30 + 35) / 100; piece.bottom.keySize = (Math.random() * 10 + 15) / 100; piece.bottom.curveStartX = (Math.random() * 20 + 5) / 100; piece.bottom.curveEndX = (Math.random() * 20 + 5) / 100; piece.bottom.curveStartY = (Math.random() * 10 + 15) / 100; piece.bottom.curveEndY = (Math.random() * 10 + 15) / 100; } else if (side == 'right') { piece.right.keyLoc = (Math.random() * 30 + 35) / 100; piece.right.keySize = (Math.random() * 10 + 15) / 100; piece.right.curveStartX = (Math.random() * 20 + 5) / 100; piece.right.curveEndX = (Math.random() * 20 + 5) / 100; piece.right.curveStartY = (Math.random() * 10 + 15) / 100; piece.right.curveEndY = (Math.random() * 10 + 15) / 100; } else if (side == 'top') { piece.top.keyLoc = (Math.random() * 30 + 35) / 100; piece.top.keySize = (Math.random() * 10 + 15) / 100; piece.top.curveStartX = (Math.random() * 20 + 5) / 100; piece.top.curveEndX = (Math.random() * 20 + 5) / 100; piece.top.curveStartY = (Math.random() * 10 + 15) / 100; piece.top.curveEndY = (Math.random() * 10 + 15) / 100; } else if (side == 'left') { piece.left.keyLoc = (Math.random() * 30 + 35) / 100; piece.left.keySize = (Math.random() * 10 + 15) / 100; piece.left.curveStartX = (Math.random() * 20 + 5) / 100; piece.left.curveEndX = (Math.random() * 20 + 5) / 100; piece.left.curveStartY = (Math.random() * 10 + 15) / 100; piece.left.curveEndY = (Math.random() * 10 + 15) / 100; } } else if (side == 'left') { oppositeInfo(puzzle.pieces[index - 1].right, piece.left); } else if (side == 'top') { oppositeInfo(puzzle.pieces[index - puzzle.numCols].bottom, piece.top); } } }; /*********************************************************************** oppositeInfo - Determines edge information of a new edge based on the already defined edge it matches to. Parameters: already defined edge, new edge ************************************************************************/ function oppositeInfo(edge, newEdge) { newEdge.keySize = edge.keySize; newEdge.keyLoc = edge.keyLoc; newEdge.curveStartX = edge.curveEndX; newEdge.curveStartY = edge.curveEndY; newEdge.curveEndX = edge.curveStartX; newEdge.curveEndY = edge.curveStartY; }; /*********************************************************************** oppositeType - Determines opposite type of edge. Parameters: type of edge Returns: opposite type ************************************************************************/ function oppositeType(type) { if (type == 'in') { return 'out'; } if (type == 'out') { return 'in'; } // in case of outer edge return 'edge'; }; /*********************************************************************** isOutsideEdge - Determines whether an edge is an outer edge of puzzle. Parameters: index of piece, side Returns: boolean value ************************************************************************/ function isOutsideEdge(index, side) { if (index < puzzle.numCols && side == 'top') { return true; } if (index % puzzle.numCols == 0 && side == 'left') { return true; } if (index % puzzle.numCols == (puzzle.numCols - 1) && side == 'right') { return true; } if ((puzzle.numCols * puzzle.numRows - 1 - puzzle.numCols) < index && side == 'bottom') { return true; } return false; }; /*********************************************************************** findPiecesToShuffle - Determine whether each puzzle piece needs to be part of shuffled array based on whether it's already placed on the puzzle, whether it's correct, and whether it's connected to any other pieces. Returns: array of pieces to shuffle ************************************************************************/ function findPiecesToShuffle() { let pieces = []; let numPieces = puzzle.numPieces; for (let i = 0; i < numPieces; i++) { puzzle.connectedPieces = []; findConnectedPieces(i); if (puzzle.pieces[i].correct == 0 && !onPuzzle(puzzle.pieces[i]) && puzzle.connectedPieces.length <= 1) { pieces.push(i); } } return pieces; }; /*********************************************************************** findRandomPieceToShuffle - Given array of pieces to shuffle, return random piece. Returns: index of random piece to shuffle (in pieces to shuffle array) ************************************************************************/ function findRandomPieceToShuffle(pieces) { let index = Math.floor(Math.random() * pieces.length); return index; }; /*********************************************************************** onPuzzle - Determine whether a piece is currently on the puzzle. (based on midpoint of piece) Parameters: piece to look at Returns: boolean value ************************************************************************/ function onPuzzle(piece) { // midpoint of piece let x = piece.x + puzzle.pieceW * .5; let y = piece.y + puzzle.pieceH * .5; if (x > puzzle.x && x < (puzzle.x + puzzle.width) && y > puzzle.y && y < (puzzle.y + puzzle.height)) { return true; } return false; }; /*********************************************************************** movePieceInPlace - moves a piece into its correct location by moving small amounts and then sleeping for specified amount of time. Parameters: dir - direction to move, slope - used to calculate position along line, index - index of piece to move ************************************************************************/ async function movePieceInPlace(dir, slope, index) { console.log("Entering - movePieceInPlace: dir - " + dir + " slope - " + slope + " index - " + index); let moveAmt = 10; while (puzzle.pieces[index].correct == 0) { if (slope == null && dir == 'up') { puzzle.pieces[index].y -= moveAmt; } else if (slope == null && dir == 'down') { puzzle.pieces[index].y += moveAmt; } else if (dir == 'left') { puzzle.pieces[index].x -= moveAmt; puzzle.pieces[index].y = Math.floor(slope * (puzzle.pieces[index].x - puzzle.pieces[index].correctX) + puzzle.pieces[index].correctY); } else if (dir == 'right') { puzzle.pieces[index].x += moveAmt; puzzle.pieces[index].y = Math.floor(slope * (puzzle.pieces[index].x - puzzle.pieces[index].correctX) + puzzle.pieces[index].correctY); } else if (dir == 'up') { puzzle.pieces[index].y -= moveAmt; puzzle.pieces[index].x = Math.floor((puzzle.pieces[index].y - puzzle.pieces[index].correctY) / slope + puzzle.pieces[index].correctX); } else if (dir == 'down') { puzzle.pieces[index].y += moveAmt; puzzle.pieces[index].x = Math.floor((puzzle.pieces[index].y - puzzle.pieces[index].correctY) / slope + puzzle.pieces[index].correctX); } // adjust orientation if (puzzle.pieces[index].orientation !== 0) { puzzle.pieces[index].orientation = (puzzle.pieces[index].orientation + 1) % 4; } // check if close enough to snap piece into place checkDoneMoving(index); redrawOnFrontCanvas(); await sleep(5); } // make correct if not already if (puzzle.pieces[index].orientation !== 0) { puzzle.pieces[index].orientation = 0; } shiftDrawPriorityToLast(index); redrawOnFrontCanvas(); }; /*********************************************************************** checkDoneMoving - checks if a piece is close enough to final position to snap into place. Parameters: index - of moving piece ************************************************************************/ function checkDoneMoving(index) { // check if it's near final placement of piece if (Math.abs(puzzle.pieces[index].x - puzzle.pieces[index].correctX) < puzzle.tolerance && Math.abs(puzzle.pieces[index].y - puzzle.pieces[index].correctY) < puzzle.tolerance) { puzzle.pieces[index].correct = 1; puzzle.pieces[index].x = puzzle.pieces[index].correctX; puzzle.pieces[index].y = puzzle.pieces[index].correctY; } }; /*********************************************************************** findFirstUnsolved - Returns the first piece that's unsolved (based on index) ************************************************************************/ function findFirstUnsolved() { for (let i = 0; i < puzzle.numPieces; i++) { if (puzzle.pieces[i].correct == 0) { return i; } } return -1; }; /*********************************************************************** findRandomUnsolved - creates array of unsolved pieces and picks random piece Returns: index of random piece ************************************************************************/ function findRandomUnsolved() { let unsolved = []; // add all unsolved pieces to array for (let i = 0; i < puzzle.numPieces; i++) { if (puzzle.pieces[i].correct == 0) { unsolved.push(i); } } // pick random piece in unsolved array let index = Math.floor(Math.random() * unsolved.length); return unsolved[index]; }; /*********************************************************************** updateTime - Called initially and then sets a timeout so it's called to update the timer every 1/2 second. Updates the timer element to reflect how long it has taken the user on current puzzle. Calculates by subtracting start time from current time for better accuracy. idea from https://stackoverflow.com/questions/5517597/plain-count-up-timer-in-javascript ************************************************************************/ function updateTime() { // find time taken by subtracting from start time let now = Math.floor(Date.now() / 1000); let diff = now - startTime; // reformat into mm:ss let m = Math.floor(diff / 60); let s = Math.floor(diff % 60); // add a leading zero if it's single digit m = checkTime(m); s = checkTime(s); // change timer element document.getElementById("timer").innerHTML = m + ":" + s; // set a timeout to update the timer t = setTimeout(updateTime, 500); }; /*********************************************************************** checkTime - adds 0 for single digit for timer Parameters: i - digit to look at Returns: digit with leading 0, if needed ************************************************************************/ function checkTime(i) { // add zero in front of numbers < 10 if (i < 10) { i = "0" + i } return i; }; /*********************************************************************** closeFullscreen - exits fullscreen and resizes canvas ************************************************************************/ function closeFullscreen() { console.log("closing full screen"); if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.mozCancelFullScreen) { /* Firefox */ document.mozCancelFullScreen(); } else if (document.webkitExitFullscreen) { /* Chrome, Safari and Opera */ document.webkitExitFullscreen(); } else if (document.msExitFullscreen) { /* IE/Edge */ document.msExitFullscreen(); } resizeCanvas(); }; /*********************************************************************** openFullscreen - opens fullscreen and resizes canvas ************************************************************************/ function openFullscreen() { console.log("opening full screen"); let elem = document.documentElement; if (elem.requestFullscreen) { elem.requestFullscreen(); } else if (elem.mozRequestFullScreen) { /* Firefox */ elem.mozRequestFullScreen(); } else if (elem.webkitRequestFullscreen) { /* Chrome, Safari and Opera */ elem.webkitRequestFullscreen(); } else if (elem.msRequestFullscreen) { /* IE/Edge */ elem.msRequestFullscreen(); } resizeCanvas(); }; /*********************************************************************** checkOnPiece - checks if coordinate is on a piece. Based on draw priority so top piece click is on will be set as clicked. Parameters: x, y - coordinate to check Returns: boolean value ************************************************************************/ function checkOnPiece(x, y) { console.log('Entering - check on piece'); let numPieces = puzzle.numPieces; // check in order of priority - top piece takes precedence for (let i = 0; i < numPieces; i++) { let currentPiece = puzzle.pieces[puzzle.drawPriority[i]]; if ((x > currentPiece.x) && (x < currentPiece.x + puzzle.pieceW) && (y > currentPiece.y) && (y < currentPiece.y + puzzle.pieceH) && currentPiece.correct == 0) { if (!puzzle.edgeOnly || (puzzle.edgeOnly && currentPiece.hasOutsideEdge())) { console.log('On piece ' + i); puzzle.clicked = currentPiece.index; // offset for where the click is taking place on the piece (offset from top left of piece) puzzle.offsetX = x - currentPiece.x; puzzle.offsetY = y - currentPiece.y; return true; } } } return false; }; /*********************************************************************** moveClickedPiece - moves the piece currently clicked on based on mouse position by drawing on front canvas. Parameters: event - info about mouse position ************************************************************************/ function moveClickedPiece(event) { //console.log("Entering - moveClickedPiece"); puzzle.pieces[puzzle.clicked].x = event.pageX - puzzle.offsetX; puzzle.pieces[puzzle.clicked].y = event.pageY - puzzle.offsetY; clearCanvas(); ctx.drawImage(backCanvas, 0, 0); let length = puzzle.connectedPieces.length; // draw all pieces connected to clicked piece for (let i = 0; i < length; i++) { calculateNewX(puzzle.connectedPieces[i].index); calculateNewY(puzzle.connectedPieces[i].index); puzzle.pieces[puzzle.connectedPieces[i].index].draw(ctx, puzzle); } }; /*********************************************************************** calculateNewX - calculates a connected piece's new x coordinate in relation to the moving piece's x coordinate Parameters: index - of piece to calculate ************************************************************************/ function calculateNewX(index) { if (puzzle.pieces[index].orientation == 0) { //let left = (puzzle.clicked - index) % puzzle.numCols <= puzzle.clicked; let left = index % puzzle.numCols < puzzle.clicked % puzzle.numCols; if (left) { puzzle.pieces[index].x = puzzle.pieces[puzzle.clicked].x - (puzzle.pieceW * mod(puzzle.clicked - index, puzzle.numCols)); } else { puzzle.pieces[index].x = puzzle.pieces[puzzle.clicked].x + (puzzle.pieceW * mod(index - puzzle.clicked, puzzle.numCols)); } } // rotated 90 degrees else if (puzzle.pieces[index].orientation == 1) { puzzle.pieces[index].x = puzzle.pieces[puzzle.clicked].x - (puzzle.pieceH * (Math.floor(index / puzzle.numCols) - Math.floor(puzzle.clicked / puzzle.numCols))); } // rotated 180 degrees else if (puzzle.pieces[index].orientation == 2) { //let right = (puzzle.clicked - index) % puzzle.numCols <= puzzle.clicked; let right = puzzle.clicked % puzzle.numCols > index % puzzle.numCols; if (right) { puzzle.pieces[index].x = puzzle.pieces[puzzle.clicked].x + puzzle.pieceW * mod(puzzle.clicked - index, puzzle.numCols); } else { puzzle.pieces[index].x = puzzle.pieces[puzzle.clicked].x - puzzle.pieceW * mod(index - puzzle.clicked, puzzle.numCols); } } // rotated 270 degrees else if (puzzle.pieces[index].orientation == 3) { puzzle.pieces[index].x = puzzle.pieces[puzzle.clicked].x + (puzzle.pieceH * (Math.floor(index / puzzle.numCols) - Math.floor(puzzle.clicked / puzzle.numCols))); } }; /*********************************************************************** calculateNewY - calculates a connected piece's new y coordinate in relation to the moving piece's y coordinate Parameters: index - of piece to calculate ************************************************************************/ function calculateNewY(index) { if (puzzle.pieces[index].orientation == 0) { puzzle.pieces[index].y = puzzle.pieces[puzzle.clicked].y + (puzzle.pieceH * (Math.floor(index / puzzle.numCols) - Math.floor(puzzle.clicked / puzzle.numCols))); } else if (puzzle.pieces[index].orientation == 1) { let left = (puzzle.clicked - index) % puzzle.numCols <= puzzle.clicked; if (left) { puzzle.pieces[index].y = puzzle.pieces[puzzle.clicked].y - (puzzle.pieceW * ((puzzle.clicked - index) % puzzle.numCols)); } else { puzzle.pieces[index].y = puzzle.pieces[puzzle.clicked].y + (puzzle.pieceW * ((puzzle.clicked - index) % puzzle.numCols)); } } else if (puzzle.pieces[index].orientation == 2) { puzzle.pieces[index].y = puzzle.pieces[puzzle.clicked].y - (puzzle.pieceH * (Math.floor(index / puzzle.numCols) - Math.floor(puzzle.clicked / puzzle.numCols))); } else if (puzzle.pieces[index].orientation == 3) { let left = (puzzle.clicked - index) % puzzle.numCols <= puzzle.clicked; if (left) { puzzle.pieces[index].y = puzzle.pieces[puzzle.clicked].y + (puzzle.pieceW * ((puzzle.clicked - index) % puzzle.numCols)); } else { puzzle.pieces[index].y = puzzle.pieces[puzzle.clicked].y - (puzzle.pieceW * ((puzzle.clicked - index) % puzzle.numCols)); } } }; /*********************************************************************** changeImage - called when creating a new puzzle to set a new puzzle image. ************************************************************************/ function changeImage() { console.log("Entering - changeImage"); removeEvents(); initialize(); puzzle.ghostImage = false; ghostImageButton.style.color = "white"; puzzle.edgeOnly = false; edgeOnlyButton.style.color = "white"; }; /*********************************************************************** redrawOnFrontCanvas - clears both canvases and draws everything on front canvas. ************************************************************************/ function redrawOnFrontCanvas() { console.log("Entering - redraw on front canvas"); clearBackgroundCanvas(); clearCanvas(); // redraw background image and border around puzzle if (puzzle.backgroundColor == "dark") { ctx.strokeStyle = "white"; ctx.drawImage(darkBackgroundImg, 0, 0, canvas.width, canvas.height); } else { ctx.strokeStyle = "black"; ctx.drawImage(lightBackgroundImg, 0, 0, canvas.width, canvas.height); } ctx.rect(puzzle.x, puzzle.y, puzzle.width, puzzle.height); ctx.stroke(); // draw ghost image in background if needed if (puzzle.ghostImage) { ctx.save(); ctx.globalAlpha = 0.4; ctx.drawImage(ghostImg, puzzle.x, puzzle.y, puzzle.width, puzzle.height); ctx.restore(); } // draw according to priority let numPieces = puzzle.drawPriority.length; for (let i = (numPieces - 1); i >= 0; i--) { let currentPiece = puzzle.pieces[puzzle.drawPriority[i]]; if (!puzzle.edgeOnly || (puzzle.edgeOnly && currentPiece.correct == 1) || (puzzle.edgeOnly && currentPiece.hasOutsideEdge())) { currentPiece.draw(ctx, puzzle); } } }; /*********************************************************************** redrawOnBackCanvas - clears both canvases and draws everything except clicked piece on background canvas. ************************************************************************/ function redrawOnBackCanvas() { console.log("draw on background canvas"); clearCanvas(); clearBackgroundCanvas(); // redraw background image and border around puzzle if (puzzle.backgroundColor == "dark") { backCtx.strokeStyle = "white"; backCtx.drawImage(darkBackgroundImg, 0, 0, canvas.width, canvas.height); } else { backCtx.strokeStyle = "black"; backCtx.drawImage(lightBackgroundImg, 0, 0, canvas.width, canvas.height); } backCtx.rect(puzzle.x, puzzle.y, puzzle.width, puzzle.height); backCtx.stroke(); // draw ghost image in background if needed if (puzzle.ghostImage) { backCtx.save(); backCtx.globalAlpha = 0.4; backCtx.drawImage(ghostImg, puzzle.x, puzzle.y, puzzle.width, puzzle.height); backCtx.restore(); } // draw according to priority let numPieces = puzzle.drawPriority.length; for (let i = (numPieces - 1); i >= 0; i--) { let currentPiece = puzzle.pieces[puzzle.drawPriority[i]]; if ((!puzzle.edgeOnly || (puzzle.edgeOnly && currentPiece.correct == 1) || (puzzle.edgeOnly && currentPiece.hasOutsideEdge())) && !inConnectedArray(currentPiece.index)) { currentPiece.draw(backCtx, puzzle); } } }; /*********************************************************************** clearCanvas - clears front canvas ************************************************************************/ function clearCanvas() { // canvas is reset every time dimensions are modified canvas.width = canvas.width; }; /*********************************************************************** clearBackgroundCanvas - clears background canvas ************************************************************************/ function clearBackgroundCanvas() { // canvas is reset every time dimensions are modified backCanvas.width = backCanvas.width; } /*********************************************************************** checkCorrect - check if piece is close enough to another connecting piece or final placement to lock into place Parameters: event - info about mouse click ************************************************************************/ function checkCorrect(event) { console.log("Entering - check correct"); if (puzzle.clicked !== -1) { // check if it's near final placement of piece if (puzzle.pieces[puzzle.clicked].orientation == 0 && Math.abs(event.pageX - puzzle.offsetX - puzzle.pieces[puzzle.clicked].correctX) < puzzle.tolerance && Math.abs(event.pageY - puzzle.offsetY - puzzle.pieces[puzzle.clicked].correctY) < puzzle.tolerance) { puzzle.pieces[puzzle.clicked].correct = 1; shiftDrawPriorityToLast(puzzle.clicked); puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked].correctX; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked].correctY; // adjust connected pieces to correct let length = puzzle.connectedPieces.length; for (let i = 0; i < length; i++) { puzzle.pieces[puzzle.connectedPieces[i].index].x = puzzle.pieces[puzzle.connectedPieces[i].index].correctX; puzzle.pieces[puzzle.connectedPieces[i].index].y = puzzle.pieces[puzzle.connectedPieces[i].index].correctY; puzzle.pieces[puzzle.connectedPieces[i].index].correct = 1; shiftDrawPriorityToLast(puzzle.connectedPieces[i].index); } } // check if it's near a piece it should connect to else { // check piece to the left (if not left edge piece) if (puzzle.clicked % puzzle.numCols !== 0) { //piece[puzzle.clicked - 1].printInfo(); let x = event.pageX - puzzle.offsetX - (puzzle.pieces[puzzle.clicked - 1].x + puzzle.pieceW); let y = Math.abs(event.pageY - puzzle.offsetY - puzzle.pieces[puzzle.clicked - 1].y); //console.log("Left piece: x diff: " + x); //console.log("Left piece: y diff: " + y); if (puzzle.pieces[puzzle.clicked - 1].orientation == puzzle.pieces[puzzle.clicked].orientation && shouldConnect(puzzle.clicked, puzzle.clicked - 1)) { console.log("should connect to the left piece"); if (puzzle.pieces[puzzle.clicked].orientation == 0) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked - 1].x + puzzle.pieceW; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked - 1].y; } else if (puzzle.pieces[puzzle.clicked].orientation == 1) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked - 1].x; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked - 1].y + puzzle.pieceW; } else if (puzzle.pieces[puzzle.clicked].orientation == 2) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked - 1].x - puzzle.pieceW; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked - 1].y; } else { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked - 1].x; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked - 1].y - puzzle.pieceW; } let length = puzzle.connectedPieces.length; for (let i = 0; i < length; i++) { calculateNewX(puzzle.connectedPieces[i].index); calculateNewY(puzzle.connectedPieces[i].index); } } } // check piece above (if not top row) if (!(puzzle.clicked < puzzle.numCols)) { //piece[puzzle.clicked - puzzle.numCols].printInfo(); let x = Math.abs(event.pageX - puzzle.offsetX - puzzle.pieces[puzzle.clicked - puzzle.numCols].x); let y = event.pageY - puzzle.offsetY - (puzzle.pieces[puzzle.clicked - puzzle.numCols].y + puzzle.pieceH); //console.log("Above piece: x diff: " + x); //console.log("Above piece: y diff: " + y); if (puzzle.pieces[puzzle.clicked - puzzle.numCols].orientation == puzzle.pieces[puzzle.clicked].orientation && shouldConnect(puzzle.clicked, puzzle.clicked - puzzle.numCols)) { console.log("should connect to the above piece"); if (puzzle.pieces[puzzle.clicked].orientation == 0) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked - puzzle.numCols].x; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked - puzzle.numCols].y + puzzle.pieceH; } else if (puzzle.pieces[puzzle.clicked].orientation == 1) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked - puzzle.numCols].x - puzzle.pieceH; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked - puzzle.numCols].y; } else if (puzzle.pieces[puzzle.clicked].orientation == 2) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked - puzzle.numCols].x; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked - puzzle.numCols].y - puzzle.pieceH; } else { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked - puzzle.numCols].x + puzzle.pieceH; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked - puzzle.numCols].y; } let length = puzzle.connectedPieces.length; for (let i = 0; i < length; i++) { calculateNewX(puzzle.connectedPieces[i].index); calculateNewY(puzzle.connectedPieces[i].index); } } } // check piece to the right (if not right edge piece) if (puzzle.clicked % puzzle.numCols !== (puzzle.numCols - 1)) { //piece[puzzle.clicked + 1].printInfo(); let x = puzzle.pieces[puzzle.clicked + 1].x - (event.pageX - puzzle.offsetX + puzzle.pieceW); let y = Math.abs(event.pageY - puzzle.offsetY - puzzle.pieces[puzzle.clicked + 1].y); //console.log("Right piece: x diff: " + x); //console.log("Right piece: y diff: " + y); if (puzzle.pieces[puzzle.clicked + 1].orientation == puzzle.pieces[puzzle.clicked].orientation && shouldConnect(puzzle.clicked, puzzle.clicked + 1)) { console.log("should connect to the right piece"); if (puzzle.pieces[puzzle.clicked].orientation == 0) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked + 1].x - puzzle.pieceW; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked + 1].y; } else if (puzzle.pieces[puzzle.clicked].orientation == 1) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked + 1].x; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked + 1].y - puzzle.pieceW; } else if (puzzle.pieces[puzzle.clicked].orientation == 2) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked + 1].x + puzzle.pieceW; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked + 1].y; } else { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked + 1].x; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked + 1].y + puzzle.pieceW; } let length = puzzle.connectedPieces.length; for (let i = 0; i < length; i++) { calculateNewX(puzzle.connectedPieces[i].index); calculateNewY(puzzle.connectedPieces[i].index); } } } // check piece below (if not bottom row) if ((puzzle.numPieces - puzzle.numCols) > puzzle.clicked) { //puzzle.pieces[puzzle.clicked + puzzle.numCols].printInfo(); let x = Math.abs(event.pageX - puzzle.offsetX - puzzle.pieces[puzzle.clicked + puzzle.numCols].x); let y = puzzle.pieces[puzzle.clicked + puzzle.numCols].y - (event.pageY - puzzle.offsetY + puzzle.pieceH); //console.log("Below piece: x diff: " + x); //console.log("Below piece: y diff: " + y); if (puzzle.pieces[puzzle.clicked + puzzle.numCols].orientation == puzzle.pieces[puzzle.clicked].orientation && shouldConnect(puzzle.clicked, puzzle.clicked + puzzle.numCols)) { console.log("should connect to the below piece"); if (puzzle.pieces[puzzle.clicked].orientation == 0) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked + puzzle.numCols].x; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked + puzzle.numCols].y - puzzle.pieceH; } else if (puzzle.pieces[puzzle.clicked].orientation == 1) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked + puzzle.numCols].x + puzzle.pieceH; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked + puzzle.numCols].y; } else if (puzzle.pieces[puzzle.clicked].orientation == 2) { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked + puzzle.numCols].x; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked + puzzle.numCols].y + puzzle.pieceH; } else { puzzle.pieces[puzzle.clicked].x = puzzle.pieces[puzzle.clicked + puzzle.numCols].x - puzzle.pieceH; puzzle.pieces[puzzle.clicked].y = puzzle.pieces[puzzle.clicked + puzzle.numCols].y; } let length = puzzle.connectedPieces.length; for (let i = 0; i < length; i++) { calculateNewX(puzzle.connectedPieces[i].index); calculateNewY(puzzle.connectedPieces[i].index); } } } } } }; /*********************************************************************** findConnectedPieces - finds the pieces currently connected to a given piece. Parameters: index of piece ************************************************************************/ function findConnectedPieces(index) { console.log("Entering - findConnectedPieces - " + index); // if connected to left if (index - 1 >= 0 && areConnected(index, index - 1)) { console.log("connected to left"); if (!inConnectedArray(index - 1)) { puzzle.connectedPieces.push(puzzle.pieces[index - 1]); findConnectedPieces(index - 1); } } // connected to above if (index - puzzle.numCols >= 0 && areConnected(index, index - puzzle.numCols)) { console.log("connected to above"); if (!inConnectedArray(index - puzzle.numCols)) { puzzle.connectedPieces.push(puzzle.pieces[index - puzzle.numCols]); findConnectedPieces(index - puzzle.numCols); } } // connected to right if (index + 1 < puzzle.numPieces && areConnected(index, index + 1)) { console.log("connected to right"); if (!inConnectedArray(index + 1)) { puzzle.connectedPieces.push(puzzle.pieces[index + 1]); findConnectedPieces(index + 1); } } // connected to below piece if (index + puzzle.numCols < puzzle.numPieces && areConnected(index, index + puzzle.numCols)) { console.log("connected to below"); if (!inConnectedArray(index + puzzle.numCols)) { puzzle.connectedPieces.push(puzzle.pieces[index + puzzle.numCols]); findConnectedPieces(index + puzzle.numCols); } } }; /*********************************************************************** inConnectedArray - checks if a certain piece is in the connected pieces array. Parameters: index of piece to check ************************************************************************/ function inConnectedArray(index) { //console.log("Entering - inConnectedArray - " + index); let numPieces = puzzle.connectedPieces.length; for (let i = 0; i < numPieces; i++) { // already in connected pieces array, return if (puzzle.connectedPieces[i].index == index) { return true; } } return false; }; /*********************************************************************** areConnected - checks if two pieces are currently connected Parameters - indeces of pieces to look at ************************************************************************/ function areConnected(a, b) { //console.log("areConnected - " + a + " " + b); //console.log("puzzle.pieceW " + puzzle.pieceW); //console.log("puzzle.pieceH " + puzzle.pieceH); //console.log("a.x: " + puzzle.pieces[a].x + " a.y: " + puzzle.pieces[a].y); //console.log("b.x: " + puzzle.pieces[b].x + " b.y: " + puzzle.pieces[b].y); // determine which direction piece b should be in relation to piece a // none let dir = "n"; if (puzzle.pieces[a].orientation == puzzle.pieces[b].orientation) { // a is left of b (when orientation is 0 for both) if ((b - 1) == a && (b % puzzle.numCols) !== 0) { // left if (puzzle.pieces[a].orientation == 0) { dir = "l"; } // below else if (puzzle.pieces[a].orientation == 1) { dir = "b"; } // right else if (puzzle.pieces[a].orientation == 2) { dir = "r"; } // above else { dir = "a"; } } // a is above b else if (b - puzzle.numCols == a) { // above if (puzzle.pieces[a].orientation == 0) { dir = "a"; } // right else if (puzzle.pieces[a].orientation == 1) { dir = "r"; } // below else if (puzzle.pieces[a].orientation == 2) { dir = "b"; } // left else { dir = "l"; } } // a is to the right of b else if ((b + 1) == a && (b % puzzle.numCols) !== (puzzle.numCols - 1)) { // right if (puzzle.pieces[a].orientation == 0) { dir = "r"; } // below else if (puzzle.pieces[a].orientation == 1) { dir = "b"; } // left else if (puzzle.pieces[a].orientation == 2) { dir = "l"; } // above else { dir = "a"; } } // a is below b else if (b + puzzle.numCols == a) { // below if (puzzle.pieces[a].orientation == 0) { dir = "b"; } // left else if (puzzle.pieces[a].orientation == 1) { dir = "l"; } // above else if (puzzle.pieces[a].orientation == 2) { dir = "a"; } // right else { dir = "r"; } } if (dir == "l") { //console.log("a is left of b"); if ((puzzle.pieces[a].orientation % 2) == 1 && Math.abs(Math.floor(puzzle.pieces[a].x + puzzle.pieceH) - Math.floor(puzzle.pieces[b].x)) <= 2 && Math.floor(puzzle.pieces[b].y) == Math.floor(puzzle.pieces[a].y)) { //console.log("connected"); return true; } else if (Math.abs(Math.floor(puzzle.pieces[a].x + puzzle.pieceW) - Math.floor(puzzle.pieces[b].x)) <= 2 && Math.floor(puzzle.pieces[b].y) == Math.floor(puzzle.pieces[a].y)) { //console.log("connected"); return true; } else { return false; } } else if (dir == "a") { //console.log("a is above b"); // go by width if odd orientation if ((puzzle.pieces[a].orientation % 2) == 1 && Math.abs(Math.floor(puzzle.pieces[a].y + puzzle.pieceW) - Math.floor(puzzle.pieces[b].y)) <= 2 && Math.floor(puzzle.pieces[b].x) == Math.floor(puzzle.pieces[a].x)) { //console.log("connected"); return true; } else if (Math.abs(Math.floor(puzzle.pieces[a].y + puzzle.pieceH) - Math.floor(puzzle.pieces[b].y)) <= 2 && Math.floor(puzzle.pieces[b].x) == Math.floor(puzzle.pieces[a].x)) { //console.log("connected"); return true; } else { return false; } } else if (dir == "r") { //console.log("a is right of b"); if ((puzzle.pieces[a].orientation % 2) == 1 && Math.abs(Math.floor(puzzle.pieces[a].x - puzzle.pieceH) - Math.floor(puzzle.pieces[b].x)) <= 2 && Math.floor(puzzle.pieces[b].y) == Math.floor(puzzle.pieces[a].y)) { //console.log("connected"); return true; } else if (Math.abs(Math.floor(puzzle.pieces[a].x - puzzle.pieceW) - Math.floor(puzzle.pieces[b].x)) <= 2 && Math.floor(puzzle.pieces[b].y) == Math.floor(puzzle.pieces[a].y)) { //console.log("connected"); return true; } else { return false; } } else if (dir == "b") { //console.log("a is below b"); if ((puzzle.pieces[a].orientation % 2) == 1 && Math.abs(Math.floor(puzzle.pieces[a].y - puzzle.pieceW) - Math.floor(puzzle.pieces[b].y)) <= 2 && Math.floor(puzzle.pieces[b].x) == Math.floor(puzzle.pieces[a].x)) { //console.log("connected"); return true; } else if (Math.abs(Math.floor(puzzle.pieces[a].y - puzzle.pieceH) - Math.floor(puzzle.pieces[b].y)) <= 2 && Math.floor(puzzle.pieces[b].x) == Math.floor(puzzle.pieces[a].x)) { //console.log("connected"); return true; } else { return false; } } } // else can't be connected return false; }; /*********************************************************************** shouldConnect - checks if two pieces are close enough to connect. Parameters: indeces of two pieces ************************************************************************/ function shouldConnect(a, b) { //console.log("shouldConnect - " + a + " " + b); //console.log("puzzle.pieceW " + puzzle.pieceW); //console.log("puzzle.pieceH " + puzzle.pieceH); //console.log("a.x: " + puzzle.pieces[a].x + " a.y: " + puzzle.pieces[a].y); //console.log("b.x: " + puzzle.pieces[b].x + " b.y: " + puzzle.pieces[b].y); // none let dir = "n"; if (puzzle.pieces[a].orientation == puzzle.pieces[b].orientation) { // a is left of b (when orientation is 0 for both) if ((b - 1) == a && (b % puzzle.numCols) !== 0) { // left if (puzzle.pieces[a].orientation == 0) { dir = "l"; } // below else if (puzzle.pieces[a].orientation == 1) { dir = "b"; } // right else if (puzzle.pieces[a].orientation == 2) { dir = "r"; } // above else { dir = "a"; } } // a is above b else if (b - puzzle.numCols == a) { // above if (puzzle.pieces[a].orientation == 0) { dir = "a"; } // right else if (puzzle.pieces[a].orientation == 1) { dir = "r"; } // below else if (puzzle.pieces[a].orientation == 2) { dir = "b"; } // left else { dir = "l"; } } // a is to the right of b else if ((b + 1) == a && (b % puzzle.numCols) !== (puzzle.numCols - 1)) { // right if (puzzle.pieces[a].orientation == 0) { dir = "r"; } // below else if (puzzle.pieces[a].orientation == 1) { dir = "b"; } // left else if (puzzle.pieces[a].orientation == 2) { dir = "l"; } // above else { dir = "a"; } } // a is below b else if (b + puzzle.numCols == a) { // below if (puzzle.pieces[a].orientation == 0) { dir = "b"; } // left else if (puzzle.pieces[a].orientation == 1) { dir = "l"; } // above else if (puzzle.pieces[a].orientation == 2) { dir = "a"; } // right else { dir = "r"; } } if (dir == "l") { //console.log("a is left of b"); if ((puzzle.pieces[a].orientation % 2) == 1 && Math.abs(Math.floor(puzzle.pieces[a].x + puzzle.pieceH) - Math.floor(puzzle.pieces[b].x)) <= puzzle.tolerance && Math.abs(Math.floor(puzzle.pieces[b].y) - Math.floor(puzzle.pieces[a].y)) < puzzle.tolerance) { //console.log("connected"); return true; } else if (Math.abs(Math.floor(puzzle.pieces[a].x + puzzle.pieceW) - Math.floor(puzzle.pieces[b].x)) <= puzzle.tolerance && Math.abs(Math.floor(puzzle.pieces[b].y) - Math.floor(puzzle.pieces[a].y)) < puzzle.tolerance) { //console.log("connected"); return true; } else { return false; } } else if (dir == "a") { //console.log("a is above b"); // go by width if odd orientation if ((puzzle.pieces[a].orientation % 2) == 1 && Math.abs(Math.floor(puzzle.pieces[a].y + puzzle.pieceW) - Math.floor(puzzle.pieces[b].y)) <= puzzle.tolerance && Math.floor(puzzle.pieces[b].x) == Math.floor(puzzle.pieces[a].x)) { //console.log("connected"); return true; } else if (Math.abs(Math.floor(puzzle.pieces[a].y + puzzle.pieceH) - Math.floor(puzzle.pieces[b].y)) <= puzzle.tolerance && Math.abs(Math.floor(puzzle.pieces[b].x) - Math.floor(puzzle.pieces[a].x)) < puzzle.tolerance) { //console.log("connected"); return true; } else { return false; } } else if (dir == "r") { //console.log("a is right of b"); if ((puzzle.pieces[a].orientation % 2) == 1 && Math.abs(Math.floor(puzzle.pieces[a].x - puzzle.pieceH) - Math.floor(puzzle.pieces[b].x)) <= puzzle.tolerance && Math.abs(Math.floor(puzzle.pieces[b].y) - Math.floor(puzzle.pieces[a].y)) < puzzle.tolerance) { //console.log("connected"); return true; } else if (Math.abs(Math.floor(puzzle.pieces[a].x - puzzle.pieceW) - Math.floor(puzzle.pieces[b].x)) <= puzzle.tolerance && Math.abs(Math.floor(puzzle.pieces[b].y) - Math.floor(puzzle.pieces[a].y)) < puzzle.tolerance) { //console.log("connected"); return true; } else { return false; } } else if (dir == "b") { //console.log("a is below b"); if ((puzzle.pieces[a].orientation % 2) == 1 && Math.abs(Math.floor(puzzle.pieces[a].y - puzzle.pieceW) - Math.floor(puzzle.pieces[b].y)) <= puzzle.tolerance && Math.abs(Math.floor(puzzle.pieces[b].x) - Math.floor(puzzle.pieces[a].x)) < puzzle.tolerance) { //console.log("connected"); return true; } else if (Math.abs(Math.floor(puzzle.pieces[a].y - puzzle.pieceH) - Math.floor(puzzle.pieces[b].y)) <= puzzle.tolerance && Math.abs(Math.floor(puzzle.pieces[b].x) - Math.floor(puzzle.pieces[a].x)) < puzzle.tolerance) { //console.log("connected"); return true; } else { return false; } } } // else can't be connected return false; }; /*********************************************************************** mod - javascript has a remainder operator, not modulo operator, so this will work for negative and positive numbers ************************************************************************/ function mod(a, b) { while (a < 0) { a += b; } return a % b; }; /*********************************************************************** shiftDrawPriorityToLast - switches a piece's draw priority to last and shifts the rest of the array ************************************************************************/ function shiftDrawPriorityToLast(index) { let i = 0; while (puzzle.drawPriority[i] !== index) { i++; } // found current index of piece in drawPriority array // move to end of drawPriority array let length = puzzle.drawPriority.length; for (let j = i; j < (length - 1); j++) { puzzle.drawPriority[j] = puzzle.drawPriority[j + 1]; } puzzle.drawPriority[length - 1] = index; }; /*********************************************************************** shiftDrawPriorityToFirst - switches a piece's draw priority to first and shifts the rest of the array ************************************************************************/ function shiftDrawPriorityToFirst(index) { // find index of piece to shift in drawPriority array let i = 0; while (puzzle.drawPriority[i] !== index) { i++; } // overwrite index and shift pieces right for (let j = i; j > 0; j--) { puzzle.drawPriority[j] = puzzle.drawPriority[j - 1]; } // set index to first in drawPriority puzzle.drawPriority[0] = index; }; /*********************************************************************** mouseDown - handler for clicking down on mouse Parameters: event - info about mouseclick ************************************************************************/ function mouseDown(event) { console.log("Entering - mousedown: x:" + event.pageX + " y:" + event.pageY); // don't do anything for right click if (event.which !== 3) { if (puzzle.drag) { if (checkOnPiece(event.pageX, event.pageY)) { clickedX = event.pageX; clickedY = event.pageY; puzzle.connectedPieces = []; puzzle.connectedPieces.push(puzzle.pieces[puzzle.clicked]); findConnectedPieces(puzzle.clicked); //console.log("Connected pieces length: " + connectedPieces.length); redrawOnBackCanvas(); //moveClickedPiece(); clearCanvas(); ctx.drawImage(backCanvas, 0, 0); let length = puzzle.connectedPieces.length; for (let i = 0; i < length; i++) { calculateNewX(puzzle.connectedPieces[i].index); calculateNewY(puzzle.connectedPieces[i].index); //console.log("Connected piece " + i + ": " + puzzle.connectedPieces[i]); console.log(puzzle.connectedPieces[i]); //console.log("compare piece: " + puzzle.pieces[connectedPieces[i].index]); console.log(puzzle.pieces[puzzle.connectedPieces[i].index]); //puzzle.connectedPieces[i].draw(ctx, puzzle, puzzle.pieceW, puzzle.pieceH); puzzle.pieces[puzzle.connectedPieces[i].index].draw(ctx, puzzle); // set to first priority for drawing for next time all pieces are drawn on front canvas shiftDrawPriorityToFirst(puzzle.connectedPieces[i].index); } } } } }; /*********************************************************************** mouseMove - handler for moving mouse. Checks if currently dragging a piece. Parameters: event - info about mouse position ************************************************************************/ function mouseMove(event) { // check if dragging a piece currently and lock piece into place if correct if ((puzzle.clicked !== -1) && (puzzle.pieces[puzzle.clicked].correct !== 1)) { moveClickedPiece(event); } }; /*********************************************************************** mouseUp - handler for letting go of mouseclick Parameters: event - info about mouseclick ************************************************************************/ function mouseUp(event) { // don't do anything for right click if (event.which !== 3) { console.log('Entering - mouseup'); if (puzzle.clicked !== -1) { //console.log("eventX/Y: " + event.pageX + " " + event.pageY); //console.log("clickedX/Y: " + clickedX + " " + clickedY); // check if user would like to drag/drop piece or click and release piece if (!(Math.abs(event.pageX - clickedX) <= 3 && Math.abs(event.pageY - clickedY) <= 3)) { checkCorrect(event); if (puzzle.complete()) { clearTimeout(t); } puzzle.clicked = -1; redrawOnFrontCanvas(); puzzle.drag = true; } // capture & drop mode else { puzzle.drag = false; } } } }; /*********************************************************************** rotate - rotates a piece clockwise 90 degrees ************************************************************************/ function rotate(event) { console.log('Entering - rotate'); // don't show context menu event.preventDefault(); if (checkOnPiece(event.pageX, event.pageY)) { // this is being changed later w/ other connected pieces //piece[puzzle.clicked].orientation = (piece[puzzle.clicked].orientation + 1) % 4; puzzle.connectedPieces = []; puzzle.connectedPieces.push(puzzle.pieces[puzzle.clicked]); findConnectedPieces(puzzle.clicked); let length = puzzle.connectedPieces.length; for (let i = 0; i < length; i++) { puzzle.pieces[puzzle.connectedPieces[i].index].orientation = (puzzle.pieces[puzzle.connectedPieces[i].index].orientation + 1) % 4; calculateNewX(puzzle.connectedPieces[i].index); calculateNewY(puzzle.connectedPieces[i].index); } if (puzzle.drag) { redrawOnFrontCanvas(); puzzle.clicked = -1; } // still want to keep all other pieces on back canvas else { moveClickedPiece(event); } } }; /*********************************************************************** toggleGhostImage - changes boolean ghostImage and button style to reflect whether ghost image is there or not. ************************************************************************/ function toggleGhostImage() { //console.log("toggleGhostImage"); //console.log("before value: " + ghostImage); if (puzzle.ghostImage) { puzzle.ghostImage = false; ghostImageButton.style.color = "white"; } else { puzzle.ghostImage = true; ghostImageButton.style.color = "#9933ff"; } //console.log("new value: " + ghostImage); redrawOnFrontCanvas(); }; /*********************************************************************** showSolution - picks a random unsolved piece and moves it into place. Finds the slope of the line from its current location to its correct location and determines the best way to move it there (by incrementing/ decrementing x or y) ************************************************************************/ function showSolution() { console.log("Entering - show solution"); // disable all user control of puzzle pieces while moving piece canvas.removeEventListener('mousedown', mouseDown, false); canvas.removeEventListener('mousemove', mouseMove, false); canvas.removeEventListener('mouseup', mouseUp); // pick a random piece to place let curPiece = findRandomUnsolved(); console.log("curPiece: " + curPiece); // so moving piece is drawn last in redraw method clicked = curPiece; var slope; // direction to move piece in var dir; // canvas is basically quadrant 4 of cartesian plane w/ positive y, so * all y by -1 for calculations // check undefined slope if (puzzle.pieces[curPiece].x == puzzle.pieces[curPiece].correctX) { //console.log("undefined slope"); slope = null; // thought this was better than undefined.. if (puzzle.pieces[curPiece].y > puzzle.pieces[curPiece].correctY) { dir = 'up'; } else { dir = 'down'; } } // moving piece right else if (puzzle.pieces[curPiece].x < puzzle.pieces[curPiece].correctX) { //console.log("piece should move right"); slope = (puzzle.pieces[curPiece].correctY - puzzle.pieces[curPiece].y) / (puzzle.pieces[curPiece].correctX - puzzle.pieces[curPiece].x); // move by incrementing x if (Math.abs(slope) <= 1) { dir = 'right'; } // move by y else if (slope > 1) { dir = 'down'; } else { dir = 'up'; } } // moving piece left else { //console.log("piece should move left"); slope = (puzzle.pieces[curPiece].y - puzzle.pieces[curPiece].correctY) / (puzzle.pieces[curPiece].x - puzzle.pieces[curPiece].correctX); // move by incrementing x if (Math.abs(slope) <= 1) { dir = 'left'; } // move by y else if (slope > 1) { dir = 'up'; } else { dir = 'down'; } } movePieceInPlace(dir, slope, curPiece); canvas.addEventListener('mousedown', mouseDown, false); canvas.addEventListener('mousemove', mouseMove, false); canvas.addEventListener('mouseup', mouseUp); }; /*********************************************************************** toggleEdgeOnly - updates value of boolean edgeOnly ************************************************************************/ function toggleEdgeOnly() { //console.log("toggleEdgeOnly"); if (puzzle.edgeOnly) { puzzle.edgeOnly = false; edgeOnlyButton.style.color = "white"; } else { puzzle.edgeOnly = true; edgeOnlyButton.style.color = "#9933ff"; } redrawOnFrontCanvas(); }; /*********************************************************************** saveImage - On upload new iamge, save image as the new puzzle image without changing puzzle in the background. Parameters: event (holds file info) ************************************************************************/ function saveImage(event) { console.log("Entering - save"); img = this.files[0]; var reader = new FileReader(); reader.onload = function () { //console.log(reader.result); newPuzzleImg = new Image(); newPuzzleImg.src = reader.result; newPuzzleImg.onload = function () { puzzleImg = newPuzzleImg; puzzle.image = newPuzzleImg; } newPuzzleGhostImg = new Image(); newPuzzleGhostImg.src = newPuzzleImg.src; newPuzzleGhostImg.onload = function () { ghostImg = newPuzzleGhostImg; } } reader.readAsDataURL(img); }; /*********************************************************************** handleFullscreen - handler for fullscreen toggle. Calls methods to either open or close fullscreen. Idea from https://www.w3schools.com/howto/howto_js_fullscreen.asp ************************************************************************/ function handleFullscreen() { console.log("Entering - handle full screen"); if (puzzle.fullscreen) { closeFullscreen(); puzzle.fullscreen = false; } else { openFullscreen(); puzzle.fullscreen = true; } }; /*********************************************************************** showImage - Display the puzzle image over the puzzle on hover over showImageButton. ************************************************************************/ function showImage() { ctx.drawImage(puzzleImg, puzzle.x, puzzle.y, puzzle.width, puzzle.height); }; /*********************************************************************** stopShwingImage - Stop displaying the puzzle image over the puzzle by redrawing. ************************************************************************/ function stopShowingImage() { redrawOnFrontCanvas(); }; /*********************************************************************** shufflePieces - Shuffles pieces in an organized way by placing them in rows on the sides of the puzzle. ************************************************************************/ function shufflePieces() { console.log("Entering - shuffle pieces"); // current location of next piece being placed let curX = 5; let curY = 5; let space = 10; // array of pieces that need to be shuffled let pieces = findPiecesToShuffle(); let side = 'left'; // place a piece each iteration of while loop while (pieces.length > 0) { // get random piece and set x and y to current shuffle location let i = findRandomPieceToShuffle(pieces); puzzle.pieces[pieces[i]].x = curX; puzzle.pieces[pieces[i]].y = curY; // calculate where next shuffle piece will go // make sure placing to the right doesn't go off canvas or on the puzzle if (side == 'left' && curX + puzzle.pieceW + space + puzzle.pieceW < puzzle.x) { curX += puzzle.pieceW + space; } else if (side == 'right' && curX + puzzle.pieceW + space + puzzle.pieceW < canvas.width) { curX += puzzle.pieceW + space; } else { // go back to top if end of last row if (curY + puzzle.pieceH + space + puzzle.pieceH > canvas.height) { curX = puzzle.x + puzzle.width + space; curY = 5; side = 'right'; } // make new row else if (side == 'left') { curY += puzzle.pieceH + space; curX = 5; } else { curY += puzzle.pieceH + space; curX = puzzle.x + puzzle.width + space; } } // remove placed piece from the array of pieces to shuffle pieces.splice(i, 1); } redrawOnFrontCanvas(); }; /*********************************************************************** createNewPuzzle - on form submitted, creates the new puzzle based on info provided by user. Sets the puzzle dimensions based on input (if valid) and changes the image, if necessary. Parameters: event ************************************************************************/ function createNewPuzzle(event) { if (parseInt(document.getElementById("rows").value) < 1 || parseInt(document.getElementById("rows").value) > 100) { document.getElementById("rows").style = "border: 2px solid red;"; if (parseInt(document.getElementById("cols").value) < 1 || parseInt(document.getElementById("cols").value) > 100) document.getElementById("cols").style = "border: 2px solid red;"; } else if (parseInt(document.getElementById("cols").value) < 1 || parseInt(document.getElementById("cols").value) > 100) { document.getElementById("cols").style = "border: 2px solid red;"; } else { document.getElementById("cols").style = "border: 2px solid black;"; document.getElementById("rows").style = "border: 2px solid black;"; let rows = parseInt(document.getElementById("rows").value); let cols = parseInt(document.getElementById("cols").value); closeForm(); changeImage(); puzzle.numRows = rows; puzzle.numCols = cols; newPuzzleSize(); } }; /*********************************************************************** backgroundColorChange - Toggle for dark or light background. ************************************************************************/ function backgroundColorChange() { console.log("Entering - background color change"); if (puzzle.backgroundColor == "dark") { console.log("changing to light"); canvas.style.backgroundColor = "#ffffff"; puzzle.backgroundColor = "light"; ctx.strokeStyle = "black"; } else { console.log("changing to dark"); canvas.style.backgroundColor = "white"; puzzle.backgroundColor = "dark"; ctx.strokeStyle = "white"; } redrawOnFrontCanvas(); }; function openForm() { document.getElementById("myForm").style.display = "block"; }; function closeForm() { document.getElementById("myForm").style.display = "none"; };