Source code for pyrat.src.mazes.big_holes_random_maze
##########################################################################################
########################################## INFO ##########################################
##########################################################################################
# This file is part of the PyRat library.
# It is meant to be used as a library, and not to be executed directly.
# Please import necessary elements using the following syntax:
# from pyrat import <element_name>
"""
This module provides functionality for generating mazes with large holes in them, which can be used in various game scenarios.
It extends ``RandomMaze`` to create a specific type of maze with larger holes, enhancing the gameplay experience by introducing more complex navigation challenges.
"""
##########################################################################################
######################################### IMPORTS ########################################
##########################################################################################
# PyRat imports
from pyrat.src.mazes.random_maze import RandomMaze
from pyrat.src.game.exceptions import PyRatException
##########################################################################################
######################################### CLASSES ########################################
##########################################################################################
[docs]
class BigHolesRandomMaze (RandomMaze):
"""
*(This class inherits from* ``RandomMaze`` *).*
This class defines a random maze with big holes here and there.
The maze is created by removing random cells from a full maze, and making sure the maze remains connected.
Cells are removed with a larger probability if they are close to an already existing hole.
You can use this class to create a maze with this algorithm.
However, if you just want to play a game, you can use the ``Game`` class instead, which will create a maze for you.
Just make sure to set the ``random_maze_algorithm`` parameter to ``RandomMazeAlgorithm.BIG_HOLES`` when creating the game.
"""
##################################################################################
# CONSTRUCTOR #
##################################################################################
[docs]
def __init__ ( self,
cell_percentage: float,
*args: object,
**kwargs: object
) -> None:
"""
Initializes a new instance of the class, and generates the maze.
Args:
cell_percentage: Percentage of cells to be reachable.
*args: Arguments to pass to the parent constructor.
**kwargs: Keyword arguments to pass to the parent constructor.
"""
# Check validity
if not isinstance(cell_percentage, float):
raise PyRatException(f"Argument 'cell_percentage' must be a real number of type float (e.g., 50.0 rather than 50), got {cell_percentage!r} of type {type(cell_percentage).__name__}")
if not (0.0 <= cell_percentage <= 100.0):
raise PyRatException(f"Argument 'cell_percentage' must be a percentage between 0.0 and 100.0, got {cell_percentage!r}")
# Protected attributes
# They must be set before calling the parent constructor, which generates the maze
self._cell_percentage = cell_percentage
# Inherit from parent class
super().__init__(*args, **kwargs)
##################################################################################
# PROTECTED METHODS #
##################################################################################
def _add_cells (self) -> None:
"""
*(This method redefines the method of the parent class with the same name).*
Adds cells to the maze by starting from a full maze and removing cells one by one.
"""
# Determine the number of cells to keep
self._target_nb_vertices = int(self.get_width() * self.get_height() * self._cell_percentage / 100)
if not (self._target_nb_vertices > 1):
raise PyRatException(f"The maze must have at least two vertices, got {self._target_nb_vertices!r} cell(s) to keep ({self._cell_percentage!r}% of a {self.get_width()}x{self.get_height()} grid): increase 'cell_percentage' or the maze size")
# Start from a full maze, with all cells connected to their neighbors
super()._add_cells()
# Remember the number of neighbors per vertex
neighbors_per_vertex = {vertex: len(self.get_neighbors(vertex)) for vertex in self.get_vertices()}
# Remove some vertices until the desired density is reached
while self.nb_vertices() > self._target_nb_vertices:
# The probability to be removed depends on the number of neighbors already removed
vertices = self.get_vertices()
selection_weights = [1 + (self.get_width() * self.get_height() - self.nb_vertices()) * (neighbors_per_vertex[vertex] - len(self.get_neighbors(vertex)))**2.0 for vertex in vertices]
# Remove a random vertex
vertex = self._rng.choices(vertices, selection_weights)[0]
neighbors = self.get_neighbors(vertex)
self.remove_vertex(vertex)
# Make sure the maze is still connected
if not self.is_connected():
self.add_vertex(vertex)
for neighbor in neighbors:
self.add_edge(vertex, neighbor)
##########################################################################################
##########################################################################################