Match Three Part 2

Written by Hunter Jansen on March 30, 2017

Last time out, I wrote about all the logic involved with my javascript match three game proof of concept. However, that post didn’t talk about the more interesting aspect of how to draw and animate the collapsing of the board. I’ll be covering that in this post. If you haven’t read that post and are interested in that logic; check it out here - or just look for the previous entry on my blog.

ScreenShot

Drawing the board (Or BoardDom, lol)

For drawing the board, I wanted to take as much advantage of the DOM as possible and wanted to avoid the manual positioning/tweening of moving cells down from their previous location. This seemed like a perfect candidate for flexbox. All a basic match three board is is a series of columns that need to align to the bottom and automatically align to bottom when tile layouts change. So that left with a dom structure like:

BoardDom

So we have a series of columns, containing a series of tiles - you’ll notice that each tile contains information about it’s place in the board and what type/colour it is. We use these columns to vertically align with flex box and flex end:

.column {
    margin-bottom: -4px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
}

And that gives us a series of tiles in a column sort of layout! So then it goes on to the harder part, removing, collapsing and cascading with animations

Preparing tiles for removal

In the previous post we created a function to determine where the tiles are that should get removed(we called them clusters). Here, we set those tiles to a deleted/blank state.

for (var i = 0; i < clusters.length; i++) {
    let cluster = clusters[i];
    let horizontalOffset = 0;
    let verticalOffset = 0;
    if (cluster.horizontal == true) {
        verticalOffset = 1;
    } else {
        horizontalOffset = 1;
    }
    for (var j = 0; j < cluster.length; j++) {
        let xval = cluster.row + horizontalOffset * j;
        let yval = cluster.column + verticalOffset * j;
        board.tiles[xval][yval].type = deletedTile;
        this.addTileClass(xval, yval, 'removing');
    }
}

Here we basically just loop through the clusters and set all the tiles set for removal and set them as a deleted tile. We call this step ‘Show Matches’ - we also have a function in place to update the class on the individual tile’s styling dependant on the type of the tile.

Creating replacement tiles

So now we have a board with a bunch of tiles set up for removal. If we were to just remove those tiles, we would have a bunch of empty space at the top of the board. Now, we could just create new tiles directly in those now empty spots at the top, like a normal match three game, we want the new tiles to drop in from the top - similar to the gif below.

dropping

So we need to create tiles ABOVE the board out of site that drop down into our view port when the deleted tiles are removed. The way I chose to do this was basically this:

  • Loop through columns
  • Find tiles marked for removal
  • Prepend the new tile to the columns tiles (since our column's flex-box is aligned to the end)

At this point, we have our board full of tiles; some marked for deletion, with new tiles outside of the viewport ready to drop in to refill the board when the tiles are removed.

for (var i = 0; i < board.columns; i++) {
    for (var j = 0; j < replacementCells[i].length; j++) {
        $(`.column[data-y='${i}']`).prepend(replacementCells[i][j]);
    }
}

Removing cells

Now for the fun part - removing cells and animating the removal. For this I knew that I wanted to use a css animation or keyframe, but I really wasn’t sure which one.

After a bunch of trial and error with various css transitions and position offsets; I opted for changing the height of the tiles to be deleted, followed by immediately removing the cells.

First, we make some classes to handle this height aspect.

.collapsing {
    animation: COLLAPSING 1s;
}

@keyframes COLLAPSING {
    from {
        min-height: 30px;
        height: 30px;
    }
    to {
        min-height: 0;
        height: 0px;
    }
}


So now when we add a class of collapsing to a tile, the animation of the tile’s from height 30px to 0px over 1second fires. All we have to do is apply the collapsing class to all of the tiles set for removal:

$('.blank')
    .addClass('collapsing')

Hooking into the end of the css keyframes

The animation above is super neat and all that, but currently, after it ends the tile goes back to it’s previous position. That’s about as useless as this box.

Useless

So now, we alter the above code a little bit to hook in to the animationend event that the browser fires when the key frame is over to remove the tiles from the board.

$('.blank')
    .addClass('collapsing')
    .on("animationend",
        (e) => {
            e.target.remove();
        });

As a result - thanks to flexbox and the collapsing height on the removal cells - we get all the cells dropping into the place that they’re supposed to be, and new cells dropping in from the top to keep the board full. With this bit, we have all the major bits that we need in order to make our match three game!

Cascading

Wrapping up

There’s obviously a lot that I didn’t talk about, like selecting tiles, and how to deal with the unknown asyncronous aspect of cascating and waiting. (Hint: promises) - so it’s a good thing that it’s all open source and available.

The end results can be found here with a demo available on my site (There’s output in the console as well).

The end results aren’t polished or entirely bug free - but that wasn’t really the point of this whole thing. This exercise was really just a proof of concept since I really wasn’t sure if it was possible to do what I wanted to or not.

TL;DR - It is possible!

Got any feedback, questions, or similar experiences?

Hit me up on twitter (link in the footer), make an issue on the repo (further up in the post), or drop me a line at hunter@hyperwidget.com

Thanks for reading! -Hunter