Developing hex map concept

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?

Rate this post: 1 Star2 Stars3 Stars4 Stars5 Stars (5 votes, average: 3.40 out of 5)
Loading ... Loading ...
If you found this post useful, please consider a small donation.

4 Responses

  1. Arxanas says:

    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).

  2. Kristian Mandrup says:

    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

  3. Pavel says:

    Hi,

    I do not see any code but comments. Could you repost it here or email it to me?

    Thank you in advance,
    Pavel.

  4. Eric says:

    Hi,

    Same here, I’d love to be able to look at your source code?

    Thank you very much!

    eric

Leave a Reply