From 90bd766f361083f6fd9e39ff080c83fcc606832f Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Sat, 21 Nov 2015 10:44:42 +0100 Subject: Reformat imported code --- src/ch/epfl/maze/graphics/Animation.java | 453 ++++----- src/ch/epfl/maze/graphics/Display.java | 1030 ++++++++++--------- src/ch/epfl/maze/graphics/GraphicComponent.java | 432 ++++---- src/ch/epfl/maze/main/Console.java | 273 +++--- src/ch/epfl/maze/main/Program.java | 124 ++- src/ch/epfl/maze/physical/Animal.java | 104 +- src/ch/epfl/maze/physical/Daedalus.java | 238 +++-- src/ch/epfl/maze/physical/Maze.java | 105 +- src/ch/epfl/maze/physical/Predator.java | 89 +- src/ch/epfl/maze/physical/Prey.java | 81 +- src/ch/epfl/maze/physical/World.java | 259 +++-- src/ch/epfl/maze/physical/pacman/Blinky.java | 40 +- src/ch/epfl/maze/physical/pacman/Clyde.java | 40 +- src/ch/epfl/maze/physical/pacman/Inky.java | 40 +- src/ch/epfl/maze/physical/pacman/PacMan.java | 29 +- src/ch/epfl/maze/physical/pacman/Pinky.java | 40 +- src/ch/epfl/maze/physical/zoo/Bear.java | 64 +- src/ch/epfl/maze/physical/zoo/Hamster.java | 56 +- src/ch/epfl/maze/physical/zoo/Monkey.java | 54 +- src/ch/epfl/maze/physical/zoo/Mouse.java | 54 +- src/ch/epfl/maze/physical/zoo/Panda.java | 52 +- src/ch/epfl/maze/physical/zoo/SpaceInvader.java | 58 +- .../epfl/maze/simulation/DaedalusSimulation.java | 681 +++++++------ src/ch/epfl/maze/simulation/MazeSimulation.java | 381 ++++---- src/ch/epfl/maze/simulation/Simulation.java | 108 +- src/ch/epfl/maze/tests/AnimalTest.java | 169 ++-- src/ch/epfl/maze/tests/Competition.java | 263 +++-- src/ch/epfl/maze/tests/DaedalusTest.java | 209 ++-- src/ch/epfl/maze/tests/GhostsTest.java | 246 +++-- src/ch/epfl/maze/tests/MazeTest.java | 117 ++- src/ch/epfl/maze/tests/WorldTest.java | 625 ++++++------ src/ch/epfl/maze/tests/ZooTest.java | 364 ++++--- src/ch/epfl/maze/util/Action.java | 149 ++- src/ch/epfl/maze/util/Direction.java | 445 +++++---- src/ch/epfl/maze/util/LabyrinthGenerator.java | 1032 ++++++++++---------- src/ch/epfl/maze/util/Statistics.java | 357 ++++--- src/ch/epfl/maze/util/Vector2D.java | 426 ++++---- 37 files changed, 4543 insertions(+), 4744 deletions(-) (limited to 'src') diff --git a/src/ch/epfl/maze/graphics/Animation.java b/src/ch/epfl/maze/graphics/Animation.java index 0502a92..47e0fc8 100644 --- a/src/ch/epfl/maze/graphics/Animation.java +++ b/src/ch/epfl/maze/graphics/Animation.java @@ -1,6 +1,12 @@ package ch.epfl.maze.graphics; -import java.awt.Graphics2D; +import ch.epfl.maze.physical.Animal; +import ch.epfl.maze.util.Action; +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +import javax.imageio.ImageIO; +import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.io.File; @@ -10,236 +16,231 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import javax.imageio.ImageIO; - -import ch.epfl.maze.physical.Animal; -import ch.epfl.maze.util.Action; -import ch.epfl.maze.util.Direction; -import ch.epfl.maze.util.Vector2D; - /** * Handles the animation of a {@code Simulation} by extrapolating the positions * of animals. - * */ public final class Animation { - /** Default number of waiting frames to display when animation is aborting. */ - public static final int DEFAULT_WAITING_FRAMES = 2; - - /** Maps animals identity to graphical components that will be animated. */ - private Map mGraphMap; - - /** Buffer of images of animals. Key format: "superclass.class" */ - private Map mImages; - - /** Drawing ratio variable. */ - private float mRatio; - - /** Control variable. */ - private boolean mDone; - - /** Current number of waiting frames, to prevent screen from flashing. */ - private int mWaitingFrames; - - /** - * Constructs an animation handler that will animate animals on a graphic - * environment by extrapolating their position. - * - * @param animals - * The {@code List} of animals that will be shown on the first - * frame - */ - - public Animation(List animals) { - mGraphMap = new TreeMap(); - mImages = new HashMap(); - - // sanity check - if (animals != null) { - // puts default action to draw animals and loads corresponding image - Action none = new Action(Direction.NONE); - for (int i = 0; i < animals.size(); i++) { - Animal animal = animals.get(i); - BufferedImage img = loadImage(animal); - Vector2D position = animal.getPosition().mul(Display.SQUARE_SIZE); - - mGraphMap.put(i, new GraphicComponent(img, position, none)); - } - } - - // default values - mDone = true; - mWaitingFrames = 0; - } - - /** - * Asks the animation to update an animal on the screen with a corresponding - * action. The animal is identified by a number, so it can be overwritten in - * case of a future update. - * - * @param animal - * Animal to update with action - * @param id - * Unique identifier for animal - * @param action - * Action that animal needs to perform - */ - - public void update(Animal animal, int id, Action action) { - // sanity checks - if (action == null) { - action = new Action(Direction.NONE, false); - } - if (animal != null) { - // retrieves BufferedImage - String folder = animal.getClass().getSuperclass().getSimpleName(); - String file = animal.getClass().getSimpleName(); - BufferedImage img = mImages.get(folder + "." + file); - if (img == null) { - img = loadImage(animal); - } - - // transforms position - Vector2D position = animal.getPosition().mul(Display.SQUARE_SIZE); - - mGraphMap.put(id, new GraphicComponent(img, position, action)); - } - } - - /** - * Asks the animation to make the animal corresponding to the identifier die - * between two squares. This will be done by animating only half of its - * action. - * - * @param id - * Identifier of animal to kill - */ - - public void updateDying(int id) { - GraphicComponent graphComp = mGraphMap.get(id); - if (graphComp != null) { - graphComp.willDieMoving(); - } - } - - /** - * Notifies the animation that updates were done, and that it can start - * animating from now. - */ - - public void doneUpdating() { - mDone = false; - } - - /** - * Paints the dt-step of the animation. - * - * @param dt - * The elapsed time between two frames - * @param g - * The graphics environment on which the graphic components will - * be painted (assumed non-null) - * @param targetWindow - * The window on which the graphic components will be painted - * (assumed non-null) - */ - - public void paint(float dt, Graphics2D g, ImageObserver targetWindow) { - mRatio += dt; - if (mRatio > 1) { - mRatio = 1; - } - - // paints every graphic component stored so far - for (Map.Entry entry : mGraphMap.entrySet()) { - GraphicComponent comp = entry.getValue(); - comp.paint(mRatio, g, targetWindow); - } - - // decides whether the animation is done - if (mDone || mRatio == 1 || mWaitingFrames == 1) { - mWaitingFrames = 0; - mDone = true; - mGraphMap.clear(); - mRatio = 0; - } - - // prevents screen from flashing when aborting - if (mWaitingFrames > 0) { - mWaitingFrames--; - } - } - - /** - * Determines whether the animation has finished. - * - * @return true if the animation is done, false otherwise - */ - - public boolean isDone() { - return mDone; - } - - /** - * Resets the animation with a new {@code List} of animals. If it is set to - * {@code null}, it just informs that it needs to abort its current job. A - * number of frames will still be painted to prevent the screen from - * flashing. - */ - - public void reset(List animals) { - mGraphMap.clear(); - if (animals != null) { - // puts default action to draw animals - Action none = new Action(Direction.NONE); - for (int i = 0; i < animals.size(); i++) { - Animal animal = animals.get(i); - - // loads corresponding image only if not already existing - String folder = animal.getClass().getSuperclass().getSimpleName(); - String file = animal.getClass().getSimpleName(); - BufferedImage img = mImages.get(folder + "." + file); - if (img == null) { - img = loadImage(animal); - } - - // transforms position - Vector2D position = animal.getPosition().mul(Display.SQUARE_SIZE); - - mGraphMap.put(i, new GraphicComponent(img, position, none)); - } - } - mWaitingFrames = DEFAULT_WAITING_FRAMES; - } - - /** - * Buffers and returns the image of an animal. It does not load its image if - * it's already been loaded. - * - * @param animal - * Animal whose image needs to be loaded or returned - * @return The buffered image of the animal - */ - - private BufferedImage loadImage(Animal animal) { - // path = "img/superclass/class.png" - String folder = animal.getClass().getSuperclass().getSimpleName(); - String file = animal.getClass().getSimpleName(); - String path = "img/" + folder + File.separator + file + ".png"; - - // adds image to buffer if not already there - BufferedImage img = mImages.get(folder + "." + file); - if (img == null) { - try { - img = ImageIO.read(new File(path)); - mImages.put(folder + "." + file, img); - } catch (IOException e) { - e.printStackTrace(); - } - } - - return img; - } + /** + * Default number of waiting frames to display when animation is aborting. + */ + public static final int DEFAULT_WAITING_FRAMES = 2; + + /** + * Maps animals identity to graphical components that will be animated. + */ + private Map mGraphMap; + + /** + * Buffer of images of animals. Key format: "superclass.class" + */ + private Map mImages; + + /** + * Drawing ratio variable. + */ + private float mRatio; + + /** + * Control variable. + */ + private boolean mDone; + + /** + * Current number of waiting frames, to prevent screen from flashing. + */ + private int mWaitingFrames; + + /** + * Constructs an animation handler that will animate animals on a graphic + * environment by extrapolating their position. + * + * @param animals The {@code List} of animals that will be shown on the first + * frame + */ + + public Animation(List animals) { + mGraphMap = new TreeMap(); + mImages = new HashMap(); + + // sanity check + if (animals != null) { + // puts default action to draw animals and loads corresponding image + Action none = new Action(Direction.NONE); + for (int i = 0; i < animals.size(); i++) { + Animal animal = animals.get(i); + BufferedImage img = loadImage(animal); + Vector2D position = animal.getPosition().mul(Display.SQUARE_SIZE); + + mGraphMap.put(i, new GraphicComponent(img, position, none)); + } + } + + // default values + mDone = true; + mWaitingFrames = 0; + } + + /** + * Asks the animation to update an animal on the screen with a corresponding + * action. The animal is identified by a number, so it can be overwritten in + * case of a future update. + * + * @param animal Animal to update with action + * @param id Unique identifier for animal + * @param action Action that animal needs to perform + */ + + public void update(Animal animal, int id, Action action) { + // sanity checks + if (action == null) { + action = new Action(Direction.NONE, false); + } + if (animal != null) { + // retrieves BufferedImage + String folder = animal.getClass().getSuperclass().getSimpleName(); + String file = animal.getClass().getSimpleName(); + BufferedImage img = mImages.get(folder + "." + file); + if (img == null) { + img = loadImage(animal); + } + + // transforms position + Vector2D position = animal.getPosition().mul(Display.SQUARE_SIZE); + + mGraphMap.put(id, new GraphicComponent(img, position, action)); + } + } + + /** + * Asks the animation to make the animal corresponding to the identifier die + * between two squares. This will be done by animating only half of its + * action. + * + * @param id Identifier of animal to kill + */ + + public void updateDying(int id) { + GraphicComponent graphComp = mGraphMap.get(id); + if (graphComp != null) { + graphComp.willDieMoving(); + } + } + + /** + * Notifies the animation that updates were done, and that it can start + * animating from now. + */ + + public void doneUpdating() { + mDone = false; + } + + /** + * Paints the dt-step of the animation. + * + * @param dt The elapsed time between two frames + * @param g The graphics environment on which the graphic components will + * be painted (assumed non-null) + * @param targetWindow The window on which the graphic components will be painted + * (assumed non-null) + */ + + public void paint(float dt, Graphics2D g, ImageObserver targetWindow) { + mRatio += dt; + if (mRatio > 1) { + mRatio = 1; + } + + // paints every graphic component stored so far + for (Map.Entry entry : mGraphMap.entrySet()) { + GraphicComponent comp = entry.getValue(); + comp.paint(mRatio, g, targetWindow); + } + + // decides whether the animation is done + if (mDone || mRatio == 1 || mWaitingFrames == 1) { + mWaitingFrames = 0; + mDone = true; + mGraphMap.clear(); + mRatio = 0; + } + + // prevents screen from flashing when aborting + if (mWaitingFrames > 0) { + mWaitingFrames--; + } + } + + /** + * Determines whether the animation has finished. + * + * @return true if the animation is done, false otherwise + */ + + public boolean isDone() { + return mDone; + } + + /** + * Resets the animation with a new {@code List} of animals. If it is set to + * {@code null}, it just informs that it needs to abort its current job. A + * number of frames will still be painted to prevent the screen from + * flashing. + */ + + public void reset(List animals) { + mGraphMap.clear(); + if (animals != null) { + // puts default action to draw animals + Action none = new Action(Direction.NONE); + for (int i = 0; i < animals.size(); i++) { + Animal animal = animals.get(i); + + // loads corresponding image only if not already existing + String folder = animal.getClass().getSuperclass().getSimpleName(); + String file = animal.getClass().getSimpleName(); + BufferedImage img = mImages.get(folder + "." + file); + if (img == null) { + img = loadImage(animal); + } + + // transforms position + Vector2D position = animal.getPosition().mul(Display.SQUARE_SIZE); + + mGraphMap.put(i, new GraphicComponent(img, position, none)); + } + } + mWaitingFrames = DEFAULT_WAITING_FRAMES; + } + + /** + * Buffers and returns the image of an animal. It does not load its image if + * it's already been loaded. + * + * @param animal Animal whose image needs to be loaded or returned + * @return The buffered image of the animal + */ + + private BufferedImage loadImage(Animal animal) { + // path = "img/superclass/class.png" + String folder = animal.getClass().getSuperclass().getSimpleName(); + String file = animal.getClass().getSimpleName(); + String path = "img/" + folder + File.separator + file + ".png"; + + // adds image to buffer if not already there + BufferedImage img = mImages.get(folder + "." + file); + if (img == null) { + try { + img = ImageIO.read(new File(path)); + mImages.put(folder + "." + file, img); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return img; + } } diff --git a/src/ch/epfl/maze/graphics/Display.java b/src/ch/epfl/maze/graphics/Display.java index ea08a06..2fddcf1 100644 --- a/src/ch/epfl/maze/graphics/Display.java +++ b/src/ch/epfl/maze/graphics/Display.java @@ -1,15 +1,12 @@ package ch.epfl.maze.graphics; -import java.awt.Canvas; -import java.awt.Color; -import java.awt.EventQueue; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; +import ch.epfl.maze.physical.World; +import ch.epfl.maze.simulation.Simulation; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.io.File; @@ -17,527 +14,506 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import javax.imageio.ImageIO; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.KeyStroke; -import javax.swing.WindowConstants; - -import ch.epfl.maze.physical.World; -import ch.epfl.maze.simulation.Simulation; - /** * Handles the display of a {@code Simulation} on a window. - * */ public final class Display implements Runnable { - /* constants */ - public static final Color BACKGROUND_COLOR = Color.GRAY; - public static final int SQUARE_SIZE = 42; - public static final int BUFFERS_NUMBER = 2; - public static final int MAX_SPEED = 32; - public static final int DEFAULT_SPEED = 2; - public static final int MIN_SPEED = 1; - public static final int ANIMATION_SLEEP = 10; - - /* lock for mutual exclusion between human interactions and display */ - private final Object mLock = new Object(); - - /* simulation and animation handlers */ - private final Simulation mSimulation; - private final Animation mAnimation; - private volatile float mSpeed; - - /* actual window frame and canvas */ - private JFrame mFrame; - private JMenuBar mMenuBar; - private Canvas mCanvas; - - /* drawing buffers */ - private BufferStrategy mStrategy; - private Map mTiles; - - /* control variables */ - private boolean mRunning; - private boolean mPaused; - private boolean mShowGrid; - private boolean mDebug; - private boolean mFinished; - - /** - * Constructs a {@code Display} that will display a simulation. - * - * @param simulation - * A {@code Simulation} to display - */ - - public Display(Simulation simulation) { - // sanity check - if (simulation == null) { - throw new IllegalArgumentException("Simulation must be defined."); - } - if (simulation.getWorld() == null) { - throw new IllegalArgumentException("World in Simulation must be defined."); - } - - // initiates instances - mSimulation = simulation; - mAnimation = new Animation(simulation.getWorld().getAnimals()); - mSpeed = DEFAULT_SPEED; - - // default control variables - mRunning = true; - mPaused = false; - mShowGrid = false; - mDebug = false; - mFinished = false; - - // creates menu - createMenu(); - - // creates canvas - createCanvas(); - - // creates window - createWindow(); - - // sets canvas and menu on frame - mFrame.setJMenuBar(mMenuBar); - mFrame.add(mCanvas); - mFrame.pack(); - mFrame.setLocationRelativeTo(null); - - // creates buffer strategy - mCanvas.createBufferStrategy(BUFFERS_NUMBER); - mStrategy = mCanvas.getBufferStrategy(); - - // loads images of tiles - mTiles = new HashMap(); - try { - mTiles.put(World.FREE, ImageIO.read(new File("img/tiles/free.png"))); - mTiles.put(World.WALL, ImageIO.read(new File("img/tiles/wall.png"))); - mTiles.put(World.START, ImageIO.read(new File("img/tiles/start.png"))); - mTiles.put(World.EXIT, ImageIO.read(new File("img/tiles/exit.png"))); - mTiles.put(World.NOTHING, ImageIO.read(new File("img/tiles/nothing.png"))); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void run() { - mFrame.setVisible(true); - mainLoop(); - mFrame.dispose(); - } - - /** - * Sets the debug control. - * - * @param debug - * The new debug value - */ - - public void setDebug(boolean debug) { - mDebug = debug; - mShowGrid = debug; - createMenu(); - mFrame.setJMenuBar(mMenuBar); - } - - /** - * Creates frame window. - */ - - private void createWindow() { - // actual window - mFrame = new JFrame("Maze solver simulation"); - - // redefines closing operation - mFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - mFrame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - synchronized (mLock) { - mRunning = false; - } - } - }); - - // sets up various options to make it look good - mFrame.setIgnoreRepaint(false); - mFrame.setBackground(BACKGROUND_COLOR); - mFrame.setFocusable(false); - mFrame.setFocusTraversalKeysEnabled(false); - mFrame.setResizable(false); - System.setProperty("sun.awt.noerasebackground", "true"); - } - - /** - * Creates canvas. - */ - - private void createCanvas() { - // actual canvas - mCanvas = new Canvas(); - - // sets canvas size - int height = SQUARE_SIZE * mSimulation.getWorld().getHeight(); - int width = SQUARE_SIZE * mSimulation.getWorld().getWidth(); - mCanvas.setSize(width, height); - - // sets up options to make it look good - mCanvas.setIgnoreRepaint(true); - mCanvas.setBackground(Color.BLACK); - mCanvas.setFocusable(true); - mCanvas.setFocusTraversalKeysEnabled(false); - } - - /** - * Creates menu. - */ - - private void createMenu() { - // creates menu bar - mMenuBar = new JMenuBar(); - - // creates "Maze" menu - JMenu menu = new JMenu("Simulation"); - menu.setMnemonic(KeyEvent.VK_M); - menu.setToolTipText("Contains main manipulation options."); - mMenuBar.add(menu); - - // creates menu items - // "Stop" - JMenuItem stopItem = new JMenuItem("Stop", KeyEvent.VK_S); - stopItem.setToolTipText("Stops simulation in its current state."); - stopItem.setAccelerator(KeyStroke.getKeyStroke("control W")); - stopItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - synchronized (mLock) { - mSimulation.stop(); - mAnimation.reset(null); - mPaused = false; - } - } - }); - menu.add(stopItem); - - // "Restart" - JMenuItem restart = new JMenuItem("Restart", KeyEvent.VK_R); - restart.setToolTipText("Restarts the simulation from the beginning."); - restart.setAccelerator(KeyStroke.getKeyStroke("control R")); - restart.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - synchronized (mLock) { - mSimulation.restart(); - mAnimation.reset(mSimulation.getWorld().getAnimals()); - mPaused = false; - mFinished = false; - } - } - }); - menu.add(restart); - - // ========================================================== - menu.addSeparator(); - - // "Pause" - final JMenuItem pauseItem = new JMenuItem(mDebug ? "Next step" : - (mPaused ? "Resume" : "Pause"), KeyEvent.VK_P); - pauseItem.setAccelerator(KeyStroke.getKeyStroke("SPACE")); - pauseItem.setToolTipText("Pauses simulation."); - pauseItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - synchronized (mLock) { - if (mPaused) { - mPaused = false; - if (!mDebug) { - pauseItem.setText("Pause"); - } - } else { - mPaused = true; - if (!mDebug) { - pauseItem.setText("Resume"); - } - } - }; - } - }); - menu.add(pauseItem); - - // "Debug mode" - JCheckBoxMenuItem debugItem = new JCheckBoxMenuItem("Debug mode"); - debugItem.setState(mDebug); - debugItem.setMnemonic(KeyEvent.VK_M); - debugItem.setAccelerator(KeyStroke.getKeyStroke("alt G")); - debugItem.setToolTipText("Enters in debug mode, allowing to move animals step by step."); - menu.add(debugItem); - - // ========================================================== - menu.addSeparator(); - - // "Accelerate" - final JMenuItem accelerateItem = new JMenuItem("Accelerate", - KeyEvent.VK_A); - // "Decelerate" - final JMenuItem decelerateItem = new JMenuItem("Decelerate", - KeyEvent.VK_D); - - accelerateItem.setToolTipText("Increases simulation speed up to a maximum."); - decelerateItem.setToolTipText("Decreases simulation speed down to a minimum."); - - accelerateItem.setAccelerator(KeyStroke.getKeyStroke('+')); - decelerateItem.setAccelerator(KeyStroke.getKeyStroke('-')); - - accelerateItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - synchronized (mLock) { - decelerateItem.setEnabled(true); - if (mSpeed < MAX_SPEED) { - mSpeed *= 2; - } - if (mSpeed == MAX_SPEED) { - accelerateItem.setEnabled(false); - } - } - } - }); - decelerateItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - synchronized (mLock) { - accelerateItem.setEnabled(true); - if (mSpeed > MIN_SPEED) { - mSpeed /= 2; - } - if (mSpeed == MIN_SPEED) { - decelerateItem.setEnabled(false); - } - } - } - }); - - menu.add(accelerateItem); - menu.add(decelerateItem); - - // ========================================================== - menu.addSeparator(); - - // "Show grid" - final JCheckBoxMenuItem gridItem = new JCheckBoxMenuItem("Show grid"); - gridItem.setState(mShowGrid); - gridItem.setMnemonic(KeyEvent.VK_G); - gridItem.setToolTipText("Criss-crosses the maze tiles."); - gridItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - synchronized (mLock) { - mShowGrid = !mShowGrid; - gridItem.setState(mShowGrid); - } - } - }); - debugItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - synchronized (mLock) { - if (!mDebug) { - mDebug = true; - mShowGrid = true; - pauseItem.setText("Next Step"); - - } else { - mShowGrid = false; - mPaused = false; - mDebug = false; - pauseItem.setText("Pause"); - } - gridItem.setState(mShowGrid); - } - } - }); - menu.add(gridItem); - - // "Exit" - JMenuItem exitItem = new JMenuItem("Exit", KeyEvent.VK_E); - exitItem.setToolTipText("Exits program."); - exitItem.setAccelerator(KeyStroke.getKeyStroke("ESCAPE")); - exitItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - synchronized (mLock) { - mRunning = false; - } - } - }); - menu.add(exitItem); - } - - /** - * Draws the labyrinth being simulated. - * - * @param g - * The graphics on which the labyrinth will be drawn - */ - - private void drawLabyrinth(Graphics2D g) { - World world = mSimulation.getWorld(); - - BufferedImage tile; - for (int y = 0; y < world.getHeight(); y++) { - for (int x = 0; x < world.getWidth(); x++) { - // retrieves corresponding image - tile = mTiles.get(world.getTile(x, y)); - if (tile == null) { - tile = mTiles.get(World.WALL); - } - - int width = x * SQUARE_SIZE; - int height = y * SQUARE_SIZE; - - g.drawImage(tile, width, height, mFrame); - - if (mShowGrid) { - g.setColor(BACKGROUND_COLOR); - g.drawRect(width, height, SQUARE_SIZE, SQUARE_SIZE); - } - } - } - } - - /** - * Draws the actual animation of the display. - * - * @param dt - * The elapsed time between two frames - * @param g - * The graphics on which the animation will be drawn - * @param width - * Width of graphics - * @param height - * Height of graphics - */ - - private void drawAnimation(float dt, Graphics2D g, int width, int height) { - // clears background - g.setColor(BACKGROUND_COLOR); - g.fillRect(0, 0, width, height); - - // paints maze - drawLabyrinth(g); - - synchronized (mLock) { - // paints next animation frame - mAnimation.paint(dt, g, mFrame); - - if (mAnimation.isDone()) { - // determines if maze is solved - if (!mFinished && mSimulation.isOver()) { - mFinished = true; - final String recordTable = mSimulation.getRecordTable(); - - // message dialog to invoke later, to prevent Display from - // crashing when a key is being held - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - JOptionPane.showMessageDialog(null, "Simulation is complete.\n\n" + recordTable); - } - }); - mPaused = true; - } else { - if (mDebug) { - mPaused = true; - } - nextMove(); - } - } - } - } - - /** - * Controls the graphics environment for the animation of the display. - * - * @param dt - * The elapsed time between two frames - */ - - private void animate(float dt) { - Graphics2D g = null; - try { - // retrieves Graphics2D from strategy - g = (Graphics2D) mStrategy.getDrawGraphics(); - - // sets options - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - drawAnimation(dt, g, mCanvas.getWidth(), mCanvas.getHeight()); - } finally { - if (g != null) { - // to call at the end - g.dispose(); - } - } - - // displays the buffer - mStrategy.show(); - } - - /** - * Runs the animation main loop. - */ - - private void mainLoop() { - long before = System.nanoTime(); - while (mRunning) { - if (mPaused || mFinished) { - animate(0); - before = System.nanoTime(); - } else { - long now = System.nanoTime(); - float dt = (now - before) * 0.000000001f * mSpeed; - if (dt < 0.001f) { - dt = 0.001f; - } - - animate(dt); - - before = now; - } - - try { - Thread.sleep(ANIMATION_SLEEP); - } catch (InterruptedException e) { - // do nothing - } - } - - // paints a last frame - animate(0); - } - - /** - * Computes the {@code Simulation}'s next move. - */ - - private void nextMove() { - synchronized (mLock) { - if (mRunning) { - mSimulation.move(mAnimation); - } - } - } + /* constants */ + public static final Color BACKGROUND_COLOR = Color.GRAY; + public static final int SQUARE_SIZE = 42; + public static final int BUFFERS_NUMBER = 2; + public static final int MAX_SPEED = 32; + public static final int DEFAULT_SPEED = 2; + public static final int MIN_SPEED = 1; + public static final int ANIMATION_SLEEP = 10; + + /* lock for mutual exclusion between human interactions and display */ + private final Object mLock = new Object(); + + /* simulation and animation handlers */ + private final Simulation mSimulation; + private final Animation mAnimation; + private volatile float mSpeed; + + /* actual window frame and canvas */ + private JFrame mFrame; + private JMenuBar mMenuBar; + private Canvas mCanvas; + + /* drawing buffers */ + private BufferStrategy mStrategy; + private Map mTiles; + + /* control variables */ + private boolean mRunning; + private boolean mPaused; + private boolean mShowGrid; + private boolean mDebug; + private boolean mFinished; + + /** + * Constructs a {@code Display} that will display a simulation. + * + * @param simulation A {@code Simulation} to display + */ + + public Display(Simulation simulation) { + // sanity check + if (simulation == null) { + throw new IllegalArgumentException("Simulation must be defined."); + } + if (simulation.getWorld() == null) { + throw new IllegalArgumentException("World in Simulation must be defined."); + } + + // initiates instances + mSimulation = simulation; + mAnimation = new Animation(simulation.getWorld().getAnimals()); + mSpeed = DEFAULT_SPEED; + + // default control variables + mRunning = true; + mPaused = false; + mShowGrid = false; + mDebug = false; + mFinished = false; + + // creates menu + createMenu(); + + // creates canvas + createCanvas(); + + // creates window + createWindow(); + + // sets canvas and menu on frame + mFrame.setJMenuBar(mMenuBar); + mFrame.add(mCanvas); + mFrame.pack(); + mFrame.setLocationRelativeTo(null); + + // creates buffer strategy + mCanvas.createBufferStrategy(BUFFERS_NUMBER); + mStrategy = mCanvas.getBufferStrategy(); + + // loads images of tiles + mTiles = new HashMap(); + try { + mTiles.put(World.FREE, ImageIO.read(new File("img/tiles/free.png"))); + mTiles.put(World.WALL, ImageIO.read(new File("img/tiles/wall.png"))); + mTiles.put(World.START, ImageIO.read(new File("img/tiles/start.png"))); + mTiles.put(World.EXIT, ImageIO.read(new File("img/tiles/exit.png"))); + mTiles.put(World.NOTHING, ImageIO.read(new File("img/tiles/nothing.png"))); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void run() { + mFrame.setVisible(true); + mainLoop(); + mFrame.dispose(); + } + + /** + * Sets the debug control. + * + * @param debug The new debug value + */ + + public void setDebug(boolean debug) { + mDebug = debug; + mShowGrid = debug; + createMenu(); + mFrame.setJMenuBar(mMenuBar); + } + + /** + * Creates frame window. + */ + + private void createWindow() { + // actual window + mFrame = new JFrame("Maze solver simulation"); + + // redefines closing operation + mFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + mFrame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + synchronized (mLock) { + mRunning = false; + } + } + }); + + // sets up various options to make it look good + mFrame.setIgnoreRepaint(false); + mFrame.setBackground(BACKGROUND_COLOR); + mFrame.setFocusable(false); + mFrame.setFocusTraversalKeysEnabled(false); + mFrame.setResizable(false); + System.setProperty("sun.awt.noerasebackground", "true"); + } + + /** + * Creates canvas. + */ + + private void createCanvas() { + // actual canvas + mCanvas = new Canvas(); + + // sets canvas size + int height = SQUARE_SIZE * mSimulation.getWorld().getHeight(); + int width = SQUARE_SIZE * mSimulation.getWorld().getWidth(); + mCanvas.setSize(width, height); + + // sets up options to make it look good + mCanvas.setIgnoreRepaint(true); + mCanvas.setBackground(Color.BLACK); + mCanvas.setFocusable(true); + mCanvas.setFocusTraversalKeysEnabled(false); + } + + /** + * Creates menu. + */ + + private void createMenu() { + // creates menu bar + mMenuBar = new JMenuBar(); + + // creates "Maze" menu + JMenu menu = new JMenu("Simulation"); + menu.setMnemonic(KeyEvent.VK_M); + menu.setToolTipText("Contains main manipulation options."); + mMenuBar.add(menu); + + // creates menu items + // "Stop" + JMenuItem stopItem = new JMenuItem("Stop", KeyEvent.VK_S); + stopItem.setToolTipText("Stops simulation in its current state."); + stopItem.setAccelerator(KeyStroke.getKeyStroke("control W")); + stopItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + synchronized (mLock) { + mSimulation.stop(); + mAnimation.reset(null); + mPaused = false; + } + } + }); + menu.add(stopItem); + + // "Restart" + JMenuItem restart = new JMenuItem("Restart", KeyEvent.VK_R); + restart.setToolTipText("Restarts the simulation from the beginning."); + restart.setAccelerator(KeyStroke.getKeyStroke("control R")); + restart.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + synchronized (mLock) { + mSimulation.restart(); + mAnimation.reset(mSimulation.getWorld().getAnimals()); + mPaused = false; + mFinished = false; + } + } + }); + menu.add(restart); + + // ========================================================== + menu.addSeparator(); + + // "Pause" + final JMenuItem pauseItem = new JMenuItem(mDebug ? "Next step" : + (mPaused ? "Resume" : "Pause"), KeyEvent.VK_P); + pauseItem.setAccelerator(KeyStroke.getKeyStroke("SPACE")); + pauseItem.setToolTipText("Pauses simulation."); + pauseItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + synchronized (mLock) { + if (mPaused) { + mPaused = false; + if (!mDebug) { + pauseItem.setText("Pause"); + } + } else { + mPaused = true; + if (!mDebug) { + pauseItem.setText("Resume"); + } + } + } + ; + } + }); + menu.add(pauseItem); + + // "Debug mode" + JCheckBoxMenuItem debugItem = new JCheckBoxMenuItem("Debug mode"); + debugItem.setState(mDebug); + debugItem.setMnemonic(KeyEvent.VK_M); + debugItem.setAccelerator(KeyStroke.getKeyStroke("alt G")); + debugItem.setToolTipText("Enters in debug mode, allowing to move animals step by step."); + menu.add(debugItem); + + // ========================================================== + menu.addSeparator(); + + // "Accelerate" + final JMenuItem accelerateItem = new JMenuItem("Accelerate", + KeyEvent.VK_A); + // "Decelerate" + final JMenuItem decelerateItem = new JMenuItem("Decelerate", + KeyEvent.VK_D); + + accelerateItem.setToolTipText("Increases simulation speed up to a maximum."); + decelerateItem.setToolTipText("Decreases simulation speed down to a minimum."); + + accelerateItem.setAccelerator(KeyStroke.getKeyStroke('+')); + decelerateItem.setAccelerator(KeyStroke.getKeyStroke('-')); + + accelerateItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + synchronized (mLock) { + decelerateItem.setEnabled(true); + if (mSpeed < MAX_SPEED) { + mSpeed *= 2; + } + if (mSpeed == MAX_SPEED) { + accelerateItem.setEnabled(false); + } + } + } + }); + decelerateItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + synchronized (mLock) { + accelerateItem.setEnabled(true); + if (mSpeed > MIN_SPEED) { + mSpeed /= 2; + } + if (mSpeed == MIN_SPEED) { + decelerateItem.setEnabled(false); + } + } + } + }); + + menu.add(accelerateItem); + menu.add(decelerateItem); + + // ========================================================== + menu.addSeparator(); + + // "Show grid" + final JCheckBoxMenuItem gridItem = new JCheckBoxMenuItem("Show grid"); + gridItem.setState(mShowGrid); + gridItem.setMnemonic(KeyEvent.VK_G); + gridItem.setToolTipText("Criss-crosses the maze tiles."); + gridItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + synchronized (mLock) { + mShowGrid = !mShowGrid; + gridItem.setState(mShowGrid); + } + } + }); + debugItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + synchronized (mLock) { + if (!mDebug) { + mDebug = true; + mShowGrid = true; + pauseItem.setText("Next Step"); + + } else { + mShowGrid = false; + mPaused = false; + mDebug = false; + pauseItem.setText("Pause"); + } + gridItem.setState(mShowGrid); + } + } + }); + menu.add(gridItem); + + // "Exit" + JMenuItem exitItem = new JMenuItem("Exit", KeyEvent.VK_E); + exitItem.setToolTipText("Exits program."); + exitItem.setAccelerator(KeyStroke.getKeyStroke("ESCAPE")); + exitItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + synchronized (mLock) { + mRunning = false; + } + } + }); + menu.add(exitItem); + } + + /** + * Draws the labyrinth being simulated. + * + * @param g The graphics on which the labyrinth will be drawn + */ + + private void drawLabyrinth(Graphics2D g) { + World world = mSimulation.getWorld(); + + BufferedImage tile; + for (int y = 0; y < world.getHeight(); y++) { + for (int x = 0; x < world.getWidth(); x++) { + // retrieves corresponding image + tile = mTiles.get(world.getTile(x, y)); + if (tile == null) { + tile = mTiles.get(World.WALL); + } + + int width = x * SQUARE_SIZE; + int height = y * SQUARE_SIZE; + + g.drawImage(tile, width, height, mFrame); + + if (mShowGrid) { + g.setColor(BACKGROUND_COLOR); + g.drawRect(width, height, SQUARE_SIZE, SQUARE_SIZE); + } + } + } + } + + /** + * Draws the actual animation of the display. + * + * @param dt The elapsed time between two frames + * @param g The graphics on which the animation will be drawn + * @param width Width of graphics + * @param height Height of graphics + */ + + private void drawAnimation(float dt, Graphics2D g, int width, int height) { + // clears background + g.setColor(BACKGROUND_COLOR); + g.fillRect(0, 0, width, height); + + // paints maze + drawLabyrinth(g); + + synchronized (mLock) { + // paints next animation frame + mAnimation.paint(dt, g, mFrame); + + if (mAnimation.isDone()) { + // determines if maze is solved + if (!mFinished && mSimulation.isOver()) { + mFinished = true; + final String recordTable = mSimulation.getRecordTable(); + + // message dialog to invoke later, to prevent Display from + // crashing when a key is being held + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + JOptionPane.showMessageDialog(null, "Simulation is complete.\n\n" + recordTable); + } + }); + mPaused = true; + } else { + if (mDebug) { + mPaused = true; + } + nextMove(); + } + } + } + } + + /** + * Controls the graphics environment for the animation of the display. + * + * @param dt The elapsed time between two frames + */ + + private void animate(float dt) { + Graphics2D g = null; + try { + // retrieves Graphics2D from strategy + g = (Graphics2D) mStrategy.getDrawGraphics(); + + // sets options + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + drawAnimation(dt, g, mCanvas.getWidth(), mCanvas.getHeight()); + } finally { + if (g != null) { + // to call at the end + g.dispose(); + } + } + + // displays the buffer + mStrategy.show(); + } + + /** + * Runs the animation main loop. + */ + + private void mainLoop() { + long before = System.nanoTime(); + while (mRunning) { + if (mPaused || mFinished) { + animate(0); + before = System.nanoTime(); + } else { + long now = System.nanoTime(); + float dt = (now - before) * 0.000000001f * mSpeed; + if (dt < 0.001f) { + dt = 0.001f; + } + + animate(dt); + + before = now; + } + + try { + Thread.sleep(ANIMATION_SLEEP); + } catch (InterruptedException e) { + // do nothing + } + } + + // paints a last frame + animate(0); + } + + /** + * Computes the {@code Simulation}'s next move. + */ + + private void nextMove() { + synchronized (mLock) { + if (mRunning) { + mSimulation.move(mAnimation); + } + } + } } diff --git a/src/ch/epfl/maze/graphics/GraphicComponent.java b/src/ch/epfl/maze/graphics/GraphicComponent.java index 2321714..e49a542 100644 --- a/src/ch/epfl/maze/graphics/GraphicComponent.java +++ b/src/ch/epfl/maze/graphics/GraphicComponent.java @@ -1,237 +1,221 @@ package ch.epfl.maze.graphics; -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.awt.image.ImageObserver; - -import javax.swing.ImageIcon; - import ch.epfl.maze.util.Action; import ch.epfl.maze.util.Direction; import ch.epfl.maze.util.Vector2D; +import javax.swing.*; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.ImageObserver; + /** * Graphic component of an animal that will be drawn by an {@link Animation}. - * */ public final class GraphicComponent { - /* constants */ - public static final int MAXIMUM_FRAMES = 4; - public static final int SQUARE_SIZE = Display.SQUARE_SIZE; - - /* drawing variables */ - private final Vector2D mPosition; - private final BufferedImage mImage; - private Action mAction; - private boolean mRotate; - - /** - * Constructs a graphic component with the image of the animal, the position - * at which the component needs to be drawn, and the corresponding action - * that it needs to perform. - * - * @param image - * Image of animal - * @param position - * Position at which the image will be drawn - * @param action - * Action that the component needs to perform - */ - - public GraphicComponent(BufferedImage image, Vector2D position, Action action) { - // sanity checks - if (image == null) { - throw new IllegalArgumentException("BufferedImage cannot be null."); - } - if (position == null) { - throw new IllegalArgumentException("Position cannot be null."); - } - if (action == null) { - action = new Action(Direction.NONE, false); - } - - // default values - mImage = image; - mPosition = position; - mRotate = true; - mAction = action; - } - - /** - * Notifies the component that it will die between two squares. - */ - - public void willDieMoving() { - mAction = new Action(mAction.getDirection(), mAction.isSuccessful(), true); - } - - /** - * Asks the graphic component to paint itself on graphic environment, by - * performing a ratio of its action. - * - * @param ratio - * Ratio of the action to be performed - * @param g - * Graphic environment - * @param targetWindow - * Window on which the graphic is being drawn - */ - - public void paint(float ratio, Graphics2D g, ImageObserver targetWindow) { - if (mAction.getDirection() == Direction.NONE) { - renderStuck(g, targetWindow); - } else { - if (ratio > 0.5) { - if (!mAction.diesBetweenSquares() && !mAction.isSuccessful()) { - renderMove(1 - ratio, g, targetWindow, true); - } else if (!mAction.diesBetweenSquares()) { - renderMove(ratio, g, targetWindow, false); - } - } else { - renderMove(ratio, g, targetWindow, false); - } - } - } - - /** - * Draws the moving component on graphics environment and target window. - *

- * The function draws the animal at {@code (position + ratio*heading)}. - * - * @param ratio - * Ratio of action performed by animation - * @param g - * Graphic environment - * @param targetWindow - * Frame display - * @param buzz - * Buzzes the animal, used when he has just hit a wall - */ - - private void renderMove(float ratio, Graphics2D g, ImageObserver targetWindow, boolean buzz) { - // transforms direction into vector - Vector2D heading = mAction.getDirection().toVector().mul(SQUARE_SIZE); - Vector2D normalized = heading.normalize(); - - // loads the correct frame - BufferedImage img = cropImage(ratio, mAction.getDirection()); - - AffineTransform reset = new AffineTransform(); - - // applies translation - double newX = (mPosition.getX() + ratio * heading.getX()); - double newY = (mPosition.getY() + ratio * heading.getY()); - reset.translate(newX, newY); - - // applies rotation - double rotation = 0; - if (buzz) { - rotation = -(Math.PI / 6.0) * Math.sin((60 * ratio) / Math.PI); - } - if (mRotate) { - rotation += Math.atan2(normalized.getY(), normalized.getX()) - Math.PI / 2; - } - reset.rotate(rotation, SQUARE_SIZE / 2, SQUARE_SIZE / 2); - - // transforms and draws image - g.setTransform(reset); - g.drawImage(img, 0, 0, targetWindow); - - // inverts transformations - reset.rotate(-rotation, SQUARE_SIZE / 2, SQUARE_SIZE / 2); - reset.translate(-newX, -newY); - g.setTransform(reset); - } - - /** - * Draws the still component on graphics environment and target window. - *

- * Draws a question mark if {@code mAction} is not successful - * - * @param g - * Graphic environment - * @param targetWindow - * Frame display - */ - - private void renderStuck(Graphics2D g, ImageObserver targetWindow) { - // loads default frame of image with default direction - BufferedImage img = cropImage(-1, Direction.NONE); - - AffineTransform reset = new AffineTransform(); - - // applies translation - double newX = mPosition.getX(); - double newY = mPosition.getY(); - reset.translate(newX, newY); - - // transforms and draws image - g.setTransform(reset); - g.drawImage(img, 0, 0, targetWindow); - - // draws interrogation mark - if (!mAction.isSuccessful()) { - ImageIcon icon = new ImageIcon("img/unknown.png"); - g.drawImage(icon.getImage(), SQUARE_SIZE - icon.getIconWidth() - 2, 2, targetWindow); - } - - // inverts translation - reset.translate(-newX, -newY); - g.setTransform(reset); - } - - /** - * Transforms the image according to frame and movement. - * - * @param ratio - * The ratio of the action to perform - * @param dir - * The direction towards which the component faces - * @return The correct frame that faces towards the direction specified - */ - - private BufferedImage cropImage(float ratio, Direction dir) { - int width = mImage.getWidth(); - int height = mImage.getHeight(); - int frames = width / SQUARE_SIZE; - int moves = height / SQUARE_SIZE; - - // sanity checks - if (width % SQUARE_SIZE != 0 || height % SQUARE_SIZE != 0) { - throw new UnsupportedOperationException( - "Image size is not a multiple of " + SQUARE_SIZE + " pixels, but " + width + "x" + height); - } - if (moves > Direction.values().length) { - throw new UnsupportedOperationException( - "Image height has more than " + Direction.values().length + " moves (" + height + ")"); - } - if (frames > MAXIMUM_FRAMES) { - throw new UnsupportedOperationException( - "Image width has more than " + MAXIMUM_FRAMES + " frames (" + frames + ")"); - } - - // selects frame - int frame = ((int) (ratio * 2 * frames)) % frames; - if (frame >= frames) { - frame = 0; - } else if (ratio < 0 || ratio > 1) { // handles bad ratio - frame = (int) (frames / 2); - } - - // selects move - int move = dir.intValue(); - mRotate = false; - if (move >= moves) { - mRotate = true; - move = 0; - } - - // selects subimage according to frame and move - BufferedImage img = mImage.getSubimage(frame * SQUARE_SIZE, move * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE); - - return img; - } + /* constants */ + public static final int MAXIMUM_FRAMES = 4; + public static final int SQUARE_SIZE = Display.SQUARE_SIZE; + + /* drawing variables */ + private final Vector2D mPosition; + private final BufferedImage mImage; + private Action mAction; + private boolean mRotate; + + /** + * Constructs a graphic component with the image of the animal, the position + * at which the component needs to be drawn, and the corresponding action + * that it needs to perform. + * + * @param image Image of animal + * @param position Position at which the image will be drawn + * @param action Action that the component needs to perform + */ + + public GraphicComponent(BufferedImage image, Vector2D position, Action action) { + // sanity checks + if (image == null) { + throw new IllegalArgumentException("BufferedImage cannot be null."); + } + if (position == null) { + throw new IllegalArgumentException("Position cannot be null."); + } + if (action == null) { + action = new Action(Direction.NONE, false); + } + + // default values + mImage = image; + mPosition = position; + mRotate = true; + mAction = action; + } + + /** + * Notifies the component that it will die between two squares. + */ + + public void willDieMoving() { + mAction = new Action(mAction.getDirection(), mAction.isSuccessful(), true); + } + + /** + * Asks the graphic component to paint itself on graphic environment, by + * performing a ratio of its action. + * + * @param ratio Ratio of the action to be performed + * @param g Graphic environment + * @param targetWindow Window on which the graphic is being drawn + */ + + public void paint(float ratio, Graphics2D g, ImageObserver targetWindow) { + if (mAction.getDirection() == Direction.NONE) { + renderStuck(g, targetWindow); + } else { + if (ratio > 0.5) { + if (!mAction.diesBetweenSquares() && !mAction.isSuccessful()) { + renderMove(1 - ratio, g, targetWindow, true); + } else if (!mAction.diesBetweenSquares()) { + renderMove(ratio, g, targetWindow, false); + } + } else { + renderMove(ratio, g, targetWindow, false); + } + } + } + + /** + * Draws the moving component on graphics environment and target window. + *

+ * The function draws the animal at {@code (position + ratio*heading)}. + * + * @param ratio Ratio of action performed by animation + * @param g Graphic environment + * @param targetWindow Frame display + * @param buzz Buzzes the animal, used when he has just hit a wall + */ + + private void renderMove(float ratio, Graphics2D g, ImageObserver targetWindow, boolean buzz) { + // transforms direction into vector + Vector2D heading = mAction.getDirection().toVector().mul(SQUARE_SIZE); + Vector2D normalized = heading.normalize(); + + // loads the correct frame + BufferedImage img = cropImage(ratio, mAction.getDirection()); + + AffineTransform reset = new AffineTransform(); + + // applies translation + double newX = (mPosition.getX() + ratio * heading.getX()); + double newY = (mPosition.getY() + ratio * heading.getY()); + reset.translate(newX, newY); + + // applies rotation + double rotation = 0; + if (buzz) { + rotation = -(Math.PI / 6.0) * Math.sin((60 * ratio) / Math.PI); + } + if (mRotate) { + rotation += Math.atan2(normalized.getY(), normalized.getX()) - Math.PI / 2; + } + reset.rotate(rotation, SQUARE_SIZE / 2, SQUARE_SIZE / 2); + + // transforms and draws image + g.setTransform(reset); + g.drawImage(img, 0, 0, targetWindow); + + // inverts transformations + reset.rotate(-rotation, SQUARE_SIZE / 2, SQUARE_SIZE / 2); + reset.translate(-newX, -newY); + g.setTransform(reset); + } + + /** + * Draws the still component on graphics environment and target window. + *

+ * Draws a question mark if {@code mAction} is not successful + * + * @param g Graphic environment + * @param targetWindow Frame display + */ + + private void renderStuck(Graphics2D g, ImageObserver targetWindow) { + // loads default frame of image with default direction + BufferedImage img = cropImage(-1, Direction.NONE); + + AffineTransform reset = new AffineTransform(); + + // applies translation + double newX = mPosition.getX(); + double newY = mPosition.getY(); + reset.translate(newX, newY); + + // transforms and draws image + g.setTransform(reset); + g.drawImage(img, 0, 0, targetWindow); + + // draws interrogation mark + if (!mAction.isSuccessful()) { + ImageIcon icon = new ImageIcon("img/unknown.png"); + g.drawImage(icon.getImage(), SQUARE_SIZE - icon.getIconWidth() - 2, 2, targetWindow); + } + + // inverts translation + reset.translate(-newX, -newY); + g.setTransform(reset); + } + + /** + * Transforms the image according to frame and movement. + * + * @param ratio The ratio of the action to perform + * @param dir The direction towards which the component faces + * @return The correct frame that faces towards the direction specified + */ + + private BufferedImage cropImage(float ratio, Direction dir) { + int width = mImage.getWidth(); + int height = mImage.getHeight(); + int frames = width / SQUARE_SIZE; + int moves = height / SQUARE_SIZE; + + // sanity checks + if (width % SQUARE_SIZE != 0 || height % SQUARE_SIZE != 0) { + throw new UnsupportedOperationException( + "Image size is not a multiple of " + SQUARE_SIZE + " pixels, but " + width + "x" + height); + } + if (moves > Direction.values().length) { + throw new UnsupportedOperationException( + "Image height has more than " + Direction.values().length + " moves (" + height + ")"); + } + if (frames > MAXIMUM_FRAMES) { + throw new UnsupportedOperationException( + "Image width has more than " + MAXIMUM_FRAMES + " frames (" + frames + ")"); + } + + // selects frame + int frame = ((int) (ratio * 2 * frames)) % frames; + if (frame >= frames) { + frame = 0; + } else if (ratio < 0 || ratio > 1) { // handles bad ratio + frame = (int) (frames / 2); + } + + // selects move + int move = dir.intValue(); + mRotate = false; + if (move >= moves) { + mRotate = true; + move = 0; + } + + // selects subimage according to frame and move + BufferedImage img = mImage.getSubimage(frame * SQUARE_SIZE, move * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE); + + return img; + } } diff --git a/src/ch/epfl/maze/main/Console.java b/src/ch/epfl/maze/main/Console.java index edad4b2..cbcb8dc 100644 --- a/src/ch/epfl/maze/main/Console.java +++ b/src/ch/epfl/maze/main/Console.java @@ -1,17 +1,8 @@ package ch.epfl.maze.main; -import java.util.Collections; -import java.util.List; -import java.util.Map; - import ch.epfl.maze.physical.Daedalus; import ch.epfl.maze.physical.Maze; -import ch.epfl.maze.physical.pacman.Blinky; -import ch.epfl.maze.physical.pacman.Clyde; -import ch.epfl.maze.physical.pacman.Inky; -import ch.epfl.maze.physical.pacman.PacMan; -import ch.epfl.maze.physical.pacman.Pinky; -import ch.epfl.maze.physical.zoo.Bear; +import ch.epfl.maze.physical.pacman.*; import ch.epfl.maze.physical.zoo.Hamster; import ch.epfl.maze.physical.zoo.Monkey; import ch.epfl.maze.physical.zoo.Mouse; @@ -23,140 +14,144 @@ import ch.epfl.maze.util.LabyrinthGenerator; import ch.epfl.maze.util.Statistics; import ch.epfl.maze.util.Vector2D; +import java.util.Collections; +import java.util.List; +import java.util.Map; + /** * Mini-project main program that will run the simulations multiple times and * show statistics on the console. - * */ public class Console { - /** Number of simulations launched. */ - public static final int NUMBER_OF_SIMULATIONS = 1000; - - public static void main(String[] args) { - Simulation simulation; - - simulation = getMazeSimulation(); - //simulation = getDaedalusSimulation(); - - System.out.print("Launching " + NUMBER_OF_SIMULATIONS + " simulations..."); - Map> results = - Statistics.computeStatistics(simulation, NUMBER_OF_SIMULATIONS); - System.out.println(" done !"); - - printStats(results); - } - - /** - * Creates a {@code MazeSimulation} suitable for statistics. - *

- * Note that there should be only ONE animal of each kind in the - * corresponding {@code Maze}. - * - * @return A {@code MazeSimulation} suitable for statistics - */ - - public static Simulation getMazeSimulation() { - int[][] labyrinth = LabyrinthGenerator.getMedium(); - Maze m = new Maze(labyrinth); - Simulation simulation = new MazeSimulation(m); - - // adds a Mouse - m.addAnimal(new Mouse(m.getStart())); - - // adds a Monkey - m.addAnimal(new Monkey(m.g