How to find adjacent tiles in hexagonal maps – ALL and EVERY case explained

Read all posts about "" game

If you read my previous post about hexagonal maps: Understanding hexagonal tiles – updated with HTML5 examples thanks to Phaser, you will see there are two ways of representing hexagonal maps and finding which tile you can find at a generic x,y coordinate, according to the way hexagons are placed on the map.

When we need to find adjacent tiles of a given tile in an hexagon map, things get even more difficult because there are some things to consider if hex maps have a even or odd number of rows and columns.

Look at this picture:

Here we have the four types of hexagonal maps, let’s see them all:

UPPER LEFT: This is a vertical hex map, so each column is basically made by two hexagon columns. The upper-left hexagon is at coordinate (0,0), then the hexagon placed exactly below is at coordinate (0,2). To find the hexagon at coordinate (0,1) we must look at the exagon touching the bottom-right side.

As you can see in the picture, having an odd number of columns – nine – will also cause our map to have some missing coordinates in the 4th column, such as (4,1), (4,3), (4,5) and so on with all odd y numbers until (4,11). This may cause errors when looking at adjacent tiles, because not all columns are complete.

Also, hexagons with even y coordinate have different rules than hexagon with odd y coordinate to find their adjacent tiles. Just look at hexagon (2,4): to find its lower left adjacent tile we must look at (x-1,y+1), while to find the lower left adjacent tile of (2,5) we must look at (x,y+1).

UPPER RIGHT:

It’s the same thing as upper left example, but this is easier to work with as it has an even number of columns, so you won’t have to deal with missing coordinates.

LOWER LEFT:

The horizontal hex map basically features the same problems we found in vertical hex maps. In this case, an even number of rows – remember, “rows” as we are talking about horizontal maps now – prevents us to deal with missing coordinates, but there will be different rules to determine adiacent tiles according to hexagon y coordinate being even or odd: keeping (1,7) as an example, the bottom left hexagon will be at (x-1,y-1), but the bottom left hexagon of (1,8) is at (x,y-1).

LOWER RIGHT:

The lower right example is a horizontal map with an odd number of lines, so you will have to deal with missing row tiles. You won’t have (3,1), (3,3) and so on.

Enough boring theory, let’s have a look at the vertical hex map example:

Move the mouse to place the marker on an hexagon and highlight adjacent tiles.

And this is the source code, made with Phaser:

