Developing hex map concept
- October 22, 2008 by Emanuele Feronato
- Filed under Game design, Javascript, Users contributions | 4 Comments
Some months ago I released some tutorials about hexagonal tiles (and I developed an Halloween game based upon Hex maps creation and rollover and Finding adjacent cells in an hex map 1 and 2).
Later, Douglas Huskins told me 10 years ago he wrote a collection of functions that would help a person navigate hex maps. The functions included: Identifying the adjacent hexes, identifying the shortest path between two hexes, the relative position of a hex from the perspective of another hex and the distance between two hexes.
This can be really useful to make some strategy games.
Unfortunately most of the work seems to be lost, but Douglas still wants to share the concept with us, so he is rewriting it in Javascript.
Read what he says:
« I could not find my electronic copy of the source files.
All I have are some old Visual Basic printouts that lacked much documentation. I have recoded it into Javascript and need to test it.
However, here are two of the functions: Move and Range.
The hex map layout this code works for is based on a hex map with the points laying horizontally. The top and bottom of a hex are flat.
Hexes are numbered such that the first set of digits represent the column number and the second half of the hex number’s digits represent the row number.
A typical hex number would be 0132. That would be a hex in the leftmost column and would be the 32nd hex down from the top.
This type of map comes in two flavors. The way to identify the two types of layouts is to compare 0101 with 0201. If 0101 is above 0201, then it is referred to as “Odd Column Up”.
The code is able to support either flavor. There is a variable set at the top of the file (mapOddUp) which will let the code switch between the two layouts.
The unit (ship/car/etc) that sits in a hex will face one of the hex sides. The faces are numbered clockwise using either 0-5 or 1-6. Again, the code supports both numbering systems by treating 0 and 6 as the same direction (straight up). There is a variable at the top of the file (mapIsUp0) that determines which facing number system to use.
The code has the ability to change the map size. There are max and min values that can be set.
If you have any questions, please let me know. I will convert the remaining functions next week. After that, I will properly test everything and add a simple interface for it. In the meantime, this should help you create hex based games. »
Here it is the source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 | /* HEX MAP LOGIC This code was developed and written by Douglas Huskins. Permission is granted to anyone to use in any program, provided this comment block remains intact as is AND credit is given to Douglas Huskins for the hex map logic. Additionally, the person must send Douglas Huskins an email stating how and in what it will be used in. The email address is a dot com domain named "huskins". The username (before the at symbol) is "doug". If you cannot figure out how to email Douglas Huskins, then a voice mail message at 925-957-0109 will work as well. */ // Valid move orders are L(eft), R(ight), F(orward), B(ack), W(ait) // If this needs to be added to, MoveStep() needs the new orders as well. var AcceptableOrders = "LRFBW"; // Configure the size and layout of the hex map. var mapOddUp = true; // If hex 0101 is above hex 0201, then set to true, else set to false. var mapIsUp0 = false; // Is the "up" facing hex side zero or six? If zero, then true. If 6 then false. var mapMinCol = 1; var mapMaxCol = 99; var mapMinRow = 1; var mapMaxRow = 99; // Create an array to store the Row, Column, Facing of each unit. // The first element is the unit number. The second element is the hex information. // Unit zero is reserved for the functions. The first game piece is stored in UnitArray[1][x]. var UnitArray = new array(21, 5); // Max of 20 units (ships, cars, whatever the game is about) // The second attribute is broken down as follows. // 0: Hex Column // 1: Hex Row // 2: Hex Facing // 3: Unit Type (not needed in this code) // 4: Unit Status (not needed in this code) function DisplayHex(UnitNumber,booWithFace) { // Use this to print a string version of the hex number (with leading zeros, if needed.) // If booWithFace is true, then include the facing after a colon. Otherwise show only the hex number. var HexCol = UnitArray[UnitNumber][0] + ""; HexCol = "0000000000" + HexCol; var HexRow = UnitArray[UnitNumber][1] + ""; HexRow = "0000000000" + HexRow; var LabelLength; if (mapMaxRow > mapMaxCol) { LabelLength = mapMaxRow + ""; } else { LabelLength = mapMaxCol + ""; } var HexFace = UnitArray[UnitNumber][2] + ""; HexFace = ":" + HexFace; if (booWithFace == false) { HexFace = ""; } return HexCol.slice(-1 * LabelLength.length) + HexRow.slice(-1 * LabelLength.length) + HexFace; } function MoveUnit(UnitNumber,MoveOrders) { // This function is the main entry for moving a unit. if (MoveOrders.length > 0) { // First off, were the MoveOrders properly written? // The function CheckAllOrders() will determine if the orders, as a whole are acceptable. // Perhaps the game does not allow two turns back to back. var booValidOrders = CheckAllOrders(MoveOrders); var i = 0; var NextMove; while (i < MoveOrders.length && booValidOrders) { // Parse the next move from the orders and execute it. NextMove = MoveOrders.toUpper().charAt(i); if (NextMove.IndexOf(AcceptableOrders) >= 0) { // Make the move and return the results in Unit[0][x] MoveStep(UnitNumber, NextMove); } // This function call CheckValidMove() will test if the specific orders were a valid move. // The function is currently empty and will return a valid move reply. // If the map needs to limit access between two hexes, then the function can be modified. // If the move is valid and the unit did not leave the map then process it. if (CheckValidMove(UnitNumber) && UnitArray[0][0] >= mapMinCol && UnitArray[0][0] <= mapMaxCol && UnitArray[0][1] >= mapMinRow && UnitArray[0][1] <= mapMaxRow) { UnitArray[UnitNumber][0] = UnitArray[0][0]; UnitArray[UnitNumber][1] = UnitArray[0][1]; UnitArray[UnitNumber][2] = UnitArray[0][2]; } else { booValidOrders = false; } // Increment and repeat for next orders. i++; } } } function FindRange(Unit1, Unit2) { // This function returns the number of hexes between the two units. // The return value is simply the number of hexes (not the number of orders it takes). // Are the two hexes above or below each other? if (UnitArray[Unit1][0] == UnitArray[Unit2][0]) { return Math.abs(UnitArray[Unit1][1] - UnitArray[Unit2][1]); } // Are the two hexes on the same line (same Row) if (UnitArray[Unit1][1] == UnitArray[Unit2][1]) { return Math.abs(UnitArray[Unit1][0] - UnitArray[Unit2][0]); } // Okay, then find the deltas for row and column. var DeltaCol = Math.abs(UnitArray[Unit1][0] - UnitArray[Unit2][0]); var DeltaRow = Math.abs(UnitArray[Unit1][1] - UnitArray[Unit2][1]) * 2; // So, are the two hexes adjacent? Just need to look at the two unchecked hexes. if (DeltaCol == 1) { if (mapOddUp && IsColEven(HexCol) && UnitArray[Unit1][1] + 1 == UnitArray[Unit2][1]) { return 1; } if (mapOddUp && IsColEven(HexCol) == false && UnitArray[Unit1][1] - 1 == UnitArray[Unit2][1]) { return 1; } if (mapOddUp == false && IsColEven(HexCol) && UnitArray[Unit1][1] - 1 == UnitArray[Unit2][1]) { return 1; } if (mapOddUp==false && IsColEven(HexCol)==false && UnitArray[Unit1][1] + 1 == UnitArray[Unit2][1]) { return 1; } } // At this point, we know the two units are at least two hexes away from each other. // To simplify, we will assign LeftHex and RightHex to the values of the Unit Numbers. // This way, all math is done from the leftmost hex. var LeftHex; var RightHex; if (UnitArray[Unit1][0] < UnitArray[Unit2][0]) { LeftHex = Unit1; RightHex = Unit2; } else { LeftHex = Unit2; RightHex = Unit1; } // The next step is to figure out if it is easier to go across then vertical or the other way around. // To be more clear: We will calculate the distance along the spine of the hexes. // The question is when we get to the point we leave the spine, do we go vertically or horizontally. if (UnitArray[LeftHex][1] > UnitArray[RightHex][1]) { // RightHex is above and to the right of LeftHex. // For every two hexes travelled, it will add 2 to the column value and subtract 1 from the row. // Next question is: Is the RightHex above the heading 1 spine? if (DeltaCol < DeltaRow) { // RightHex is above the heading 1 spine. The distance is calculated by figuring out the // row value after moving along heading 1 until the columns match. if (mapOddUp) { if (IsColEven(UnitArray[LeftHex][1])) { // In this case we will safely undershoot. return DeltaCol + (Math.floor(DeltaCol / 2) - UnitArray[RightHex][1]); } else { // In this case, we may overshoot by one hex if we don't adjust for it. return DeltaCol + (Math.round(DeltaCol / 2) - UnitArray[RightHex][1]); } } else { // Same as above, however with mapOddUp set to false. if (IsColEven(UnitArray[LeftHex][1])) { return DeltaCol + (Math.round(DeltaCol / 2) - UnitArray[RightHex][1]); } else { return DeltaCol + (Math.floor(DeltaCol / 2) - UnitArray[RightHex][1]); } } } else { // RightHex is on or below the heading 1 spine but above the midline. Distance is calculated by // figuring out the column value after moving along heading 1 until the rows match. return DeltaRow + (UnitArray[RightHex][0] - (UnitArray[LeftHex][0] + DeltaRow)); } } else { // RightHex is below and to the right of LeftHex. // For every two hexes travelled, it will add 2 to the column value and add 1 to the row. // Next question is: Is the RightHex above the heading 2 spine? if (DeltaCol < DeltaRow) { // The RightHex is above the heading 2 spine, but below the midline. return DeltaRow + (UnitArray[RightHex][0] - (UnitArray[LeftHex][0] + DeltaRow)); } else { // The RightHex is below the heading 2 spine and to the right of LeftHex. if (mapOddUp) { if (IsColEven(UnitArray[LeftHex][1])) { return DeltaCol + (Math.round(DeltaCol / 2) - UnitArray[RightHex][1]); } else { return DeltaCol + (Math.floor(DeltaCol / 2) - UnitArray[RightHex][1]); } } else { if (IsColEven(UnitArray[LeftHex][1])) { return DeltaCol + (Math.floor(DeltaCol / 2) - UnitArray[RightHex][1]); } else { return DeltaCol + (Math.round(DeltaCol / 2) - UnitArray[RightHex][1]); } } } } } // This section is for the custom validation code. // Modify this code as needed to address unique issues with the game orders or board. function CheckAllOrders(MoveOrders) { // Here is where you can validate the overall orders before they are processed. // If you have some reason to not like the way the orders are written, write the code here. // Set the value to false if you want to invalidate the entire collection of orders. return true; } function CheckValidMove(UnitNumber) { // Here is where you can control how the internal portion of the map is designed. // If you want to limit access between two hexes, then you can check for them here. // UnitArray[UnitNumber] is the starting hex. UnitArray[0] is the ending hex. // Set the value to false if you want to invalidate the move, otherwise return with a true. return true; } // Below this point are the support functions to manage the above functions. function IsColEven(HexCol) { // This function returns a true if the Hex's current row is even. // It is used by MoveStep() to determine the next hex. if(HexCol % 2 == 0) { return true; } else { return false; } } function MoveStep(UnitNumber, NextOrder) { var HexCol = UnitArray[UnitNumber][0]; var HexRow = UnitArray[UnitNumber][1]; var HexFace = UnitArray[UnitNumber][2]; switch(NextOrder) { case "L": if (mapIsUp0) { if (HexFace == 0) { HexFace = 5; } else { HexFace--; } } else { if (HexFace == 1) { HexFace = 6; } else { HexFace--; } } break; case "R": if (mapIsUp0) { if (HexFace == 5) { HexFace = 0; } else { HexFace++; } } else { if (HexFace == 6) { HexFace = 1; } else { HexFace++; } } break; case "F": switch(HexFace) { case 0: HexRow--; break; case 1: HexCol++; if ((IsColEven(HexCol) == false && mapOddUp) || (IsColEven(HexCol) && mapOddUp == false)) { HexRow--; } break; case 2: HexCol++; if ((IsColEven(HexCol) && mapOddUp) || (IsColEven(HexCol) == false && mapOddUp == false)) { HexRow++; } break; case 3: HexRow++; break; case 4: HexCol--; if ((IsColEven(HexCol) && mapOddUp) || (IsColEven(HexCol) == false && mapOddUp == false)) { HexRow++; } break; case 5: HexCol--; if ((IsColEven(HexCol) == false && mapOddUp) || (IsColEven(HexCol) && mapOddUp == false)) { HexRow--; } break; case 6: HexRow--; break; } break; case "B": switch (HexFace) { case 0: HexRow++; break; case 1: HexCol--; if ((IsColEven(HexCol) && mapOddUp) || (IsColEven(HexCol) == false && mapOddUp == false)) { HexRow++; } break; case 2: HexCol--; if ((IsColEven(HexCol) == false && mapOddUp) || (IsColEven(HexCol) && mapOddUp == false)) { HexRow--; } break; case 3: HexRow--; break; case 4: HexCol++; if ((IsColEven(HexCol) == false && mapOddUp) || (IsColEven(HexCol) && mapOddUp == false)) { HexRow--; } break; case 5: HexCol++; if ((IsColEven(HexCol) && mapOddUp) || (IsColEven(HexCol) == false && mapOddUp == false)) { HexRow++; } break; case 6: HexRow++; break; } break; case "W": break; default: } // Now return the results in the "zero" element of the Unit Array UnitArray[0][0] = HexCol; UnitArray[0][1] = HexRow; UnitArray[0][2] = HexFace; } |
This should help you all in the creation of a strategy game… at least it’s helping me…
Someone out there willing to translate it into AS3?
4 Responses
Leave a Reply
TUTORIAL SERIES:
- Una guida completa al gioco del poker online e una selezione dei migliori casino online.
- casino online
- migliori casino online
- BlackJack online
- casinò online


(5 votes, average: 3.40 out of 5)

Reminds me of RPG Maker XP’s RGSS. That was a scriping system with a bunch of classes and functions, to make an RPG (didn’t see that coming!). You could also edit it and add your own scripts, making the game highly customizable.
Unfortunately, it was written in Ruby (powerful but not supported).
Hi,
I have been looking for such a library in javascript for use with the new Canvas 2D. I don’t see the source code in the text box, only the comments. Could you please send the source code to:
kmandrup@gmail.com
Then I would love to build on that. I already made some code myself to draw a tiled hexagonal map, but need code to determine hexagon clicked, movement etc.
I found some old articles on the web too… but this looks great!
Good luck!
Kristan
Hi,
I do not see any code but comments. Could you repost it here or email it to me?
Thank you in advance,
Pavel.
Hi,
Same here, I’d love to be able to look at your source code?
Thank you very much!
eric