Skip to content

Commit 6587324

Browse files
authored
Merge pull request #198 from antonsoo/fix/automatic-diagonal-heuristic
Bound automatic diagonal heuristics by two cardinal steps
2 parents 84dd3bb + 76bda52 commit 6587324

3 files changed

Lines changed: 41 additions & 2 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version
88

99
### Fixed
1010

11+
- `tcod.path.SimpleGraph` caps its automatic diagonal heuristic at two cardinal steps when those are cheaper.
1112
- Many-to-many `tcod.path.path2d` queries retain weighted predecessors when reconstructing their route.
1213
- Copying or pickling Perlin and Simplex noise no longer advances a shared random generator while preparing an unused wavelet tile.
1314
- `tcod.image.Image.rotate90` updates the width and height of rectangular images, keeping NumPy array conversions consistent with the rotated pixels.

‎tcod/path.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,10 @@ def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int
10661066
self._shape = self._subgraph._shape[0], self._subgraph._shape[1]
10671067
self._shape_c = self._subgraph._shape_c
10681068
self._subgraph.add_edges(edge_map=edge_map, cost=cost)
1069-
self.set_heuristic(cardinal=cardinal * greed, diagonal=diagonal * greed)
1069+
self.set_heuristic(
1070+
cardinal=cardinal * greed,
1071+
diagonal=(min(diagonal, cardinal * 2) if cardinal else diagonal) * greed,
1072+
)
10701073

10711074
@property
10721075
def ndim(self) -> int:
@@ -1091,7 +1094,8 @@ def set_heuristic(self, *, cardinal: int, diagonal: int) -> None:
10911094
`cardinal` and `diagonal` are weights for the heuristic.
10921095
Higher values are more greedy.
10931096
The default values are set to ``cardinal * greed`` and
1094-
``diagonal * greed`` when the :any:`SimpleGraph` is created.
1097+
``diagonal * greed`` when the :any:`SimpleGraph` is created, with the
1098+
diagonal estimate capped at two cardinal steps if cardinal movement is enabled.
10951099
"""
10961100
self._subgraph.set_heuristic(cardinal=cardinal, diagonal=diagonal)
10971101

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Automatic heuristics must account for cheaper cardinal alternatives."""
2+
3+
from typing import Literal
4+
5+
import numpy as np
6+
import pytest
7+
8+
import tcod.path
9+
10+
11+
@pytest.mark.parametrize("order", ["C", "F"])
12+
def test_expensive_diagonal_heuristic(order: Literal["C", "F"]) -> None:
13+
"""Eight cardinal steps beat the tempting nine-cost mixed route."""
14+
cost = np.array(
15+
[[1, 0, 0, 0, 1], [1, 1, 1, 1, 1], [1, 0, 1, 0, 1], [0, 1, 1, 1, 1], [0, 0, 1, 1, 1]],
16+
dtype=np.int16,
17+
order=order,
18+
)
19+
graph = tcod.path.SimpleGraph(cost=cost, cardinal=1, diagonal=3)
20+
pathfinder = tcod.path.Pathfinder(graph)
21+
pathfinder.add_root((0, 0))
22+
path = pathfinder.path_to((4, 4))
23+
total = sum(3 if np.all(a != b) else 1 for a, b in zip(path, path[1:]))
24+
assert total == 8
25+
assert pathfinder.distance[4, 4] == 8
26+
27+
28+
def test_diagonal_only_graph() -> None:
29+
"""Disabling cardinal movement retains the diagonal estimate and edges."""
30+
graph = tcod.path.SimpleGraph(cost=np.ones((3, 3), dtype=np.int16), cardinal=0, diagonal=3)
31+
pathfinder = tcod.path.Pathfinder(graph)
32+
pathfinder.add_root((0, 0))
33+
np.testing.assert_array_equal(pathfinder.path_to((2, 2)), [[0, 0], [1, 1], [2, 2]])
34+
assert pathfinder.distance[2, 2] == 6

0 commit comments

Comments
 (0)