Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From \$16.50/m

# Creating a Hexagonal Minesweeper

Difficulty:BeginnerLength:LongLanguages:

In this tutorial, I will try to introduce the interesting world of hexagonal tile-based games using the easiest of approaches. You will learn how to convert a two-dimensional array data to a corresponding hexagonal level layout on screen and vice versa. Using the information gained, we will be creating a hexagonal minesweeper game in two different hexagonal layouts.

This will get you started with exploring simple hexagonal board games and puzzle games and will be a good starting point to learn more complicated approaches like the axial or cubic hexagonal coordinate systems.

## 1. Hexagonal Tiles and Layouts

In the current generation of casual gaming, we don't see many games which use a hexagonal tile-based approach. Those we come across are usually puzzle games, board games, or strategy games. Also, most of our requirements are met by the square grid approach or isometric approach. This leads to the natural question: "Why do we need a different and obviously complicated hexagonal approach?" Let's find out.

### Advantages of the Hexagonal Approach

So what makes the hexagonal tile-based approach relevant, since we already have other approaches learned and perfected? Let me list some of the reasons.

• Smaller number of neighbour tiles: When compared to a square grid, which will have eight neighbour tiles, a hexagonal tile will only have six neighbours. This reduces computations for complicated algorithms.
• All neighbour tiles are at the same distance: For a square grid, the four diagonal neighbours are far away when compared to the horizontal or vertical neighbours. Neighbours being at equal distances is a great relief when we are calculating heuristics and reduces the overhead of using two different methods to calculate something depending on the neighbour.
• Uniqueness: These days, millions of casual games are coming out and are competing for the player's time. Great games are failing to get an audience, and one thing that can be guaranteed to grab a player's attention is uniqueness. A game using a hexagonal approach will visually stand out from the rest, and the game will seem more interesting to a crowd who are bored with all the conventional gameplay mechanics.

I would say the last reason should be enough for you to master this new approach. Adding that unique gameplay element over your game logic could make all the difference and enable you to make a great game.

The other reasons are purely technical and would only come into effect once you are dealing with complicated algorithms or larger tile sets. There are many other aspects also which can be listed as advantages of the hexagonal approach, but most of them will depend on the player's personal interest.

### Layouts

A hexagon is a polygon with six sides, and a hexagon with all sides having the same length is called a regular hexagon. For theory purposes, we will consider our hexagonal tiles to be regular hexagons, but they could be squashed or elongated in practice.

The interesting thing is that a hexagon can be placed in two different ways: the pointy corners could be aligned vertically or horizontally. When pointy tops are aligned vertically, it is called a horizontal layout, and when they are aligned horizontally, it is called a vertical layout. You may think that the names are misnomers with respect to the explanation provided. This is not the case as the naming is not done based on the pointy corners but the way a grid of tiles gets laid out. The image below shows the different tile alignments and corresponding layouts.

The choice of layout entirely depends on your game's visuals and gameplay. Yet your choice does not end here as each of these layouts could be implemented in two different ways.

Let's consider a horizontal hexagonal grid layout. Alternative rows of the grid would need to be horizontally offset by `hexTileWidth/2`. This means we could choose to offset either the odd rows or the even rows. If we also display the corresponding row, column values, these variants would look like the image below.

Similarly, the vertical layout could be implemented in two variations while offsetting alternative columns by `hexTileHeight/2` as shown below.

## 2. Implementing Hexagonal Layouts

From here on onwards, please start referring to the source code provided along with this tutorial for better understanding

The images above, with the rows and columns displayed, make it easier to visualise a direct correlation with a two-dimensional array which stores the level data. Let's say we have a simple two-dimensional array `levelData` as below.

To make it easier to visualise, I will show the intended result here in both vertical and horizontal variations.

Let's start with horizontal layout, which is the image on the left side. In each row, if taken individually, the neighbour tiles are horizontally offset by `hexTileWidth`. Alternative rows are horizontally offset by a value of `hexTileWidth/2`. The vertical height difference between each row is `hexTileHeight*3/4`

To understand how we arrived at such a value for the height offset, we need to consider the fact that the top and bottom triangular portions of a horizontally laid out hexagon are exactly `hexTileHeight/4`

This means that the hexagon has a rectangular `hexTileHeight/2` portion in the middle, a triangular `hexTileHeight/4` portion on top, and an inverted triangular `hexTileHeight/4` portion on the bottom. This information is enough to create the code necessary to lay out the hexagonal grid on screen.

