From 1cf27d9a60ddc04e9c66ba303a8e5229b4005fa2 Mon Sep 17 00:00:00 2001 From: wlame <4479808+wlame@users.noreply.github.com> Date: Thu, 9 Jan 2025 01:42:45 +0100 Subject: [PATCH 1/8] Grid idea drafts --- hexometry.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/hexometry.py b/hexometry.py index 53cd51b..a6f28b1 100644 --- a/hexometry.py +++ b/hexometry.py @@ -6,7 +6,7 @@ import random import functools -from typing import TypeAlias, Iterator, Callable, Self +from typing import TypeAlias, Iterator, Callable, override __version__ = '1.0.1' @@ -243,3 +243,56 @@ def hex_to_decart_corners(coord: Coord, scale_factor: float) -> list[DecartCoord (x - scale_factor / 2, y + 3**0.5 / 2 * scale_factor), ] return [(round(x, _FLOAT_PRECISION), round(y, _FLOAT_PRECISION)) for x, y in coordinates] + + +from typing import TypeVar, Generic + +HexValue = TypeVar('HexValue') + + +class Grid(dict[Coord, HexValue]): + """Generic Hex Grid of some values.""" + + def __init__(self, default: HexValue | Callable[[Coord], HexValue]): + self.default: HexValue | None = None if callable(default) else default + self.get_default: Callable | None = default if callable(default) else None + + @classmethod + def load_from_array(cls, array: list) -> Grid: + return cls(None) # todo + + def normalize(self, value: HexValue) -> HexValue: + return value + + def __setitem__(self, key: Coord, value: HexValue) -> None: + super().__setitem__(key, self.normalize(value)) + + def __getitem__(self, key: Coord) -> HexValue | None: + if key in self: + return super().__getitem__(key) + elif self.get_default is not None: + return self.get_default(key) + + return self.default + + +class BlockageGrid(Grid[float]): + """Hex grid of float blockage values. Useful for calculating Route cost. + Blockage values are weights from 0.0 to 1.0 + where 1.0 means hex at this coordinates is blocked for traversing + """ + + MIN_VALUE = 0.0 + MAX_VALUE = 1.0 + + def __init__(self, default_blockage_level: float = 0.0): + super().__init__(default=default_blockage_level) + + @override + def normalize(self, value: float): + if value < self.MIN_VALUE: + return self.MIN_VALUE + if value > self.MAX_VALUE: + return self.MAX_VALUE + + return value From 7c6b7e7abed33c43f27c9be02127ebb118623315 Mon Sep 17 00:00:00 2001 From: wlame <4479808+wlame@users.noreply.github.com> Date: Sun, 12 Jan 2025 22:18:45 +0100 Subject: [PATCH 2/8] Add iterate_route generator. --- hexometry.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/hexometry.py b/hexometry.py index a6f28b1..cdaf9dc 100644 --- a/hexometry.py +++ b/hexometry.py @@ -27,7 +27,7 @@ class Direction(enum.Enum): @functools.cached_property def _all(self) -> list[Self]: - return list(Direction) + return list(Direction) # type: ignore def __repr__(self) -> str: return self.value @@ -207,6 +207,14 @@ def traverse_route(start: Coord, route: Route) -> Coord: return coord +def iterate_route(start: Coord, route: Route) -> Iterator[tuple[Direction, Coord]]: + """Generates pairs of Direction to next Hex and its coordinates.""" + coord = start + for direction in route: + coord = get_neighbour(coord, direction) + yield direction, coord + + def hex_to_decart(coord: Coord, scale_factor: float) -> DecartCoord: """Converts a hex coordinate to a decart coordinates assuming the (0, 0) coordinates are matched in hex grid and decart grid @@ -257,15 +265,11 @@ def __init__(self, default: HexValue | Callable[[Coord], HexValue]): self.default: HexValue | None = None if callable(default) else default self.get_default: Callable | None = default if callable(default) else None - @classmethod - def load_from_array(cls, array: list) -> Grid: - return cls(None) # todo - - def normalize(self, value: HexValue) -> HexValue: + def normalize(self, hex: Coord, value: HexValue) -> HexValue: return value def __setitem__(self, key: Coord, value: HexValue) -> None: - super().__setitem__(key, self.normalize(value)) + super().__setitem__(key, self.normalize(key, value)) def __getitem__(self, key: Coord) -> HexValue | None: if key in self: From b525a76ca39031703f11298e33d9913b6b00a75c Mon Sep 17 00:00:00 2001 From: wlame <4479808+wlame@users.noreply.github.com> Date: Sun, 12 Jan 2025 22:20:30 +0100 Subject: [PATCH 3/8] Add Dijkstra path finding algorithm. --- hexometry.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/hexometry.py b/hexometry.py index cdaf9dc..9a0d52a 100644 --- a/hexometry.py +++ b/hexometry.py @@ -6,7 +6,7 @@ import random import functools -from typing import TypeAlias, Iterator, Callable, override +from typing import TypeAlias, Iterator, Callable, Self, override __version__ = '1.0.1' @@ -253,6 +253,7 @@ def hex_to_decart_corners(coord: Coord, scale_factor: float) -> list[DecartCoord return [(round(x, _FLOAT_PRECISION), round(y, _FLOAT_PRECISION)) for x, y in coordinates] +# will be in separate module from typing import TypeVar, Generic HexValue = TypeVar('HexValue') @@ -279,11 +280,17 @@ def __getitem__(self, key: Coord) -> HexValue | None: return self.default + def __repr__(self) -> str: + return f'<{self.__class__.__name__}({super().__repr__()})>' + + def hexes(self) -> Iterator[Coord]: + yield from self.keys() + class BlockageGrid(Grid[float]): """Hex grid of float blockage values. Useful for calculating Route cost. - Blockage values are weights from 0.0 to 1.0 - where 1.0 means hex at this coordinates is blocked for traversing + Blockage values are weights from 0 to 1 + where 1 means hex at this coordinates is blocked for traversing """ MIN_VALUE = 0.0 @@ -293,10 +300,63 @@ def __init__(self, default_blockage_level: float = 0.0): super().__init__(default=default_blockage_level) @override - def normalize(self, value: float): + def normalize(self, hex: Coord, value: float): if value < self.MIN_VALUE: return self.MIN_VALUE if value > self.MAX_VALUE: return self.MAX_VALUE return value + + +import heapq + + +def dijkstra(start: Coord, end: Coord, weights: BlockageGrid | None = None, step_penalty=0.00001) -> Route: + """ + Returns a route from `start` coordinates to `end`, respecting `weights` grid if provided. + If there is no route (weights grid blocks any route) will retunt empty route []. + `step_penalty` — minimal penalty to each step to track + how far from start we get and look for the shortest way. + Could be useful to balance between minimizing distance or sum of weights on it. + """ + + if weights is None: + # no weights grid provided, assuming all hexes field is available + # fallback to cheapest default route calculation + return start >> end + + if step_penalty <= 0: + raise ValueError('step_penalty should be positive float number') # to avoid infinite loops + + queue = [(0, start)] + distances: dict[Coord, float] = {start: 0.0} + previous: dict[Coord, Coord] = {} + directions: dict[Coord, Direction] = {} + + while queue: + current_distance, current_hex = heapq.heappop(queue) + + if current_hex == end: + break + + for direction, neighbour in get_directed_neighbours(current_hex): + if weights[neighbour] >= BlockageGrid.MAX_VALUE: # hex is blocked + continue + + distance_to_neighbour = current_distance + step_penalty + weights[neighbour] + + if neighbour not in distances or distance_to_neighbour < distances[neighbour]: + distances[neighbour] = distance_to_neighbour # best value + directions[neighbour] = direction # how we get there + previous[neighbour] = current_hex # where we made last step from + heapq.heappush(queue, (distance_to_neighbour, neighbour)) + + # Reconstruct the path + path = Route() + while end in previous: + path.append(directions[end]) + end = previous[end] + + path.reverse() + return path From 7db0255ec5a0671c6e56988eef6a6af905c915f6 Mon Sep 17 00:00:00 2001 From: wlame <4479808+wlame@users.noreply.github.com> Date: Sun, 2 Feb 2025 18:54:42 +0100 Subject: [PATCH 4/8] Refactor library: split grids to separate files --- src/hexometry/__init__.py | 17 +++ hexometry.py => src/hexometry/coordinates.py | 114 --------------- src/hexometry/grids.py | 107 ++++++++++++++ src/tests/__init__.py | 0 tests.py => src/tests/geometry_test.py | 27 ++++ src/tests/grids_test.py | 146 +++++++++++++++++++ 6 files changed, 297 insertions(+), 114 deletions(-) create mode 100644 src/hexometry/__init__.py rename hexometry.py => src/hexometry/coordinates.py (68%) create mode 100644 src/hexometry/grids.py create mode 100644 src/tests/__init__.py rename tests.py => src/tests/geometry_test.py (92%) create mode 100644 src/tests/grids_test.py diff --git a/src/hexometry/__init__.py b/src/hexometry/__init__.py new file mode 100644 index 0000000..0e0d018 --- /dev/null +++ b/src/hexometry/__init__.py @@ -0,0 +1,17 @@ +""" +**Hexometry** + +A Python library for working with hexagonal grids, providing tools for coordinate manipulation, pathfinding, and grid operations. + +Key Features: +- Hexagonal coordinate system conversions +- Neighbor finding and distance calculations +- Route generation between coordinates +- Pathfinding using Dijkstra's algorithm with optional penalties +- Grid management for hex-based applications +""" + +from .coordinates import * +from .grids import * + +__version__ = '1.0.1' diff --git a/hexometry.py b/src/hexometry/coordinates.py similarity index 68% rename from hexometry.py rename to src/hexometry/coordinates.py index 9a0d52a..a14e11a 100644 --- a/hexometry.py +++ b/src/hexometry/coordinates.py @@ -1,5 +1,3 @@ -"""Hexometry module""" - import collections import enum import math @@ -9,9 +7,6 @@ from typing import TypeAlias, Iterator, Callable, Self, override -__version__ = '1.0.1' - - _FLOAT_PRECISION = 4 @@ -251,112 +246,3 @@ def hex_to_decart_corners(coord: Coord, scale_factor: float) -> list[DecartCoord (x - scale_factor / 2, y + 3**0.5 / 2 * scale_factor), ] return [(round(x, _FLOAT_PRECISION), round(y, _FLOAT_PRECISION)) for x, y in coordinates] - - -# will be in separate module -from typing import TypeVar, Generic - -HexValue = TypeVar('HexValue') - - -class Grid(dict[Coord, HexValue]): - """Generic Hex Grid of some values.""" - - def __init__(self, default: HexValue | Callable[[Coord], HexValue]): - self.default: HexValue | None = None if callable(default) else default - self.get_default: Callable | None = default if callable(default) else None - - def normalize(self, hex: Coord, value: HexValue) -> HexValue: - return value - - def __setitem__(self, key: Coord, value: HexValue) -> None: - super().__setitem__(key, self.normalize(key, value)) - - def __getitem__(self, key: Coord) -> HexValue | None: - if key in self: - return super().__getitem__(key) - elif self.get_default is not None: - return self.get_default(key) - - return self.default - - def __repr__(self) -> str: - return f'<{self.__class__.__name__}({super().__repr__()})>' - - def hexes(self) -> Iterator[Coord]: - yield from self.keys() - - -class BlockageGrid(Grid[float]): - """Hex grid of float blockage values. Useful for calculating Route cost. - Blockage values are weights from 0 to 1 - where 1 means hex at this coordinates is blocked for traversing - """ - - MIN_VALUE = 0.0 - MAX_VALUE = 1.0 - - def __init__(self, default_blockage_level: float = 0.0): - super().__init__(default=default_blockage_level) - - @override - def normalize(self, hex: Coord, value: float): - if value < self.MIN_VALUE: - return self.MIN_VALUE - if value > self.MAX_VALUE: - return self.MAX_VALUE - - return value - - -import heapq - - -def dijkstra(start: Coord, end: Coord, weights: BlockageGrid | None = None, step_penalty=0.00001) -> Route: - """ - Returns a route from `start` coordinates to `end`, respecting `weights` grid if provided. - If there is no route (weights grid blocks any route) will retunt empty route []. - `step_penalty` — minimal penalty to each step to track - how far from start we get and look for the shortest way. - Could be useful to balance between minimizing distance or sum of weights on it. - """ - - if weights is None: - # no weights grid provided, assuming all hexes field is available - # fallback to cheapest default route calculation - return start >> end - - if step_penalty <= 0: - raise ValueError('step_penalty should be positive float number') # to avoid infinite loops - - queue = [(0, start)] - distances: dict[Coord, float] = {start: 0.0} - previous: dict[Coord, Coord] = {} - directions: dict[Coord, Direction] = {} - - while queue: - current_distance, current_hex = heapq.heappop(queue) - - if current_hex == end: - break - - for direction, neighbour in get_directed_neighbours(current_hex): - if weights[neighbour] >= BlockageGrid.MAX_VALUE: # hex is blocked - continue - - distance_to_neighbour = current_distance + step_penalty + weights[neighbour] - - if neighbour not in distances or distance_to_neighbour < distances[neighbour]: - distances[neighbour] = distance_to_neighbour # best value - directions[neighbour] = direction # how we get there - previous[neighbour] = current_hex # where we made last step from - heapq.heappush(queue, (distance_to_neighbour, neighbour)) - - # Reconstruct the path - path = Route() - while end in previous: - path.append(directions[end]) - end = previous[end] - - path.reverse() - return path diff --git a/src/hexometry/grids.py b/src/hexometry/grids.py new file mode 100644 index 0000000..5d26d98 --- /dev/null +++ b/src/hexometry/grids.py @@ -0,0 +1,107 @@ +import heapq + +from typing import Callable, Iterator, TypeVar, override + +from .coordinates import Coord, Route, get_directed_neighbours + + +HexValue = TypeVar('HexValue') + + +class Grid(dict[Coord, HexValue]): + """Generic Hex Grid of some values.""" + + def __init__(self, default: HexValue | Callable[[Coord], HexValue]): + self.default: HexValue | None = None if callable(default) else default + self.get_default: Callable | None = default if callable(default) else None + + def normalize(self, hex: Coord, value: HexValue) -> HexValue: + return value + + def __setitem__(self, key: Coord, value: HexValue) -> None: + super().__setitem__(key, self.normalize(key, value)) + + def __getitem__(self, key: Coord) -> HexValue | None: + if key in self: + return super().__getitem__(key) + elif self.get_default is not None: + return self.get_default(key) + + return self.default + + def __repr__(self) -> str: + return f'<{self.__class__.__name__}({super().__repr__()})>' + + def hexes(self) -> Iterator[Coord]: + yield from self.keys() + + +class BlockageGrid(Grid[float]): + """Hex grid of float blockage values. Useful for calculating Route cost. + Blockage values are penalties from 0 to 1 + where 1 means hex at this coordinates is blocked for traversing + """ + + MIN_VALUE = 0.0 + MAX_VALUE = 1.0 + + def __init__(self, default_blockage_level: float = 0.0): + super().__init__(default=default_blockage_level) + + @override + def normalize(self, hex: Coord, value: float): + if value < self.MIN_VALUE: + return self.MIN_VALUE + if value > self.MAX_VALUE: + return self.MAX_VALUE + + return value + + +def dijkstra(start: Coord, end: Coord, penalties: BlockageGrid | None = None, step_penalty=0.00001) -> Route: + """ + Returns a route from `start` coordinates to `end`, respecting `penalties` grid if provided. + If there is no route (penalties grid blocks any route) will return empty route []. + `step_penalty` — minimal penalty to each step to track + how far from start we get and look for the shortest way. + Could be useful to balance between minimizing distance or sum of penalties on it. + """ + if step_penalty <= 0: + raise ValueError('step_penalty should be positive float number') # to avoid infinite loops + + if penalties is None: + # no penalties grid provided, assuming all hexes field is available + # fallback to cheapest default route calculation + return start >> end + + queue = [(0, start)] + distances: dict[Coord, float] = {start: 0.0} + previous: dict[Coord, Coord] = {} + directions: dict[Coord, Direction] = {} + + while queue: + current_distance, current_hex = heapq.heappop(queue) + + if current_hex == end: + break + + for direction, neighbour in get_directed_neighbours(current_hex): + if penalties[neighbour] >= BlockageGrid.MAX_VALUE: # hex is blocked + continue + + distance_to_neighbour = current_distance + step_penalty + penalties[neighbour] + + if neighbour not in distances or distance_to_neighbour < distances[neighbour]: + distances[neighbour] = distance_to_neighbour # best value + directions[neighbour] = direction # how we get there + previous[neighbour] = current_hex # where we made last step from + heapq.heappush(queue, (distance_to_neighbour, neighbour)) + + # Reconstruct the path + path = Route() + while end in previous: + path.append(directions[end]) + end = previous[end] + + path.reverse() + return path diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests.py b/src/tests/geometry_test.py similarity index 92% rename from tests.py rename to src/tests/geometry_test.py index d41c900..04eb8ee 100644 --- a/tests.py +++ b/src/tests/geometry_test.py @@ -5,10 +5,12 @@ import pytest + from hexometry import ( Coord, Direction, Route, + iterate_route, get_route, get_directed_neighbours, hex_to_decart, @@ -289,3 +291,28 @@ def test_hex_to_decart_corners(hex_xy, scale_factor, expected_corners_coordinate if scale_factor == 1: assert round(c) == hex_to_decart_corners(c, scale_factor=1) + + +def test_iterate_route(): + c1 = Coord(0, 0) + route = ['↗', '→', '→', '↘', '←'] + expected_coordinates = [ + Coord(0, 1), + Coord(1, 1), + Coord(2, 1), + Coord(3, 0), + Coord(2, 0), + ] + + route_iterator = iterate_route(c1, route) + for step, (direction, coord) in enumerate(route_iterator): + assert direction == route[step] + assert coord == expected_coordinates[step] + + +def test_iterate_route_with_empty_route(): + coord = Coord(random.randint(-100, 100), random.randint(-100, 100)) + route_iterator = iterate_route(coord, Route()) + + with pytest.raises(StopIteration): + next(route_iterator) diff --git a/src/tests/grids_test.py b/src/tests/grids_test.py new file mode 100644 index 0000000..31c6136 --- /dev/null +++ b/src/tests/grids_test.py @@ -0,0 +1,146 @@ +import pytest + +from hexometry.coordinates import Coord, Direction, Route +from hexometry.grids import Grid, BlockageGrid, dijkstra + + +def test_grid_repr(): + grid = Grid(default=None) + assert repr(grid) == '' + + +def test_grid_initialization_with_default_value(): + grid = Grid(default=0) + assert grid.default == 0 + + +def test_grid_initialization_with_callable_default(): + def default(coord): + return coord.x + coord.y + + grid = Grid(default=default) + assert grid.default is None + assert grid[Coord(1, 2)] == 3 + + +def test_grid_setitem_normalizes_value(): + class TestGrid(Grid[int]): + def normalize(self, hex: Coord, value: int): + return value * 2 + + grid = TestGrid(default=0) + grid[Coord(1, 2)] = 5 + assert grid[Coord(1, 2)] == 10 + + +def test_grid_getitem_returns_stored_value(): + grid = Grid(default=0) + grid[Coord(1, 2)] = 5 + assert grid[Coord(1, 2)] == 5 + + +def test_grid_getitem_returns_default_value(): + grid = Grid(default=0) + assert grid[Coord(3, 4)] == 0 + + +def test_grid_getitem_with_callable_default(): + def get_z_coord(coord): + return 0 - coord.x - coord.y + + grid = Grid(default=get_z_coord) + assert grid[Coord(1, 2)] == -3 + + +def test_grid_getitem_calls_callable_default(): + def default(coord): + return coord.x + coord.y + + grid = Grid(default=default) + assert grid[Coord(1, 2)] == 3 + + +def test_grid_hexes_iterator(): + grid = Grid(default=0) + grid[Coord(1, 2)] = 5 + grid[Coord(3, 4)] = 10 + hexes = list(grid.hexes()) + assert len(hexes) == 2 + assert Coord(1, 2) in hexes + assert Coord(3, 4) in hexes + + +def test_blockagegrid_initialization(): + blockage_grid = BlockageGrid(default_blockage_level=0.5) + assert blockage_grid.default == 0.5 + assert blockage_grid.get_default is None + + +def test_blockagegrid_normalize_clamps_values(): + blockage_grid = BlockageGrid() + assert blockage_grid.normalize(Coord(1, 2), -0.1) == 0.0 + assert blockage_grid.normalize(Coord(1, 2), 1.5) == 1.0 + assert blockage_grid.normalize(Coord(1, 2), 0.7) == 0.7 + + +def test_blockagegrid_getitem_returns_clamped_values(): + blockage_grid = BlockageGrid(default_blockage_level=0.5) + blockage_grid[Coord(1, 2)] = -0.1 + assert blockage_grid[Coord(1, 2)] == 0.0 + + blockage_grid[Coord(3, 4)] = 1.5 + assert blockage_grid[Coord(3, 4)] == 1.0 + + +def test_dijkstra_without_penalties_fallbacks_to_default_route_calcilation(): + start = Coord(0, 0) + end = Coord(2, 2) + route = dijkstra(start, end) + expected_route = start >> end + assert route == expected_route + + +penalties_grids_test_cases = [ + # coordinates: start, end + # penalties grid: {value: [(x, y), ...]} + # expected route: [] + ( + (0, 0), (4, 0), + {1.0: [(0,1), (1, 0), (3, -1), (3, 0)]}, + ['↘', '→', '↗', '↗', '→', '↘'], + ), + ( + (0, 0), (1, 1), + {1.0: [(-1,1), (0, 1), (1, 0), (1, -1)]}, + ['←', '↖', '↗', '→', '→', '↘'], + ), + ( + (0, 0), (1, 1), + { + 1.0: [(-1,1), (0, 1), (1, 0), (1, -1), (-1, 0)], + 0.8: [(0, -1)], + }, + ['↙', '↘', '→', '↗', '↗', '↖'], + ), + ( + (0, 0), (1, 1), + {1.0: [(-1,1), (0, 1), (1, 0), (1, -1), (0, -1), (-1, 0)]}, + [], + ), +] + +@pytest.mark.parametrize('start, end, penalties, expected', penalties_grids_test_cases) +def test_dijkstra_with_blockage_grid(start: Coord, end: Coord, penalties: BlockageGrid, expected: Route): + penalties_map = BlockageGrid() + for penalty_value, coordinates in penalties.items(): + for coord in coordinates: + penalties_map[coord] = penalty_value + + route = dijkstra(start, end, penalties=penalties_map) + expected_route = Route([Direction(d) for d in expected]) + assert route == expected_route + + +def test_dijkstra_with_negative_step_penalty(): + with pytest.raises(ValueError): + dijkstra((0,0), (100, 100), penalties=BlockageGrid(), step_penalty=-0.5) From 70679603ee7bcd7d7e232f37cf4c15361601a59d Mon Sep 17 00:00:00 2001 From: wlame <4479808+wlame@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:01:47 +0100 Subject: [PATCH 5/8] Fix tests workflow --- .github/workflows/run-pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-pytest.yml b/.github/workflows/run-pytest.yml index 11414dc..c51c765 100644 --- a/.github/workflows/run-pytest.yml +++ b/.github/workflows/run-pytest.yml @@ -27,6 +27,6 @@ jobs: curl -LsSf https://astral.sh/uv/install.sh | sh uv sync - name: Run tests - run: uv run pytest --cov=hexometry --cov-fail-under=100 tests.py + run: uv run pytest --cov-report=term-missing --cov=hexometry --cov-fail-under=100 src/tests - name: Lint run: uv run ruff check From 5db24ace2d6a84612b45b2739264d6035b9240c4 Mon Sep 17 00:00:00 2001 From: wlame <4479808+wlame@users.noreply.github.com> Date: Tue, 4 Feb 2025 21:21:20 +0100 Subject: [PATCH 6/8] Remove @override to run on Python3.11 --- src/hexometry/grids.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hexometry/grids.py b/src/hexometry/grids.py index 5d26d98..298ca42 100644 --- a/src/hexometry/grids.py +++ b/src/hexometry/grids.py @@ -1,6 +1,6 @@ import heapq -from typing import Callable, Iterator, TypeVar, override +from typing import Callable, Iterator, TypeVar from .coordinates import Coord, Route, get_directed_neighbours @@ -48,7 +48,6 @@ class BlockageGrid(Grid[float]): def __init__(self, default_blockage_level: float = 0.0): super().__init__(default=default_blockage_level) - @override def normalize(self, hex: Coord, value: float): if value < self.MIN_VALUE: return self.MIN_VALUE From 5a4c23e0a54a6c1077b7406a86a0b0946853d8b8 Mon Sep 17 00:00:00 2001 From: wlame <4479808+wlame@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:23:05 +0100 Subject: [PATCH 7/8] Fix imports, satisfy Ruff. --- src/hexometry/__init__.py | 24 ++++++++++++++++++++++-- src/hexometry/coordinates.py | 2 +- src/hexometry/grids.py | 2 +- src/tests/geometry_test.py | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/hexometry/__init__.py b/src/hexometry/__init__.py index 0e0d018..de76769 100644 --- a/src/hexometry/__init__.py +++ b/src/hexometry/__init__.py @@ -11,7 +11,27 @@ - Grid management for hex-based applications """ -from .coordinates import * -from .grids import * +from .coordinates import ( + Direction, + Coord, + Route, + DecartCoord, +) +from .grids import ( + Grid, + BlockageGrid, + dijkstra, +) __version__ = '1.0.1' + + +__all__ = [ + Direction, + Coord, + Route, + DecartCoord, + Grid, + BlockageGrid, + dijkstra, +] diff --git a/src/hexometry/coordinates.py b/src/hexometry/coordinates.py index a14e11a..3e22593 100644 --- a/src/hexometry/coordinates.py +++ b/src/hexometry/coordinates.py @@ -4,7 +4,7 @@ import random import functools -from typing import TypeAlias, Iterator, Callable, Self, override +from typing import TypeAlias, Iterator, Callable, Self _FLOAT_PRECISION = 4 diff --git a/src/hexometry/grids.py b/src/hexometry/grids.py index 298ca42..7e115f0 100644 --- a/src/hexometry/grids.py +++ b/src/hexometry/grids.py @@ -2,7 +2,7 @@ from typing import Callable, Iterator, TypeVar -from .coordinates import Coord, Route, get_directed_neighbours +from .coordinates import Coord, Direction, Route, get_directed_neighbours HexValue = TypeVar('HexValue') diff --git a/src/tests/geometry_test.py b/src/tests/geometry_test.py index 04eb8ee..a3c346e 100644 --- a/src/tests/geometry_test.py +++ b/src/tests/geometry_test.py @@ -6,7 +6,7 @@ import pytest -from hexometry import ( +from hexometry.coordinates import ( Coord, Direction, Route, From e998cc97503ce502162aa13abc30972675d5815c Mon Sep 17 00:00:00 2001 From: wlame <4479808+wlame@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:37:58 +0100 Subject: [PATCH 8/8] Remove redundant lines --- src/hexometry/coordinates.py | 2 +- src/tests/geometry_test.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hexometry/coordinates.py b/src/hexometry/coordinates.py index 3e22593..f608687 100644 --- a/src/hexometry/coordinates.py +++ b/src/hexometry/coordinates.py @@ -22,7 +22,7 @@ class Direction(enum.Enum): @functools.cached_property def _all(self) -> list[Self]: - return list(Direction) # type: ignore + return list(Direction) def __repr__(self) -> str: return self.value diff --git a/src/tests/geometry_test.py b/src/tests/geometry_test.py index a3c346e..bbeca37 100644 --- a/src/tests/geometry_test.py +++ b/src/tests/geometry_test.py @@ -135,8 +135,8 @@ def test_coord_repr(): def test_get_neighbor_for_big_distance_works(): - assert len(list(get_neighbours((123, 234), distance=3000))) == 18000 # type: ignore - assert len(list(get_neighbours((123, 234), distance=300, within=True))) == 270900 # type: ignore + assert len(list(get_neighbours((123, 234), distance=3000))) == 18000 + assert len(list(get_neighbours((123, 234), distance=300, within=True))) == 270900 def test_get_directed_neighbours():