-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feedback #1
base: feedback
Are you sure you want to change the base?
Feedback #1
Changes from all commits
c74b329
a5d93c1
d4473d3
a325e63
4b881cb
bf96831
1659eb8
d19b726
32566dc
4c270ec
94f4158
1ebe3b1
bda41fe
4cbc09e
29aeb39
0c833ce
0825285
c50ab7e
449f3d7
3c684ca
0e5b2ad
b349ada
81901ca
592ba33
6a6b3d5
c9fd81a
8555ef7
77f8eec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-24ddc0f5d75046c5622901739e7c5dd533143b0c8e959d652212380cedb1ea36.svg)](https://classroom.github.com/a/poMAp5Go) | ||
|
||
This repository contains all the files necessary for a online Jigsaw Puzzle game. The website can be found at: | ||
|
||
https://birkbeck2.github.io/web-project-adeledsg/index.html | ||
|
||
This online game was made in the context of a homework assignment and is not for commercial use. It is submitted along a report via Turnitin. | ||
|
||
# File content | ||
The following HTML and Javascript files are used to create the game: | ||
|
||
| File | Content | | ||
| --- | --- | | ||
| `index.html` | Landing page | | ||
| `puzzle1.html` | Game 1 - 2x2 Puzzle | | ||
| `puzzle2.html` | Game 2 - 3x3 Puzzle | | ||
| `puzzle3.html` | Game 3 - 4x4 Puzzle | | ||
| `interactive1.js` | Javascript for Puzzle 1 | | ||
| `interactive2.js` | Javascript for Puzzle 2 | | ||
| `interactive13.js` | Javascript for Puzzle 3 | | ||
|
||
Additionally, a global stylesheet entitled `style.css` is used to set styles for all four HTML pages above. | ||
|
||
Detailed comments providing explanation on implementation details, the files' structure, and references are mainly included in interactive1.js and style.CSS. As interactive2.js and intercative3.js follow the exact same logic, no further comments have been provided. | ||
|
||
All images (individual puzzle pieces) can be found in either the `puzzle1`, `puzzle2`, or `puzzle3` folder. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<!DOCTYPE html> | ||
|
||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>Jigsaw Puzzle</title> | ||
<link rel="stylesheet" href="style.css"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
</head> | ||
|
||
<body> | ||
<header> | ||
|
||
</header> | ||
<main> | ||
<section> | ||
<h1>Pick a jigsaw to complete</h1> | ||
<ul id = "buttons"> | ||
<li><a href="puzzle1.html" id="puzzle1" class="button">Puzzle 1 (Easy)</a></li> | ||
<li><a href="puzzle2.html" id="puzzle2" class="button">Puzzle 2 (Medium)</a></li> | ||
<li><a href="puzzle3.html" id="puzzle3" class="button">Puzzle 3 (Hard)</a></li> | ||
</ul> | ||
</section> | ||
</main> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
// I. Dynamically creating 4 div (box for the pieces) within my Puzzle container and then the img element within each div. | ||
let puzzleContainer = document.getElementById("puzzle-container"); | ||
|
||
function creatingGrid() { //2*2 puzzle | ||
let rows = 2; | ||
let columns = 2; | ||
|
||
for(let i=0; i<rows*columns; i++){ //creating 4divs and 4img | ||
let pieceDiv = document.createElement('div') // creating div element | ||
pieceDiv.className = "piece"; //class name added | ||
puzzleContainer.appendChild(pieceDiv); //Nesting them in puzzlecontainer. | ||
let img = document.createElement("img"); //creating img element | ||
img.className = "images"; //class name added | ||
img.id = `img${i}`; //unique ids | ||
img.setAttribute("draggable", "true"); // Attribute required for events below, source: https://medium.com/@tatismolin/how-to-implement-drag-and-drop-functionality-using-vanilla-javascript-9ddfe2402695 | ||
pieceDiv.appendChild(img); //dynamically created 4 with individual id elements and nested them in the divs just created above. | ||
} | ||
} | ||
creatingGrid(); //calling function | ||
|
||
let img0 = document.getElementById("img0"); //Retrieving img elements just created | ||
img0.src = "puzzle1/piece1.png"; //adding the source for the img elements. | ||
img0.width = "200"; // Specifying width -space when browser loading the page | ||
img0.height = "232"; //Specifying height -space when browser loading the page | ||
img0.alt = "puzzle piece 1" //Specifying al text - accessibility | ||
|
||
let img1 = document.getElementById("img1"); //Same as above, done 4 times | ||
img1.src = "puzzle1/piece2.png"; | ||
img1.width = "232"; | ||
img1.height = "200"; | ||
img1.alt = "puzzle piece 2" | ||
|
||
let img2 = document.getElementById("img2"); | ||
img2.src = "puzzle1/piece3.png"; | ||
img2.width = "232"; | ||
img2.height = "200"; | ||
img2.alt = "puzzle piece 3" | ||
|
||
let img3 = document.getElementById("img3"); | ||
img3.src = "puzzle1/piece4.png"; | ||
img3.width = "199"; | ||
img3.height = "232"; | ||
img3.alt = "puzzle piece 4" | ||
|
||
//II. Adding event listeners to start and reset buttons (Game Management) | ||
let pieceContainer = document.getElementById("piece-container"); | ||
|
||
document.addEventListener('DOMContentLoaded', function () { // Assuring page is loaded | ||
let startButton = document.getElementById('start'); | ||
let resetButton = document.getElementById('reset'); | ||
|
||
// Event listener for the start button | ||
|
||
startButton.addEventListener('click', function(e){ | ||
startButton.style.display = 'none'; // Hiding the start button | ||
resetButton.style.display = 'flex'; // Showing the reset button | ||
|
||
Array.from(dropZone).forEach(function(zone){ //Iterating through elements with this class name as DOM returns the elements as an array | ||
zone.style.border = "1px solid black"; //adding borders to div to indicate users where to put the pieces | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great use of Array.from and forEach |
||
|
||
let pieces = document.getElementsByClassName("piece"); | ||
let element = e.target; //moving pieces (div which include their nested img) currently in Puzzle container to Piece container on click event for start button. | ||
e.preventDefault(); | ||
|
||
Array.from(pieces).forEach(function(piece){ //Iterating through my piece divs (DOM understand class name as an array of all the element with that class) | ||
let leftPosition = Math.floor(Math.random()*50); //giving them random position (iterated through numbers for the multiplier to make sure that the piece where relatively contained within the piece container. | ||
let topPosition = Math.floor(Math.random()*50); | ||
piece.style.position = "absolute"; | ||
piece.style.left = `${leftPosition}%`; | ||
piece.style.top = `${topPosition}%` | ||
pieceContainer.appendChild(piece); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great idea! |
||
}); | ||
|
||
// Event listener for the reset button | ||
resetButton.addEventListener('click', function(e) { | ||
resetButton.style.display = 'none'; // Hiding the reset button | ||
startButton.style.display = 'flex'; // Showing the start button | ||
let element = e.target; | ||
window.location.reload(); // https://www.freecodecamp.org/news/javascript-refresh-page-how-to-reload-a-page-in-js/#:~:text=The%20simplest%20way%20to%20refresh,and%20loading%20the%20latest%20content. | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the start and reset buttons--they definitely put the user in control of the game and feel nice and intuitive. |
||
}); | ||
|
||
|
||
//III. After adding the event, I realised that I needed to create new divs that will remain in the puzzle container so I can later drop the divs that have the nested img in them. | ||
function creatingDropZones() { //2*2 puzzle - same principle as creatingGrid() | ||
let rows = 2; | ||
let columns = 2; | ||
|
||
for(let i=0; i<rows*columns; i++){ | ||
let dropZone = document.createElement('div') | ||
dropZone.className = "drop-zone"; | ||
puzzleContainer.appendChild(dropZone); | ||
} | ||
} | ||
creatingDropZones(); | ||
|
||
|
||
//IV. Implementing logic of the game with drag and drop events: | ||
|
||
/* A. Creating unique ID dynamically to my drag and drop divs, to later be able to create a function to match the correct place of each piece*/ | ||
let dropZone = document.getElementsByClassName("drop-zone"); | ||
|
||
Array.from(dropZone).forEach(function(dropZone, i){ | ||
dropZone.id = `drop-${i}`; | ||
}); | ||
|
||
let pieceDiv = document.getElementsByClassName("piece"); | ||
|
||
Array.from(pieceDiv).forEach(function(pieceDiv, i){ | ||
pieceDiv.id = `drag-${i}`; | ||
}); | ||
|
||
//B. Adding drag and drop events to the divs. Source: https://www.youtube.com/watch?v=_G8G1OrEOrI&ab_channel=DarwinTech | ||
|
||
/*Process explained: | ||
After implementing drag and drop, I realised that it does not work for mobile devices as they are mouse based events. | ||
Instead I need to use touch start, move and end. As such, to make my code as clear as possible, I have created 3 functions below. | ||
Since event listeners can take 2 parameters, each take either drag/drop/move or touch event as a firt parameter along with calling the appropriate function that will handle their common logic and account for their differences.*/ | ||
let draggableImages = document.getElementsByClassName("images"); | ||
var globalDraggedItemId = null; // set data and get data are methods that do not exist for touch events. Creating global variable to maintain state. | ||
|
||
function handleStart(e) { | ||
e.stopPropagation(); // ensure that the click event is just on the image not its parent | ||
if (e.type === 'dragstart') { | ||
console.log(e); | ||
e.dataTransfer.setData('text/plain', e.currentTarget.id); //Seeting the data to add id to my target element that is my div and retriving it in drop- source: https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setData | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent. This is complicated stuff! |
||
} | ||
if (e.type === 'touchstart') { | ||
e.preventDefault(); | ||
} | ||
globalDraggedItemId = e.currentTarget.id; // setting data, source https://stackoverflow.com/questions/53530511/imitating-drag-and-drop-events-with-touch-events-for-mobile-devices | ||
} | ||
|
||
function handleOverMove(e){ | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
let draggedImage = e.currentTarget; | ||
draggedImage.style.zIndex = 1000; // Without giving high index to the images and low to their divs, clicking on images didnt work if a div was above it. | ||
let originalContainer = draggedImage.parentElement; | ||
originalContainer.style.zIndex = 1; | ||
} | ||
|
||
function handleDropEnd(e) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
|
||
let draggedItem; | ||
let targetContainer = e.currentTarget; | ||
|
||
if (targetContainer.querySelector('img')){ //if my dropdiv (target container) has an image already, user cant put another there. | ||
|
||
} else { | ||
if (e.type === 'drop') { | ||
let data = e.dataTransfer.getData("text/plain"); //retrieving id and moving it the div to target. | ||
draggedItem = document.getElementById(data); | ||
} else if (e.type === 'touchend') { | ||
draggedItem = document.getElementById(globalDraggedItemId); | ||
} | ||
if (draggedItem) { | ||
e.currentTarget.appendChild(draggedItem); | ||
globalDraggedItemId = null; // Reset the global variable | ||
} | ||
|
||
checkPosition(); //calling function for alert messages below | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is really impressive that you have handled both click-and-drag for mouse users and double-touch usability for touch screens. I even found the UX better on my phone than with a mouse. |
||
|
||
} | ||
|
||
} | ||
|
||
|
||
Array.from(draggableImages).forEach(function(image){ //Iterating through each element and assigning touch and mouse event to each. | ||
image.addEventListener('dragstart', handleStart); | ||
image.addEventListener('touchstart', handleStart); | ||
}); | ||
|
||
Array.from(dropZone).forEach(function(zone){ | ||
zone.addEventListener('dragover', handleOverMove); | ||
zone.addEventListener('touchmove', handleOverMove); | ||
zone.addEventListener('drop', handleDropEnd); | ||
zone.addEventListener('touchend', handleDropEnd); | ||
}); | ||
|
||
// V. Finish message | ||
let correctPosition = { // creating an object using individual ids created earlier. | ||
img0: "drop-0", | ||
img1: "drop-1", | ||
img2: "drop-2", | ||
img3: "drop-3", | ||
}; | ||
|
||
function checkPosition() { | ||
let allPlaced = true; | ||
let allCorrect = true; | ||
|
||
for (let [imgId, dropId] of Object.entries(correctPosition)) { //iterating through my object that contains the correct positions of each piece. | ||
let image = document.getElementById(imgId); | ||
let currentDrop = image.parentElement.id; | ||
|
||
// Checking if all pieces are placed in the drop zone divs | ||
if (!image.parentElement || !image.parentElement.classList.contains('drop-zone')) { | ||
allPlaced = false; | ||
} | ||
|
||
// Checking if the images' parents' id are that of the dropzone id => aka they are they correctly placed | ||
if (currentDrop !== dropId) { | ||
allCorrect = false; | ||
} | ||
} | ||
|
||
if (allPlaced && !allCorrect) {// All pieces are placed, but some are wrong | ||
setTimeout(() => { | ||
alert("You are almost there! But some pieces are in the wrong place."); | ||
}, 500); // Delay to allow for the puzzle to be visually completed before showing the message | ||
} else if (allPlaced && allCorrect) { // All pieces are placed correctly | ||
Array.from(dropZone).forEach(function(drop){ //purely aesthetics considerations, otherwise, we would see the borders | ||
drop.style.border = "none"; | ||
}) | ||
setTimeout(() => { | ||
alert("Congratulations! You completed the puzzle."); | ||
}, 500); | ||
} | ||
|
||
return true; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is quite a complex function! Bravo for tackling the logic here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good for a simple puzzle, but as you say in the report, it would be good to make the code DRYer (referring to the principle of "don't repeat yourself"). Maybe the best way to do that is to store the data needed to construct the puzzle in a bit of JSON, and then iterate over it to populate the HTML.
By the way, I do like the style of short lines and good descriptive variables, as it is nice and readable.