With the `HexTile` prototype, I have added some additional functionalities to the `Phaser.Sprite` prototype which enables it to display the `i` and `j` values. The code essentially places a new hexagonal tile `Sprite` at `startX` and `startY`. This code can be changed to display the even offset variant just by removing an operator in the `if` condition like this: `if(i%2===0)`.

For a vertical layout (the image on the right half), neighbour tiles in every column are vertically offset by `hexTileHeight`. Each alternate column is vertically offset by `hexTileHeight/2`. Applying the logic which we applied for vertical offset for the horizontal layout, we can see that the horizontal offset for the vertical layout between neighbour tiles in a row is `hexTileWidth*3/4`. The corresponding code is below.

In the same way as with the horizontal layout, we can switch to the even offset variant just by removing the `!` operator in the top `if` condition. I am using a Phaser `Group` to collect all the `hexTiles` named `hexGrid`. For simplicity, I am using the centre point of the hexagonal tile image as an anchor, or else we would need to consider the image offsets as well.

One thing to notice is that the tile width and tile height values in the horizontal layout are not equal to the tile width and tile height values in the vertical layout. But when using the same image for both layouts, we could just rotate the tile image 90 degrees and swap the values of tile width and tile height.

## 3. Finding the Array Index of a Hexagonal Tile

The array to screen placement logic was interestingly straightforward, but the reverse is not so easy. Consider that we need to find the array index of the hexagonal tile on which we have tapped. The code to achieve this is not pretty, and it is usually arrived at by some trial and error.

If we consider the horizontal layout, it may seem that the middle rectangular portion of the hexagonal tile can easily help us figure out the `j` value as it is just a matter of dividing the `x` value by `hexTileWidth` and taking the integer value. But unless we know the `i` value, we don't know if we are on an odd or even row. An approximate value of `i` can be found by dividing the y value by `hexTileHeight*3/4`

Now come the complicated parts of the hexagonal tile: the top and bottom triangular portions. The image below will help us understand the problem at hand.

The regions 2, 3, 5, 6, 8, and 9 together form one tile. The most complicated part is to find if the tapped position is in 1/2 or 3/4 or 7/8 or 9/10. For this, we need to consider all the individual triangular regions and check against them using the slope of the slanted edge.

This slope can be found from the height and width of each triangular region, which respectively are `hexTileHeight/4` and `hexTileWidth/2`. Let me show you the function which does this.

First, we find `xVal` and `yVal` the same way we would do for a square grid. Then we find the remaining horizontal (`dX`) and vertical (`dY`) values after removing the tile multiplier offset. Using these values, we try to figure out if the point is within any of the complicated triangular regions.

If found, we make corresponding changes to the initial values of `xVal` and `yVal`. As I have said earlier, the code is not pretty and not straightforward. The easiest way to understand this would be to call `findHexTile` on mouse move, and then put `console.log` inside each of those conditions and move the mouse over various regions within one hexagonal tile. This way, you can see how each intra-hexagonal region is handled.

The code changes for the vertical layout are shown below.

## 4. Finding Neighbours

Now that we've found the tile on which we have tapped, let's find all six neighbouring tiles. This is a very easy problem to solve once we visually analyse the grid. Let's consider the horizontal layout.

The image above shows the odd and even rows of a horizontally laid out hexagonal grid when a middle tile has the value of `0` for both `i` and `j`. From the image, it becomes clear that if the row is odd, then for a tile at `i,j` the neighbours are `i, j-1`, `i-1,j-1`, `i-1,j`, `i,j+1`, `i+1,j`, and `i+1,j-1`. When the row is even, then for a tile at `i,j` the neighbours are `i, j-1``i-1,j``i-1,j+1``i,j+1``i+1,j+1`, and `i+1,j`. This could be manually calculated easily.

Let's analyse a similar image for the odd and even columns of a vertically aligned hexagonal grid.

When we have an odd column, a tile at `i,j` will have `i,j-1``i-1,j-1`, `i-1,j`, `i-1,j+1`, `i,j+1`, and `i+1,j` as neighbours. Similarly, for an even column, the neighbours are `i+1,j-1`, `i,j-1`, `i-1,j`, `i,j+1`, `i+1,j+1`, and `i+1,j`

## 5. Hexagonal Minesweeper

With the above knowledge, we can try to make a hexagonal minesweeper game in the two different layouts. Let's break down the features of a minesweeper game.

