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
Hexagongiven 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
Hexagongiven 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
Hexagongiven 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_coordsoffset 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
radiusdistance 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
ORIGINby 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