CREATING THE PUZZLE
First, create your image. It should be 3×3 squares. Resulting in 9 boxes. The last needs to be a transparent image. Export and save.
Put in a container and give it a class of ” puzzle-container “
Next drop in the grid, create a 3 x 3 grid. Ensure the cell is the same size as your image square.
Rename the cell class to ” puzzle-piece “
Then in the custom attributes add:
- Name: data-id
- Value: 1
Do that for all the cell class of ” puzzle-piece ” but change the value to correspond to the puzzle place, 1 for the 1st piece, 2 for the second piece and so on. HOWEVER, for the last transparent piece. Give it a value of 0.
Then, drop the images in to the cells in the correct order.
Give the image of a class ” puzzle image “
Ensure the images have the alt class “Puzzle Piece 1”, the next “Puzzle Piece 2” and so on.
Add this code in the head
<style>
/* Hide the empty slot */
.puzzle-piece[data-id="0"] {
display: none;
}
/* Optional: Hover effect */
.puzzle-piece:hover {
opacity: 0.8;
}
/* Responsive design adjustments */
@media (max-width: 600px) {
.puzzle-container {
width: 100%;
height: auto;
aspect-ratio: 1 / 1; /* Maintain square aspect ratio */
}
}
</style>
Add this to the body. NOTE: Where I put “URL OF IMAGE 1” etc put the url of the actual image into it.
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
const gridSize = 3; // 3x3 grid
const totalPieces = gridSize * gridSize;
const puzzleContainer = document.querySelector('.puzzle-container');
const pieces = Array.from(document.querySelectorAll('.puzzle-piece'));
const congratsMessage = document.querySelector('.congrats-message');
const restartButton = document.getElementById('restart-button');
const closeButton = document.getElementById('close-button');
const moveCountElement = document.querySelector('.move-count');
const timerCountElement = document.querySelector('.timer-count'); // Select the timer-count element
// Map piece numbers to image URLs
const imageSources = {
1: 'URL OF IMAGE 1',
2: 'URL OF IMAGE 2',
3: 'URL OF IMAGE 3',
4: 'URL OF IMAGE 4',
5: 'URL OF IMAGE 5',
6: 'URL OF IMAGE 6',
7: 'URL OF IMAGE 7',
8: 'URL OF IMAGE 8',
};
let positions = [];
let moveCount = 0; // Initialize move count
let timerInterval = null;
let elapsedTime = 0; // Time in seconds
let isTimerStarted = false; // Flag to track if timer has started
// Function to update the move count display
function updateMoveCount() {
if (moveCountElement) {
moveCountElement.textContent = `Moves: ${moveCount}`;
console.log(`Move count updated: ${moveCount}`);
} else {
console.error('Move count element not found. Ensure there is a div with class "move-count".');
}
}
// Function to update the timer display
function updateTimerCount() {
if (timerCountElement) {
timerCountElement.textContent = `Time: ${elapsedTime}s`;
console.log(`Timer updated: ${elapsedTime}s`);
} else {
console.error('Timer count element not found. Ensure there is a div with class "timer-count".');
}
}
// Function to start the timer
function startTimer() {
// Prevent multiple timers
if (isTimerStarted) return;
isTimerStarted = true;
console.log('Timer started.');
// Start a new timer
timerInterval = setInterval(() => {
elapsedTime++;
updateTimerCount();
}, 1000); // Update every second
}
// Function to stop the timer
function stopTimer() {
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
console.log('Timer stopped.');
}
}
// Function to initialize the puzzle
function initPuzzle() {
// Initialize positions array with IDs from 1 to 8 and 0 for empty slot
positions = [];
for (let i = 0; i < totalPieces; i++) {
positions.push(i === totalPieces - 1 ? 0 : i + 1); // [1,2,3,4,5,6,7,8,0]
}
// Shuffle positions to start the game
positions = shuffle(positions.slice());
// Ensure that there is exactly one empty slot
if (positions.filter(id => id === 0).length !== 1) {
console.error('There must be exactly one empty slot (data-id="0").');
return;
}
// Map shuffled positions to the puzzle pieces
pieces.forEach((piece, index) => {
const posIndex = positions[index];
piece.dataset.position = index.toString(); // Current grid position
if (posIndex === 0) {
// Empty slot
piece.dataset.id = '0';
// Use a transparent image for the empty slot
const img = piece.querySelector('img');
if (img) {
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; // 1x1 transparent pixel
img.alt = '';
} else {
console.error(`Image element not found in piece with data-id 0`);
}
} else {
piece.dataset.id = posIndex.toString();
// Update the image source based on the shuffled position
const img = piece.querySelector('img');
if (img) {
img.src = imageSources[posIndex];
img.alt = `Puzzle Piece ${posIndex}`;
} else {
console.error(`Image element not found in piece with data-id ${posIndex}`);
}
}
// Set the grid position
setPosition(piece, index);
});
// Hide the congratulatory message if it's visible
if (congratsMessage) {
congratsMessage.style.display = 'none';
} else {
console.error('Congratulatory message div not found. Ensure there is a div with class "congrats-message".');
}
// Reset move count and timer
moveCount = 0;
updateMoveCount();
stopTimer();
isTimerStarted = false;
elapsedTime = 0;
updateTimerCount();
console.log('Puzzle initialized with positions:', positions);
}
// Initialize the puzzle
initPuzzle();
// Add click event listeners to the pieces
pieces.forEach(piece => {
piece.addEventListener('click', () => {
movePiece(piece);
});
});
// Event listener for the restart button
if (restartButton) {
restartButton.addEventListener('click', () => {
initPuzzle();
});
} else {
console.error('Restart button not found. Ensure there is a button with ID "restart-button".');
}
// Event listener for the close button
if (closeButton) {
closeButton.addEventListener('click', () => {
if (congratsMessage) {
congratsMessage.style.display = 'none';
console.log('Congratulatory message closed.');
} else {
console.error('Congratulatory message div not found.');
}
});
} else {
console.error('Close button not found. Ensure there is a button with ID "close-button".');
}
// Function to set the position of a piece in the grid
function setPosition(piece, positionIndex) {
piece.style.gridColumnStart = (positionIndex % gridSize) + 1;
piece.style.gridRowStart = Math.floor(positionIndex / gridSize) + 1;
piece.dataset.position = positionIndex.toString();
// Show the piece
piece.style.display = '';
}
// Function to move a piece if it's adjacent to the empty slot
function movePiece(piece) {
const emptyPiece = pieces.find(p => p.dataset.id === '0');
if (!emptyPiece) {
console.error('Empty piece not found. Ensure there is a puzzle-piece with data-id="0".');
return;
}
const emptyIndex = parseInt(emptyPiece.dataset.position);
const pieceIndex = parseInt(piece.dataset.position);
console.log(`Attempting to move piece ${piece.dataset.id} from position ${pieceIndex} to empty position ${emptyIndex}`);
const isAdjacent = checkAdjacent(pieceIndex, emptyIndex);
if (isAdjacent) {
// Swap positions
swapPieces(piece, emptyPiece);
// Increment move count
moveCount++;
updateMoveCount();
// Start the timer on the first move
if (!isTimerStarted) {
startTimer();
}
// Check if the puzzle is solved
if (checkWin()) {
stopTimer(); // Stop the timer when the puzzle is solved
showCongratsMessage();
}
} else {
console.log(`Piece ${piece.dataset.id} is not adjacent to the empty slot.`);
}
}
// Function to swap two pieces
function swapPieces(piece1, piece2) {
const pos1 = parseInt(piece1.dataset.position);
const pos2 = parseInt(piece2.dataset.position);
// Swap positions in the positions array
[positions[pos1], positions[pos2]] = [positions[pos2], positions[pos1]];
console.log(`Swapped piece ${piece1.dataset.id} (position ${pos1}) with piece ${piece2.dataset.id} (position ${pos2})`);
// Update dataset positions
piece1.dataset.position = pos2.toString();
piece2.dataset.position = pos1.toString();
// Update positions in the DOM
setPosition(piece1, pos2);
setPosition(piece2, pos1);
}
// Function to check if two indices are adjacent in the grid
function checkAdjacent(index1, index2) {
const row1 = Math.floor(index1 / gridSize);
const col1 = index1 % gridSize;
const row2 = Math.floor(index2 / gridSize);
const col2 = index2 % gridSize;
// Check if the positions are next to each other
const isAdjacent = (Math.abs(row1 - row2) + Math.abs(col1 - col2)) === 1;
console.log(`Checking adjacency between positions ${index1} and ${index2}: ${isAdjacent}`);
return isAdjacent;
}
// Function to check if the puzzle is solved
function checkWin() {
const correctOrder = [1, 2, 3, 4, 5, 6, 7, 8, 0];
for (let i = 0; i < positions.length; i++) {
if (positions[i] !== correctOrder[i]) {
return false;
}
}
console.log('Puzzle solved!');
return true;
}
// Function to show the congratulatory message
function showCongratsMessage() {
if (congratsMessage) {
congratsMessage.style.display = 'flex'; // Use 'flex' to match the CSS styling
console.log('Congratulatory message displayed.');
} else {
console.error('Congratulatory message div not found. Ensure there is a div with class "congrats-message".');
}
}
// Function to shuffle the positions array
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// Swap elements
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
}
// Ensure the puzzle is solvable
if (!isSolvable(array)) {
// Swap two tiles (excluding the empty slot) to make it solvable
if (array[0] !== 0 && array[1] !== 0) {
[array[0], array[1]] = [array[1], array[0]];
console.log('Shuffled array was unsolvable. Swapped first two tiles to make it solvable.');
} else {
[array[1], array[2]] = [array[2], array[1]];
console.log('Shuffled array was unsolvable. Swapped second and third tiles to make it solvable.');
}
}
console.log('Shuffled positions:', array);
return array;
}
// Function to check if the shuffled puzzle is solvable
function isSolvable(array) {
let inversions = 0;
for (let i = 0; i < array.length; i++) {
for (let j = i + 1; j < array.length; j++) {
if (array[i] && array[j] && array[i] > array[j]) {
inversions++;
}
}
}
const emptyRow = gridSize - Math.floor(array.indexOf(0) / gridSize);
if (gridSize % 2 !== 0) {
// Odd grid size, solvable if inversions are even
const solvable = inversions % 2 === 0;
console.log(`Odd grid size. Inversions: ${inversions}. Solvable: ${solvable}`);
return solvable;
} else {
// Even grid size
if (emptyRow % 2 === 0) {
const solvable = inversions % 2 !== 0;
console.log(`Even grid size. Empty row from bottom: ${emptyRow}. Inversions: ${inversions}. Solvable: ${solvable}`);
return solvable;
} else {
const solvable = inversions % 2 === 0;
console.log(`Even grid size. Empty row from bottom: ${emptyRow}. Inversions: ${inversions}. Solvable: ${solvable}`);
return solvable;
}
}
}
});
</script>
CONGRATULATIONS MESSAGE
I also added the function to give a congratulations message and to restart the puzzle or not.
Create a Div with the class “congrats-message”
Throw another Div within it and class the class “congrats-content”
Put your message in there.
Then create a button for restart, and give it an ID (which can be found in the settings) of “restart-button”
Then create another button but with the ID “close-button”
ADDING STATS
I also included stats for the puzzle, which are a timer and number of moves.
Timer
Create a div with the class “timer”, then throw a paragraph within it, give the class “timer-count” for the paragraph. Throw in dummy text, like timer.
Moves
Create a div with the class “move-counter”, then throw a paragraph within it, give it the class “move-count” for the paragraph. Throw in dummy text, like move count.
And that should be it!
Leave a Reply
You must be logged in to post a comment.