1. There will be N number of mines hidden inside the grid.
2. If we tap on a tile with a mine, the game is over.
3. If we tap on a tile which has a neighbouring mine, it will display the number of mines immediately around it.
4. If we tap on a mine without any neighbouring mines, it would lead to the revealing of all the connected tiles which do not have mines.
5. We can tap and hold to mark a tile as a mine.
6. The game is finished when we reveal all tiles without mines.

We can easily store a value in the `levelData` array to indicate a mine. The same method can be used to populate the value of nearby mines on the neighbouring tiles' array index.

On game start, we will randomly populate the `levelData` array with N number of mines. After this, we will update the values for all the neighbouring tiles. We will use a recursive method to chain reveal all the connected blank tiles when the player taps on a tile which doesn't have a mine as neighbour.

### Level Data

We need to create a nice looking hexagonal grid, as shown in the image below.

This can be done by only displaying a portion of the `levelData` array. If we use `-1` as the value for a non-usable tile and `0` as the value for a usable tile, then our `levelData` for achieving the above result will look like this.

While looping through the array, we would only add hexagonal tiles when the `levelData` has a value of `0`. For the vertical alignment, the same `levelData` can be used, but we would need to transpose the array. Here is a nifty method which can do this for you.

### Adding Mines and Updating Neighbours

By default, our `levelData` has only two values, `-1` and `0`, of which we would be using only the area with `0`. To indicate that a tile contains a mine, we can use the value of `10`

A blank hexagonal tile can have a maximum of six mines near it as it has six neighbouring tiles. We can store this information also in the `levelData` once we have added all the mines. Essentially, a `levelData` index having a value of `10` has a mine, and if it contains any values from `0` to `6`, that indicates the number of neighbouring mines. After populating mines and updating neighbours, if an array element is still `0`, it indicates that it is a blank tile without any neighbouring mines.

We can use the following methods for our purposes.

For every mine added in `addMines`, we are incrementing the array value stored in all of its neighbours. The `getNeighbors` method won't return a tile which is outside our effective area or if it contains a mine.

### Tap Logic

When the player taps on a tile, we need to find the corresponding array element using the `findHexTile` method explained earlier. If the tile index is within our effective area, then we just compare the value at the array index to find if it is a mine or blank tile.

We keep track of the total number of blank tiles using the variable `blankTiles` and the number of tiles revealed using `revealedTiles`. Once they are equal, we have won the game.

When we tap on a tile with an array value of `0`, we need to recursively reveal the region with all the connected blank tiles. This is done by the function `recursiveReveal`, which receives the tile indices of the tapped tile.

In this function, we find the neighbours of each tile and reveal that tile's value, meanwhile adding neighbour tiles to an array. We keep repeating this with the next element in the array until the array is empty. The recursion stops when we meet array elements containing a mine, which is ensured by the fact that `getNeighbors` won't return a tile with a mine.

### Marking and Revealing Tiles

You must have noticed that I am using `hexTile.reveal()`, which is made possible by creating a `HexTile` prototype which keeps most of the attributes related to our hexagonal tile. I use the `reveal` function to display the tile value text and set the tile's colour. Similarly, the `toggleMark` function is used to mark the tile as a mine when we tap and hold. `HexTile` also has a `revealed` attribute which tracks whether it is tapped and revealed or not.

Check out the hexagonal minesweeper with horizontal orientation below. Tap to reveal tiles, and tap-hold to mark mines. There is no game over as of now, but if you reveal a value of `10`, then it is hasta la vista baby!

See the code in JSFiddle

Changes for the Vertical Version

As I am using the same image of hexagonal tile for both orientations, I rotate the Sprite for the vertical alignment. The below code in the `HexTile` prototype does this.

The minesweeper logic remains the same for the vertically aligned hexagonal grid with the difference for `findHextile` and `getNeighbors` logic which now need to accommodate the alignment difference. As mentioned earlier, we also need to use the transpose of the level array with corresponding layout loop.

The rest of the code in the source is simple and straightforward. I would like you to try and add the missing restart, game win, and game over functionality.

## Conclusion

This approach of a hexagonal tile-based game using a two-dimensional array is more of a layman's approach. More interesting and functional approaches involve altering the coordinate system to different types using equations.

The most important ones are axial coordinates and cubic coordinates. There will be a follow-up tutorial series which will discuss these approaches. Meanwhile, I would recommend reading Amit's incredibly thorough article on hexagonal grids