Constructing an Hexagon Map for a Sphere

Square maps are great for tabletop games, but what about hexagons?

Hexagons are often used to simulate a space where things are more equally spaced in every direction, but these are not easy to represent in a computer array. Not easy, but possible.

The second thing about hexagons is that you can use them to map a space onto a sphere, like a football with a mix of hexagons and 12 pentagons. The football has 20 hexagons and 12 pentagons, but if you use more hexagons, you will always have 12 pentagons like the points on a 20 sided dice (Icosahedron - https://en.wikipedia.org/wiki/Icosahedron)

In [1]:
from copy import copy
%matplotlib inline
Import Local Functions
In [2]:
from hex_map import world_map, local_map
from hex_map_navigate import build_the_world
from hex_map_construct import EMPTY, COMMON, POLE, PENTAGON, VIRTUAL, EDGE, VERBOTEN, WRAP

How do these hexagons map to a sphere

The blue hexagons are mapped to the hexagons on a football. The green hexagons are in fact pentagons, so the edge attached to a yellow hexagon does not exist in reality, The purple hexagons are the same location on the sphere, but are also in fact the pentagons on the football. They are equivalent to the green hexagons. The aqua hexagons are the "poles" of the sphere and therefore equivalent and also pentagons. The red hexagons are not accessible. The yellow hexagons should be mapped to another hexagon in the map.

Initalise the World
In [3]:
turtle = build_the_world()
world_map(turtle.bitmap)

The solution to navigating the map is to create a virtual turtle and head out to find a valid tile to land on and a valid direction to point. There is also the consideration of tiles that have two or more logical tiles, but only one physical tile.

In [4]:
from hex_map_navigate import from_wrap

def move(turtle):
    if turtle.state(COMMON):
        print("COMMON")
        turtle = from_common(turtle)
    if turtle.state(POLE):
        print("POLE")
    if turtle.state(PENTAGON):
        print("PENTAGON")
    if turtle.state(EDGE):
        print("EDGE")
    if turtle.state(WRAP):
        print("WRAP")
        turtle = from_wrap(turtle)
    return turtle
Testing the movement from the WRAP tile

This has been been done, but can probably be improved with a test harness

Testing the movement from the COMMON tile

Moving from COMMON to COMMON is very simple, but all other translations are difficult...

The most difficult translation is COMMON to VIRTUAL as we have to determine what kind of VIRTUAL this might be.

In [5]:
def from_common(turtle):
    """Move the turtle from the COMMON node."""
    if turtle.is_next(COMMON):
        print("COMMON")
        turtle.move()
    elif turtle.is_next(POLE):
        print("POLE")
        turtle = from_common_to_pole(turtle)
    elif turtle.is_next(PENTAGON):
        print("PENTAGON")
        turtle = from_common_to_pentagon(turtle)
    elif turtle.is_next(VIRTUAL):
        print("VIRTUAL")
        turtle = from_common_to_virtual(turtle)
    elif turtle.is_next(EDGE):
        print("EDGE")
        turtle = from_common_to_edge(turtle)
    elif turtle.is_next(WRAP):
        print("WRAP")
        turtle = from_common_to_wrap(turtle)
    else:
        print(f"The turtle is {turtle}")
    return turtle
In [6]:
from hex_map_construct import STEPS

def from_common_to_pole(turtle):
    turtle.move()
    if turtle.is_north():
        poles = range(turtle.x, 0, STEPS * -2)
    else:
        poles = range(turtle.x, turtle.bitmap.shape[0], STEPS * 2)
    for x in poles:
        if turtle.bitmap[x, turtle.y] == POLE:
            print(f"Translate from turtle.x={turtle.x} to x={x} and Turn Left")
            turtle.x = x
            turtle.left()
    return turtle
In [7]:
def from_common_to_pentagon(turtle):
    turtle.move()
    if not turtle.is_next(COMMON):
        print("Next Cell is Not COMMON - Turn Left")
        turtle.left()
    return turtle
In [8]:
def from_common_to_virtual(turtle):
    turtle.move()
    if turtle.is_adjacent(EDGE):
        if turtle.is_adjacent(WRAP):
            print("Wrap, Rotate and Advance")
        if turtle.is_adjacent(PENTAGON):
            print("Rotate and Advance")
        else:
            print("Rotate, Translate and Advance")
    else:
        if turtle.is_east():
            print("Wrap")
    return turtle
In [9]:
def from_common_to_edge(turtle):
    turtle.move()
    return turtle
In [10]:
def from_common_to_wrap(turtle):
    turtle.move()
    return turtle
A test for the turtle

Place the turtle, show it, move it and see where it went.

In [11]:
def test_starts(turtle, starts, horizon=4):
    for x, y, f in starts:
        turtle.place(x, y, f)
        print(turtle)
        local_map(turtle, horizon=horizon)
        turtle = move(turtle)
        print(turtle)
        local_map(turtle, horizon=horizon)
COMMON to COMMON

Pretty simple...

In [12]:
test_starts(turtle, [(3, 37, 1)])
X: 3 - Y: 37 - F: 1
COMMON
COMMON
X: 4 - Y: 38 - F: 1
COMMON to POLE

Not as simple... Involves translations and rotations.

In [13]:
test_starts(turtle, [(x, 49, 0) for x in range(7, 7 + STEPS * 2 * 5, STEPS * 2)], horizon=12)
X: 7 - Y: 49 - F: 0
COMMON
POLE
Translate from turtle.x=7 to x=7 and Turn Left
POLE
X: 7 - Y: 51 - F: 5
X: 17 - Y: 49 - F: 0
COMMON
POLE
Translate from turtle.x=17 to x=17 and Turn Left
Translate from turtle.x=17 to x=7 and Turn Left
POLE
X: 7 - Y: 51 - F: 4
X: 27 - Y: 49 - F: 0
COMMON
POLE
Translate from turtle.x=27 to x=27 and Turn Left
Translate from turtle.x=27 to x=17 and Turn Left
Translate from turtle.x=17 to x=7 and Turn Left
POLE
X: 7 - Y: 51 - F: 3
X: 37 - Y: 49 - F: 0
COMMON
POLE
Translate from turtle.x=37 to x=37 and Turn Left
Translate from turtle.x=37 to x=27 and Turn Left
Translate from turtle.x=27 to x=17 and Turn Left
Translate from turtle.x=17 to x=7 and Turn Left
POLE
X: 7 - Y: 51 - F: 2
X: 47 - Y: 49 - F: 0
COMMON
POLE
Translate from turtle.x=47 to x=47 and Turn Left
Translate from turtle.x=47 to x=37 and Turn Left
Translate from turtle.x=37 to x=27 and Turn Left
Translate from turtle.x=27 to x=17 and Turn Left
Translate from turtle.x=17 to x=7 and Turn Left
POLE
X: 7 - Y: 51 - F: 1
In [14]:
test_starts(turtle, [(x, 8, 3) for x in range(12, 12 + STEPS * 2 * 5, STEPS * 2)], horizon=12)
X: 12 - Y: 8 - F: 3
COMMON
POLE
Translate from turtle.x=12 to x=12 and Turn Left
Translate from turtle.x=12 to x=22 and Turn Left
Translate from turtle.x=22 to x=32 and Turn Left
Translate from turtle.x=32 to x=42 and Turn Left
Translate from turtle.x=42 to x=52 and Turn Left
POLE
X: 52 - Y: 6 - F: 4
X: 22 - Y: 8 - F: 3
COMMON
POLE
Translate from turtle.x=22 to x=22 and Turn Left
Translate from turtle.x=22 to x=32 and Turn Left
Translate from turtle.x=32 to x=42 and Turn Left
Translate from turtle.x=42 to x=52 and Turn Left
POLE
X: 52 - Y: 6 - F: 5
X: 32 - Y: 8 - F: 3
COMMON
POLE
Translate from turtle.x=32 to x=32 and Turn Left
Translate from turtle.x=32 to x=42 and Turn Left
Translate from turtle.x=42 to x=52 and Turn Left
POLE
X: 52 - Y: 6 - F: 0
X: 42 - Y: 8 - F: 3
COMMON
POLE
Translate from turtle.x=42 to x=42 and Turn Left
Translate from turtle.x=42 to x=52 and Turn Left
POLE
X: 52 - Y: 6 - F: 1
X: 52 - Y: 8 - F: 3
COMMON
POLE
Translate from turtle.x=52 to x=52 and Turn Left
POLE
X: 52 - Y: 6 - F: 2
COMMON to PENTAGON

Middling simplicity... Involves conditional rotations.

In [15]:
test_starts(turtle, [(11, 37, 2), (11, 35, 1), (12, 34, 0), (13, 35, 5), (13, 37, 4)])
X: 11 - Y: 37 - F: 2
COMMON
PENTAGON
PENTAGON
X: 12 - Y: 36 - F: 2
X: 11 - Y: 35 - F: 1
COMMON
PENTAGON
PENTAGON
X: 12 - Y: 36 - F: 1
X: 12 - Y: 34 - F: 0
COMMON
PENTAGON
Next Cell is Not COMMON - Turn Left
PENTAGON
X: 12 - Y: 36 - F: 5
X: 13 - Y: 35 - F: 5
COMMON
PENTAGON
PENTAGON
X: 12 - Y: 36 - F: 5
X: 13 - Y: 37 - F: 4
COMMON
PENTAGON
PENTAGON
X: 12 - Y: 36 - F: 4
In [16]:
test_starts(turtle, [(16, 20, 1), (16, 22, 2), (17, 23, 3), (18, 22, 4), (18, 20, 5)])
X: 16 - Y: 20 - F: 1
COMMON
PENTAGON
PENTAGON
X: 17 - Y: 21 - F: 1
X: 16 - Y: 22 - F: 2
COMMON
PENTAGON
PENTAGON
X: 17 - Y: 21 - F: 2
X: 17 - Y: 23 - F: 3
COMMON
PENTAGON
Next Cell is Not COMMON - Turn Left
PENTAGON
X: 17 - Y: 21 - F: 2
X: 18 - Y: 22 - F: 4
COMMON
PENTAGON
PENTAGON
X: 17 - Y: 21 - F: 4
X: 18 - Y: 20 - F: 5
COMMON
PENTAGON
PENTAGON
X: 17 - Y: 21 - F: 5
COMMON to VIRTUAL

Complex... Involves North, South and Equator conditional translations and possible rotations.

In [17]:
test_starts(turtle, [(7, 49, 5), (5, 43, 5), (3, 37, 5), (3, 35, 4), (5, 29, 4), (7, 23, 4), (8, 20, 4)])
X: 7 - Y: 49 - F: 5
COMMON
VIRTUAL
Rotate, Translate and Advance
X: 6 - Y: 50 - F: 5
X: 5 - Y: 43 - F: 5
COMMON
VIRTUAL
Rotate, Translate and Advance
X: 4 - Y: 44 - F: 5
X: 3 - Y: 37 - F: 5
COMMON
VIRTUAL
Wrap, Rotate and Advance
Rotate, Translate and Advance
X: 2 - Y: 38 - F: 5
X: 3 - Y: 35 - F: 4
COMMON
VIRTUAL
X: 2 - Y: 34 - F: 4
X: 5 - Y: 29 - F: 4
COMMON
VIRTUAL
X: 4 - Y: 28 - F: 4
X: 7 - Y: 23 - F: 4
COMMON
VIRTUAL
X: 6 - Y: 22 - F: 4
X: 8 - Y: 20 - F: 4
COMMON
VIRTUAL
Wrap, Rotate and Advance
Rotate, Translate and Advance
X: 7 - Y: 19 - F: 4
In [18]:
test_starts(turtle, [(47, 49, 1), (49, 43, 1), (51, 37, 1), (53, 31, 1), (55, 25, 1), (56, 22, 1), (56, 20, 2)])
X: 47 - Y: 49 - F: 1
COMMON
VIRTUAL
Rotate, Translate and Advance
X: 48 - Y: 50 - F: 1
X: 49 - Y: 43 - F: 1
COMMON
VIRTUAL
Rotate, Translate and Advance
X: 50 - Y: 44 - F: 1
X: 51 - Y: 37 - F: 1
COMMON
VIRTUAL
Wrap, Rotate and Advance
Rotate, Translate and Advance
X: 52 - Y: 38 - F: 1
X: 53 - Y: 31 - F: 1
COMMON
VIRTUAL
Wrap
X: 54 - Y: 32 - F: 1
X: 55 - Y: 25 - F: 1
COMMON
VIRTUAL
Wrap
X: 56 - Y: 26 - F: 1
X: 56 - Y: 22 - F: 1
COMMON
VIRTUAL
Wrap
X: 57 - Y: 23 - F: 1
X: 56 - Y: 20 - F: 2
COMMON
VIRTUAL
Wrap, Rotate and Advance
Rotate, Translate and Advance
X: 57 - Y: 19 - F: 2
COMMON to EDGE

Not simple... Involves conditional translations and rotations.

In [22]:
test_starts(turtle, [(3, 37, 0), (11, 37, 0), (13, 37, 0)])
X: 3 - Y: 37 - F: 0
COMMON
EDGE
EDGE
X: 3 - Y: 39 - F: 0
X: 11 - Y: 37 - F: 0
COMMON
EDGE
EDGE
X: 11 - Y: 39 - F: 0
X: 13 - Y: 37 - F: 0
COMMON
EDGE
EDGE
X: 13 - Y: 39 - F: 0