Module botroyale.util.hexagon
Home of the Hexagon
class.
The Hexagon represents a whole-number point in hex-space. It can also be
thought of as a whole-number vector from the ORIGIN
(0, 0, 0) in hex-space.
Coordinates
See the offset (x, y) coordinates with Hexagon.xy
and the cube (q, r, s)
coordinates with Hexagon.cube
.
Hexagons are normally not created manually, rather are returned from methods of
existing hexagons. To create a new Hexagon instance, see Hexagon.from_xy()
,
Hexagon.from_qr()
and Hexagon.from_floats()
.
Neighboring hexagons
Hexagon.neighbors
will return a hexagon's 6 nearest neighbors:
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
neighbors = hex.neighbors # tuple of the 6 nearest hexagons
assert hex.neighbors == hex.ring(1)
Hexagon.doubles
and Hexagon.diagonals
together make up the second ring:
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
assert set(hex.ring(2)) == set(hex.doubles) | set(hex.diagonals)
Distance
To find the hex-wise distance between two hexagons, use Hexagon.get_distance()
:
import botroyale as br
hex = br.Hexagon.from_xy(5, -6)
distance_from_center = hex.get_distance(br.CENTER) # 8 tiles distance
Range
To find all hexagons within a certain distance, use Hexagon.range()
:
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
tiles_in_range = hex.range(3) # tuple of the 37 hexagons within 3 distance
Ring
To find all hexagon at exactly a certain distance, use Hexagon.ring()
:
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
tiles_in_ring = hex.ring(3) # tuple of the 18 hexagons at exactly 3 distance
Straight line
To find hexagons in a straight line, use Hexagon.straight_line()
:
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
neighbor = hex.neighbors[0] # A neighbor of hex
# The first hex that continues the line from hex to neighbor
next_in_line = next(hex.straight_line(neighbor))
# Iterating through hexagons in a straight line
for hex_in_line in hex.straight_line(neighbor):
...
Rotation
To find a hexagon from rotation, use Hexagon.rotate()
:
import botroyale as br
hex = br.Hexagon.from_xy(-2, 18)
mirrored_hex = hex.rotate(3) # the hex on the opposite side of the origin
Operators
Hexagons are also vectors, and can be added and subtracted:
import botroyale as br
hex1 = br.Hexagon.from_qr(1, 2)
hex2 = br.Hexagon.from_qr(-1, -2)
center = hex1 - hex2 # equivalent to br.CENTER
Expand source code Browse git
"""Home of the `botroyale.util.hexagon.Hexagon` class.
The Hexagon represents a whole-number point in hex-space. It can also be
thought of as a whole-number vector from the `ORIGIN` (0, 0, 0) in hex-space.
### Coordinates
See the offset (x, y) coordinates with `Hexagon.xy` and the cube (q, r, s)
coordinates with `Hexagon.cube`.
Hexagons are normally not created manually, rather are returned from methods of
existing hexagons. To create a new Hexagon instance, see `Hexagon.from_xy`,
`Hexagon.from_qr` and `Hexagon.from_floats`.
### Neighboring hexagons
`Hexagon.neighbors` will return a hexagon's 6 nearest neighbors:
```python
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
neighbors = hex.neighbors # tuple of the 6 nearest hexagons
assert hex.neighbors == hex.ring(1)
```
`Hexagon.doubles` and `Hexagon.diagonals` together make up the second ring:
```python
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
assert set(hex.ring(2)) == set(hex.doubles) | set(hex.diagonals)
```
### Distance
To find the hex-wise distance between two hexagons, use `Hexagon.get_distance`:
```python
import botroyale as br
hex = br.Hexagon.from_xy(5, -6)
distance_from_center = hex.get_distance(br.CENTER) # 8 tiles distance
```
### Range
To find all hexagons *within* a certain distance, use `Hexagon.range`:
```python
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
tiles_in_range = hex.range(3) # tuple of the 37 hexagons within 3 distance
```
### Ring
To find all hexagon *at exactly* a certain distance, use `Hexagon.ring`:
```python
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
tiles_in_ring = hex.ring(3) # tuple of the 18 hexagons at exactly 3 distance
```
### Straight line
To find hexagons in a straight line, use `Hexagon.straight_line`:
```python
import botroyale as br
hex = br.CENTER # Hexagon(0, 0, 0)
neighbor = hex.neighbors[0] # A neighbor of hex
# The first hex that continues the line from hex to neighbor
next_in_line = next(hex.straight_line(neighbor))
# Iterating through hexagons in a straight line
for hex_in_line in hex.straight_line(neighbor):
...
```
### Rotation
To find a hexagon from rotation, use `Hexagon.rotate`:
```python
import botroyale as br
hex = br.Hexagon.from_xy(-2, 18)
mirrored_hex = hex.rotate(3) # the hex on the opposite side of the origin
```
### Operators
Hexagons are also vectors, and can be added and subtracted:
```python
import botroyale as br
hex1 = br.Hexagon.from_qr(1, 2)
hex2 = br.Hexagon.from_qr(-1, -2)
center = hex1 - hex2 # equivalent to br.CENTER
```
"""
from typing import Sequence, Generator
import math
import functools
SQRT3 = math.sqrt(3)
WIDTH_HEIGHT_RATIO = SQRT3 / 2
GRID_OFFSET = -1
class Hexagon:
"""See module documentation for details."""
def __init__(self, q: int, r: int, s: int):
"""Initialize the class.
The arguments "q", "r", and "s" are components of the cube coordinates.
"""
self.__cube: tuple[int, int, int] = (q, r, s)
assert all(isinstance(c, int) for c in self.__cube)
assert sum(self.__cube) == 0
self.__offset: tuple[int, int] = convert_cube2offset(q, r, s)
@functools.cache
def get_distance(self, hex: "Hexagon") -> int:
"""Number of steps (to a neighbor hex) required to reach hex from self."""
delta = self - hex
return max(abs(c) for c in delta.cube)
def straight_line(
self, neighbor: "Hexagon", max_distance: int = 20
) -> Generator["Hexagon", None, None]:
"""Returns a generator that yields the hexes following a straight line.
The line intersects self and neighbor. Generator values do not include
self or neighbor. *max_distance* determines how many values to generate.
"""
assert neighbor in self.neighbors
dir = neighbor - self
counter = 0
while counter < max_distance:
counter += 1
neighbor += dir
yield neighbor
@functools.cache
def ring(self, radius: int) -> tuple["Hexagon", ...]:
"""Returns a tuple of hexes that are `radius` distance from self.
The resulting hexes are always in the same order, with regards to their
relative position from self.
"""
if radius < 0:
raise ValueError(f"Radius must be non-negative, got: {radius}")
if radius == 0:
return (self,)
if radius == 1:
return self.neighbors
ring = []
dir_ngbr = self + DIRECTIONS[4]
hex = list(self.straight_line(dir_ngbr, max_distance=radius - 1))[-1]
for i in range(6):
for _ in range(radius):
ring.append(hex)
hex = hex + DIRECTIONS[i]
return tuple(ring)
@functools.cache
def range(
self, distance: int, include_center: bool = True
) -> tuple["Hexagon", ...]:
"""Returns a tuple of all the hexes within a distance from self.
The resulting hexes are always in the same order, with regards to their
relative position from self.
include_center -- include self in the results
"""
results = []
for q in range(-distance, distance + 1):
for r in range(
max(-distance, -q - distance), min(+distance, -q + distance) + 1
):
s = -q - r
results.append(self + Hexagon(q, r, s))
if not include_center:
results.remove(self)
return tuple(results)
@functools.cache
def rotate(self, rotations: int = 1) -> "Hexagon":
"""Return the hex given by rotating self about `ORIGIN` by 60° per rotation."""
assert isinstance(rotations, int)
hex = self
if rotations > 0:
while rotations > 0:
hex = Hexagon(-hex.r, -hex.s, -hex.q)
rotations -= 1
elif rotations < 0:
while rotations < 0:
hex = Hexagon(-hex.s, -hex.q, -hex.r)
rotations += 1
return hex
# Nearby hexes
@property
def neighbors(self) -> tuple["Hexagon", ...]:
"""The 6 adjascent hexes.
The resulting hexes are always in the same order, with regards to their
relative position from *self*.
"""
return _get_neighbors(self)
@property
def doubles(self) -> tuple["Hexagon", ...]:
"""The 6 hexes that are 2 distance away and not diagonals.
These resulting hexes can be found on a straight line originating from
*self*, and are always in the same order with regards to their relative
position from *self*.
Doubles and diagonals are complementary parts of `Hexagon.ring` with
`radius=2`.
"""
return _get_doubles(self)
@property
def diagonals(self) -> tuple["Hexagon", ...]:
"""The 6 hexes that are 2 distance away and are diagonals.
These resulting hexes cannot be found on a straight line originating
from *self*, and are always in the same order with regards to their
relative position from *self*.
Doubles and diagonals are complementary parts of `Hexagon.ring` with
`radius=2`.
"""
return _get_diagonals(self)
# Operations
def __add__(self, other: "Hexagon") -> "Hexagon":
"""Cube addition of hexagons. Can be used like vectors."""
if not isinstance(other, type(self)):
raise ValueError(f"Cannot add {type(other)} with {type(self)}")
return Hexagon(self.q + other.q, self.r + other.r, self.s + other.s)
def __sub__(self, other: "Hexagon") -> "Hexagon":
"""Cube subtraction of hexagons. Can be used like vectors."""
if not isinstance(other, type(self)):
raise ValueError(f"Cannot subtract {type(other)} with {type(self)}")
return Hexagon(self.q - other.q, self.r - other.r, self.s - other.s)
def __eq__(self, other: "Hexagon") -> bool:
"""Returns if self and other share coordinates."""
if not isinstance(other, type(self)):
return False
return self.cube == other.cube
@classmethod
def round_(cls, fq: float, fr: float, fs: float) -> "Hexagon":
"""Takes floating point cube coordinates and returns the nearest hex."""
q = round(fq)
r = round(fr)
s = round(fs)
dq = abs(q - fq)
dr = abs(r - fr)
ds = abs(s - fs)
if dq > dr and dq > ds:
q = -r - s
elif dr > ds:
r = -q - s
else:
s = -q - r
return cls(q, r, s)
# Position in 2D space
@functools.cache
def pixel_position(self, radius: int) -> tuple[int, int]:
"""Position in pixels of self, given the radius of a hexagon in pixels."""
offset_r = (self.y % 2 == 1) / 2
x = radius * SQRT3 * (self.x + offset_r)
y = radius * 3 / 2 * self.y
return x, y
@functools.cache
def pixel_position_to_hex(
self, radius: int, pixel_coords: Sequence[float]
) -> "Hexagon":
"""The hex at position `pixel_coords` offset from self."""
x, y = pixel_coords[0] / radius, pixel_coords[1] / radius
q = (SQRT3 / 3 * x) + (-1 / 3 * y)
r = 2 / 3 * y
offset = self.round_(q, r, -q - r)
return offset - self
# Constructors
@classmethod
def from_xy(cls, x: int, y: int) -> "Hexagon":
"""Return the `Hexagon` given the offset (x, y) coordinates."""
return cls(*convert_offset2cube(x, y))
@classmethod
def from_qr(cls, q: int, r: int) -> "Hexagon":
"""Return the `Hexagon` given the partial cube coordinates (q, r)."""
s = -q - r
return cls(q, r, s)
@classmethod
def from_floats(cls, q: float, r: float, s: float) -> "Hexagon":
"""Alias for `Hexagon.round_`."""
return cls.round_(q, r, s)
# Representations
@property
def x(self) -> int:
"""X component of the offset (x, y) coordinates."""
return self.__offset[0]
@property
def y(self) -> int:
"""Y component of the offset (x, y) coordinates."""
return self.__offset[1]
@property
def xy(self) -> tuple[int, int]:
"""Offset coordinates."""
return self.__offset
@property
def q(self) -> int:
"""Q component of the cube (q, r, s) coordinates."""
return self.__cube[0]
@property
def r(self) -> int:
"""R component of the cube (q, r, s) coordinates."""
return self.__cube[1]
@property
def s(self) -> int:
"""S component of the cube (q, r, s) coordinates."""
return self.__cube[2]
@property
def qr(self) -> tuple[int, int]:
"""Q and R components of the cube (q, r, s) coordinates."""
return self.__cube[:2]
@property
def cube(self) -> tuple[int, int, int]:
"""Cube (q, r, s) coordinates."""
return self.__cube
def __repr__(self):
"""Repr."""
return f"<Hex {self.x}, {self.y}>"
def __hash__(self):
"""Hash."""
return hash(self.cube)
# Common Hexagon getters
@functools.cache
def _get_neighbors(hex: Hexagon) -> tuple[Hexagon, ...]:
return tuple(hex + dir for dir in DIRECTIONS)
@functools.cache
def _get_doubles(hex: Hexagon) -> tuple[Hexagon, ...]:
return tuple(hex + doub for doub in DOUBLES)
@functools.cache
def _get_diagonals(hex: Hexagon) -> tuple[Hexagon, ...]:
return tuple(hex + diag for diag in DIAGONALS)
# Coordinate conversion (cube and offset)
@functools.cache
def convert_offset2cube(x: int, y: int) -> Hexagon:
"""Convert offset coordinates (x, y) to cube coordinates (q, r, s)."""
q = x - (y + GRID_OFFSET * (y & 1)) // 2
r = y
s = -q - r
return q, r, s
@functools.cache
def convert_cube2offset(q: int, r: int, s: int) -> tuple[int, int]:
"""Convert cube coordinates (q, r, s) to offset coordinates (x, y)."""
col = q + (r + GRID_OFFSET * (r & 1)) // 2
row = r
return col, row
# Order of directions, doubles, and diagonals are expected to be constant
ORIGIN: Hexagon = Hexagon(0, 0, 0)
"""The origin of the coordinate system.
(0, 0, 0) in cube coordinates and (0, 0) in offset coordinates."""
DIRECTIONS: tuple[Hexagon, ...] = (
Hexagon(1, 0, -1),
Hexagon(1, -1, 0),
Hexagon(0, -1, 1),
Hexagon(-1, 0, 1),
Hexagon(-1, 1, 0),
Hexagon(0, 1, -1),
)
"""The 6 adjascent hexes to `ORIGIN`.
Can be considered the 6 directional normal vectors in hex-space."""
DOUBLES: tuple[Hexagon, ...] = (
Hexagon(2, 0, -2),
Hexagon(2, -2, 0),
Hexagon(0, -2, 2),
Hexagon(-2, 0, 2),
Hexagon(-2, 2, 0),
Hexagon(0, 2, -2),
)
"""The doubles.
6 hexes that are 2 distance away and can be found on a straight line originating
from `ORIGIN`."""
DIAGONALS: tuple[Hexagon, ...] = (
Hexagon(2, -1, -1),
Hexagon(1, -2, 1),
Hexagon(-1, -1, 2),
Hexagon(-2, 1, 1),
Hexagon(-1, 2, -1),
Hexagon(1, 1, -2),
)
"""The diagonals.
6 hexes that are 2 distance away and cannot be found on a straight line
originating from `ORIGIN`."""
# Alias for xy constructor
Hex = Hexagon.from_xy
__all__ = [
"Hexagon",
"Hex",
"ORIGIN",
"DIRECTIONS",
"DOUBLES",
"DIAGONALS",
]
Global variables
var DIAGONALS : tuple[Hexagon, ...]
-
The diagonals.
6 hexes that are 2 distance away and cannot be found on a straight line originating from
ORIGIN
. var DIRECTIONS : tuple[Hexagon, ...]
-
The 6 adjascent hexes to
ORIGIN
.Can be considered the 6 directional normal vectors in hex-space.
var DOUBLES : tuple[Hexagon, ...]
-
The doubles.
6 hexes that are 2 distance away and can be found on a straight line originating from
ORIGIN
. var ORIGIN : Hexagon
-
The origin of the coordinate system.
(0, 0, 0) in cube coordinates and (0, 0) in offset coordinates.
Functions
def Hex(x: int, y: int) ‑> Hexagon
-
Return the
Hexagon
given the offset (x, y) coordinates.Expand source code Browse git
@classmethod def from_xy(cls, x: int, y: int) -> "Hexagon": """Return the `Hexagon` given the offset (x, y) coordinates.""" return cls(*convert_offset2cube(x, y))
Classes
class Hexagon (q: int, r: int, s: int)
-
See module documentation for details.
Initialize the class.
The arguments "q", "r", and "s" are components of the cube coordinates.
Expand source code Browse git
class Hexagon: """See module documentation for details.""" def __init__(self, q: int, r: int, s: int): """Initialize the class. The arguments "q", "r", and "s" are components of the cube coordinates. """ self.__cube: tuple[int, int, int] = (q, r, s) assert all(isinstance(c, int) for c in self.__cube) assert sum(self.__cube) == 0 self.__offset: tuple[int, int] = convert_cube2offset(q, r, s) @functools.cache def get_distance(self, hex: "Hexagon") -> int: """Number of steps (to a neighbor hex) required to reach hex from self.""" delta = self - hex return max(abs(c) for c in delta.cube) def straight_line( self, neighbor: "Hexagon", max_distance: int = 20 ) -> Generator["Hexagon", None, None]: """Returns a generator that yields the hexes following a straight line. The line intersects self and neighbor. Generator values do not include self or neighbor. *max_distance* determines how many values to generate. """ assert neighbor in self.neighbors dir = neighbor - self counter = 0 while counter < max_distance: counter += 1 neighbor += dir yield neighbor @functools.cache def ring(self, radius: int) -> tuple["Hexagon", ...]: """Returns a tuple of hexes that are `radius` distance from self. The resulting hexes are always in the same order, with regards to their relative position from self. """ if radius < 0: raise ValueError(f"Radius must be non-negative, got: {radius}") if radius == 0: return (self,) if radius == 1: return self.neighbors ring = [] dir_ngbr = self + DIRECTIONS[4] hex = list(self.straight_line(dir_ngbr, max_distance=radius - 1))[-1] for i in range(6): for _ in range(radius): ring.append(hex) hex = hex + DIRECTIONS[i] return tuple(ring) @functools.cache def range( self, distance: int, include_center: bool = True ) -> tuple["Hexagon", ...]: """Returns a tuple of all the hexes within a distance from self. The resulting hexes are always in the same order, with regards to their relative position from self. include_center -- include self in the results """ results = [] for q in range(-distance, distance + 1): for r in range( max(-distance, -q - distance), min(+distance, -q + distance) + 1 ): s = -q - r results.append(self + Hexagon(q, r, s)) if not include_center: results.remove(self) return tuple(results) @functools.cache def rotate(self, rotations: int = 1) -> "Hexagon": """Return the hex given by rotating self about `ORIGIN` by 60° per rotation.""" assert isinstance(rotations, int) hex = self if rotations > 0: while rotations > 0: hex = Hexagon(-hex.r, -hex.s, -hex.q) rotations -= 1 elif rotations < 0: while rotations < 0: hex = Hexagon(-hex.s, -hex.q, -hex.r) rotations += 1 return hex # Nearby hexes @property def neighbors(self) -> tuple["Hexagon", ...]: """The 6 adjascent hexes. The resulting hexes are always in the same order, with regards to their relative position from *self*. """ return _get_neighbors(self) @property def doubles(self) -> tuple["Hexagon", ...]: """The 6 hexes that are 2 distance away and not diagonals. These resulting hexes can be found on a straight line originating from *self*, and are always in the same order with regards to their relative position from *self*. Doubles and diagonals are complementary parts of `Hexagon.ring` with `radius=2`. """ return _get_doubles(self) @property def diagonals(self) -> tuple["Hexagon", ...]: """The 6 hexes that are 2 distance away and are diagonals. These resulting hexes cannot be found on a straight line originating from *self*, and are always in the same order with regards to their relative position from *self*. Doubles and diagonals are complementary parts of `Hexagon.ring` with `radius=2`. """ return _get_diagonals(self) # Operations def __add__(self, other: "Hexagon") -> "Hexagon": """Cube addition of hexagons. Can be used like vectors.""" if not isinstance(other, type(self)): raise ValueError(f"Cannot add {type(other)} with {type(self)}") return Hexagon(self.q + other.q, self.r + other.r, self.s + other.s) def __sub__(self, other: "Hexagon") -> "Hexagon": """Cube subtraction of hexagons. Can be used like vectors.""" if not isinstance(other, type(self)): raise ValueError(f"Cannot subtract {type(other)} with {type(self)}") return Hexagon(self.q - other.q, self.r - other.r, self.s - other.s) def __eq__(self, other: "Hexagon") -> bool: """Returns if self and other share coordinates.""" if not isinstance(other, type(self)): return False return self.cube == other.cube @classmethod def round_(cls, fq: float, fr: float, fs: float) -> "Hexagon": """Takes floating point cube coordinates and returns the nearest hex.""" q = round(fq) r = round(fr) s = round(fs) dq = abs(q - fq) dr = abs(r - fr) ds = abs(s - fs) if dq > dr and dq > ds: q = -r - s elif dr > ds: r = -q - s else: s = -q - r return cls(q, r, s) # Position in 2D space @functools.cache def pixel_position(self, radius: int) -> tuple[int, int]: """Position in pixels of self, given the radius of a hexagon in pixels.""" offset_r = (self.y % 2 == 1) / 2 x = radius * SQRT3 * (self.x + offset_r) y = radius * 3 / 2 * self.y return x, y @functools.cache def pixel_position_to_hex( self, radius: int, pixel_coords: Sequence[float] ) -> "Hexagon": """The hex at position `pixel_coords` offset from self.""" x, y = pixel_coords[0] / radius, pixel_coords[1] / radius q = (SQRT3 / 3 * x) + (-1 / 3 * y) r = 2 / 3 * y offset = self.round_(q, r, -q - r) return offset - self # Constructors @classmethod def from_xy(cls, x: int, y: int) -> "Hexagon": """Return the `Hexagon` given the offset (x, y) coordinates.""" return cls(*convert_offset2cube(x, y)) @classmethod def from_qr(cls, q: int, r: int) -> "Hexagon": """Return the `Hexagon` given the partial cube coordinates (q, r).""" s = -q - r return cls(q, r, s) @classmethod def from_floats(cls, q: float, r: float, s: float) -> "Hexagon": """Alias for `Hexagon.round_`.""" return cls.round_(q, r, s) # Representations @property def x(self) -> int: """X component of the offset (x, y) coordinates.""" return self.__offset[0] @property def y(self) -> int: """Y component of the offset (x, y) coordinates.""" return self.__offset[1] @property def xy(self) -> tuple[int, int]: """Offset coordinates.""" return self.__offset @property def q(self) -> int: """Q component of the cube (q, r, s) coordinates.""" return self.__cube[0] @property def r(self) -> int: """R component of the cube (q, r, s) coordinates.""" return self.__cube[1] @property def s(self) -> int: """S component of the cube (q, r, s) coordinates.""" return self.__cube[2] @property def qr(self) -> tuple[int, int]: """Q and R components of the cube (q, r, s) coordinates.""" return self.__cube[:2] @property def cube(self) -> tuple[int, int, int]: """Cube (q, r, s) coordinates.""" return self.__cube def __repr__(self): """Repr.""" return f"<Hex {self.x}, {self.y}>" def __hash__(self): """Hash.""" return hash(self.cube)
Subclasses
Static methods
def from_floats(q: float, r: float, s: float) ‑> Hexagon
-
Alias for
Hexagon.round_()
.Expand source code Browse git
@classmethod def from_floats(cls, q: float, r: float, s: float) -> "Hexagon": """Alias for `Hexagon.round_`.""" return cls.round_(q, r, s)
def from_qr(q: int, r: int) ‑> Hexagon
-
Return the
Hexagon
given the partial cube coordinates (q, r).Expand source code Browse git
@classmethod def from_qr(cls, q: int, r: int) -> "Hexagon": """Return the `Hexagon` given the partial cube coordinates (q, r).""" s = -q - r return cls(q, r, s)
def from_xy(x: int, y: int) ‑> Hexagon
-
Return the
Hexagon
given the offset (x, y) coordinates.Expand source code Browse git
@classmethod def from_xy(cls, x: int, y: int) -> "Hexagon": """Return the `Hexagon` given the offset (x, y) coordinates.""" return cls(*convert_offset2cube(x, y))
def round_(fq: float, fr: float, fs: float) ‑> Hexagon
-
Takes floating point cube coordinates and returns the nearest hex.
Expand source code Browse git
@classmethod def round_(cls, fq: float, fr: float, fs: float) -> "Hexagon": """Takes floating point cube coordinates and returns the nearest hex.""" q = round(fq) r = round(fr) s = round(fs) dq = abs(q - fq) dr = abs(r - fr) ds = abs(s - fs) if dq > dr and dq > ds: q = -r - s elif dr > ds: r = -q - s else: s = -q - r return cls(q, r, s)
Instance variables
var cube : tuple[int, int, int]
-
Cube (q, r, s) coordinates.
Expand source code Browse git
@property def cube(self) -> tuple[int, int, int]: """Cube (q, r, s) coordinates.""" return self.__cube
var diagonals : tuple['Hexagon', ...]
-
The 6 hexes that are 2 distance away and are diagonals.
These resulting hexes cannot be found on a straight line originating from self, and are always in the same order with regards to their relative position from self.
Doubles and diagonals are complementary parts of
Hexagon.ring()
withradius=2
.Expand source code Browse git
@property def diagonals(self) -> tuple["Hexagon", ...]: """The 6 hexes that are 2 distance away and are diagonals. These resulting hexes cannot be found on a straight line originating from *self*, and are always in the same order with regards to their relative position from *self*. Doubles and diagonals are complementary parts of `Hexagon.ring` with `radius=2`. """ return _get_diagonals(self)
var doubles : tuple['Hexagon', ...]
-
The 6 hexes that are 2 distance away and not diagonals.
These resulting hexes can be found on a straight line originating from self, and are always in the same order with regards to their relative position from self.
Doubles and diagonals are complementary parts of
Hexagon.ring()
withradius=2
.Expand source code Browse git
@property def doubles(self) -> tuple["Hexagon", ...]: """The 6 hexes that are 2 distance away and not diagonals. These resulting hexes can be found on a straight line originating from *self*, and are always in the same order with regards to their relative position from *self*. Doubles and diagonals are complementary parts of `Hexagon.ring` with `radius=2`. """ return _get_doubles(self)
var neighbors : tuple['Hexagon', ...]
-
The 6 adjascent hexes.
The resulting hexes are always in the same order, with regards to their relative position from self.
Expand source code Browse git
@property def neighbors(self) -> tuple["Hexagon", ...]: """The 6 adjascent hexes. The resulting hexes are always in the same order, with regards to their relative position from *self*. """ return _get_neighbors(self)
var q : int
-
Q component of the cube (q, r, s) coordinates.
Expand source code Browse git
@property def q(self) -> int: """Q component of the cube (q, r, s) coordinates.""" return self.__cube[0]
var qr : tuple[int, int]
-
Q and R components of the cube (q, r, s) coordinates.
Expand source code Browse git
@property def qr(self) -> tuple[int, int]: """Q and R components of the cube (q, r, s) coordinates.""" return self.__cube[:2]
var r : int
-
R component of the cube (q, r, s) coordinates.
Expand source code Browse git
@property def r(self) -> int: """R component of the cube (q, r, s) coordinates.""" return self.__cube[1]
var s : int
-
S component of the cube (q, r, s) coordinates.
Expand source code Browse git
@property def s(self) -> int: """S component of the cube (q, r, s) coordinates.""" return self.__cube[2]
var x : int
-
X component of the offset (x, y) coordinates.
Expand source code Browse git
@property def x(self) -> int: """X component of the offset (x, y) coordinates.""" return self.__offset[0]
var xy : tuple[int, int]
-
Offset coordinates.
Expand source code Browse git
@property def xy(self) -> tuple[int, int]: """Offset coordinates.""" return self.__offset
var y : int
-
Y component of the offset (x, y) coordinates.
Expand source code Browse git
@property def y(self) -> int: """Y component of the offset (x, y) coordinates.""" return self.__offset[1]
Methods
def get_distance(self, hex: Hexagon) ‑> int
-
Number of steps (to a neighbor hex) required to reach hex from self.
Expand source code Browse git
@functools.cache def get_distance(self, hex: "Hexagon") -> int: """Number of steps (to a neighbor hex) required to reach hex from self.""" delta = self - hex return max(abs(c) for c in delta.cube)
def pixel_position(self, radius: int) ‑> tuple[int, int]
-
Position in pixels of self, given the radius of a hexagon in pixels.
Expand source code Browse git
@functools.cache def pixel_position(self, radius: int) -> tuple[int, int]: """Position in pixels of self, given the radius of a hexagon in pixels.""" offset_r = (self.y % 2 == 1) / 2 x = radius * SQRT3 * (self.x + offset_r) y = radius * 3 / 2 * self.y return x, y
def pixel_position_to_hex(self, radius: int, pixel_coords: Sequence[float]) ‑> Hexagon
-
The hex at position
pixel_coords
offset from self.Expand source code Browse git
@functools.cache def pixel_position_to_hex( self, radius: int, pixel_coords: Sequence[float] ) -> "Hexagon": """The hex at position `pixel_coords` offset from self.""" x, y = pixel_coords[0] / radius, pixel_coords[1] / radius q = (SQRT3 / 3 * x) + (-1 / 3 * y) r = 2 / 3 * y offset = self.round_(q, r, -q - r) return offset - self
def range(self, distance: int, include_center: bool = True) ‑> tuple['Hexagon', ...]
-
Returns a tuple of all the hexes within a distance from self.
The resulting hexes are always in the same order, with regards to their relative position from self.
include_center – include self in the results
Expand source code Browse git
@functools.cache def range( self, distance: int, include_center: bool = True ) -> tuple["Hexagon", ...]: """Returns a tuple of all the hexes within a distance from self. The resulting hexes are always in the same order, with regards to their relative position from self. include_center -- include self in the results """ results = [] for q in range(-distance, distance + 1): for r in range( max(-distance, -q - distance), min(+distance, -q + distance) + 1 ): s = -q - r results.append(self + Hexagon(q, r, s)) if not include_center: results.remove(self) return tuple(results)
def ring(self, radius: int) ‑> tuple['Hexagon', ...]
-
Returns a tuple of hexes that are
radius
distance from self.The resulting hexes are always in the same order, with regards to their relative position from self.
Expand source code Browse git
@functools.cache def ring(self, radius: int) -> tuple["Hexagon", ...]: """Returns a tuple of hexes that are `radius` distance from self. The resulting hexes are always in the same order, with regards to their relative position from self. """ if radius < 0: raise ValueError(f"Radius must be non-negative, got: {radius}") if radius == 0: return (self,) if radius == 1: return self.neighbors ring = [] dir_ngbr = self + DIRECTIONS[4] hex = list(self.straight_line(dir_ngbr, max_distance=radius - 1))[-1] for i in range(6): for _ in range(radius): ring.append(hex) hex = hex + DIRECTIONS[i] return tuple(ring)
def rotate(self, rotations: int = 1) ‑> Hexagon
-
Return the hex given by rotating self about
ORIGIN
by 60° per rotation.Expand source code Browse git
@functools.cache def rotate(self, rotations: int = 1) -> "Hexagon": """Return the hex given by rotating self about `ORIGIN` by 60° per rotation.""" assert isinstance(rotations, int) hex = self if rotations > 0: while rotations > 0: hex = Hexagon(-hex.r, -hex.s, -hex.q) rotations -= 1 elif rotations < 0: while rotations < 0: hex = Hexagon(-hex.s, -hex.q, -hex.r) rotations += 1 return hex
def straight_line(self, neighbor: Hexagon, max_distance: int = 20) ‑> Generator[Hexagon, None, None]
-
Returns a generator that yields the hexes following a straight line.
The line intersects self and neighbor. Generator values do not include self or neighbor. max_distance determines how many values to generate.
Expand source code Browse git
def straight_line( self, neighbor: "Hexagon", max_distance: int = 20 ) -> Generator["Hexagon", None, None]: """Returns a generator that yields the hexes following a straight line. The line intersects self and neighbor. Generator values do not include self or neighbor. *max_distance* determines how many values to generate. """ assert neighbor in self.neighbors dir = neighbor - self counter = 0 while counter < max_distance: counter += 1 neighbor += dir yield neighbor