T’is the season, by which I mean the season where some of us have down-time to do a little practical learning with code. And what better way to learn this time of year than by making a toy you can actually play with. A couple of days ago I set myself the task of creating a sliding puzzle game, that you can actually play, using modern front-end development techniques. In this article I’m going to run through the techniques I used and explain how I put them all together to create the final product which you can see on CodePen.

Setting up the core structure

We’re going to begin where I often do and that’s with the markup. Establishing the HTML structure of your app / web page / component can not only help you ensure you’ll have an accessible UI but it’ll aid your understanding of how the interactions might work and behave as well. I knew I wanted to use CSS Grid so I immediately put together a 3 by 3 grid with 8 buttons and 1 blank div:

However I couldn’t just let these items flow automatically in the grid, I was going to need to know where they were within it. By using grid-template-areas I could assign a part of the grid, or grid ‘cell’, a name which was a single letter in this case.

Once I had these I could allocate each of the items in the grid a grid-area, however to be more versatile I used CSS Custom Properties. I created a custom property simply called --area which I set as a style attribute with a value on each of the buttons and the blank div.

The values on each of the items in the grid are alphabetical A to I in order. This is just the kind of scenario where a custom property can be used, where you want to give an element a style value but you still have flexibility on how that value is used. The --area value is then picked up in the CSS with grid-area: var(--area, auto), the auto value is a default incase the custom property hasn’t been provided a value.

We’re going to need an image as well so people have an actual puzzle to put together. Unsplash has a useful CDN that accepts search parameters, so with a few with festive words added I could pull through a selection of seasonable imagery. Then it’s just a matter of applying the image as a background to each of the buttons. To apply just a portion of the image to each button I scaled up the background to 3 times the size and then position it differently for each button depending on its position on the grid.

Applying interactions and animations

A part inspiration to this idea was a library that caught my eye on GitHub. Alex Holachek created a tool specifically designed to animate CSS Grid which currently, at the time of writing of this article, is quite hard to achieve cleanly.

This library would allow me to animate the tiles sliding from one position to another. Another bonus to this library is that it’s purely aesthetic and won’t affect actual interactions and how the game works. So if the library stops working, the game will still be playable.

In order to animate these tiles I need to listen to an event, in this case the click event. In its simplest form; whenever a tile is clicked I want that tile to move to the empty slot. In my HTML there is a blank div in the empty slot, so really I want the button tile and blank item to swap. Note that I’m not concerning myself with whether the tile can move or not, this is something I can solve later on when I have the essential behaviours working.

Because I set the positions using Custom Properties I was able to pluck them from the style attribute using getPropertyValue('--area') and set them with setProperty('--area', newValue), meaning I could easily take those values and assign them to the opposing element.

Applying game logic

One of the main challenges around creating this game is that not all tiles can be moved, and they change as the empty slot moves around the puzzle. If you think about it though, the empty slot or blank div is always next to the tiles that can be moved. From this I can deduce what tiles should be “unlocked”. If the empty slot is in position A, for example, only B and D tiles can be moved.

In this case a move is really a click and because I simply swap the empty div with the clicked button I don’t need to figure out the complex logic of tile sliding direction. The best method I could think of to work out which tiles could be moved when the empty slot was in a certain position was to note them down in some sort of key reference store, which is what JavaScript objects are ideal for.

So now when I want to know which tiles can be clicked I can just find the key in this object and it’ll give back the tile areas that can be clicked. I can also work out the ones that cannot be clicked. Whenever a tile is clicked the empty slot moves, so when it moves I check its new position against the reference object and get back the tiles that can be unlocked. I can find out all the tiles that need to be locked as well. To lock the tiles I used the HTML disabled attribute which prevents the button from being clickable and blocks any event listeners.

Another thing possible with this key reference object is checking if the user has won. What use is a game if you can’t win? With another check of the tiles I can compare the order of the area values in the DOM with the Object.keys() of the key reference object (Object.keys is the actual names of the keys in the object). The original order of the markup was A to I in alphabetical order so, since I wrote the key reference keys in alphabetical order, if they were to match I know the user has completed the puzzle!

Setting up the game

The last thing I need to do is set up the challenge for the user to tackle, which means shuffling the tiles up for the user to …un-shuffle. Some of you are probably thinking “a bit of Math.random() on the tile areas and we’re done” and you’d be mostly right, except in some cases that can lead to an impossible puzzle. This was made apparent to me after some user testing from people on CodePen and on Twitter.

It’s all to do with inversions; when two items in a list that were in order but are now not. In order for sliding puzzles like these to work there must be an even number of inversions. I would go into detail about this but there’s a nice StackExchange comment that explains it much better than I ever could.

With the assistance of Array.reduce(), Array.filter() and a couple of other things I’m able to know how many inversions there are in the newly shuffled array of areas. If the number is even it will move on, if it’s odd or 0 it’ll shuffle again until the result meets the criteria to move on:

Once it’s happy with the shuffled areas it will set them on the elements, animate them with the CSS Grid animation library and finally check which tiles can and cannot be moved.

A touch of style

Like I said at the top of this article, t’is the season! So I’d better get in the spirit and style up this puzzle. I went for a look that reminded me of the old puzzle games I use to get in cereal boxes. Some box shadow to give the toy some smooth rounded edges, a carefully positioned pseudo element for a shiny finish, even some subtle texture on the background, a fun font for the heading and some ASCI stars to finish it off.

See the Pen Sliding Puzzle Game – Animated CSS Grid by David Darnes (@daviddarnes) on CodePen.

I am extremely happy with the final result. I can’t think of the last time I built a game like this from (pretty much) scratch. I learnt a lot of stuff as built it and I established some conventions that I hadn’t realised I’m doing already. However I think the most important part of this demo is exactly that, a demo of the powerful tools we have built directly into the browser that we can access with vanilla JavaScript and CSS. Feel free to poke around the code on CodePen, or have a play. 🕹

If you’d like to discuss your startup or project, get in touch with Simpleweb today.

Related Stories