public class AStarAgent extends HeuristicAgent {
  private static final long serialVersionUID = 4696525877860033142L;
  private static final double STEP_COST = 1.0;

  private transient Point m_exit;

  private transient int m_direction_index;
  private transient ArrayList <Direction> m_directions;

  /**
   * Inicializa el agente A* con la distancia de Manhattan por defecto.
   *
   * @param env
   *          Entorno en el que colocar al agente.
   */
  public AStarAgent (Environment env) {
    super(env);
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getAlgorithmName()
   */
  @Override
  public String getAlgorithmName () {
    return "A*";
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
   */
  @Override
  public Color getAlgorithmColor () {
    return Color.RED;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#setPosition(java.awt.Point)
   */
  @Override
  public void setPosition (Point pos) {
    super.setPosition(pos);
    resetMemory();
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#setEnvironment(gui.environment.Environment)
   */
  @Override
  public void setEnvironment (Environment env) {
    super.setEnvironment(env);
    resetMemory();
    m_exit = m_env.getMaze().getExit();
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getNextMovement()
   */
  @Override
  public Direction getNextMovement () {
    if (m_directions == null || (m_direction_index == m_directions.size() && !m_pos.equals(m_exit)))
      calculatePath();

    return m_directions != null && m_direction_index != m_directions.size()?
        m_directions.get(m_direction_index) : Direction.NONE;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#doMovement(es.ull.mazesolver.util.Direction)
   */
  @Override
  public void doMovement (Direction dir) {
    super.doMovement(dir);
    if (m_directions.get(m_direction_index) == dir && m_directions != null)
      m_direction_index++;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#resetMemory()
   */
  @Override
  public void resetMemory () {
    m_directions = null;
    m_direction_index = 0;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getConfigurationPanel()
   */
  @Override
  public AgentConfigurationPanel getConfigurationPanel () {
    return new HeuristicAgentConfigurationPanel(this);
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#clone()
   */
  @Override
  public Object clone () {
    AStarAgent ag = new AStarAgent(m_env);
    ag.setAgentColor(getAgentColor());
    ag.setDistanceCalculator(m_dist);

    return ag;
  }

  /**
   * Clase comparadora de caminos que tiene en cuenta el coste real del camino
   * y el coste estimado entre la última posición del camino y el punto en el
   * que se encuentra la salida del laberinto.
   */
  private class HeuristicPathComparator implements Comparator <Path> {
    /*
     * (non-Javadoc)
     *
     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
     */
    @Override
    public int compare (Path o1, Path o2) {
      double h1 = m_dist.distance(o1.getEndPoint(), m_exit);
      double h2 = m_dist.distance(o2.getEndPoint(), m_exit);

      // La ordenación se hace por coste acumulado (m_cost) + coste estimado
      // (distancia a la salida)
      return Double.compare(o1.getCost() + h1, o2.getCost() + h2);
    }
  }

  /**
   * Recalcula el camino hacia la salida del laberinto desde la posición actual.
   */
  private void calculatePath () {
    Path solution = null;

    ArrayList <Path> closed = new ArrayList <Path>();
    PriorityQueue <Path> open = new PriorityQueue <Path>(10, new HeuristicPathComparator());

    // Inicialmente la lista abierta contiene una trayectoria formada por sólo
    // el nodo de inicio
    open.add(new Path(m_pos));

    // Repetimos mientras hayan posibles trayectorias por recorrer
    while (!open.isEmpty()) {
      // Sacamos la trayectoria de la lista abierta
      Path path = open.poll();

      // Si la trayectoria finaliza en la salida, se trata de la trayectoria
      // óptima: Salimos del bucle
      if (path.getEndPoint().equals(m_exit)) {
        solution = path;
        break;
      }

      // Insertamos la trayectoria en la lista cerrada
      insertPath(path, closed);

      // Expandimos la trayectoria actual para obtener los posibles pasos que
      // se pueden dar desde la posición actual
      Point pos = path.getEndPoint();
      int n_steps = path.getLength();
      for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) {
        Direction dir = Direction.fromIndex(i);

        // Para añadir el paso a la trayectoria tiene que ser posible llevarlo
        // a cabo en el entorno y además no puede ser el paso opuesto al que
        // dio justo antes en la trayectoria (sabemos que así no se va a generar
        // una mejor solución, porque repite posiciones)
        if (m_env.movementAllowed(pos, dir)) {
          Point n_pos = dir.movePoint(pos);
          if (n_steps < 2 || !path.getPoint(n_steps - 2).equals(n_pos)) {
            Path expanded_path = path.addStep(dir, STEP_COST);
            Path discarded = insertPath(expanded_path, open);
            if (discarded != null && discarded != expanded_path)
              insertPath(discarded, closed);
          }
        }
      }
    }

    // Si no encontramos una solución, al menos nos acercamos lo más posible a
    // la salida, por si más tarde se puede llegar (un agente se ha quitado de
    // la trayectoria hasta la salida)
    if (solution == null) {
      double dist = Double.MAX_VALUE;
      for (Path p: closed) {
        double p_dist = m_dist.distance(p.getEndPoint(), m_exit);
        if (p_dist < dist) {
          solution = p;
          dist = p_dist;
        }
      }
    }

    ArrayList <Point> path = solution.getPath();
    m_directions = new ArrayList <Direction>(path.size() - 1);
    for (int i = 1; i < path.size(); i++)
      m_directions.add(Direction.fromPoints(path.get(i - 1), path.get(i)));

    m_direction_index = 0;
  }

  /**
   * Buscamos en la lista una trayectoria que acabe en el mismo punto que la que
   * está siendo procesada. Si la encuentra, dependiendo de los costes de ambas
   * trayectorias sólo se deja en la lista la de menor coste. Si no la
   * encuentra, simplemente añade la trayectoria a la lista.
   *
   * @param path
   *          Trayectoria a añadir a la lista.
   * @param list
   *          Lista de trayectorias a la que se quiere añadir la trayectoria.
   * @return La trayectoria eliminada de la lista, la trayectoria de entrada si
   *         no fue insertada o null, dependiendo del caso.
   */
  private Path insertPath (Path path, Collection <Path> list) {
    Path discarded = null;
    boolean add_path = true;

    Iterator <Path> iter = list.iterator();
    while (iter.hasNext()) {
      Path p = iter.next();
      if (p.endsInTheSamePoint(path)) {
        HeuristicPathComparator comp = new HeuristicPathComparator();
        if (comp.compare(p, path) <= 0) {
          discarded = path;
          add_path = false;
        }
        else {
          discarded = p;
          iter.remove();
        }
        break;
      }
    }

    if (add_path)
      list.add(path);

    return discarded;
  }

  /**
   * Extrae la información del objeto a partir de una forma serializada del
   * mismo.
   *
   * @param input
   *          Flujo de entrada con la información del objeto.
   * @throws ClassNotFoundException
   *           Si se trata de un objeto de otra clase.
   * @throws IOException
   *           Si no se puede leer el flujo de entrada.
   */
  private void readObject (ObjectInputStream input) throws ClassNotFoundException, IOException {
    input.defaultReadObject();
    m_pos = new Point();
  }
} public abstract class Agent implements Cloneable, Serializable {
  private static final long serialVersionUID = 1L;
  private static int s_agent_count = 0;

  private transient int m_agent_id;
  private transient String m_name;
  private Color m_color;

  /**
   * Entorno en el que reside el agente.
   */
  protected transient Environment m_env;

  /**
   * Posición en la que se encuentra el agente.
   */
  protected transient Point m_pos;

  /**
   * Crea un nuevo agente en el entorno. Le asigna un ID único y lo coloca en el
   * punto (0,0).
   *
   * @param env
   *          Entorno al que va a ser asignado dicho agente.
   */
  protected Agent (Environment env) {
    m_agent_id = s_agent_count++;
    m_name = getAlgorithmName() + " " + String.valueOf(m_agent_id);
    m_color = getAlgorithmColor();
    m_pos = new Point();
    setEnvironment(env);
  }

  /**
   * Establece la posición del agente.
   * <br><br>
   * Este método se puede sobrecargar en las clases derivadas para mantener
   * coherente la memoria de la que disponga el mismo.
   *
   * @param pos
   *          Nueva posición del agente.
   */
  public void setPosition (Point pos) {
    m_pos.x = pos.x;
    m_pos.y = pos.y;
  }

  /**
   * Obtiene la posición en el eje X del agente.
   *
   * @return Posición en el eje X del agente.
   */
  public int getX () {
    return m_pos.x;
  }

  /**
   * Obtiene la posición en el eje Y del agente.
   *
   * @return Posición en el eje Y de agente.
   */
  public int getY () {
    return m_pos.y;
  }

  /**
   * Obtiene la posición del agente.
   *
   * @return Posición del agente.
   */
  public Point getPos () {
    return new Point(m_pos);
  }

  /**
   * Cambia el entorno en el que se sitúa el agente.
   * <br><br>
   * Este método debería sobrecargarse en las clases derivadas que contengan
   * información acerca del camino a seguir por el agente (un plan) de forma que
   * éste siga siendo coherente tras el cambio de laberinto.
   *
   * @param env
   *          Entorno donde colocar el agente.
   */
  public void setEnvironment (Environment env) {
    if (env != null)
      m_env = env;
    else
      throw new IllegalArgumentException(
          MainWindow.getTranslations().exception().invalidEnvironment());
  }

  /**
   * Obtiene el entorno en el que se encuentra el agente.
   *
   * @return Entorno en el que se encuentra el agente.
   */
  public Environment getEnvironment () {
    return m_env;
  }

  /**
   * Obtiene la visión que tiene el agente al mirar en la dirección indicada.
   *
   * @param dir
   *          Dirección hacia la que mirar.
   * @return Lo que vería el agente si mira en la dirección especificada.
   */
  public MazeCell.Vision look (Direction dir) {
    return m_env.look(m_pos, dir);
  }

  /*
   * (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 m_env.hashCode();
  }

  /**
   * Hace que el agente realice el movimiento especificado, sin comprobar que se
   * trata de un movimiento válido.
   * <br><br>
   * La clase base sólo cambia la posición del agente, si se desea más
   * funcionalidad, se debe sobrecargar en las clases derivadas.
   *
   * @param dir
   *          Dirección hacia la que mover el agente.
   */
  public void doMovement (Direction dir) {
    m_pos = dir.movePoint(m_pos);
  }

  /**
   * Cambia el nombre del agente.
   *
   * @param name
   *          Nombre del agente.
   */
  public void setAgentName (String name) {
    if (name != null && !name.isEmpty())
      m_name = name;
  }

  /**
   * Pregunta al agente el nombre que le identifica.
   *
   * @return Nombre identificador del agente. Por defecto incluye el nombre del
   *         algoritmo que implementa.
   */
  public String getAgentName () {
    return m_name;
  }

  /**
   * Cambia el color del agente.
   *
   * @param color
   *          Nuevo color del agente.
   */
  public void setAgentColor (Color color) {
    if (color != null)
      m_color = color;
  }

  /**
   * Pregunta al agente su color.
   *
   * @return El color del agente.
   */
  public Color getAgentColor () {
    return m_color;
  }

  /**
   * Carga la descripción serializada del agente de un fichero, lo crea y lo
   * devuelve.
   *
   * @param filename
   *          Nombre del fichero de entrada.
   * @param env
   *          Entorno en el que cargar el agente.
   * @return El agente creado.
   * @throws IOException
   *           Si no es posible leer el fichero.
   */
  public static Agent loadFile (String filename, Environment env) throws IOException {
    try {
      Agent ag;
      FileInputStream file_in = new FileInputStream(filename);
      ObjectInputStream in = new ObjectInputStream(file_in);
      ag = (Agent) in.readObject();
      in.close();
      file_in.close();

      ag.m_agent_id = s_agent_count++;
      ag.m_name = ag.getAlgorithmName() + " " + String.valueOf(ag.m_agent_id);
      ag.m_pos = new Point();
      ag.setEnvironment(env);

      return ag;
    }
    catch (ClassNotFoundException c) {
      throw new IOException(c);
    }
  }

  /**
   * Guarda la instancia del agente en un fichero utilizando su serialización.
   *
   * @param filename
   *          Nombre del fichero de salida.
   * @throws IOException
   *           Si no es posible guardar el fichero.
   */
  public void saveFile (String filename) throws IOException {
    FileOutputStream file_out = new FileOutputStream(filename);
    ObjectOutputStream out = new ObjectOutputStream(file_out);
    out.writeObject(this);
    out.close();
    file_out.close();
  }

  /**
   * Pregunta al agente el nombre del algoritmo que implementa.
   *
   * @return Nombre del algoritmo que implementa.
   */
  public abstract String getAlgorithmName ();


  /**
   * Pregunta al agente el color por defecto de los agentes de su tipo.
   *
   * @return Color por defecto de los agentes que implementan ese algoritmo.
   */
  public abstract Color getAlgorithmColor ();

  /**
   * Obtiene el siguiente movimiento dado el estado actual del agente.
   *
   * @return La dirección en la que el agente quiere realizar el siguiente
   *         movimiento.
   */
  public abstract Direction getNextMovement ();

  /**
   * Elimina la memoria que el agente tenga sobre el entorno. No elimina su
   * configuración, sino que lo deja en el estado inicial.
   */
  public abstract void resetMemory ();

  /**
   * Obtiene el panel de configuración asociado al agente.
   *
   * @return Un panel de configuración para el agente.
   */
  public abstract AgentConfigurationPanel getConfigurationPanel ();

  /*
   * (non-Javadoc)
   *
   * @see java.lang.Object#clone()
   */
  @Override
  public abstract Object clone ();

} public class DStarAgent extends HeuristicAgent implements BlackboardCommunication {
  private static final long serialVersionUID = 1342168437798267323L;

  private static String BLACKBOARD_CHANNEL = "D* Agents Channel";

  /**
   * Representa el estado del algoritmo, que es lo que es compartido entre
   * agentes D* como pizarra.
   */
  /**
   *
   */
  private static class AlgorithmState {
    /**
     * No se trata del laberinto en el que el agente se mueve, sino la
     * representación de lo que el agente conoce sobre el laberinto. Todas
     * aquellas zonas que el agente no ha visitado supone que no contienen
     * paredes.
     */
    public Maze maze;

    /**
     * Posición de la celda del laberinto más cercana a su salida.
     */
    public Point exit;

    /**
     * Representa la matriz de posiciones del laberinto con el estado del agente
     * asociado a cada celda.
     */
    public ArrayList <ArrayList <State>> state_maze;

    /**
     * Lista "open" de estados del algoritmo.
     */
    public PriorityQueue <State> open;

    /**
     * Valor de "k_old" del algoritmo.
     */
    public double k_old;
  }

  private transient AlgorithmState m_st;

  /**
   * Crea un nuevo agente D* en el entorno indicado.
   *
   * @param env
   *          Entorno en el que se va a colocar al agente.
   */
  public DStarAgent (Environment env) {
    super(env);
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getAlgorithmName()
   */
  @Override
  public String getAlgorithmName () {
    return "D*";
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
   */
  @Override
  public Color getAlgorithmColor () {
    return Color.BLUE;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#setEnvironment(gui.environment.Environment)
   */
  @Override
  public void setEnvironment (Environment env) {
    super.setEnvironment(env);
    resetMemory();

    BlackboardManager mgr = env.getBlackboardManager();
    try {
      setBlackboard(mgr.getBlackboard(BLACKBOARD_CHANNEL));
    }
    catch (Exception e) {
      Maze real_maze = env.getMaze();

      m_st = new AlgorithmState();
      m_st.maze = new Maze(new EmptyMaze(real_maze.getHeight(), real_maze.getWidth()));

      // Creamos la matriz de estados, donde cada celda representa un nodo en el
      // grafo que manipula el algoritmo. Esto será lo que se comparta entre
      // todos los agentes.
      m_st.state_maze = new ArrayList <ArrayList <State>>(m_st.maze.getHeight());
      for (int i = 0; i < m_st.maze.getHeight(); i++) {
        m_st.state_maze.add(new ArrayList <State>(m_st.maze.getWidth()));
        for (int j = 0; j < m_st.maze.getWidth(); j++)
          m_st.state_maze.get(i).add(new State(new Point(j, i)));
      }

      // La salida la colocaremos dentro del laberinto para que el agente pueda
      // utilizarla como un estado más, luego en el método getNextMovement() se
      // encarga de moverse al exterior si está en el punto al lado de la salida
      m_st.exit = real_maze.getExit();
      if (m_st.exit.x < 0)
        m_st.exit.x++;
      else if (m_st.exit.x == m_st.maze.getWidth())
        m_st.exit.x--;
      else if (m_st.exit.y < 0)
        m_st.exit.y++;
      else /* m_st.exit.y == m_st.maze.getHeight() */
        m_st.exit.y--;

      BLACKBOARD_CHANNEL = mgr.addBlackboard(m_st, BLACKBOARD_CHANNEL);
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getNextMovement()
   */
  @Override
  public Direction getNextMovement () {
    // Si estamos al lado de la salida evitamos cálculos y salimos directamente
    for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) {
      Direction dir = Direction.fromIndex(i);
      if (m_env.look(m_pos, dir) == MazeCell.Vision.OFFLIMITS)
        return dir;
    }

    // Si no se sabe a dónde moverse, hay que calcular la ruta completa
    if (m_st.state_maze.get(m_pos.y).get(m_pos.x).backpointer == null)
      calculatePath();

    // Obtenemos las celdas real y estimada para compararlas y actualizar el
    // mapa consecuentemente
    MazeCell known_cell = m_st.maze.get(m_pos.y, m_pos.x);
    MazeCell real_cell = m_env.getMaze().get(m_pos.y, m_pos.x);

    // Comprobamos en todas las direcciones que las paredes están colocadas en
    // los mismos sitios
    boolean changed = false;
    for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) {
      Direction dir = Direction.fromIndex(i);

      // En las direcciones en las que encontremos diferencias hacemos que la
      // representación interna del agente se actualice
      if (real_cell.hasWall(dir) != known_cell.hasWall(dir)) {
        known_cell.toggleWall(dir);

        Point new_point = dir.movePoint(m_pos);
        if (m_st.maze.containsPoint(new_point)) {
          m_st.maze.get(new_point.y, new_point.x).toggleWall(dir.getOpposite());
          modifyCost(m_st.state_maze.get(new_point.y).get(new_point.x));
          changed = true;
        }
      }
    }

    // Si la representación del laberinto se modifica, esto significa también
    // que las distancias desde la posición actual hacia alguna de sus vecinas
    // ha cambiado, así que hay que actualizar la ruta calculada por si ha
    // dejado de ser factible.
    if (changed)
      calculatePartialPath(m_st.state_maze.get(m_pos.y).get(m_pos.x));

    // printBackpointers();

    Point next_pos = m_st.state_maze.get(m_pos.y).get(m_pos.x).backpointer.point;
    Direction dir = Direction.fromPoints(m_pos, next_pos);
    if (look(dir) != MazeCell.Vision.WALL)
      return dir;
    else {
      modifyCost(m_st.state_maze.get(next_pos.y).get(next_pos.x));
      calculatePartialPath(m_st.state_maze.get(m_pos.y).get(m_pos.x));
      next_pos = m_st.state_maze.get(m_pos.y).get(m_pos.x).backpointer.point;
      return Direction.fromPoints(m_pos, next_pos);
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#resetMemory()
   */
  @Override
  public void resetMemory () {
    if (m_st != null) {
      if (m_st.state_maze != null) {
        for (ArrayList <State> l: m_st.state_maze)
          for (State s: l)
            s.reset();
      }

      for (int i = 0; i < m_st.maze.getHeight(); i++)
        for (int j = 0; j < m_st.maze.getWidth(); j++)
          m_st.maze.get(i, j).removeWalls();

      m_st.open = new PriorityQueue <State>();
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getConfigurationPanel()
   */
  @Override
  public AgentConfigurationPanel getConfigurationPanel () {
    return new HeuristicAgentConfigurationPanel(this);
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#clone()
   */
  @Override
  public Object clone () {
    DStarAgent ag = new DStarAgent(m_env);
    ag.setAgentColor(getAgentColor());
    ag.setDistanceCalculator(m_dist);

    return ag;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.util.BlackboardCommunication#getBlackboard()
   */
  @Override
  public Object getBlackboard () {
    return m_st.state_maze;
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * es.ull.mazesolver.agent.util.BlackboardCommunication#setBlackboard(java
   * .lang.Object)
   */
  @Override
  public void setBlackboard (Object blackboard) {
    try {
      m_st = (AlgorithmState) blackboard;
      if (blackboard == null)
        throw new Exception();
    }
    catch (Exception e) {
      throw new IllegalArgumentException("The blackboard is not the format expected");
    }
  }

  /**
   * Define los posibles valores con los que puede estar etiquetado un estado.
   */
  private static enum Tag {
    NEW, OPEN, CLOSED
  }

  /**
   * Representa un estado dentro del algoritmo D*.
   */
  private class State implements Comparable <State> {
    // No nos vale Double.MAX_VALUE porque Double.MAX_VALUE + 1.0 ==
    // Double.MAX_VALUE
    private static final double BIG_COST = 1000000.0;

    public Point point;
    public State backpointer; // b(X)
    public Tag tag; // t(X)
    public double path_cost; // h(X)
    public double previous_cost; // p(X)
    public double key_value; // k(X)

    /**
     * Crea un estado a partir de su posición en el laberinto. Se marca como
     * "nuevo" y se le asignan costes de infinito para todas sus propiedades.
     *
     * @param pos
     */
    public State (Point pos) {
      point = (Point) pos.clone();
      reset();
    }

    /**
     * Restaura el estado a sus valores iniciales.
     */
    public void reset () {
      backpointer = null;
      tag = Tag.NEW;
      path_cost = previous_cost = key_value = BIG_COST;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    @Override
    public int compareTo (State s) {
      return Double.compare(key_value, s.key_value);
    }

    /**
     * Analiza los vecinos que tiene en todas las direcciones y devuelve todos
     * aquellos que existen. Es decir, las celdas adyacentes que están dentro
     * del laberinto.
     *
     * @return Una lista con los vecinos del estado.
     */
    public ArrayList <State> getNeighbours () {
      ArrayList <State> neighbours = new ArrayList <State>();

      for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) {
        Direction dir = Direction.fromIndex(i);
        Point new_pos = dir.movePoint(point);
        if (m_st.maze.containsPoint(new_pos))
          neighbours.add(m_st.state_maze.get(new_pos.y).get(new_pos.x));
      }

      return neighbours;
    }
  }

  /**
   * Recalcula la ruta hasta la salida desde el punto actual. Utiliza el
   * conocimiento que se tiene actualmente sobre el laberinto para hacerlo. Este
   * método crea desde cero la estructura de estados, por lo que se debe
   * utilizar sólo una vez por entorno.
   */
  private void calculatePath () {
    State initial = m_st.state_maze.get(m_pos.y).get(m_pos.x);
    State goal = m_st.state_maze.get(m_st.exit.y).get(m_st.exit.x);

    goal.path_cost = 0.0;
    insert(goal);

    double value = 0.0;
    while (initial.tag != Tag.CLOSED && value != -1)
      value = processState();
  }

  /**
   * Una vez se encuentra un obstáculo nuevo y se llama a
   * {@link DStarAgent#modifyCost}, se debe llamar a este método para que
   * recalcule el camino hacia la salida de una forma mucho más eficiente que
   * utilizar {@link DStarAgent#calculatePath}. Sólo recalcula aquella parte del
   * camino previamente calculado que ha sido invalidada tras la modificación.
   */
  private void calculatePartialPath (State x) {
    // FIXME Cuando no hay camino hasta la salida, nunca acaba el bucle porque
    // el coste de cada posición se aumenta en cada iteración y nunca se saca
    // de OPEN.

    // Cuando devuelve kmin > x.path_cost lo que hay en OPEN suelen ser los
    // vecinos de x, que hay que actualizar o eso parece...

    // Modificación del algoritmo original: Seguir procesando el estado hasta
    // que no haya nada en la lista abierta (todos los caminos son óptimos)
    while (!m_st.open.isEmpty())
      processState();
  }

  /**
   * Computa los costes del nodo actual hacia el destino cuando se ejecuta
   * repetidamente hasta que el nodo actual se etiqueta como "cerrado".
   *
   * @return El valor de Kmin. Devolverá -1 si no hay ninguna solución factible.
   */
  private double processState () {
    State x = minState();
    if (x == null)
      return -1;

    m_st.k_old = getKmin();
    delete(x);

    ArrayList <State> neighbours = x.getNeighbours();

    // Reducimos el coste del nodo actual si se puede desde alguno de sus
    // vecinos, pero sólo si el camino actual a los vecinos es óptimo
    for (State y: neighbours) {
      if (y.tag == Tag.CLOSED && y.path_cost <= m_st.k_old
          && x.path_cost > y.path_cost + distance(y, x)) {
        x.backpointer = y;
        x.path_cost = y.path_cost + distance(y, x);
      }
    }

    for (State y: neighbours) {
      // Propagación del coste a los estados no visitados
      if (y.tag == Tag.NEW) {
        y.backpointer = x;
        y.path_cost = x.path_cost + distance(x, y);
        y.previous_cost = y.path_cost;
        insert(y);
      }
      else {
        // Propagación de costes a través de los backpointers
        if (y.backpointer == x && y.path_cost != x.path_cost + distance(x, y)) {
          if (y.tag == Tag.OPEN) {
            if (y.path_cost < y.previous_cost)
              y.previous_cost = y.path_cost;

            y.path_cost = x.path_cost + distance(x, y);
          }
          else {
            y.path_cost = x.path_cost + distance(x, y);
            y.previous_cost = y.path_cost;
          }
          insert(y);
        }
        else {
          // Mejora los costes de los vecinos si puede
          if (y.backpointer != x && y.path_cost > x.path_cost + distance(x, y)) {
            if (x.previous_cost >= x.path_cost) {
              y.backpointer = x;
              y.path_cost = x.path_cost + distance(x, y);

              if (y.tag == Tag.CLOSED)
                y.previous_cost = y.path_cost;

              insert(y);
            }
            else {
              x.previous_cost = x.path_cost;
              insert(x);
            }
          }
          else {
            if (y.backpointer != x && x.path_cost > y.path_cost + distance(y, x)
                && y.tag == Tag.CLOSED && y.path_cost > m_st.k_old) {
              y.previous_cost = y.path_cost;
              insert(y);
            }
          }
        }
      }
    }

    return getKmin();
  }

  /**
   * Notifica al algoritmo que se ha detectado una incoherencia entre las
   * distancias que se utilizaron al procesar el nodo X y lo que han medido los
   * sensores, de manera que lo introduce en la lista abierta para volver a ser
   * tratado.
   *
   * @param x
   *          Estado que ha detectado incoherencia entre su representación del
   *          entorno y lo detectado por sus sensores.
   * @return El valor de Kmin.
   */
  private double modifyCost (State x) {
    if (x.tag == Tag.CLOSED) {
      x.previous_cost = x.path_cost;
      insert(x);
    }
    return getKmin();
  }

  /**
   * Obtiene el estado "open" con valor menor de k.
   *
   * @return El estado de la lista abierta con menor valor de k.
   */
  private State minState () {
    return m_st.open.isEmpty()? null : m_st.open.peek();
  }

  /**
   * Obtiene el valor de k más bajo que tiene un estado en "open".
   *
   * @return El valor de k más pequeño que hay en la lista abierta.
   */
  private double getKmin () {
    State min = minState();
    return min != null? min.key_value : -1.0;
  }

  /**
   * Se elimina el estado indicado de la lista abierta y se modifica su etiqueta
   * por "cerrado".
   *
   * @param s
   *          Estado que eliminar.
   */
  private void delete (State s) {
    m_st.open.remove(s);
    s.tag = Tag.CLOSED;
  }

  /**
   * Se inserta el estado en la lista abierta, modificando su etiqueta y
   * calculando el valor de k que tiene asociado a partir de h y p.
   *
   * @param s
   *          Estado que insertar.
   */
  private void insert (State s) {
    // Reposicionamiento del elemento si ya estaba, en lugar de inserción
    if (s.tag == Tag.OPEN)
      m_st.open.remove(s);

    s.key_value = Math.min(s.path_cost, s.previous_cost);
    s.tag = Tag.OPEN;
    m_st.open.add(s);
  }

  /**
   * Calcula la distancia entre dos estados vecinos teniendo en cuenta que si
   * hay una pared que los separa, la distancia es infinita.
   *
   * @param x
   *          Estado X.
   * @param y
   *          Estado Y.
   * @return El valor de la distancia entre ambos estados.
   */
  private double distance (State x, State y) {
    Point pos = x.point;
    if (m_st.maze.get(pos.y, pos.x).hasWall(Direction.fromPoints(pos, y.point)))
      return State.BIG_COST;
    else
      return m_dist.distance(pos, y.point);
  }

  /**
   * Imprime por consola la matriz de movimientos actual del agente.
   */
  @SuppressWarnings ("unused")
  private void printBackpointers () {
    for (int i = 0; i < m_st.maze.getHeight(); i++) {
      for (int j = 0; j < m_st.maze.getWidth(); j++) {
        State s = m_st.state_maze.get(i).get(j);
        if (s.backpointer == null)
          System.out.print("·");
        else {
          switch (Direction.fromPoints(s.point, s.backpointer.point)) {
            case UP:
              System.out.print("\u2191");
              break;
            case DOWN:
              System.out.print("\u2193");
              break;
            case LEFT:
              System.out.print("\u2190");
              break;
            case RIGHT:
              System.out.print("\u2192");
              break;
            default:
              System.out.print("·");
              break;
          }
        }
      }
      System.out.println();
    }
    System.out.println();
  }
} public class HillClimbAgent extends HeuristicAgent {
  private static final long serialVersionUID = 6553751391274900253L;

  private transient boolean m_backtracking;
  private transient Stack <Direction> m_stack;
  private transient boolean [][] m_visited;

  /**
   * Crea el agente en el entorno indicado y con la distancia de Manhattan por
   * defecto.
   *
   * @param env
   *          Entorno en el que se quiere colocar.
   */
  public HillClimbAgent (Environment env) {
    super(env);
    m_stack = new Stack <Direction>();
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#setEnvironment(gui.environment.Environment)
   */
  @Override
  public void setEnvironment (Environment env) {
    super.setEnvironment(env);

    Maze maze = env.getMaze();
    m_visited = new boolean [maze.getHeight()] [maze.getWidth()];
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getAlgorithmName()
   */
  @Override
  public String getAlgorithmName () {
    return "Hill Climbing";
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
   */
  @Override
  public Color getAlgorithmColor () {
    return Color.ORANGE;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getNextMovement()
   */
  @Override
  public Direction getNextMovement () {
    Direction dir = selectDirection();
    if (dir == Direction.NONE && !m_stack.empty()) {
      Direction head = m_stack.peek().getOpposite();
      if (m_env.movementAllowed(m_pos, head)) {
        m_backtracking = true;
        dir = m_stack.pop().getOpposite();
      }
    }

    return dir;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#doMovement(maze.Direction)
   */
  @Override
  public void doMovement (Direction dir) {
    Point prev = m_pos;
    m_visited[m_pos.y][m_pos.x] = true;
    super.doMovement(dir);

    if (!prev.equals(m_pos)) {
      if (!m_backtracking)
        m_stack.push(dir);
      else
        m_backtracking = false;
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#setPosition(java.awt.Point)
   */
  @Override
  public void setPosition (Point pos) {
    resetMemory();
    super.setPosition(pos);
  };

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#resetMemory()
   */
  @Override
  public void resetMemory () {
    m_stack.clear();
    for (int i = 0; i < m_visited.length; i++) {
      for (int j = 0; j < m_visited[i].length; j++)
        m_visited[i][j] = false;
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getConfigurationPanel()
   */
  @Override
  public AgentConfigurationPanel getConfigurationPanel () {
    return new HeuristicAgentConfigurationPanel(this);
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#clone()
   */
  @Override
  public Object clone () {
    HillClimbAgent ag = new HillClimbAgent(m_env);
    ag.setAgentColor(getAgentColor());
    ag.setDistanceCalculator(m_dist);

    return ag;
  }

  /**
   * Elige la dirección que le lleva a la celda más cercana no visitada que no
   * tiene una pared delante.
   *
   * @return La dirección seleccionada. Devuelve {@code Direction.NONE} si no
   *         hay ninguna celda adyacente accesible no visitada.
   */
  private Direction selectDirection () {
    Point exit = m_env.getMaze().getExit();
    TreeMap <Direction, Double> costs = new TreeMap <Direction, Double>();

    for (int i = 0; i < Direction.MAX_DIRECTIONS; i++) {
      Direction dir = Direction.fromIndex(i);
      Point pos = dir.movePoint(m_pos);
      if (pos.equals(exit))
        return dir;

      if (look(dir) == MazeCell.Vision.EMPTY && !m_visited[pos.y][pos.x])
        costs.put(dir, m_dist.distance(pos, exit));
    }

    Direction closest = Direction.NONE;
    double closest_val = Double.MAX_VALUE;
    for (Entry <Direction, Double> entry: costs.entrySet()) {
      if (entry.getValue() < closest_val) {
        closest_val = entry.getValue();
        closest = entry.getKey();
      }
    }

    return closest;
  }

  /**
   * Extrae la información del objeto a partir de una forma serializada del
   * mismo.
   *
   * @param input
   *          Flujo de entrada con la información del objeto.
   * @throws ClassNotFoundException
   *           Si se trata de un objeto de otra clase.
   * @throws IOException
   *           Si no se puede leer el flujo de entrada.
   */
  private void readObject (ObjectInputStream input) throws ClassNotFoundException, IOException {
    input.defaultReadObject();
    m_stack = new Stack <Direction>();
  }
} public class PATableAgent extends Agent {
  private static final long serialVersionUID = -4993197615980679396L;

  private Direction [][][][] m_table;

  /**
   * Crea el agente a partir de un entorno, con la configuración por defecto.
   * @param env Entorno en el que crear el agente.
   */
  public PATableAgent (Environment env) {
    super(env);

    m_table = new Direction[][][][]{
      {
        {
          {Direction.DOWN, Direction.LEFT}, // 0,0,0,0 ; 0,0,0,1
          {Direction.RIGHT, Direction.DOWN} // 0,0,1,0 ; 0,0,1,1
        },
        {
          {Direction.UP, Direction.LEFT},   // 0,1,0,0 ; 0,1,0,1
          {Direction.RIGHT, Direction.UP}   // 0,1,1,0 ; 0,1,1,1
        }
      },
      {
        {
          {Direction.DOWN, Direction.LEFT}, // 1,0,0,0 ; 1,0,0,1
          {Direction.RIGHT, Direction.DOWN} // 1,0,1,0 ; 1,0,1,1
        },
        {
          {Direction.RIGHT, Direction.LEFT},  // 1,1,0,0 ; 1,1,0,1
          {Direction.RIGHT, Direction.NONE}   // 1,1,1,0 ; 1,1,1,1
        }
      }
    };
  }

  /**
   * Cambia la tabla de percepción-acción del agente por otra. Cada dimensión
   * del array debe tener 2 posiciones (EMPTY y WALL) y tiene una dimensión por
   * cada dirección alrededor del agente (UP, DOWN, LEFT y RIGHT). El valor de
   * cada celda de la tabla puede ser cualquier {@link Direction}.
   *
   * @param pa_table La nueva tabla de percepción-acción. Se almacenará una
   * copia de la misma.
   */
  public void setPerceptionActionTable (Direction[][][][] pa_table) {
    m_table = deepDataCopy(pa_table);
  }

  /**
   * @return Una copia de la tabla de percepción-acción del agente.
   */
  public Direction[][][][] getPerceptionActionTable () {
    return deepDataCopy(m_table);
  }

  /* (non-Javadoc)
   * @see agent.Agent#getAlgorithmName()
   */
  @Override
  public String getAlgorithmName () {
    return "Perception-Action Table";
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
   */
  @Override
  public Color getAlgorithmColor () {
    return Color.GREEN;
  }

  /* (non-Javadoc)
   * @see agent.Agent#getNextMovement()
   */
  @Override
  public Direction getNextMovement () {
    // Cualquier lugar al que no nos podamos mover se considerará una "pared"
    Vision up = m_env.movementAllowed(m_pos, Direction.UP)? Vision.EMPTY : Vision.WALL;
    Vision down = m_env.movementAllowed(m_pos, Direction.DOWN)? Vision.EMPTY : Vision.WALL;
    Vision left = m_env.movementAllowed(m_pos, Direction.LEFT)? Vision.EMPTY : Vision.WALL;
    Vision right = m_env.movementAllowed(m_pos, Direction.RIGHT)? Vision.EMPTY : Vision.WALL;

    return m_table[visionToIndex(up)][visionToIndex(down)]
                  [visionToIndex(left)][visionToIndex(right)];
  }

  /* (non-Javadoc)
   * @see agent.Agent#resetMemory()
   */
  @Override
  public void resetMemory () {
    // No tiene memoria, así que no hacemos nada
  }

  /* (non-Javadoc)
   * @see agent.Agent#getConfigurationPanel()
   */
  @Override
  public AgentConfigurationPanel getConfigurationPanel () {
    return new PATableAgentConfigurationPanel(this);
  }

  /* (non-Javadoc)
   * @see agent.Agent#clone()
   */
  @Override
  public Object clone () {
    PATableAgent ag = new PATableAgent(m_env);
    ag.setAgentColor(getAgentColor());
    ag.setPerceptionActionTable(m_table);

    return ag;
  }

  /**
   * Traduce una visión a índice entre 0 y 1.
   *
   * @param vision Visión a traducir.
   * @return Índice asociado a la visión.
   */
  private static int visionToIndex (Vision vision) {
    switch (vision) {
      case EMPTY:
      case OFFLIMITS:
        return 0;
      case WALL:
      case AGENT:
        return 1;
      default:
        return -1;
    }
  }

  /**
   * Hace una copia profunda de la configuración de un agente.
   *
   * @param data Array tetra-dimensional con las direcciones asociadas a cada
   *        percepción del agente.
   * @return Copia profunda del array de datos de entrada.
   */
  private static Direction[][][][] deepDataCopy (Direction[][][][] data) {
    Direction [][][][] result = new Direction[2][2][2][2];
    for (int i = 0; i < 2; i++)
      for (int j = 0; j < 2; j++)
        for (int k = 0; k < 2; k++)
          for (int l = 0; l < 2; l++)
            result[i][j][k][l] = data[i][j][k][l];

    return result;
  }

} public class RecursiveAgent extends Agent {

  private static final long serialVersionUID = 1L;

  private transient boolean m_backtracking;
  private transient Stack <Direction> m_stack;
  private transient boolean [][] m_visited;

  /**
   * Crea el agente en el entorno indicado.
   *
   * @param env
   *          Entorno en el que se quiere colocar.
   */
  public RecursiveAgent (Environment env) {
    super(env);
    m_stack = new Stack <Direction>();
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#setEnvironment(gui.environment.Environment)
   */
  @Override
  public void setEnvironment (Environment env) {
    super.setEnvironment(env);

    Maze maze = env.getMaze();
    m_visited = new boolean [maze.getHeight()] [maze.getWidth()];
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getAlgorithmName()
   */
  @Override
  public String getAlgorithmName () {
    return "Recursive Backtracking";
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
   */
  @Override
  public Color getAlgorithmColor () {
    return Color.YELLOW;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getNextMovement()
   */
  @Override
  public Direction getNextMovement () {
    Direction dir = selectDirection();
    if (dir == Direction.NONE && !m_stack.empty()) {
      Direction head = m_stack.peek().getOpposite();
      if (m_env.movementAllowed(m_pos, head)) {
        m_backtracking = true;
        dir = m_stack.pop().getOpposite();
      }
    }
    return dir;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#doMovement(maze.Direction)
   */
  @Override
  public void doMovement (Direction dir) {
    Point prev = m_pos;
    m_visited[m_pos.y][m_pos.x] = true;
    super.doMovement(dir);

    if (!prev.equals(m_pos)) {
      if (!m_backtracking)
        m_stack.push(dir);
      else
        m_backtracking = false;
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#setPosition(java.awt.Point)
   */
  @Override
  public void setPosition (Point pos) {
    resetMemory();
    super.setPosition(pos);
  };

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#resetMemory()
   */
  @Override
  public void resetMemory () {
    m_stack.clear();
    for (int i = 0; i < m_visited.length; i++) {
      for (int j = 0; j < m_visited[i].length; j++)
        m_visited[i][j] = false;
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getConfigurationPanel()
   */
  @Override
  public AgentConfigurationPanel getConfigurationPanel () {
    return new SimpleAgentConfigurationPanel(this);
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#clone()
   */
  @Override
  public Object clone () {
    RecursiveAgent ag = new RecursiveAgent(m_env);
    ag.setAgentColor(getAgentColor());

    return ag;
  }

  /**
   * Elige la dirección que le lleva a una no visitada que no tiene una pared
   * delante.
   *
   * @return La dirección seleccionada. Devuelve {@code Direction.NONE} si no
   *         hay ninguna celda adyacente accesible no visitada.
   */
  private Direction selectDirection () {
    Point exit = m_env.getMaze().getExit();
    for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) {
      Direction dir = Direction.fromIndex(i);
      Point next = dir.movePoint(m_pos);
      if (next.equals(exit))
        return dir;
      else if (look(dir) == MazeCell.Vision.EMPTY && !m_visited[next.y][next.x]) {
        return dir;
      }
    }
    return Direction.NONE;
  }

  /**
   * Extrae la información del objeto a partir de una forma serializada del
   * mismo.
   *
   * @param input
   *          Flujo de entrada con la información del objeto.
   * @throws ClassNotFoundException
   *           Si se trata de un objeto de otra clase.
   * @throws IOException
   *           Si no se puede leer el flujo de entrada.
   */
  private void readObject (ObjectInputStream input) throws ClassNotFoundException, IOException {
    input.defaultReadObject();
    m_stack = new Stack <Direction>();
  }
} public class SARulesAgent extends Agent {
  private static final long serialVersionUID = -6154538349034427858L;
  public static final int MINIMUM_WIDTH = 300;
  public static final int MINIMUM_HEIGHT = 100;
  public static final String DEFAULT_AGENT_SRC =
      "// Reglas de máxima prioridad para salir del laberinto\n"
    + "DOWN OFFLIMITS -> MOVE DOWN.\n"
    + "RIGHT OFFLIMITS -> MOVE RIGHT.\n"
    + "LEFT OFFLIMITS -> MOVE LEFT.\n"
    + "UP OFFLIMITS -> MOVE UP.\n\n"

    + "// Reglas para moverse al primer sitio no visitado donde haya un hueco\n"
    + "// Siempre intenta acercarse a la esquina inferior derecha\n"
    + "DOWN FREE & DOWN ~VISITED => GO DOWN.\n"
    + "RIGHT FREE & RIGHT ~VISITED => GO RIGHT.\n"
    + "LEFT FREE & LEFT ~VISITED => GO LEFT.\n" + "UP FREE & UP ~VISITED => GO UP.\n\n"

    + "// Reglas para mover al agente si todo alrededor está visitado u\n"
    + "// ocupado. Utilizamos varias operaciones lógicas para demostrar\n"
    + "// la flexibilidad del lenguaje. Se pueden traducir como:\n"
    + "//     <dirección> FREE -> MOVE <dirección>.\n"
    + "Up Not Wall and Up ~Agent -> MOVE up.\n"
    + "not (left wall OR left agent) -> move left.\n"
    + "right !wall And right !agent -> move right.\n"
    + "!(down wall or down agent) -> move down.\n";

  private String m_code;
  private transient SituationActionErrorHandler m_error_handler;
  private transient ArrayList <SituationActionRule> m_rules;
  private transient boolean [][] m_visited;

  /**
   * Crea un agente basado en reglas de situación-acción con el comportamiento
   * por defecto.
   *
   * @param env
   *          Entorno donde se sitúa el agente.
   */
  public SARulesAgent (Environment env) {
    super(env);
    m_error_handler = new SituationActionErrorHandler();
    m_rules = new ArrayList <SituationActionRule>();
    m_code = DEFAULT_AGENT_SRC;
    compileCode();
  }

  /**
   * @return El código fuente actualmente cargado en el agente.
   */
  public String getCode () {
    return m_code;
  }

  /**
   * Cambia el código fuente cargado. Se debe llamar a {@link #compileCode()}
   * para que el nuevo código pueda ser ejecutado por el agente.
   * @param code
   *          Nuevo código fuente.
   */
  public void setCode (String code) {
    m_code = code;
  }

  /**
   * Convierte el código fuente guardado en m_code en la representación de las
   * reglas de situación-acción.
   *
   * @return {@code true} si la compilación fue exitosa y {@code false} si no.
   */
  public boolean compileCode () {
    m_error_handler.resetErrorList();

    ArrayList <SituationActionRule> rules = new ArrayList <SituationActionRule>();
    InputStream stream = new ByteArrayInputStream(m_code.getBytes(StandardCharsets.UTF_8));

    try {
      ANTLRInputStream input = new ANTLRInputStream(stream);
      SituationActionLexer lexer = new SituationActionLexer(input);
      CommonTokenStream tokens = new CommonTokenStream(lexer);
      SituationActionParser parser = new SituationActionParser(tokens);

      lexer.removeErrorListeners();
      parser.removeErrorListeners();
      lexer.addErrorListener(m_error_handler);
      parser.addErrorListener(m_error_handler);

      rules = new ArrayList <SituationActionRule>();
      for (Sa_ruleContext i: parser.program().sa_rule())
        rules.add(SituationActionRule.createFromTree(i));
    }
    catch (Exception e) {
      return false;
    }

    if (m_error_handler.hasErrors())
      return false;
    else {
      m_rules = rules;
      return true;
    }
  }

  /**
   * @return La lista de errores de compilación.
   */
  public ArrayList<String> getCompilationErrors () {
    return m_error_handler.getErrors();
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#setEnvironment(Environment)
   */
  public void setEnvironment (Environment env) {
    super.setEnvironment(env);
    Maze maze = m_env.getMaze();
    m_visited = new boolean [maze.getHeight()] [maze.getWidth()];
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getAlgorithmName()
   */
  @Override
  public String getAlgorithmName () {
    return "Situation-Action Rules";
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
   */
  @Override
  public Color getAlgorithmColor () {
    return Color.MAGENTA;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getNextMovement()
   */
  @Override
  public Direction getNextMovement () {
    // Recorremos las reglas y nos quedamos con la primera acción para la
    // que se cumple la situación (arquitectura de subsunción).
    for (SituationActionRule r: m_rules) {
      RuleAction act = r.getAction(this);
      if (act != null)
        return act.getDirection();
    }
    return Direction.NONE;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#doMovement(maze.Direction)
   */
  @Override
  public void doMovement (Direction dir) {
    // Marcamos la celda actual como visitada
    m_visited[m_pos.y][m_pos.x] = true;
    super.doMovement(dir);
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#resetMemory()
   */
  public void resetMemory () {
    for (boolean [] i: m_visited)
      for (int j = 0; j < i.length; j++)
        i[j] = false;
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#getConfigurationPanel()
   */
  @Override
  public AgentConfigurationPanel getConfigurationPanel () {
    return new SARulesAgentConfigurationPanel(this);
  }

  /**
   * Indica si una celda adyacente al agente ha sido visitada ya por el mismo.
   *
   * @param dir
   *          Dirección en la que hay que mirar.
   * @return Si la celda adyacente en esa dirección ha sido visitada o no.
   */
  public boolean hasVisited (Direction dir) {
    Point p = dir.movePoint(m_pos);
    Maze maze = m_env.getMaze();

    if (!maze.containsPoint(p))
      return false;

    return m_visited[p.y][p.x];
  }

  /*
   * (non-Javadoc)
   *
   * @see agent.Agent#duplicate()
   */
  @Override
  public Object clone () {
    SARulesAgent ag = new SARulesAgent(m_env);
    ag.setAgentColor(getAgentColor());
    ag.m_code = m_code;
    ag.m_rules = new ArrayList <SituationActionRule>(m_rules.size());
    for (SituationActionRule r: m_rules)
      ag.m_rules.add((SituationActionRule) r.clone());

    return ag;
  }

  /**
   * Extrae la información del objeto a partir de una forma serializada del
   * mismo.
   *
   * @param input
   *          Flujo de entrada con la información del objeto.
   * @throws ClassNotFoundException
   *           Si se trata de un objeto de otra clase.
   * @throws IOException
   *           Si no se puede leer el flujo de entrada.
   */
  private void readObject (ObjectInputStream input) throws ClassNotFoundException, IOException {
    input.defaultReadObject();

    m_error_handler = new SituationActionErrorHandler();
    m_rules = new ArrayList <SituationActionRule>();
    compileCode();
  }
} public class SimulatedAnnealingAgent extends HeuristicAgent {
  private static final long serialVersionUID = 1607730980597645713L;

  private static final double DISTANCE_BOOSTING = 5.0;
  private static final int DEFAULT_TEMPERATURE = 5000;
  private static final double DEFAULT_COOLING_RATE = 0.005;

  private transient int m_actual_temp;
  private int m_initial_temp;
  private double m_cooling_rate;

  /**
   * Crea el agente en el entorno indicado con una configuración por defecto que
   * no está adaptada a ningún laberinto en concreto.
   *
   * @param env
   *          Entorno en el que colocar al agente.
   */
  public SimulatedAnnealingAgent (Environment env) {
    super(env);
    m_actual_temp = m_initial_temp = DEFAULT_TEMPERATURE;
    m_cooling_rate = DEFAULT_COOLING_RATE;
  }

  /**
   * @return La temperatura inicial usada por el agente.
   */
  public int getInitialTemperature () {
    return m_initial_temp;
  }

  /**
   * Cambia la temperatura inicial del agente.
   *
   * @param temp La nueva temperatura inicial.
   */
  public void setInitialTemperature (int temp) {
    m_initial_temp = temp;
  }

  /**
   * @return El ratio de enfriamiento por iteración del agente.
   */
  public double getCoolingRate () {
    return m_cooling_rate;
  }

  /**
   * Cambia el ratio de enfriamiento del agente por iteración.
   *
   * @param rate El nuevo ratio de enfriamiento.
   */
  public void setCoolingRate (double rate) {
    m_cooling_rate = rate;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmName()
   */
  @Override
  public String getAlgorithmName () {
    return "Simulated Annealing";
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
   */
  @Override
  public Color getAlgorithmColor () {
    return Color.CYAN;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getNextMovement()
   */
  @Override
  public Direction getNextMovement () {
    // Enfriamos la temperatura si aún está caliente
    if (m_actual_temp > 1)
      m_actual_temp *= 1.0 - m_cooling_rate;

    // Obtenemos los estados vecinos y los permutamos aleatoriamente
    ArrayList <Direction> neighbours = getNeighbours();
    Collections.shuffle(neighbours);

    // Si no hay vecinos no nos movemos a ningún lado
    if (neighbours.isEmpty())
      return Direction.NONE;

    // Seleccionamos aleatoriamente alguna dirección de movimiento de acuerdo
    // a la temperatura actual
    Point exit = m_env.getMaze().getExit();
    for (int i = 0; i < neighbours.size(); i++) {
      Direction dir = neighbours.get(i);
      double diff = m_dist.distance(dir.movePoint(m_pos), exit) - m_dist.distance(m_pos, exit);

      // Si la nueva posición está más cerca de la salida se acepta
      // directamente, si no se acepta con una probabilidad directamente
      // proporcional a la temperatura actual. Aumentamos la distancia real
      // entre las 2 posiciones para que el proceso de convergencia acabe menos
      // drásticamente.
      if (diff < 0.0 || Math.exp((-DISTANCE_BOOSTING * diff) / m_actual_temp) > Math.random())
        return dir;
    }

    return Direction.NONE;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#resetMemory()
   */
  @Override
  public void resetMemory () {
    m_actual_temp = m_initial_temp;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getConfigurationPanel()
   */
  @Override
  public AgentConfigurationPanel getConfigurationPanel () {
    return new SimulatedAnnealingAgentConfigurationPanel(this);
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#clone()
   */
  @Override
  public Object clone () {
    SimulatedAnnealingAgent ag = new SimulatedAnnealingAgent(m_env);
    ag.setAgentColor(getAgentColor());
    ag.setDistanceCalculator(m_dist);
    ag.m_cooling_rate = m_cooling_rate;
    ag.m_initial_temp = m_initial_temp;
    return ag;
  }

  /**
   * Extrae la información del objeto a partir de una forma serializada del
   * mismo.
   *
   * @param input
   *          Flujo de entrada con la información del objeto.
   * @throws ClassNotFoundException
   *           Si se trata de un objeto de otra clase.
   * @throws IOException
   *           Si no se puede leer el flujo de entrada.
   */
  private void readObject (ObjectInputStream input) throws ClassNotFoundException, IOException {
    input.defaultReadObject();
    m_actual_temp = m_initial_temp;
  }

  /**
   * Busca las celdas adyacentes accesibles desde la posición del agente. No
   * tiene en cuenta otros agentes.
   *
   * @return Una lista con las direcciones vecinas (accesibles) del agente.
   */
  public ArrayList <Direction> getNeighbours () {
    ArrayList <Direction> neighbours = new ArrayList <Direction>();

    for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) {
      Direction dir = Direction.fromIndex(i);
      if (m_env.look(m_pos, dir) != MazeCell.Vision.WALL)
        neighbours.add(dir);
    }

    return neighbours;
  }

} public class WallFollowerAgent extends Agent {
  private static final long serialVersionUID = -2234924006984636419L;

  private transient Direction m_last_dir;
  private Rotation m_rot;

  /**
   * Crea el agente en el entorno indicado.
   *
   * @param env
   *          Entorno en el que colocar el agente.
   */
  public WallFollowerAgent (Environment env) {
    super(env);
    m_last_dir = Direction.RIGHT;
    m_rot = Rotation.CW;
  }

  /**
   * Indica hacia qué lado girar primero para seguir la pared.
   *
   * @param rot
   *          Determina la pared que seguirá el agente.
   *          <ul>
   *            <li>{@code Rotation.CLOCKWISE}: Sigue la pared a su derecha.</li>
   *            <li>{@code Rotation.COUNTER_CLOCKWISE}: Sigue la pared a su
   *                izquierda.</li>
   *          </ul>
   */
  public void setRotation (Rotation rot) {
    m_rot = rot;
  }

  /**
   * Obtiene la pared que sigue el agente.
   *
   * @return La pared que el agente sigue.
   */
  public Rotation getRotation () {
    return m_rot;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmName()
   */
  @Override
  public String getAlgorithmName () {
    return "Wall Follower";
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
   */
  @Override
  public Color getAlgorithmColor () {
    return Color.GRAY;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getNextMovement()
   */
  @Override
  public Direction getNextMovement () {
    // Primero intentamos girar al lado que estamos siguiendo. Si está libre,
    // nos movemos en esa dirección
    Direction new_dir = m_last_dir.rotate(m_rot);
    if (m_env.movementAllowed(m_pos, new_dir)) {
      m_last_dir = new_dir;
      return new_dir;
    }
    // Si no podemos, nos movemos en la dirección contraria hasta encontrar un
    // espacio o dar un giro completo
    else {
      // Restamos la dirección que acabamos de ver y Direction.NONE del total
      // de direcciones posibles
      for (int i = 0; i < Direction.MAX_DIRECTIONS - 2; i++) {
        new_dir = new_dir.rotate(m_rot.getOpposite());
        if (m_env.movementAllowed(m_pos, new_dir)) {
          m_last_dir = new_dir;
          return new_dir;
        }
      }
    }
    // Si no encontramos ninguna posibilidad de movimiento, no nos movemos
    return Direction.NONE;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#resetMemory()
   */
  @Override
  public void resetMemory () {
    m_last_dir = Direction.RIGHT;
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#getConfigurationPanel()
   */
  @Override
  public AgentConfigurationPanel getConfigurationPanel () {
    return new WallFollowerAgentConfigurationPanel(this);
  }

  /*
   * (non-Javadoc)
   *
   * @see es.ull.mazesolver.agent.Agent#clone()
   */
  @Override
  public Object clone () {
    WallFollowerAgent ag = new WallFollowerAgent(m_env);
    ag.setAgentColor(getAgentColor());

    return ag;
  }

  /**
   * Extrae la información del objeto a partir de una forma serializada del
   * mismo.
   *
   * @param input
   *          Flujo de entrada con la información del objeto.
   * @throws ClassNotFoundException
   *           Si se trata de un objeto de otra clase.
   * @throws IOException
   *           Si no se puede leer el flujo de entrada.
   */
  private void readObject (ObjectInputStream input) throws ClassNotFoundException, IOException {
    input.defaultReadObject();
    m_last_dir = Direction.RIGHT;
  }

} public abstract class AgentConfigurationPanel extends JPanel implements Translatable {
  private static final long serialVersionUID = 1L;

  private JPanel m_root;
  private ArrayList <EventListener> m_listeners;
  private JButton m_accept, m_cancel;

  /**
   * Agente que se quiere configurar.
   */
  protected Agent m_agent;

  /**
   * Lista de mensajes de error obtenidos al intentar guardar la configuración
   * de un agente.
   */
  protected ArrayList <String> m_errors;

  /**
   * Lista de mensajes de éxito obtenidos tras guardar la configuración de un
   * agente.
   */
  protected ArrayList <String> m_success;

  /**
   * Interfaz de escucha de eventos.
   */
  public static interface EventListener {
    /**
     * LLamado cuando ocurre el evento de tipo "Exitoso". Estos eventos son
     * notificados cuando se guarda el agente y el cambio es aceptado.
     *
     * @param msgs
     *          Lista de mensajes que se quieren mostrar al usuario.
     */
    public void onSuccess (ArrayList <String> msgs);

    /**
     * LLamado cuando ocurre el evento de tipo "Cancelar". Estos eventos son
     * notificados cuando se cancela la edición de las propiedades.
     */
    public void onCancel ();

    /**
     * LLamado cuando ocurre el evento de tipo "Error". Estos eventos son
     * notificados cuando se intenta guardar y no se aceptan las modificaciones.
     *
     * @param errors
     *          Lista de mensajes de error a mostrar al usuario.
     */
    public void onError (ArrayList <String> errors);
  }

  /**
   * Construye la interfaz del panel de configuración de agentes.
   *
   * @param agent
   *          Agente que se quiere configurar.
   */
  public AgentConfigurationPanel (Agent agent) {
    m_root = new JPanel();
    m_listeners = new ArrayList <EventListener>();
    m_agent = agent;
    m_errors = new ArrayList <String>();
    m_success = new ArrayList <String>();

    createGUI(m_root);
    createControls();
    translate();
  }

  /**
   * Añade un oyente de eventos.
   *
   * @param listener
   *          Clase oyente que se quiere añadir.
   */
  public final void addEventListener (EventListener listener) {
    if (!m_listeners.contains(listener))
      m_listeners.add(listener);
  }

  /**
   * Elimina un oyente de eventos. Si no es un oyente, la lista de oyentes
   * permanece intacta.
   *
   * @param listener
   *          Clase oyente que se quiere añadir.
   */
  public final void removeEventListener (EventListener listener) {
    m_listeners.remove(listener);
  }

  /* (non-Javadoc)
   * @see es.ull.mazesolver.translations.Translatable#translate()
   */
  @Override
  public void translate () {
    ButtonTranslations tr = MainWindow.getTranslations().button();

    m_accept.setText(tr.ok());
    m_cancel.setText(tr.cancel());
  }

  /**
   * Provoca que la configuración actualmente almacenada en el panel de
   * configuración se guarde en el agente, modificando su comportamiento.
   *
   * Este método debe ser implementado por cada agente.
   *
   * @return <ul>
   *         <li><b>true</b> si se pudo guardar el resultado.</li>
   *         <li><b>false</b> si la configuración indicada no es válida.</li>
   *         </ul>
   */
  protected abstract boolean accept ();

  /**
   * Cancela la operación de configuración, dejando al agente en su estado de
   * partida.
   *
   * Este método debe ser implementado por cada agente.
   */
  protected abstract void cancel ();

  /**
   * Crea la interfaz gráfica de usuario, que es la que se mostrará al mismo.
   * Estará personalizada para el agente específico, pero no incluirá los
   * botones de "Aceptar" y "Cancelar", que se proporcionan por defecto.
   *
   * @param root
   *          Panel padre de todos los elementos que se creen. Si se intenta
   *          utilizar el panel padre de la clase en lugar de éste, el panel de
   *          configuración no se mostrará correctamente.
   */
  protected abstract void createGUI (JPanel root);

  /**
   * Se crean los controles de "Aceptar" y "Cancelar" y los coloca en el panel
   * junto a los controles personalizados del agente, que ya deben haber sido
   * creados.
   */
  private void createControls () {
    setLayout(new BorderLayout());

    JPanel controls = new JPanel(new FlowLayout(FlowLayout.RIGHT));
    m_accept = new JButton();
    m_cancel = new JButton();

    m_accept.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed (ActionEvent e) {
        if (accept())
          onSuccess();
        else
          onError();
      }
    });

    m_cancel.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed (ActionEvent e) {
        cancel();
        onCancel();
      }
    });

    controls.add(m_accept);
    controls.add(m_cancel);

    add(m_root, BorderLayout.CENTER);
    add(controls, BorderLayout.SOUTH);
  }

  /**
   * Método llamado cuando el usuario aplica los cambios y éstos son guardados
   * correctamente. Notifica a los {@link EventListener#onSuccess}.
   */
  private void onSuccess () {
    for (EventListener listener: m_listeners)
      listener.onSuccess(m_success);
  }

  /**
   * Método llamado cuando el usuario cancela la operación y el agente ha
   * quedado como al principio. Notifica a los {@link EventListener#onCancel}.
   */
  private void onCancel () {
    for (EventListener listener: m_listeners)
      listener.onCancel();
  }

  /**
   * Método llamado cuando el usuario intenta aplicar los cambios y no es
   * posible porque la configuración no es válida. Notifica a los {@link
   * EventListener#onError}.
   */
  private void onError () {
    for (EventListener listener: m_listeners)
      listener.onError(m_errors);
  }
} 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;
  }

} public interface Translations {
  MenuTranslations menu ();
  ButtonTranslations button ();
  MessageTranslations message ();
  ExceptionTranslations exception ();
  EnumTranslations enums ();

  ConsoleTranslations console ();
  EnvSelectorTranslations env ();
  MazeSelectorTranslations maze ();
  AgentSelectorTranslations agent ();

  SimulatorResultTranslations simulation ();
  Languages languages ();

  OtherTranslations other ();
}
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.TreeMap;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import es.ull.mazesolver.agent.AStarAgent;
import es.ull.mazesolver.agent.Agent;
import es.ull.mazesolver.agent.DStarAgent;
import es.ull.mazesolver.agent.HillClimbAgent;
import es.ull.mazesolver.agent.PATableAgent;
import es.ull.mazesolver.agent.RecursiveAgent;
import es.ull.mazesolver.agent.SARulesAgent;
import es.ull.mazesolver.agent.SimulatedAnnealingAgent;
import es.ull.mazesolver.agent.WallFollowerAgent;
import es.ull.mazesolver.gui.configuration.AgentConfigurationPanel;
import es.ull.mazesolver.gui.environment.Environment;
import es.ull.mazesolver.translations.Translations;
/*
 * 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 AgentSelectorDialog.java
 * @date 15/11/2014
 */
package es.ull.mazesolver.gui;




/**
 * Interfaz gráfica para permitir al usuario elegir qué agente añadir al
 * entorno.
 */
public class AgentSelectorDialog extends JDialog {
  private static final long serialVersionUID = 1L;

  private static final TreeMap <String, Class <? extends Agent>> ALGORITHMS = new TreeMap <>();
  static {
    ALGORITHMS.put("Perception-Action Tables", PATableAgent.class);
    ALGORITHMS.put("Situation-Action Rules", SARulesAgent.class);
    ALGORITHMS.put("A*", AStarAgent.class);
    ALGORITHMS.put("D*", DStarAgent.class);
    ALGORITHMS.put("Hill Climbing", HillClimbAgent.class);
    ALGORITHMS.put("Simulated Annealing", SimulatedAnnealingAgent.class);
    ALGORITHMS.put("Recursive Backtracking", RecursiveAgent.class);
    ALGORITHMS.put("Wall Follower", WallFollowerAgent.class);
  }

  private int max_agents;
  private JComboBox <String> m_agents;
  private JSpinner m_amount;

  private Agent [] m_result;
  private Agent m_template_agent = null;

  /**
   * Crea el diálogo de creación de agentes.
   *
   * @param parent
   *          Ventana padre del diálogo.
   * @param available_cells
   *          Número de celdas vacías actualmente en el laberinto.
   */
  public AgentSelectorDialog (Window parent, int available_cells) {
    super(parent);
    max_agents = available_cells - 1;

    buildInterface();
    setResizable(false);
    setModal(true);
  }

  /**
   * Muestra el diálogo por pantalla y devuelve la lista de agentes que se deben
   * crear como consecuencia de la selección del usuario.
   *
   * @return Lista de agentes que se quieren añadir al entorno o null si no se
   *         quiere añadir ninguno.
   */
  public Agent [] showDialog () {
    setVisible(true);
    return m_result;
  }

  /**
   * Construye la interfaz gráfica y configura los listeners.
   */
  private void buildInterface () {
    final Translations tr = MainWindow.getTranslations();

    setTitle(tr.agent().createNewAgent());
    setLayout(new BorderLayout());

    JLabel ags = new JLabel(tr.agent().algorithm() + ":");
    JLabel amo = new JLabel(tr.agent().amount() + ":");

    JPanel global = new JPanel(new BorderLayout(5, 5));
    JPanel labels = new JPanel(new GridLayout(2, 1, 5, 5));
    JPanel controls = new JPanel(new GridLayout(2, 1, 5, 5));

    m_agents = new JComboBox <String>(ALGORITHMS.keySet().toArray(new String [ALGORITHMS.size()]));
    m_amount = new JSpinner(new SpinnerNumberModel(1, 1, max_agents, 1));

    labels.add(ags);
    controls.add(m_agents);
    labels.add(amo);
    controls.add(m_amount);

    global.add(labels, BorderLayout.WEST);
    global.add(controls, BorderLayout.CENTER);
    global.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

    JButton ok = new JButton(tr.button().ok());
    JButton cancel = new JButton(tr.button().cancel());
    JButton config = new JButton(tr.button().configure() + "...");

    JPanel button_panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
    button_panel.add(ok);
    button_panel.add(cancel);
    button_panel.add(config);

    add(global, BorderLayout.CENTER);
    add(button_panel, BorderLayout.SOUTH);
    pack();

    // Cuando se cambia la clase del combobox hay que actualizar el agente
    // plantilla
    m_agents.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed (ActionEvent e) {
        m_template_agent = createSelectedAgent();
      }
    });

    ok.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed (ActionEvent e) {
        int amount = (Integer) m_amount.getValue();

        if (m_template_agent == null)
          m_template_agent = createSelectedAgent();

        m_result = new Agent [amount];
        m_result[0] = m_template_agent;

        // Clonamos el agente en todas las posiciones (todos serán iguales)
        for (int i = 1; i < amount; i++) {
          m_result[i] = (Agent) m_result[0].clone();
        }

        setVisible(false);
        dispose();
      }
    });

    cancel.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed (ActionEvent e) {
        setVisible(false);
        dispose();
      }
    });

    config.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed (ActionEvent e) {
        if (m_template_agent == null)
          m_template_agent = createSelectedAgent();

        final AgentConfigurationPanel config_panel = m_template_agent.getConfigurationPanel();