Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Maze Generator and multiple destinations #9

Merged
merged 10 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/resources/images/focused100x100_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/resources/images/maze100x100_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/resources/images/perlin100x100_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/resources/scripts/tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
#####################################################################

from sIArena.terrain.Terrain import Coordinate, Terrain, Path
from sIArena.terrain.generator.PernilGenerator import PernilGenerator
from sIArena.terrain.generator.PerlinGenerator import PerlinGenerator

terrain = PernilGenerator().generate_random_terrain(
terrain = PerlinGenerator().generate_random_terrain(
n=25,
m=25,
min_height=0,
Expand Down
4 changes: 2 additions & 2 deletions docs/rst/getting_started/tldr.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ Use the following code changing some parameters:

from sIArena.terrain.generator.Generator import TerrainGenerator
from sIArena.terrain.generator.FocusedGenerator import FocusedGenerator
from sIArena.terrain.generator.PernilGenerator import PernilGenerator
from sIArena.terrain.generator.PerlinGenerator import PerlinGenerator

terrain = PernilGenerator().generate_random_terrain(
terrain = PerlinGenerator().generate_random_terrain(
n=20,
m=20,
min_height=0,
Expand Down
4 changes: 3 additions & 1 deletion docs/rst/modules/elements/example.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ The following snippet shows the different features of the elements:
terrain.get_neighbors((0, 0)) # Output: [(0, 1), (1, 0)]
terrain.get_neighbors((1, 1)) # Output: [(0, 1), (1, 0), (1, 2), (2, 1)]


# PATH
#######

Expand All @@ -64,5 +63,8 @@ The following snippet shows the different features of the elements:
# Check the path is complete
terrain.is_valid_path(path, terrain) # Output: True

# Check the path is complete
terrain.why_complete_path(path, terrain) # Output: True, str

# Check the path cost
terrain.get_path_cost(path, terrain) # Output: 12
1 change: 1 addition & 0 deletions docs/rst/modules/elements/path.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ In order to be **complete**, the path must be valid and:

- The first coordinate must be the ``origin`` point of the terrain.
- The last coordinate must be the ``destination`` point of the terrain.
- In case of multiple destinations, the path must pass through all of them (order is not important).


In Python, the Path is represented as a list of coordinates:
Expand Down
72 changes: 72 additions & 0 deletions docs/rst/modules/elements/terrain.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ Methods
- ``path``: ``Path``
- ``is_complete_path``: returns ``True`` if the given Path is valid and complete.
- ``path``: ``Path``
- ``why_complete_path``: returns the same value as ``is_complete_path`` and also retrieves a string with information why the path is not complete (if this is the case).
- ``path``: ``Path``

*Some of this methods use the element* :ref:`elements_path` *that is seeing afterwards.*

Expand Down Expand Up @@ -148,3 +150,73 @@ In order to learn how to visualize a 2D plot of the terrain, please refer to the
.. image:: /resources/images/3dplot_5_5.png

In order to learn how to visualize a 3D plot of the terrain, please refer to the :ref:`plotting_3d` section.


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

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 MultipleDestinationTerrain


The use and methods of this class are similar to ``Terrain`` ones.
It changes:

- The argument ``destination`` in the constructor is now a set of ``Coordinate``.
- 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 ``MultipleDestinationTerrain``:

.. code-block:: python

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 = MultipleDestinationTerrain(matrix, destination=destinations)

# To get the destinations of the terrain
destinations = terrain.destinations


Sequential Destinations Terrain
-------------------------------

There is other class for Terrain that is called ``SequentialDestinationTerrain``.
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 SequentialDestinationTerrain


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 ``SequentialDestinationTerrain``:

.. code-block:: python

from sIArena.terrain.Terrain import SequentialDestinationTerrain
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 = SequentialDestinationTerrain(matrix, destination=destinations)

# To get the destinations of the terrain
destinations = terrain.destinations
74 changes: 69 additions & 5 deletions docs/rst/modules/generation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
Generate Terrain
################

.. warning::

Coming soon.
TODO

.. contents::
:local:
:backlinks: none
:depth: 2

There are several classes that help to generate a random terrain so the user does not have to create the matrix manually.

The class ``Generator`` has a function ``generate_random_terrain`` that create an object of type ``Terrain`` (or ``DestinationSetTerrain`` if set).
These are the arguments for such function (some arguments are not used for different Generators):

- ``n: int`` number of rows
- ``m: int`` number of columns
- ``min_height: int = 0`` minimum height of the terrain
Expand All @@ -23,3 +23,67 @@ Generate Terrain
- ``seed: int = None`` seed for the random number generator
- ``origin: Coordinate = None`` origin of the terrain
- ``destination: Coordinate = None`` destination of the terrain
- ``terrain_ctor: Terrain = Terrain`` whether to use ``Terrain``or ``DestinationSetTerrain``
- ``cost_function: callable = None`` cost function for the terrain (if None use default)

There exist different generators that create the random matrix from different criteria:

.. code-block:: python

from sIArena.terrain.generator.FocusedGenerator import FocusedGenerator
from sIArena.terrain.generator.PerlinGenerator import PerlinGenerator
from sIArena.terrain.generator.MazeGenerator import MazeGenerator

In order to generate a terrain, the user must create a generator object and call the function ``generate_random_terrain``:

.. code-block:: python

generator = FocusedGenerator()
terrain = generator.generate_random_terrain(n=10, m=10)


Focused Generator
=================

This generator generates the map from top-left corner to bottom-right corner.
It generates each cell depending on the contiguous cells and a distribution probability.

It tends to create very craggy and with diagonal mountains.

.. code-block:: python

terrain = FocusedGenerator().generate_random_terrain(n=100, m=100, seed=0)

.. image:: /resources/images/focused100x100_0.png


Perlin Generator
================

This generator uses perlin noise to generate the terrain.

It tends to create smooth terrains with some hills and valleys.

.. code-block:: python

terrain = PerlinGenerator().generate_random_terrain(n=100, m=100, seed=0)

.. image:: /resources/images/perlin100x100_0.png



Maze Generator
==============

This generator creates a maze.
This is, it creates a terrain with 1 width valley and 1 width very high wall.
The valley connects the whole map, so the terrain can be walked through without climbing any wall.

The origin and destination must be set afterwards.
It is assured to connect every valley point, and the top-left corner and bottom-right corner are always in a valley.

.. code-block:: python

terrain = MazeGenerator().generate_random_terrain(n=100, m=100, seed=0)

.. image:: /resources/images/maze100x100_0.png
36 changes: 31 additions & 5 deletions docs/rst/modules/measure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,38 @@
Measure tools
#############

.. warning::

Coming soon.
TODO

.. contents::
:local:
:backlinks: none
:depth: 2

There is a built-in function prepared to measure the time consumption of a path-finding algorithm.

The function ``measure_function`` receives a function that generates a path given a terrain.
The argument ``search_function`` must be a function that receives a terrain and returns a path.

.. code-block:: python

from sIArena.measurements.measurements import measure_function

def search_function(terrain: Terrain) -> Path:
# Your path-finding algorithm here
return path

time = measure_function(search_function)

This function receives the following arguments:

search_function,
terrain: Terrain,
iterations: int = 1,
debug: bool = False,
max_seconds: float = 60*5

- ``search_function``: The function that generates a path given a terrain.
- ``terrain: Terrain``: The terrain to be used in the path-finding algorithm.
- ``iterations: int = 1``: The number of times the function will be executed. The default value is 1. This is used to get the average time of the function.
- ``debug: bool = False``: If True, the function will print the time of each iteration.
- ``max_seconds: float = 60*5``: The maximum time that the function will run.

The function will fail with an exception if the path generated by the function is not correct or if the function takes more than the maximum time to run.
1 change: 1 addition & 0 deletions docs/spelling/spelling_wordlist.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Colab
IArena
sIArena
perlin
4 changes: 2 additions & 2 deletions resources/measurement_template.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"\n",
"from sIArena.terrain.plot.plot_2D import plot_terrain_2D\n",
"from sIArena.terrain.plot.plot_3D import plot_terrain_3D\n",
"from sIArena.terrain.generator.PernilGenerator import PernilGenerator\n",
"from sIArena.terrain.generator.PerlinGenerator import PerlinGenerator\n",
"\n",
"N = 50\n",
"M = 50\n",
Expand All @@ -32,7 +32,7 @@
"ORIGIN = None\n",
"DESTINATION = None\n",
"\n",
"terrain = PernilGenerator().generate_random_terrain(\n",
"terrain = PerlinGenerator().generate_random_terrain(\n",
" n=N,\n",
" m=M,\n",
" min_height=MIN_HEIGHT,\n",
Expand Down
5 changes: 3 additions & 2 deletions src/sIArena/measurements/measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ def func_wrapper(func, terrain, result):
else:
path = result[0]

if not terrain.is_complete_path(path):
raise ValueError(f"Found Incorrect path with function {search_function.__name__}: {path}")
valid = terrain.why_complete_path(path)
if not valid[0]:
raise ValueError(f"Function {search_function.__name__} returned an invalid path: {valid[1]}")

cost = terrain.get_path_cost(path)
if cost < best_path_cost:
Expand Down
Loading
Loading