/* * This file is part of MazeSolver. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Copyright (c) 2014 MazeSolver * Sergio M. Afonso Fumero <theSkatrak@gmail.com> * Kevin I. Robayna Hernández <kevinirobaynahdez@gmail.com> */ /** * @file MazeCreationAlgorithm.java * @date 21/10/2014 */ package es.ull.mazesolver.maze; import java.awt.Point; import java.util.ArrayList; import es.ull.mazesolver.gui.MainWindow; import es.ull.mazesolver.util.Direction; import es.ull.mazesolver.util.Pair; /** * Interfaz que encapsula un algoritmo de creación de laberintos. */ public abstract class MazeCreationAlgorithm { /** * Número mínimo de filas que puede tener un laberinto (altura mímia). */ public static final int MIN_ROWS = 5; /** * Número mínimo de columnas que puede tener un laberinto (anchura mínima). */ public static final int MIN_COLUMNS = 5; /** * Número de filas del laberinto que se va a crear. */ protected int m_rows; /** * Numero de columnas del laberinto que se va a crear. */ protected int m_columns; /** * Matriz de celdas del laberinto que se va a crear o se esá creando. */ protected ArrayList <ArrayList <MazeCell>> m_maze; /** * Punto de salida del laberinto. */ protected Point m_maze_exit; private int m_cycles, m_walls; /** * Constructor. Crea una nueva instancia de la clase. * * @param rows * Número de filas del laberinto. * @param columns * Número de columnas del laberinto. */ public MazeCreationAlgorithm (int rows, int columns) { if (rows < MIN_ROWS || columns < MIN_COLUMNS) throw new IllegalArgumentException( MainWindow.getTranslations().exception().tooSmallRowsCols()); m_rows = rows; m_columns = columns; m_maze = initializeMaze(); } /** * Devuelve la posición de la salida del laberinto. * * @return La posición de la salida del laberinto. */ public Point getExit () { return new Point(m_maze_exit); } /** * Crea el laberinto, coloca la salida y añade los ciclos y paredes indicados. * * @return La matriz que contiene las celdas del laberinto. */ public ArrayList <ArrayList <MazeCell>> createMaze () { runCreationAlgorithm(); createExit(); addRandomCycles(m_cycles); addRandomWalls(m_walls); return m_maze; } /** * Establece el número de ciclos que se quiere que genere el algoritmo. * * @param n_cycles * Número de ciclos. */ public void setCycles (int n_cycles) { m_cycles = n_cycles; } /** * Establece el número de componentes separadas que se quiere tener al generar * el laberinto. Si se especifica 1, no se modifica el laberinto perfecto. * * @param n_components * Número de componentes. */ public void setComponents (int n_components) { m_walls = n_components - 1; } /** * Ejecuta el algoritmo de creación del laberinto, dejando el resultado en la * variable miembro {@link MazeCreationAlgorithm#m_maze}. * * Cuando se llama a este método, la variable está inicializada con un mapa en * el que todas las celdas están rodeadas de paredes. */ protected abstract void runCreationAlgorithm (); /** * Este método crea un laberinto vacío a partir del número de filas y columnas * especificado en el constructor. Hace más sencillo a las subclases * implementar el método {@link MazeCreationAlgorithm#createMaze}. * * @return Un laberinto vacío del tamaño especificado. */ protected ArrayList <ArrayList <MazeCell>> initializeMaze () { ArrayList <ArrayList <MazeCell>> maze = new ArrayList <ArrayList <MazeCell>>(m_rows); for (int y = 0; y < m_rows; y++) { maze.add(new ArrayList <MazeCell>(m_columns)); for (int x = 0; x < m_columns; x++) maze.get(y).add(new MazeCell()); } return maze; } /** * Abre un pasillo entre la celda (x, y) y su adyacente en la dirección * indicada. * * @param y * Posición en el eje Y (FILA). * @param x * Posición en el eje X (COLUMNA). * @param dir * Dirección hacia la que abrir el camino. */ protected void openPassage (int y, int x, final Direction dir) { Pair <Integer, Integer> desp = dir.decompose(); m_maze.get(y).get(x).unsetWall(dir); m_maze.get(y + desp.second).get(x + desp.first).unsetWall(dir.getOpposite()); } /** * Abre una salida en una casilla aleatoria por los bordes del laberinto. */ private void createExit () { // Decidimos en qué borde vamos a crear la salida Direction dir = Direction.random(); // Posición en la que se abrirá el hueco: Puede ser tanto una coordenada en // X como en Y int pos; if (dir.isVertical()) pos = (int) (Math.random() * m_columns); else pos = (int) (Math.random() * m_rows); // Cogemos la celda de salida y abrimos el hueco m_maze_exit = new Point(); switch (dir) { case UP: m_maze_exit.move(pos, 0); break; case DOWN: m_maze_exit.move(pos, m_rows - 1); break; case LEFT: m_maze_exit.move(0, pos); break; case RIGHT: m_maze_exit.move(m_columns - 1, pos); break; default: break; } m_maze.get(m_maze_exit.y).get(m_maze_exit.x).unsetWall(dir); m_maze_exit.setLocation(dir.movePoint(m_maze_exit)); } /** * Elimina paredes de un laberinto perfecto ya creado para crear ciclos y * hacer que deje de serlo. * * @param n * Número de paredes a quitar. */ private void addRandomCycles (int n) { if (n > Maze.perfectMazeWalls(m_rows, m_columns)) throw new IllegalArgumentException( MainWindow.getTranslations().exception().tooManyWalls()); int k = 0; Direction dir; while (k < n) { int x = (int) (Math.random() * m_columns); int y = (int) (Math.random() * m_rows); Point p = new Point(x, y); ArrayList <Direction> directions = new ArrayList <Direction>(); for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) { dir = Direction.fromIndex(i); Point p2 = dir.movePoint(p); if (p2.y >= 0 && p2.y < m_rows && p2.x >= 0 && p2.x < m_columns && m_maze.get(p.y).get(p.x).hasWall(dir)) directions.add(dir); } if (!directions.isEmpty()) { dir = directions.get((int) (Math.random() * directions.size())); openPassage(p.y, p.x, dir); k++; } } } /** * Añade paredes al laberinto perfecto ya creado para que esté compuesto por * distintas componentes inaccesibles entre sí y deje de ser perfecto. * * @param n * Número de paredes para añadir. */ private void addRandomWalls (int n) { if (n > Maze.perfectMazeEdges(m_rows, m_columns)) throw new IllegalArgumentException( MainWindow.getTranslations().exception().tooManyWalls()); int k = 0; Direction dir; while (k < n) { int x = (int) (Math.random() * m_columns); int y = (int) (Math.random() * m_rows); Point p = new Point(x, y); ArrayList <Direction> directions = new ArrayList <Direction>(); for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) { dir = Direction.fromIndex(i); Point p2 = dir.movePoint(p); if (p2.y >= 0 && p2.y < m_rows && p2.x >= 0 && p2.x < m_columns && !m_maze.get(p.y).get(p.x).hasWall(dir)) directions.add(dir); } if (!directions.isEmpty()) { dir = directions.get((int) (Math.random() * directions.size())); m_maze.get(p.y).get(p.x).setWall(dir); p = dir.movePoint(p); m_maze.get(p.y).get(p.x).setWall(dir.getOpposite()); k++; } } } }