window.onload = function() {
	
	var game = new Phaser.Game(640, 480, Phaser.CANVAS, "", {preload: onPreload, create: onCreate});                

	var hexagonWidth = 80;
	var hexagonHeight = 70;
	var gridSizeX = 10;
	var gridSizeY = 12;
	var columns = [Math.ceil(gridSizeY/2),Math.floor(gridSizeY/2)];
     var moveIndex;
     var sectorWidth = hexagonWidth/4*3;
     var sectorHeight = hexagonHeight;
     var gradient = (hexagonWidth/4)/(hexagonHeight/2);
     var marker;
     var hexagonGroup;
     var hexagonArray = [];
     
	function onPreload() {
		game.load.image("hexagon", "hexagon.png");
		game.load.image("marker", "marker.png");
	}

	function onCreate() {
		hexagonGroup = game.add.group();
		game.stage.backgroundColor = "#ffffff"
	     for(var i = 0; i < gridSizeX/2; i ++){
	          hexagonArray[i] = [];
			for(var j = 0; j < gridSizeY; j ++){
				if(gridSizeX%2==0 || i+1<gridSizeX/2 || j%2==0){
					var hexagonX = hexagonWidth*i*1.5+(hexagonWidth/4*3)*(j%2);
					var hexagonY = hexagonHeight*j/2;	
					var hexagon = game.add.sprite(hexagonX,hexagonY,"hexagon");
					hexagonGroup.add(hexagon);
					hexagonArray[i][j]=hexagon;
					var hexagonText = game.add.text(hexagonX+hexagonWidth/4+5,hexagonY+5,i+","+j);
					hexagonText.font = "arial";
					hexagonText.fontSize = 12;
					hexagonGroup.add(hexagonText);
				}
			}
		}
		hexagonGroup.y = (game.height-hexagonHeight*Math.ceil(gridSizeY/2))/2;
          if(gridSizeY%2==0){
               hexagonGroup.y-=hexagonHeight/4;
          }
		hexagonGroup.x = (game.width-Math.ceil(gridSizeX/2)*hexagonWidth-Math.floor(gridSizeX/2)*hexagonWidth/2)/2;
          if(gridSizeX%2==0){
               hexagonGroup.x-=hexagonWidth/8;
          }
		marker = game.add.sprite(0,0,"marker");
		marker.anchor.setTo(0.5);
		marker.visible=false;
		hexagonGroup.add(marker);
          moveIndex = game.input.addMoveCallback(checkHex, this);   		
	}
     
     function checkHex(){
          var candidateX = Math.floor((game.input.worldX-hexagonGroup.x)/sectorWidth);
          var candidateY = Math.floor((game.input.worldY-hexagonGroup.y)/sectorHeight);
          var deltaX = (game.input.worldX-hexagonGroup.x)%sectorWidth;
          var deltaY = (game.input.worldY-hexagonGroup.y)%sectorHeight; 
          if(candidateX%2==0){
               if(deltaX<((hexagonWidth/4)-deltaY*gradient)){
                    candidateX--;
                    candidateY--;
               }
               if(deltaX<((-hexagonWidth/4)+deltaY*gradient)){
                    candidateX--;
               }
          }    
          else{
               if(deltaY>=hexagonHeight/2){
                    if(deltaX<(hexagonWidth/2-deltaY*gradient)){
                         candidateX--;
                    }
               }
               else{
                    if(deltaX<deltaY*gradient){
                         candidateX--;
                    }
                    else{
                         candidateY--;
                    }
               }
          }
          placeMarker(candidateX,candidateY);
     }
     
     function placeMarker(posX,posY){
     	for(var i = 0; i < gridSizeX/2; i ++){
			for(var j = 0; j < gridSizeY; j ++){
				if(gridSizeX%2==0 || i+1<gridSizeX/2 || j%2==0){
					hexagonArray[i][j].tint = 0xffffff;
				}
			}
		}
		if(posX<0 || posY<0 || posX>=gridSizeX || posY>columns[posX%2]-1){
			marker.visible=false;
		}
		else{
			marker.visible=true;
			marker.x = hexagonWidth/4*3*posX+hexagonWidth/2;
			marker.y = hexagonHeight*posY;
			if(posX%2==0){
				marker.y += hexagonHeight/2;
			}
			else{
				marker.y += hexagonHeight;
			}
			var markerX = Math.floor(posX/2);
			var markerY = posY*2+posX%2;
			hexagonArray[markerX][markerY].tint = 0xff8800;
			// up
			if(markerY-2>=0){
				hexagonArray[markerX][markerY-2].tint = 0xff0000;
			}
			// down
			if(markerY+2<gridSizeY){
				hexagonArray[markerX][markerY+2].tint = 0xff0000;
			}
			// right
			if(markerX+markerY%2<gridSizeX/2 && (gridSizeX%2==0 || markerX<Math.floor(gridSizeX/2))){
				//up
				if(markerY-1>=0){
					hexagonArray[markerX+markerY%2][markerY-1].tint = 0xff0000;
				}
				// down
				if(markerY+1<gridSizeY){
					hexagonArray[markerX+markerY%2][markerY+1].tint = 0xff0000;
				}
			}
			// left
			if(markerX-1+markerY%2>=0){
				// up
				if(markerY-1>=0){
					hexagonArray[markerX-1+markerY%2][markerY-1].tint = 0xff0000;
				}
				// down
				if(markerY+1<gridSizeY){
					hexagonArray[markerX-1+markerY%2][markerY+1].tint = 0xff0000;
				}
			}
		}
	}	
}

And this is the horizontal hex map example, with missing tiles too:

With its source code:

