Skip to content

Commit

Permalink
Add Sequential Terrain
Browse files Browse the repository at this point in the history
Signed-off-by: jparisu <[email protected]>
  • Loading branch information
jparisu committed Aug 28, 2024
1 parent 1ed3296 commit 7a7b13b
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 16 deletions.
52 changes: 43 additions & 9 deletions docs/rst/modules/elements/terrain.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,18 @@ In order to learn how to visualize a 2D plot of the terrain, please refer to the
In order to learn how to visualize a 3D plot of the terrain, please refer to the :ref:`plotting_3d` section.


Multiple destinations Terrain
Multiple Destinations Terrain
-----------------------------

There is other class for Terrain that is called ``DestinationSetTerrain``.
There is other class for Terrain that is called ``MultipleDestinationTerrain``.
This class allows to have multiple destinations in the terrain.
This means that the path must pass through all of them in order to be considered complete.
The destinations are not sorted, so they can be visited in any order.

.. code-block:: python
from sIArena.terrain.Terrain import DestinationSetTerrain
from sIArena.terrain.Terrain import MultipleDestinationTerrain
This class allows to have multiple destinations in the terrain.
This means that the path must pass through all of them in order to be considered complete.
The destinations are not sorted, so they can be visited in any order.
The use and methods of this class are similar to ``Terrain`` ones.
It changes:
Expand All @@ -172,17 +172,51 @@ It changes:
- The method ``is_complete_path`` now checks if the path passes through all the destinations.
- To get the destinations, use the attribute ``destinations``, that is a set of ``Coordinate``.

Example on how to create a ``DestinationSetTerrain``:
Example on how to create a ``MultipleDestinationTerrain``:

.. code-block:: python
from sIArena.terrain.Terrain import DestinationSetTerrain
from sIArena.terrain.Terrain import MultipleDestinationTerrain
from sIArena.terrain.Coordinate import Coordinate
matrix = np.array(...)
destinations = {Coordinate(4,4), Coordinate(0,4)}
# It uses the top-left cell as origin by default
terrain = DestinationSetTerrain(matrix, destination=destinations)
terrain = MultipleDestinationTerrain(matrix, destination=destinations)
# To get the destinations of the terrain
destinations = terrain.destinations
Sequencial Destinations Terrain
-------------------------------

There is other class for Terrain that is called ``SequencialDestinationTerrain``.
This class have multiple destinations, but in this case the path must pass through them in the same order as they are provided.

.. code-block:: python
from sIArena.terrain.Terrain import SequencialDestinationTerrain
The use and methods of this class are similar to ``Terrain`` ones.
It changes:

- The argument ``destination`` in the constructor is now a list of ``Coordinate``.
- The method ``is_complete_path`` now checks if the path passes through all the destinations in the same order as they are provided.
- To get the destinations, use the attribute ``destinations``, that is a list of ``Coordinate``.

Example on how to create a ``SequencialDestinationTerrain``:

.. code-block:: python
from sIArena.terrain.Terrain import SequencialDestinationTerrain
from sIArena.terrain.Coordinate import Coordinate
matrix = np.array(...)
destinations = [Coordinate(4,4), Coordinate(0,4)]
# It uses the top-left cell as origin by default
terrain = SequencialDestinationTerrain(matrix, destination=destinations)
# To get the destinations of the terrain
destinations = terrain.destinations
111 changes: 104 additions & 7 deletions src/sIArena/terrain/Terrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,18 @@ def get_path_cost(self, path: Path) -> int:


def is_complete_path(self, path: Path) -> bool:
"""True if valid path"""
return self.is_valid_path(path)

def why_complete_path(self, path: Path) -> Tuple[bool, str]:
return self.why_valid_path(path)

def is_valid_path(self, path: Path) -> bool:
return self.why_valid_path(path)[0]

def why_valid_path(self, path: Path) -> Tuple[bool, str]:
"""Returns True if the given path is valid"""
return self.why_valid_path(path)

def why_valid_path(self, path: Path) -> Tuple[bool, str]:
"""Returns True if the given path is valid"""
if path is None or len(path) == 0:
return False
return False, "Empty path"

for i in range(len(path) - 1):
if path[i + 1] not in self.get_neighbors(path[i]):
Expand Down Expand Up @@ -234,7 +232,7 @@ def get_destinations(self) -> List[Coordinate]:
return [self.destination]


class DestinationSetTerrain (NoPathTerrain):
class MultipleDestinationTerrain (NoPathTerrain):
"""
This class represents a Terrain with an origin and a set of destinations that the paths must go through without order.
"""
Expand Down Expand Up @@ -326,5 +324,104 @@ def __str__(self):
return s


def get_destinations(self) -> Set[Coordinate]:
return self.destinations



class SequencialDestinationTerrain (NoPathTerrain):
"""
This class represents a Terrain with an origin and a list of destinations that the paths must go through in order.
"""

def __init__(
self,
matrix: List[List[int]],
origin: Coordinate = None,
destination: List[Coordinate] = None,
cost_function: callable = default_cost_function,
):
"""
Construct a terrain from a matrix of integers
:param matrix: matrix of integers
:param origin: origin of the path (if None top left corner)
:param destinations: list of destinations in order(if None bottom right corner)
"""
super().__init__(matrix, cost_function)
self.origin = origin
self.destinations = destination

if self.origin is None:
self.origin = (0, 0)
else:
self.origin = (origin[0], origin[1])

if self.destinations is None:
self.destinations = [(self.n - 1, self.m - 1)]
else:
self.destinations = destination

# Check that the origin is valid
if self.origin[0] < 0 and self.origin[0] >= self.n:
raise AttributeError(f"Origin row is out of bounds: {self.origin[0]}")
if self.origin[1] < 0 and self.origin[1] >= self.m:
raise AttributeError(f"Origin column is out of bounds: {self.origin[1]}")

# Check that the destinations are valid
for destination in self.destinations:
if destination[0] < 0 and destination[0] >= self.n:
raise AttributeError(f"Destination row is out of bounds: {destination[0]}")
if destination[1] < 0 and destination[1] >= self.m:
raise AttributeError(f"Destination column is out of bounds: {destination[1]}")


def is_complete_path(self, path: Path) -> bool:
return self.why_complete_path(path)[0]

def why_complete_path(self, path: Path) -> Tuple[bool, str]:
"""Returns True if the given path goes from the origin to all the destinations in order"""
# Check that the path is valid
valid = self.why_valid_path(path)
if not valid[0]:
return valid

# Check that the path goes from the origin to all the destinations in order
if path[0] != self.origin:
return False, f"Path does not start in the origin {self.origin}"

path_index = 1
for i in range(len(self.destinations)):
while path[path_index] != self.destinations[i]:
path_index += 1
if path_index >= len(path):
return False, f"Path does not go through the destination {self.destinations[i]}"

return True, "Complete path"


def __str__(self):
"""Returns a string representation of the terrain"""
# Calculate the maximum length of a cell
max_length = len(str(self.matrix.max()))
# Create the string representation
s = "+" + ("-" * (max_length + 5) + "+") * self.m + "\n"
k = 1
for i in range(self.n):
for j in range(self.m):
s += "|"
if (i,j) == self.origin:
s += "<0> "
elif (i,j) in self.destinations:
s += f"<{k}> "
k += 1
else:
s += " "
s += str(self[(i,j)]).rjust(max_length) + " "
s += "|\n"
s += "+" + ("-" * (max_length + 5) + "+") * self.m + "\n"
return s


def get_destinations(self) -> List[Coordinate]:
return self.destinations

0 comments on commit 7a7b13b

Please sign in to comment.