/*
* 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 HuntAndKill.java
* @date 10 Nov 2014
*/
package es.ull.mazesolver.maze.algorithm;
import java.awt.Point;
import java.util.ArrayList;
import es.ull.mazesolver.maze.MazeCreationAlgorithm;
import es.ull.mazesolver.util.Direction;
/**
* Implementación del algoritmo de Hunt And Kill para la generación aleatoria de
* laberintos perfectos.
*/
public class HuntAndKill extends MazeCreationAlgorithm {
private ArrayList <ArrayList <Boolean>> m_included_cells;
/**
* 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 HuntAndKill (int rows, int columns) {
super(rows, columns);
// Creamos una matriz de visitados para saber en cada momento cuáles son
// las celdas que no se han visitado todavía.
m_included_cells = new ArrayList <ArrayList <Boolean>>(rows);
for (int y = 0; y < rows; y++) {
m_included_cells.add(new ArrayList <Boolean>(columns));
for (int x = 0; x < columns; x++)
m_included_cells.get(y).add(false);
}
}
/*
* (non-Javadoc)
*
* @see maze.MazeCreationAlgorithm#runCreationAlgorithm()
*/
@Override
public void runCreationAlgorithm () {
int x = (int) (Math.random() * m_rows);
int y = (int) (Math.random() * m_columns);
Point p = new Point(x, y);
while (p != null) {
walk(p);
p = hunt();
}
}
/**
* Dada una posición de inicio va explorando dicho camino mientras no
* llegue a un punto sin salida.
*
* @param p
* Punto con las coordenadas x e y de las cuales se quiere empezar a
* buscar un nuevo camino.
*/
private void walk (Point p) {
Direction dir = getRandomDirection(p.y, p.x);
while (dir != Direction.NONE) {
openPassage(p.y, p.x, dir);
p = dir.movePoint(p);
m_included_cells.get(p.y).set(p.x, true);
dir = getRandomDirection(p.y, p.x);
}
}
/**
* Busca por todo el tablero una casilla explorada y que puede ser el origen
* de una nueva exploración (el método {@link HuntAndKill#walk} puede empezar
* por esa casilla). Dicha casilla con ese posible camino es explorado por el
* método hunt (kill).
*
* @return Devuelve una posición por la cual el "cursor" puede seguir
* explorando.
*/
private Point hunt () {
for (int y = 0; y < m_rows; y++) {
for (int x = 0; x < m_columns; x++) {
Direction dir = getRandomDirection(y, x);
if (dir != Direction.NONE) {
openPassage(y, x, dir);
Point p = dir.movePoint(new Point(x, y));
m_included_cells.get(p.y).set(p.x, true);
return p;
}
}
}
return null;
}
/**
* Obtiene una dirección aleatoria desde la posición indicada que la conecte
* con una posición no explorada dentro del laberinto que se está creando.
*
* @param y
* Posición en el eje Y desde la que se quiere partir.
* @param x
* Posición en el eje X desde la que se quiere partir.
* @return Una direccion aleatoria dentro de las posibles a las que ir
* en la casilla dada por las posiciones i y j.
*/
private Direction getRandomDirection (int y, int x) {
ArrayList <Direction> directions = new ArrayList <Direction>();
Point actual = new Point(x, y);
// Comprobamos qué posiciones de alrededor son válidas y no se han visitado
// Suponemos que la posición proporcionada es válida para empezar
for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) {
Direction dir = Direction.fromIndex(i);
Point next = dir.movePoint(actual);
if (next.y >= 0 && next.y < m_rows && next.x >= 0 && next.x < m_columns
&& !m_included_cells.get(next.y).get(next.x))
directions.add(dir);
}
if (directions.isEmpty())
return Direction.NONE;
else
return directions.get((int) (Math.random() * directions.size()));
}
}