window.onload = function() {
	
	var game = new Phaser.Game(640, 480, Phaser.CANVAS, "", {preload: onPreload, create: onCreate});                

	var hexagonWidth = 70;
	var hexagonHeight = 80;
	var gridSizeX = 17;
	var gridSizeY = 7;
	var columns = [Math.ceil(gridSizeX/2),Math.floor(gridSizeX/2)];
     var moveIndex;
     var sectorWidth = hexagonWidth;
     var sectorHeight = hexagonHeight/4*3;
     var gradient = (hexagonHeight/4)/(hexagonWidth/2);
     var marker;
     var hexagonGroup;
     var hexagonArray = [];
     
	function onPreload() {
		game.load.image("hexagon", "hexagon.png");
		game.load.image("marker", "marker.png");
	}

	function onCreate() {
		hexagonGroup = game.add.group();
		game.stage.backgroundColor = "#ffffff"
	     for(var i = 0; i < gridSizeY/2; i ++){
	     	hexagonArray[i] = [];
			for(var j = 0; j < gridSizeX; j ++){
				if(gridSizeY%2==0 || i+1<gridSizeY/2 || j%2==0){
					var hexagonX = hexagonWidth*j/2;
					var hexagonY = hexagonHeight*i*1.5+(hexagonHeight/4*3)*(j%2);	
					var hexagon = game.add.sprite(hexagonX,hexagonY,"hexagon");
					hexagonGroup.add(hexagon);
					hexagonArray[i][j]=hexagon;
					var hexagonText = game.add.text(hexagonX+hexagonWidth/3+5,hexagonY+15,i+","+j);
					hexagonText.font = "arial";
					hexagonText.fontSize = 12;
					hexagonGroup.add(hexagonText);
				}
			}
		}
		hexagonGroup.x = (game.width-hexagonWidth*Math.ceil(gridSizeX/2))/2;
          if(gridSizeX%2==0){
               hexagonGroup.x-=hexagonWidth/4;
          }
		hexagonGroup.y = (game.height-Math.ceil(gridSizeY/2)*hexagonHeight-Math.floor(gridSizeY/2)*hexagonHeight/2)/2;
          if(gridSizeY%2==0){
               hexagonGroup.y-=hexagonHeight/8;
          }
		marker = game.add.sprite(0,0,"marker");
		marker.anchor.setTo(0.5);
		marker.visible=false;
		hexagonGroup.add(marker);
          moveIndex = game.input.addMoveCallback(checkHex, this);   		
	}
     
     function checkHex(){
          var candidateX = Math.floor((game.input.worldX-hexagonGroup.x)/sectorWidth);
          var candidateY = Math.floor((game.input.worldY-hexagonGroup.y)/sectorHeight);
          var deltaX = (game.input.worldX-hexagonGroup.x)%sectorWidth;
          var deltaY = (game.input.worldY-hexagonGroup.y)%sectorHeight; 
          if(candidateY%2==0){
               if(deltaY<((hexagonHeight/4)-deltaX*gradient)){
                    candidateX--;
                    candidateY--;
               }
               if(deltaY<((-hexagonHeight/4)+deltaX*gradient)){
                    candidateY--;
               }
          }    
          else{
               if(deltaX>=hexagonWidth/2){
                    if(deltaY<(hexagonHeight/2-deltaX*gradient)){
                         candidateY--;
                    }
               }
               else{
                    if(deltaY<deltaX*gradient){
                         candidateY--;
                    }
                    else{
                         candidateX--;
                    }
               }
          }
          placeMarker(candidateX,candidateY);
     }
     
     function placeMarker(posX,posY){
     	 for(var i = 0; i < gridSizeY/2; i ++){
			for(var j = 0; j < gridSizeX; j ++){
				if(gridSizeY%2==0 || i+1<gridSizeY/2 || j%2==0){
					hexagonArray[i][j].tint = 0xffffff;
				}
			}
		}
		if(posX<0 || posY<0 || posY>=gridSizeY || posX>columns[posY%2]-1){
			marker.visible=false;
		}
		else{
			marker.visible=true;
			marker.x = hexagonWidth*posX;
			marker.y = hexagonHeight/4*3*posY+hexagonHeight/2;
			if(posY%2==0){
				marker.x += hexagonWidth/2;
			}
			else{
				marker.x += hexagonWidth;
			}
			var markerX = posX*2+posY%2;
			var markerY = Math.floor(posY/2);
			hexagonArray[markerY][markerX].tint = 0xff8800;
			// left
			if(markerX-2>=0){
				hexagonArray[markerY][markerX-2].tint = 0xff0000;
			}
			// right
			if(markerX+2<gridSizeX){
				hexagonArray[markerY][markerX+2].tint = 0xff0000;
			}
			// up
			if(markerY-1+markerX%2>=0){
				// left
				if(markerX-1>=0){
					hexagonArray[markerY-1+markerX%2][markerX-1].tint = 0xff0000;
				}
				// right
				if(markerX+1<gridSizeX){
					hexagonArray[markerY-1+markerX%2][markerX+1].tint = 0xff0000;
				}
			}
			// down
			if(markerY+markerX%2<gridSizeY/2 && (gridSizeY%2==0 || markerY<Math.floor(gridSizeY/2))){
				// left
				if(markerX-1>=0){
					hexagonArray[markerY+markerX%2][markerX-1].tint = 0xff0000;
				}
				// right
				if(markerX+1<gridSizeX){
					hexagonArray[markerY+markerX%2][markerX+1].tint = 0xff0000;
				}
			} 
		}
	}	
}

Now you are ready to create any kind of hexagon driven game.

Download the source code of the full examples.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers