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”
For me, I made it so it is an absolute div that will fill up the whole screen. You can design it as you want.
Make sure it is hidden when you are done. You can do this by going to Layout>Display>None
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.