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
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
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
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.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.
If you’d like to discuss your startup or project, get in touch with Simpleweb today.