/*
* 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 Environment.java
* @date 25/10/2014
*/
package es.ull.mazesolver.gui.environment;
import java.awt.Container;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import com.tomtessier.scrollabledesktop.BaseInternalFrame;
import es.ull.mazesolver.agent.Agent;
import es.ull.mazesolver.gui.MainWindow;
import es.ull.mazesolver.maze.Maze;
import es.ull.mazesolver.maze.MazeCell;
import es.ull.mazesolver.util.BlackboardManager;
import es.ull.mazesolver.util.Direction;
import es.ull.mazesolver.util.InteractionMode;
import es.ull.mazesolver.util.MessageManager;
import es.ull.mazesolver.util.Pair;
import es.ull.mazesolver.util.SimulationResults;
/**
* Una instancia de esta clase representa un entorno de ejecución, formado por
* un laberinto y por un conjunto de agentes.
*/
public class Environment extends BaseInternalFrame {
private static final long serialVersionUID = 1L;
private static final int WINDOW_BORDER_WIDTH = 11;
private static final int WINDOW_BORDER_HEIGHT = 33;
private static final int WINDOWS_OFFSET = 20;
private static int s_instance = 0;
private static Point s_start_pos = new Point();
private Maze m_maze;
private ArrayList <Agent> m_agents;
private int m_selected, m_hovered;
private BlackboardManager m_blackboard_mgr;
private MessageManager m_message_mgr;
private MouseListener m_agent_click = new MouseAdapter() {
@Override
public void mousePressed (MouseEvent e) {
m_selected = getAgentIndexUnderMouse(e.getPoint());
repaint();
super.mousePressed(e);
}
};
private MouseMotionListener m_agent_hover_drag = new MouseMotionListener() {
@Override
public void mouseMoved (MouseEvent e) {
m_hovered = getAgentIndexUnderMouse(e.getPoint());
repaint();
}
@Override
public void mouseDragged (MouseEvent e) {
Agent ag = getSelectedAgent();
if (ag != null) {
Point grid_pos = EnvironmentPanel.screenCoordToGrid(e.getPoint());
if (m_maze.containsPoint(grid_pos)) {
ag.setPosition(grid_pos);
repaint();
}
}
}
};
private MouseListener m_wall_click = new MouseAdapter() {
@Override
public void mousePressed (MouseEvent e) {
EnvironmentEditionPanel panel = (EnvironmentEditionPanel) getContentPane();
Pair<Point, Direction> selected = panel.getWallAt(e.getPoint());
if (selected != null) {
Point pos = selected.first;
Direction dir = selected.second;
Point adj = dir.movePoint(pos);
// Si las dos celdas están dentro, se crea/eliminan las dos paredes que
// las unen
if (m_maze.containsPoint(adj)) {
m_maze.get(pos.y, pos.x).toggleWall(dir);
m_maze.get(adj.y, adj.x).toggleWall(dir.getOpposite());
}
// Si sólo una de las dos celdas está dentro hay que cambiar la posición
// de la salida a ese punto
else {
if (dir.isVertical())
m_maze.setExit(pos.x, dir);
else // Horizontal
m_maze.setExit(pos.y, dir);
}
}
repaint();
super.mousePressed(e);
}
};
/**
* Constructor para las clases de tipo entorno.
*
* @param maze
* Laberinto en el que se basa el entorno. Puede ser compartido entre
* varios entornos.
* @param name
* Nombre del entorno.
*/
public Environment (Maze maze, String name) {
super(name, false, false, false, false);
++s_instance;
setMaze(maze);
setVisible(true);
setLocation(s_start_pos);
s_start_pos.x += WINDOWS_OFFSET;
s_start_pos.y += WINDOWS_OFFSET;
m_selected = m_hovered = -1;
m_agents = new ArrayList <Agent>();
m_blackboard_mgr = new BlackboardManager();
m_message_mgr = new MessageManager();
moveToFront();
}
/**
* Constructor para las clases de tipo entorno.
*
* @param maze
* Laberinto en el que se basa el entorno. Puede ser compartido entre
* varios entornos.
*/
public Environment (Maze maze) {
this(maze, "Env " + (s_instance + 1));
}
/**
* Cambia el nombre del entorno.
*
* @param name
* Nuevo nombre del entorno.
*/
public void setEnvName (String name) {
setTitle(name);
}
/**
* @return El nombre del entorno.
*/
public String getEnvName () {
return getTitle();
}
/**
* Cambia el modo de interacción con el entorno.
*
* @param mode
* Nuevo modo de interacción con el entorno.
*/
public void setInteractionMode (InteractionMode mode) {
Container panel = null;
switch (mode) {
case SIMULATION:
panel = new EnvironmentSimulationPanel(this);
panel.addMouseListener(m_agent_click);
panel.addMouseMotionListener(m_agent_hover_drag);
break;
case EDITION:
panel = new EnvironmentEditionPanel(this);
panel.addMouseListener(m_wall_click);
break;
}
setContentPane(panel);
updateSize();
}
/**
* Obtiene el laberinto contenido en el entorno.
*
* @return Laberinto base del entorno.
*/
public Maze getMaze () {
return m_maze;
}
/**
* Cambia el laberinto del entorno.
* <br><br>
* Esta operación invalidaría la memoria almacenada del/los agentes en caso
* de que éstos contuvieran información sobre la ruta a llevar a cabo en el
* entorno, pero no cuando la memoria fuera un conjunto de reglas o una tabla
* de percepción-acción o cuando el agente no tuviera ninguna memoria.
*
* @param maze
* Laberinto en el que se basa el entorno.
*/
public void setMaze (Maze maze) {
if (maze != null) {
m_maze = maze;
repaint();
}
else
throw new IllegalArgumentException(
MainWindow.getTranslations().exception().invalidMaze());
}
/**
* Actualiza el tamaño de la ventana con respecto al tamaño de su contenido,
* que es la representación del entorno.
*/
public void updateSize () {
EnvironmentPanel panel = ((EnvironmentPanel) getContentPane());
panel.updateSize();
setSize(panel.getWidth() + WINDOW_BORDER_WIDTH, panel.getHeight() + WINDOW_BORDER_HEIGHT);
repaint();
}
/**
* Este método permite saber lo que puede ver un agente si mira en una
* dirección específica. La versión en la clase principal no maneja agentes,
* por lo que debe ser sobrecargada para gestionarlos.
*
* @param pos
* Posición desde la que mirar.
* @param dir
* Dirección hacia la que mirar.
* @return Lo que vería un agente en la posición especificada si mirara hacia
* la dirección indicada.
*/
public MazeCell.Vision look (Point pos, Direction dir) {
// Si el agente está fuera del laberinto, no dejamos que se mueva. De esta
// forma, cuando un agente sale del laberinto se queda quieto fuera del
// mismo y no vuelve a entrar ni se va lejos de la salida.
if (!m_maze.containsPoint(pos) || m_maze.get(pos.y, pos.x).hasWall(dir))
return MazeCell.Vision.WALL;
Point n_pos = dir.movePoint(pos);
if (!m_maze.containsPoint(n_pos))
return MazeCell.Vision.OFFLIMITS;
for (Agent ag: m_agents) {
if (ag.getX() == n_pos.getX() && ag.getY() == n_pos.getY())
return MazeCell.Vision.AGENT;
}
return MazeCell.Vision.EMPTY;
}
/**
* Indica si a partir de una posición, el movimiento hacia una determinada
* posición es posible o no.
*
* @param pos
* Posición de partida.
* @param dir
* Dirección de movimiento.
* @return true si se puede y false si no.
*/
public boolean movementAllowed (Point pos, Direction dir) {
MazeCell.Vision vision = look(pos, dir);
return vision == MazeCell.Vision.EMPTY || vision == MazeCell.Vision.OFFLIMITS;
}
/**
* Añade un agente al entorno.
*
* @param ag
* Agente que se quiere añadir al entorno.
*/
public void addAgent (Agent ag) {
if (ag == null)
throw new IllegalArgumentException(
MainWindow.getTranslations().exception().invalidAgent());
if (!m_agents.contains(ag)) {
ag.setEnvironment(this);
// Buscamos un hueco donde colocar el agente
loops:
for (int y = 0; y < m_maze.getHeight(); y++) {
for (int x = 0; x < m_maze.getWidth(); x++) {
boolean used = false;
for (Agent i: m_agents) {
if (i.getX() == x && i.getY() == y) {
used = true;
break;
}
}
if (!used) {
ag.setPosition(new Point(x, y));
break loops;
}
}
}
m_agents.add(ag);
repaint();
}
}
/**
* Elimina un agente del entorno.
*
* @param ag
* Referencia al agente que se quiere eliminar.
*/
public void removeAgent (Agent ag) {
// Si se encuentra el agente, se elimina de la lista de agentes y si estaba
// seleccionado se quita el estado de selección
if (m_agents.contains(ag)) {
if (getSelectedAgent() == ag)
m_selected = -1;
m_agents.remove(ag);
}
else
throw new IllegalArgumentException(
MainWindow.getTranslations().exception().agentNotInEnvironment());
repaint();
}
/**
* Extrae una referencia al agente seleccionado dentro del entorno.
*
* @return Agente seleccionado actualmente en el entorno o null si no hay
* ninguno seleccionado.
*/
public Agent getSelectedAgent () {
if (m_selected == -1)
return null;
else
return m_agents.get(m_selected);
}
/**
* Extrae una referencia al agente dentro del entorno que tiene el cursor
* situado encima.
*
* @return Agente sobre el que se tiene el cursor actualmente en el entorno o
* null si el cursor no está encima de ningún agente.
*/
public Agent getHoveredAgent () {
if (m_hovered == -1)
return null;
else
return m_agents.get(m_hovered);
}
/**
* Extrae una referencia a un agente del entorno.
*
* @param index
* Índice del agente que se quiere consultar.
* @return Agente número 'index' dentro del entorno.
*/
public Agent getAgent (int index) {
if (index < 0 || index >= m_agents.size())
throw new IllegalArgumentException(
MainWindow.getTranslations().exception().indexOutOfRange());
return m_agents.get(index);
}
/**
* Extrae una copia profunda de la lista de agentes que hay dentro del
* entorno. Hay que tener en cuenta que cualquier modificación en esta lista
* no va a tener ninguna repercusión en los agentes o el entorno original.
*
* @return Copia de la lista de agentes dentro del entorno.
*/
public ArrayList <Agent> getAgents () {
ArrayList <Agent> agents = new ArrayList <Agent>();
for (Agent i: m_agents)
agents.add((Agent) i.clone());
return agents;
}
/**
* Obtiene el número de agentes que se encuentran en el entorno.
*
* @return Número de agentes actualmente en el entorno.
*/
public int getAgentCount () {
return m_agents.size();
}
/**
* Obtiene el gestor de pizarras del entorno.
*
* @return El gestor de pizarras del entorno.
*/
public BlackboardManager getBlackboardManager () {
return m_blackboard_mgr;
}
/**
* Obtiene el gestor de mensajes del entorno.
*
* @return El gestor de mensajes del entorno.
*/
public MessageManager getMessageManager () {
return m_message_mgr;
}
/**
* Ejecuta un paso de la simulación de ejecución de los agentes en el entorno
* y devuelve el resultado de la ejecución.
*
* @param results
* Objeto que representa el resultado de la simulación, que será
* actualizado en este método para notificar de agentes que han
* llegado a la salida y para contar los pasos que han dado.
* @return true si todos los agentes han salido del laberinto y false en otro
* caso.
*/
public boolean runStep (SimulationResults results) {
m_message_mgr.flushMessageQueues();
Direction[] movements = new Direction[m_agents.size()];
// Paso 1: Obtener movimientos a partir del estado actual del entorno
for (int i = 0; i < m_agents.size(); ++i) {
Agent ag = m_agents.get(i);
// Si el agente ya salió del laberinto no lo movemos, pero si no ha
// salido hacemos que calcule su siguiente movimiento
if (m_maze.containsPoint(ag.getPos())) {
movements[i] = ag.getNextMovement();
results.agentIterated(ag);
}
else
movements[i] = Direction.NONE;
}
// Esta variable indica si la simulación ha terminado (todos los agentes
// han salido del laberinto)
boolean ended = true;
// Paso 2: Realizar movimientos (modificar el entorno)
for (int i = 0; i < m_agents.size(); ++i) {
Agent ag = m_agents.get(i);
// Restringimos el movimiento del agente para que no atraviese paredes
// u otros agentes independientemente de errores que se hayan podido
// cometer a la hora de programar a los agentes
if (movementAllowed(ag.getPos(), movements[i])) {
ag.doMovement(movements[i]);
results.agentWalked(ag);
}
if (m_maze.containsPoint(ag.getPos()))
ended = false;
else
results.agentFinished(ag);
}
repaint();
return ended;
}
/**
* @return El número de instancias de la clase que han sido creadas.
*/
public static int getInstancesCreated () {
return s_instance;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals (Object obj) {
return this == obj;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode () {
return new Integer(s_instance).hashCode();
}
/**
* Obtiene el índice de la lista de agentes del agente que está situado bajo
* el cursor.
*
* @param mouse_pos
* Posición del ratón.
* @return Índice del agente colocado en la posición indicada dentro del
* entorno.
*/
private int getAgentIndexUnderMouse (Point mouse_pos) {
Point maze_pos = EnvironmentPanel.screenCoordToGrid(mouse_pos);
int ag_index = -1;
for (int i = 0; i < m_agents.size(); i++) {
Agent current = m_agents.get(i);
if (current.getX() == maze_pos.getX() && current.getY() == maze_pos.getY()) {
ag_index = i;
break;
}
}
return ag_index;
}
}