package ch.epfl.maze.simulation; import ch.epfl.maze.graphics.Animation; import ch.epfl.maze.physical.Animal; import ch.epfl.maze.physical.Maze; import ch.epfl.maze.physical.World; import ch.epfl.maze.util.Action; import ch.epfl.maze.util.Direction; import ch.epfl.maze.util.Vector2D; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * Simulation of a maze solver. Handles the next move of each animal, as well as * the animation by notifying the changes to it. The simulation finishes when * every animal has found the exit. * * @author EPFL */ public final class MazeSimulation implements Simulation { /* limit to the step counter, over which the animals are considered lost */ public static final int COUNTER_LIMIT = 10000; /* simulation components */ private Maze mMaze; private Map> mArrivalTimes; private int mStepCounter; /** * Constructs a simulation with a {@code Maze} to simulate. * * @param maze The maze to simulate */ public MazeSimulation(Maze maze) { mMaze = maze; mArrivalTimes = new TreeMap>(); mStepCounter = 0; } @Override public void move(Animation listener) { if (isOver()) { return; } // increments counter mStepCounter++; // if counter exceeded limit, it considers animals lost if (mStepCounter > COUNTER_LIMIT) { List animals = mMaze.getAnimals(); List lostAnimals = new LinkedList(); for (Animal animal : animals) { mMaze.removeAnimal(animal); lostAnimals.add(animal); } mArrivalTimes.put(Integer.MAX_VALUE, lostAnimals); // infinite return; } // asks animals to move moveAnimals(listener); // notifies animation that all the changes are done if (listener != null) { listener.doneUpdating(); } } @Override public boolean isOver() { return mMaze.isSolved(); } @Override public World getWorld() { return mMaze; } @Override public int getSteps() { return mStepCounter; } public Map> getArrivalTimes() { return new TreeMap>(mArrivalTimes); } public String getRecordTable() { String recordTable = ""; int position = 1; for (Map.Entry> entry : mArrivalTimes.entrySet()) { // only returns the 10 first if (position > 10) { return recordTable; } for (Animal animal : entry.getValue()) { if (entry.getKey() == Integer.MAX_VALUE) { recordTable += "-- "; recordTable += animal.getClass().getSimpleName(); recordTable += " - never finished\n"; } else { recordTable += position + ". "; recordTable += animal.getClass().getSimpleName(); recordTable += " - " + entry.getKey() + " steps\n"; } } position += entry.getValue().size(); } return recordTable; } @Override public void restart() { mMaze.reset(); mArrivalTimes.clear(); mStepCounter = 0; } @Override public void stop() { List forgottenAnimals = new LinkedList(); for (Animal animal : mMaze.getAnimals()) { forgottenAnimals.add(animal); mMaze.removeAnimal(animal); } mArrivalTimes.put(Integer.MAX_VALUE, forgottenAnimals); } /** * Moves the animals in the maze. * * @param listener The listener to which the function will notify the changes * (can be null) */ private void moveAnimals(Animation listener) { List animals = mMaze.getAnimals(); for (int i = 0; i < animals.size(); i++) { Animal animal = animals.get(i); Vector2D position = animal.getPosition(); Direction[] choices = mMaze.getChoices(position); // tries to make animal move Direction choice; try { choice = animal.move(choices); if (!animal.getPosition().equals(position)) { System.err.println("Error : Animal position changed while choosing direction."); System.err.println("\tDid you call setPosition(Vector2D) or update(Direction) ?\n"); animal.setPosition(position); choice = null; } } catch (Exception E) { System.err.print("Exception occurred while moving animals: "); E.printStackTrace(); choice = null; } // if animal could move if (choice != null) { Vector2D futurePosition = animal.getPosition(); futurePosition = futurePosition.addDirectionTo(choice); int x = futurePosition.getX(); int y = futurePosition.getY(); if (mMaze.isFree(x, y)) { // asks animation to draw the action of the animal if (listener != null) { Action action = new Action(choice, true); listener.update(animal, i, action); } // if at the end of the maze if (mMaze.getTile(x, y) == World.EXIT) { mMaze.removeAnimal(animal); // records arrival time if (mArrivalTimes.get(mStepCounter) == null) { mArrivalTimes.put(mStepCounter, new LinkedList()); } mArrivalTimes.get(mStepCounter).add(animal); } else { animal.update(choice); } } else if (listener != null) { // asks animation to draw an interrupted movement Action action = new Action(choice, false); listener.update(animal, i, action); } } else if (listener != null) { // asks animation to draw a confused animal Action action = new Action(Direction.NONE, false); listener.update(animal, i, action); } } } }