Short intro
I've recently had the joy of learning how to make a slider puzzle mini-game for a bigger game project and decided to share the progress here.
We will be using plain javascript (with some CSS and HTML) to make a slider puzzle that will -hopefully- look something like :
Getting started
The source code and images are provided on Github
You can also try the hosted Netlify version HERE
The size of our puzzle will be 4x4.
let's start by defining the size of a row, the total number of tiles we need, and a div 'container' to contain our puzzle:
let size = 4;
let numberOfTiles = size ** 2;
let empty = numberOfTiles;
let container=document.createElement("div");
container.id="puzzle";
let shuffled=false;
loadTiles();
shuffle();
Our first function will be to load our tiles
where we will loop over every tile row by row.
function loadTiles() {
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
Each tile will represent a piece of the image.
To make keeping track easier we will also give them an index and name, the index will keep track of the tile and the name will be the piece or image currently on this tile.
let newTile = document.createElement("img");
let id = x * size + y + 1;
newTile.id=id;
newTile.name = id;
newTile.className = "tile";
newTile.src = `./images/${id}.png`;
Next, we will make the tiles interactive by clicking on them using an event listener with a swap function that we will define later.
The last step of our loadTiles function will be appending the new tile to our container.
newTile.addEventListener("click", function () {
swap(id);
});
container.append(newTile);
}
}
document.body.appendChild(container);
}
Shuffle
This is all we need to do to show a solved puzzle(minus the CSS). However as it is not much fun solving something that is already solved, we will continue by making our shuffle function!
Avoid creating an impossible puzzle
You can't simply place the tiles in any randomly generated order as this can lead to unsolvable puzzles, ending up with one tile in the wrong place like this example here:
The right way
There are a few ways to avoid this, For this project, I chose to solve it by starting from our solved puzzle and shuffling it by executing a randomized number of quick slides in random directions, mimicking the player's movement.
We start by defining that our puzzle will be shuffled between 300 and 500 times.
function shuffle() {
let minShuffles = 300;
let totalShuffles = minShuffles + Math.floor(Math.random() * 200);
We make a loop that will execute the 'swap' function for every shuffle and give it a random direction.
As our puzzle is a 4x4 we can add or remove 1 to our empty tile for swapping left or right, or 4 to swap up and down.
We will also add a small delay per shuffle using the SetTimeout function, this creates a nice visual effect as it shuffles.
for (let i = 0; i <= totalShuffles; i++) {
setTimeout(function timer() {
let x = Math.floor(Math.random() * 4);
let direction = 0;
if (x == 0) {
direction = empty+ 1;
} else if (x == 1) {
direction = empty- 1;
} else if (x == 2) {
direction = empty+ size;
} else if (x == 3) {
direction = empty- size;
}
swap(direction);
if (i > totalShuffles - 1) {
shuffled = true;
}
}, i * 10);
}
As you can see there is also a new global boolean 'shuffled' that gets set to true on the last shuffle, this is to prevent our game from falsely flagging our un-shuffled puzzle as completed (more on this later)!
Swapping tiles
Now it is finally time to write our swap function!
This will take an input of the tile you want to swap with the empty one. And starts by checking if this is a valid move (the number has to be between 1 and 16 to stay on our 4x4 board)
function swap(tile) {
if (tile < 1 || tile > numberOfTiles) {
return;
}
Next, our swap will check which direction the move is by comparing it to our empty tile, and verify per direction if the move can be made (we can only swap tiles bordering our empty tile!).
//swap right
if (tile == empty + 1) {
if (tile % size != 1) {
setSelected(tile);
}
//left
} else if (tile == empty - 1) {
if (tile % size != 0) {
setSelected(tile);
}
//up
} else if (tile == empty + size) {
setSelected(tile);
//down
} else if (tile == empty - size) {
setSelected(tile);
}
if (shuffled) {
if (checkHasWon()) {
setTimeout(() => {
alert("You solved the puzzle!");
}, 500);
}
As you can see there are 2 new functions here we still have to implement.
Firstly When the move can be made we call our setSelected function, this will change our empty tile with the tile we want to move.
To do this we will swap their name, image, and 'selected' class.
function setSelected(index) {
let currentTile = document.getElementById(`${empty}`);
let newTile = document.getElementById(`${index}`);
let swap = currentTile.src;
currentTile.src = newTile.src;
newTile.src = swap;
swap = currentTile.name;
currentTile.name = newTile.name;
newTile.name = swap;
newTile.classList.add("selected");
currentTile.classList.remove("selected");
empty = index;
}
We've also added a checkHasWon function at the end of the swap function, which as you may have guessed will validate if the puzzle is completed.
Thanks to the way the code is set up all this function has to do is loop over our 16 tiles and compare the assigned name and index, if any of them fail it will return false otherwise at the end of the loop it will return true.
function checkHasWon() {
for (let b = 1; b <= numberOfTiles; b++) {
currentTile = document.getElementById(`${b}`);
currentTileValue = currentTile.name;
if (b != parseInt(currentTileValue)) {
return false;
}
}
return true;
}
Keyboard controls
By now you should have a functioning slider puzzle game allowing you to click the tiles to move them around, if you want to add a keyboard functionality to your game you can do this simply by adding an onkeydown event to the window that calls swap on keypresses.
const RIGHT_ARROW = 39;
const LEFT_ARROW = 37;
const UP_ARROW = 40;
const DOWN_ARROW = 38;
window.onkeydown = function (event) {
if (event.keyCode === RIGHT_ARROW) {
swap(empty + 1);
} else if (event.keyCode === LEFT_ARROW) {
swap(empty - 1);
} else if (event.keyCode === UP_ARROW) {
swap(empty + size);
} else if (event.keyCode === DOWN_ARROW) {
swap(empty - size);
}
}
Small ending note
Thank you for taking the time to follow this little article along,
Hope you enjoyed it as much as I enjoyed building it.
Top comments (0)