From 655ac88f4e73b2df532a451aedf5a561ea1b0d2c Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Sat, 21 Nov 2015 10:36:18 +0100 Subject: Import project structure --- img/Animal/Bear.png | Bin 0 -> 2783 bytes img/Animal/Hamster.png | Bin 0 -> 3553 bytes img/Animal/Monkey.png | Bin 0 -> 3242 bytes img/Animal/Mouse.png | Bin 0 -> 4565 bytes img/Animal/Panda.png | Bin 0 -> 3339 bytes img/Animal/SpaceInvader.png | Bin 0 -> 5679 bytes img/Predator/Blinky.png | Bin 0 -> 754 bytes img/Predator/Clyde.png | Bin 0 -> 782 bytes img/Predator/Inky.png | Bin 0 -> 761 bytes img/Predator/Pinky.png | Bin 0 -> 789 bytes img/Prey/PacMan.big.png | Bin 0 -> 500 bytes img/Prey/PacMan.png | Bin 0 -> 363 bytes img/tiles/exit.png | Bin 0 -> 226 bytes img/tiles/free.png | Bin 0 -> 221 bytes img/tiles/nothing.png | Bin 0 -> 222 bytes img/tiles/start.png | Bin 0 -> 225 bytes img/tiles/wall.png | Bin 0 -> 222 bytes img/unknown.png | Bin 0 -> 347 bytes labyrinth.txt | 16 + src/ch/epfl/maze/graphics/Animation.java | 245 ++++++++++ src/ch/epfl/maze/graphics/Display.java | 543 +++++++++++++++++++++ src/ch/epfl/maze/graphics/GraphicComponent.java | 237 +++++++++ src/ch/epfl/maze/main/Console.java | 162 ++++++ src/ch/epfl/maze/main/Program.java | 106 ++++ src/ch/epfl/maze/physical/Animal.java | 77 +++ src/ch/epfl/maze/physical/Daedalus.java | 136 ++++++ src/ch/epfl/maze/physical/Maze.java | 79 +++ src/ch/epfl/maze/physical/Predator.java | 59 +++ src/ch/epfl/maze/physical/Prey.java | 55 +++ src/ch/epfl/maze/physical/World.java | 146 ++++++ src/ch/epfl/maze/physical/pacman/Blinky.java | 39 ++ 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 | 32 ++ src/ch/epfl/maze/physical/pacman/Pinky.java | 39 ++ src/ch/epfl/maze/physical/zoo/Bear.java | 46 ++ src/ch/epfl/maze/physical/zoo/Hamster.java | 43 ++ src/ch/epfl/maze/physical/zoo/Monkey.java | 41 ++ src/ch/epfl/maze/physical/zoo/Mouse.java | 41 ++ src/ch/epfl/maze/physical/zoo/Panda.java | 43 ++ src/ch/epfl/maze/physical/zoo/SpaceInvader.java | 52 ++ .../epfl/maze/simulation/DaedalusSimulation.java | 364 ++++++++++++++ src/ch/epfl/maze/simulation/MazeSimulation.java | 211 ++++++++ src/ch/epfl/maze/simulation/Simulation.java | 84 ++++ src/ch/epfl/maze/tests/AnimalTest.java | 100 ++++ src/ch/epfl/maze/tests/Competition.java | 163 +++++++ src/ch/epfl/maze/tests/DaedalusTest.java | 123 +++++ src/ch/epfl/maze/tests/GhostsTest.java | 149 ++++++ src/ch/epfl/maze/tests/MazeTest.java | 91 ++++ src/ch/epfl/maze/tests/WorldTest.java | 330 +++++++++++++ src/ch/epfl/maze/tests/ZooTest.java | 209 ++++++++ src/ch/epfl/maze/util/Action.java | 101 ++++ src/ch/epfl/maze/util/Direction.java | 235 +++++++++ src/ch/epfl/maze/util/LabyrinthGenerator.java | 533 ++++++++++++++++++++ src/ch/epfl/maze/util/Statistics.java | 197 ++++++++ src/ch/epfl/maze/util/Vector2D.java | 228 +++++++++ 56 files changed, 5435 insertions(+) create mode 100644 img/Animal/Bear.png create mode 100644 img/Animal/Hamster.png create mode 100644 img/Animal/Monkey.png create mode 100644 img/Animal/Mouse.png create mode 100644 img/Animal/Panda.png create mode 100644 img/Animal/SpaceInvader.png create mode 100644 img/Predator/Blinky.png create mode 100644 img/Predator/Clyde.png create mode 100644 img/Predator/Inky.png create mode 100644 img/Predator/Pinky.png create mode 100644 img/Prey/PacMan.big.png create mode 100644 img/Prey/PacMan.png create mode 100644 img/tiles/exit.png create mode 100644 img/tiles/free.png create mode 100644 img/tiles/nothing.png create mode 100644 img/tiles/start.png create mode 100644 img/tiles/wall.png create mode 100644 img/unknown.png create mode 100644 labyrinth.txt create mode 100644 src/ch/epfl/maze/graphics/Animation.java create mode 100644 src/ch/epfl/maze/graphics/Display.java create mode 100644 src/ch/epfl/maze/graphics/GraphicComponent.java create mode 100644 src/ch/epfl/maze/main/Console.java create mode 100644 src/ch/epfl/maze/main/Program.java create mode 100644 src/ch/epfl/maze/physical/Animal.java create mode 100644 src/ch/epfl/maze/physical/Daedalus.java create mode 100644 src/ch/epfl/maze/physical/Maze.java create mode 100644 src/ch/epfl/maze/physical/Predator.java create mode 100644 src/ch/epfl/maze/physical/Prey.java create mode 100644 src/ch/epfl/maze/physical/World.java create mode 100644 src/ch/epfl/maze/physical/pacman/Blinky.java create mode 100644 src/ch/epfl/maze/physical/pacman/Clyde.java create mode 100644 src/ch/epfl/maze/physical/pacman/Inky.java create mode 100644 src/ch/epfl/maze/physical/pacman/PacMan.java create mode 100644 src/ch/epfl/maze/physical/pacman/Pinky.java create mode 100644 src/ch/epfl/maze/physical/zoo/Bear.java create mode 100644 src/ch/epfl/maze/physical/zoo/Hamster.java create mode 100644 src/ch/epfl/maze/physical/zoo/Monkey.java create mode 100644 src/ch/epfl/maze/physical/zoo/Mouse.java create mode 100644 src/ch/epfl/maze/physical/zoo/Panda.java create mode 100644 src/ch/epfl/maze/physical/zoo/SpaceInvader.java create mode 100644 src/ch/epfl/maze/simulation/DaedalusSimulation.java create mode 100644 src/ch/epfl/maze/simulation/MazeSimulation.java create mode 100644 src/ch/epfl/maze/simulation/Simulation.java create mode 100644 src/ch/epfl/maze/tests/AnimalTest.java create mode 100644 src/ch/epfl/maze/tests/Competition.java create mode 100644 src/ch/epfl/maze/tests/DaedalusTest.java create mode 100644 src/ch/epfl/maze/tests/GhostsTest.java create mode 100644 src/ch/epfl/maze/tests/MazeTest.java create mode 100644 src/ch/epfl/maze/tests/WorldTest.java create mode 100644 src/ch/epfl/maze/tests/ZooTest.java create mode 100644 src/ch/epfl/maze/util/Action.java create mode 100644 src/ch/epfl/maze/util/Direction.java create mode 100644 src/ch/epfl/maze/util/LabyrinthGenerator.java create mode 100644 src/ch/epfl/maze/util/Statistics.java create mode 100644 src/ch/epfl/maze/util/Vector2D.java diff --git a/img/Animal/Bear.png b/img/Animal/Bear.png new file mode 100644 index 0000000..1ba6ec6 Binary files /dev/null and b/img/Animal/Bear.png differ diff --git a/img/Animal/Hamster.png b/img/Animal/Hamster.png new file mode 100644 index 0000000..3aadd70 Binary files /dev/null and b/img/Animal/Hamster.png differ diff --git a/img/Animal/Monkey.png b/img/Animal/Monkey.png new file mode 100644 index 0000000..bcea339 Binary files /dev/null and b/img/Animal/Monkey.png differ diff --git a/img/Animal/Mouse.png b/img/Animal/Mouse.png new file mode 100644 index 0000000..6b9e95d Binary files /dev/null and b/img/Animal/Mouse.png differ diff --git a/img/Animal/Panda.png b/img/Animal/Panda.png new file mode 100644 index 0000000..199238a Binary files /dev/null and b/img/Animal/Panda.png differ diff --git a/img/Animal/SpaceInvader.png b/img/Animal/SpaceInvader.png new file mode 100644 index 0000000..cd9ebe8 Binary files /dev/null and b/img/Animal/SpaceInvader.png differ diff --git a/img/Predator/Blinky.png b/img/Predator/Blinky.png new file mode 100644 index 0000000..7143fd2 Binary files /dev/null and b/img/Predator/Blinky.png differ diff --git a/img/Predator/Clyde.png b/img/Predator/Clyde.png new file mode 100644 index 0000000..04c7c30 Binary files /dev/null and b/img/Predator/Clyde.png differ diff --git a/img/Predator/Inky.png b/img/Predator/Inky.png new file mode 100644 index 0000000..f6efa4a Binary files /dev/null and b/img/Predator/Inky.png differ diff --git a/img/Predator/Pinky.png b/img/Predator/Pinky.png new file mode 100644 index 0000000..d50102e Binary files /dev/null and b/img/Predator/Pinky.png differ diff --git a/img/Prey/PacMan.big.png b/img/Prey/PacMan.big.png new file mode 100644 index 0000000..c1b0e17 Binary files /dev/null and b/img/Prey/PacMan.big.png differ diff --git a/img/Prey/PacMan.png b/img/Prey/PacMan.png new file mode 100644 index 0000000..d766f25 Binary files /dev/null and b/img/Prey/PacMan.png differ diff --git a/img/tiles/exit.png b/img/tiles/exit.png new file mode 100644 index 0000000..5286a5a Binary files /dev/null and b/img/tiles/exit.png differ diff --git a/img/tiles/free.png b/img/tiles/free.png new file mode 100644 index 0000000..1804c42 Binary files /dev/null and b/img/tiles/free.png differ diff --git a/img/tiles/nothing.png b/img/tiles/nothing.png new file mode 100644 index 0000000..5219d54 Binary files /dev/null and b/img/tiles/nothing.png differ diff --git a/img/tiles/start.png b/img/tiles/start.png new file mode 100644 index 0000000..ff429b6 Binary files /dev/null and b/img/tiles/start.png differ diff --git a/img/tiles/wall.png b/img/tiles/wall.png new file mode 100644 index 0000000..c216e26 Binary files /dev/null and b/img/tiles/wall.png differ diff --git a/img/unknown.png b/img/unknown.png new file mode 100644 index 0000000..97c58b8 Binary files /dev/null and b/img/unknown.png differ diff --git a/labyrinth.txt b/labyrinth.txt new file mode 100644 index 0000000..4b7a42b --- /dev/null +++ b/labyrinth.txt @@ -0,0 +1,16 @@ + +To load this labyrinth, write : + + int[][] labyrinth = LabyrinthGenerator.readFromFile("labyrinth.txt"); + +1 2 1 1 1 1 1 1 1 1 1 +1 0 0 0 0 1 0 0 0 0 1 +1 0 1 1 1 1 1 0 1 0 1 +1 0 1 0 0 0 0 0 1 0 1 +1 0 1 0 1 1 1 0 1 0 1 +1 0 0 0 1 0 1 0 1 1 1 +1 1 1 1 1 0 1 0 1 0 3 +1 0 0 0 0 0 1 0 1 0 1 +1 0 1 1 1 1 1 0 1 0 1 +1 0 0 0 0 0 0 0 0 0 1 +1 1 1 1 1 1 1 1 1 1 1 \ No newline at end of file diff --git a/src/ch/epfl/maze/graphics/Animation.java b/src/ch/epfl/maze/graphics/Animation.java new file mode 100644 index 0000000..0502a92 --- /dev/null +++ b/src/ch/epfl/maze/graphics/Animation.java @@ -0,0 +1,245 @@ +package ch.epfl.maze.graphics; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.ImageObserver; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +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; + } +} diff --git a/src/ch/epfl/maze/graphics/Display.java b/src/ch/epfl/maze/graphics/Display.java new file mode 100644 index 0000000..ea08a06 --- /dev/null +++ b/src/ch/epfl/maze/graphics/Display.java @@ -0,0 +1,543 @@ +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 java.awt.image.BufferStrategy; +import java.awt.image.BufferedImage; +import java.io.File; +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); + } + } + } +} diff --git a/src/ch/epfl/maze/graphics/GraphicComponent.java b/src/ch/epfl/maze/graphics/GraphicComponent.java new file mode 100644 index 0000000..2321714 --- /dev/null +++ b/src/ch/epfl/maze/graphics/GraphicComponent.java @@ -0,0 +1,237 @@ +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; + +/** + * 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; + } +} diff --git a/src/ch/epfl/maze/main/Console.java b/src/ch/epfl/maze/main/Console.java new file mode 100644 index 0000000..edad4b2 --- /dev/null +++ b/src/ch/epfl/maze/main/Console.java @@ -0,0 +1,162 @@ +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.zoo.Hamster; +import ch.epfl.maze.physical.zoo.Monkey; +import ch.epfl.maze.physical.zoo.Mouse; +import ch.epfl.maze.physical.zoo.Panda; +import ch.epfl.maze.simulation.DaedalusSimulation; +import ch.epfl.maze.simulation.MazeSimulation; +import ch.epfl.maze.simulation.Simulation; +import ch.epfl.maze.util.LabyrinthGenerator; +import ch.epfl.maze.util.Statistics; +import ch.epfl.maze.util.Vector2D; + +/** + * 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.getStart())); + + // adds a Hamster + m.addAnimal(new Hamster(m.getStart())); + + // adds a Bear (if this bonus is coded) + // m.addAnimal(new Bear(m.getStart())); + + // adds a Panda + m.addAnimal(new Panda(m.getStart())); + + return simulation; + } + + /** + * Creates a {@code DaedalusSimulation} suitable for statistics. + *

+ * Note that there should be only ONE animal of each kind in the + * corresponding {@code Daedalus}. + * + * @return A {@code DaedalusSimulation} suitable for statistics + */ + + public static Simulation getDaedalusSimulation() { + int[][] labyrinth = LabyrinthGenerator.getPacMan(); + Daedalus d = new Daedalus(labyrinth); + Simulation simulation = new DaedalusSimulation(d); + + // adds Pac-Man + d.addPrey(new PacMan(new Vector2D(9, 15))); + + // adds Blinky + d.addPredator(new Blinky(new Vector2D(17, 1))); + + // adds Pinky + d.addPredator(new Pinky(new Vector2D(1, 1))); + + // adds Inky + d.addPredator(new Inky(new Vector2D(17, 17))); + + // adds Clyde + d.addPredator(new Clyde(new Vector2D(1, 17))); + + return simulation; + } + + /** + * Pretty-prints the statistics computed in the parameters. + * + * @param results + * Statistics of arrival times for every animals/preys + */ + + public static void printStats(Map> results) { + // computes statistics + for (Map.Entry> entry : results.entrySet()) { + String name = entry.getKey(); + List list = entry.getValue(); + if (list.isEmpty()) { + continue; + } + Collections.sort(list); + + String max, min, std, mean, median, total; + // handles infinite values + if (Statistics.total(list) == Integer.MAX_VALUE) { + total = "Infinite"; + mean = "Infinite"; + std = "Infinite"; + max = "Infinite"; + } else { + total = Integer.toString(Statistics.total(list)); + mean = Integer.toString(Statistics.mean(list)); + std = Double.toString(Statistics.std(list)); + max = Integer.toString(list.get(list.size() - 1)); + } + // min and median are special + min = (list.get(0) == Integer.MAX_VALUE) ? + "Infinite" : Integer.toString(list.get(0)); + median = (list.get(list.size() / 2) == Integer.MAX_VALUE) ? + "Infinite" : Integer.toString(list.get(list.size() / 2)); + + System.out.println("\n\n========== " + name + " ==========\n"); + System.out.println(" * total number of steps : " + total); + System.out.println(" * average steps : " + mean); + System.out.println(" * median steps : " + median); + System.out.println(" * standard deviation : " + std); + System.out.println(" * minimum steps : " + min); + System.out.println(" * maximum steps : " + max); + System.out.println("\nDistribution :"); + Statistics.printDistribution(list); + } + } +} diff --git a/src/ch/epfl/maze/main/Program.java b/src/ch/epfl/maze/main/Program.java new file mode 100644 index 0000000..a295ae2 --- /dev/null +++ b/src/ch/epfl/maze/main/Program.java @@ -0,0 +1,106 @@ +package ch.epfl.maze.main; + +import ch.epfl.maze.graphics.Display; +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.zoo.Hamster; +import ch.epfl.maze.physical.zoo.Monkey; +import ch.epfl.maze.physical.zoo.Mouse; +import ch.epfl.maze.physical.zoo.Panda; +import ch.epfl.maze.simulation.DaedalusSimulation; +import ch.epfl.maze.simulation.MazeSimulation; +import ch.epfl.maze.simulation.Simulation; +import ch.epfl.maze.util.LabyrinthGenerator; +import ch.epfl.maze.util.Vector2D; + +/** + * Mini-project main program that will run the simulations on a {@code Display}. + * + */ + +public class Program { + + /** + * Runs one of the two available simulations + * + * @see #getMazeSimulation() + * @see #getDaedalusSimulation() + */ + + public static void main(String[] args) { + Simulation simulation; + + simulation = getMazeSimulation(); + //simulation = getDaedalusSimulation(); + + Display display = new Display(simulation); + display.run(); + } + + /** + * Creates a {@code MazeSimulation} with every animal implementations. + * + * @return A {@code MazeSimulation} to display + */ + + 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.getStart())); + + // adds a Hamster + m.addAnimal(new Hamster(m.getStart())); + + // adds a Bear (if this bonus is coded) + //m.addAnimal(new Bear(m.getStart())); + + // adds a Panda + m.addAnimal(new Panda(m.getStart())); + + return simulation; + } + + /** + * Creates a {@code DaedalusSimulation} with every ghost implementation and + * 3 Pac-Mans. + * + * @return A {@code DaedalusSimulation} to display + */ + + public static Simulation getDaedalusSimulation() { + int[][] labyrinth = LabyrinthGenerator.getPacMan(); + Daedalus d = new Daedalus(labyrinth); + Simulation simulation = new DaedalusSimulation(d); + + // adds Pac-Mans + d.addPrey(new PacMan(new Vector2D(9, 15))); + d.addPrey(new PacMan(new Vector2D(10, 15))); + d.addPrey(new PacMan(new Vector2D(8, 15))); + + // adds Blinky + d.addPredator(new Blinky(new Vector2D(17, 1))); + + // adds Pinky + d.addPredator(new Pinky(new Vector2D(1, 1))); + + // adds Inky + d.addPredator(new Inky(new Vector2D(17, 17))); + + // adds Clyde + d.addPredator(new Clyde(new Vector2D(1, 17))); + + return simulation; + } +} diff --git a/src/ch/epfl/maze/physical/Animal.java b/src/ch/epfl/maze/physical/Animal.java new file mode 100644 index 0000000..8749f61 --- /dev/null +++ b/src/ch/epfl/maze/physical/Animal.java @@ -0,0 +1,77 @@ +package ch.epfl.maze.physical; + +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +/** + * Animal inside a {@code World} that can move depending on the available + * choices it has at its position. + * + */ + +abstract public class Animal { + + /** + * Constructs an animal with a specified position. + * + * @param position + * Position of the animal in the labyrinth + */ + + public Animal(Vector2D position) { + // TODO + } + + /** + * Retrieves the next direction of the animal, by selecting one choice among + * the ones available from its position. + * + * @param choices + * The choices left to the animal at its current position (see + * {@link ch.epfl.maze.physical.World#getChoices(Vector2D) + * World.getChoices(Vector2D)}) + * @return The next direction of the animal, chosen in {@code choices} + */ + + abstract public Direction move(Direction[] choices); + + /** + * Updates the animal position with a direction. + *

+ * Note : Do not call this function in {@code move(Direction[] + * choices)} ! + * + * @param dir + * Direction that the animal has taken + */ + + public final void update(Direction dir) { + // TODO + } + + /** + * Sets new position for Animal. + *

+ * Note : Do not call this function in {@code move(Direction[] + * choices)} ! + * + * @param position + */ + + public final void setPosition(Vector2D position) { + // TODO + } + + /** + * Returns position vector of animal. + * + * @return Current position of animal. + */ + + public final Vector2D getPosition() { + // TODO + return null; + } + + abstract public Animal copy(); +} diff --git a/src/ch/epfl/maze/physical/Daedalus.java b/src/ch/epfl/maze/physical/Daedalus.java new file mode 100644 index 0000000..329ab92 --- /dev/null +++ b/src/ch/epfl/maze/physical/Daedalus.java @@ -0,0 +1,136 @@ +package ch.epfl.maze.physical; + +import java.util.ArrayList; +import java.util.List; + +/** + * Daedalus in which predators hunt preys. Once a prey has been caught by a + * predator, it will be removed from the daedalus. + * + */ + +public final class Daedalus extends World { + + /** + * Constructs a Daedalus with a labyrinth structure + * + * @param labyrinth + * Structure of the labyrinth, an NxM array of tiles + */ + + public Daedalus(int[][] labyrinth) { + super(labyrinth); + // TODO + } + + @Override + public boolean isSolved() { + // TODO + return false; + } + + /** + * Adds a predator to the daedalus. + * + * @param p + * The predator to add + */ + + public void addPredator(Predator p) { + // TODO + } + + /** + * Adds a prey to the daedalus. + * + * @param p + * The prey to add + */ + + public void addPrey(Prey p) { + // TODO + } + + /** + * Removes a predator from the daedalus. + * + * @param p + * The predator to remove + */ + + public void removePredator(Predator p) { + // TODO + } + + /** + * Removes a prey from the daedalus. + * + * @param p + * The prey to remove + */ + + public void removePrey(Prey p) { + // TODO + } + + @Override + public List getAnimals() { + // TODO + return null; + } + + /** + * Returns a copy of the list of all current predators in the daedalus. + * + * @return A list of all predators in the daedalus + */ + + public List getPredators() { + // TODO + return new ArrayList(); + } + + /** + * Returns a copy of the list of all current preys in the daedalus. + * + * @return A list of all preys in the daedalus + */ + + public List getPreys() { + // TODO + return new ArrayList(); + } + + /** + * Determines if the daedalus contains a predator. + * + * @param p + * The predator in question + * @return true if the predator belongs to the world, false + * otherwise. + */ + + public boolean hasPredator(Predator p) { + // TODO + return false; + } + + /** + * Determines if the daedalus contains a prey. + * + * @param p + * The prey in question + * @return true if the prey belongs to the world, false + * otherwise. + */ + + public boolean hasPrey(Prey p) { + // TODO + return false; + } + + @Override + public void reset() { + // TODO + } +} diff --git a/src/ch/epfl/maze/physical/Maze.java b/src/ch/epfl/maze/physical/Maze.java new file mode 100644 index 0000000..2f70996 --- /dev/null +++ b/src/ch/epfl/maze/physical/Maze.java @@ -0,0 +1,79 @@ +package ch.epfl.maze.physical; + +import java.util.ArrayList; +import java.util.List; + +/** + * Maze in which an animal starts from a starting point and must find the exit. + * Every animal added will have its position set to the starting point. The + * animal is removed from the maze when it finds the exit. + * + */ + +public final class Maze extends World { + + /** + * Constructs a Maze with a labyrinth structure. + * + * @param labyrinth + * Structure of the labyrinth, an NxM array of tiles + */ + + public Maze(int[][] labyrinth) { + super(labyrinth); + // TODO + } + + @Override + public boolean isSolved() { + // TODO + return false; + } + + @Override + public List getAnimals() { + // TODO + return new ArrayList(); + } + + /** + * Determines if the maze contains an animal. + * + * @param a + * The animal in question + * @return true if the animal belongs to the world, false + * otherwise. + */ + + public boolean hasAnimal(Animal a) { + // TODO + return false; + } + + /** + * Adds an animal to the maze. + * + * @param a + * The animal to add + */ + + public void addAnimal(Animal a) { + // TODO + } + + /** + * Removes an animal from the maze. + * + * @param a + * The animal to remove + */ + + public void removeAnimal(Animal a) { + // TODO + } + + @Override + public void reset() { + // TODO + } +} diff --git a/src/ch/epfl/maze/physical/Predator.java b/src/ch/epfl/maze/physical/Predator.java new file mode 100644 index 0000000..5aa3be2 --- /dev/null +++ b/src/ch/epfl/maze/physical/Predator.java @@ -0,0 +1,59 @@ +package ch.epfl.maze.physical; + +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +/** + * Predator that kills a prey when they meet with each other in the labyrinth. + * + */ + +abstract public class Predator extends Animal { + + /* constants relative to the Pac-Man game */ + public static final int SCATTER_DURATION = 14; + public static final int CHASE_DURATION = 40; + + /** + * Constructs a predator with a specified position. + * + * @param position + * Position of the predator in the labyrinth + */ + + public Predator(Vector2D position) { + super(position); + // TODO + } + + /** + * Moves according to a random walk, used while not hunting in a + * {@code MazeSimulation}. + * + */ + + @Override + public final Direction move(Direction[] choices) { + // TODO + return Direction.NONE; + } + + /** + * Retrieves the next direction of the animal, by selecting one choice among + * the ones available from its position. + *

+ * In this variation, the animal knows the world entirely. It can therefore + * use the position of other animals in the daedalus to hunt more + * effectively. + * + * @param choices + * The choices left to the animal at its current position (see + * {@link ch.epfl.maze.physical.World#getChoices(Vector2D) + * World.getChoices(Vector2D)}) + * @param daedalus + * The world in which the animal moves + * @return The next direction of the animal, chosen in {@code choices} + */ + + abstract public Direction move(Direction[] choices, Daedalus daedalus); +} diff --git a/src/ch/epfl/maze/physical/Prey.java b/src/ch/epfl/maze/physical/Prey.java new file mode 100644 index 0000000..dc1b3b9 --- /dev/null +++ b/src/ch/epfl/maze/physical/Prey.java @@ -0,0 +1,55 @@ +package ch.epfl.maze.physical; + +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +/** + * Prey that is killed by a predator when they meet each other in the labyrinth. + * + */ + +abstract public class Prey extends Animal { + + /** + * Constructs a prey with a specified position. + * + * @param position + * Position of the prey in the labyrinth + */ + + public Prey(Vector2D position) { + super(position); + // TODO + } + + /** + * Moves according to a random walk, used while not being hunted in a + * {@code MazeSimulation}. + * + */ + + @Override + public final Direction move(Direction[] choices) { + // TODO + return Direction.NONE; + } + + /** + * Retrieves the next direction of the animal, by selecting one choice among + * the ones available from its position. + *

+ * In this variation, the animal knows the world entirely. It can therefore + * use the position of other animals in the daedalus to evade predators more + * effectively. + * + * @param choices + * The choices left to the animal at its current position (see + * {@link ch.epfl.maze.physical.World#getChoices(Vector2D) + * World.getChoices(Vector2D)}) + * @param daedalus + * The world in which the animal moves + * @return The next direction of the animal, chosen in {@code choices} + */ + + abstract public Direction move(Direction[] choices, Daedalus daedalus); +} diff --git a/src/ch/epfl/maze/physical/World.java b/src/ch/epfl/maze/physical/World.java new file mode 100644 index 0000000..34bf334 --- /dev/null +++ b/src/ch/epfl/maze/physical/World.java @@ -0,0 +1,146 @@ +package ch.epfl.maze.physical; + +import java.util.List; + +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +/** + * World that is represented by a labyrinth of tiles in which an {@code Animal} + * can move. + * + */ + +public abstract class World { + + /* tiles constants */ + public static final int FREE = 0; + public static final int WALL = 1; + public static final int START = 2; + public static final int EXIT = 3; + public static final int NOTHING = -1; + + /** + * Constructs a new world with a labyrinth. The labyrinth must be rectangle. + * + * @param labyrinth + * Structure of the labyrinth, an NxM array of tiles + */ + + public World(int[][] labyrinth) { + // TODO + } + + /** + * Determines whether the labyrinth has been solved by every animal. + * + * @return true if no more moves can be made, false otherwise + */ + + abstract public boolean isSolved(); + + /** + * Resets the world as when it was instantiated. + */ + + abstract public void reset(); + + /** + * Returns a copy of the list of all current animals in the world. + * + * @return A list of all animals in the world + */ + + abstract public List getAnimals(); + + /** + * Checks in a safe way the tile number at position (x, y) in the labyrinth. + * + * @param x + * Horizontal coordinate + * @param y + * Vertical coordinate + * @return The tile number at position (x, y), or the NONE tile if x or y is + * incorrect. + */ + + public final int getTile(int x, int y) { + // TODO + return 0; + } + + /** + * Determines if coordinates are free to walk on. + * + * @param x + * Horizontal coordinate + * @param y + * Vertical coordinate + * @return true if an animal can walk on tile, false otherwise + */ + + public final boolean isFree(int x, int y) { + // TODO + return false; + } + + /** + * Computes and returns the available choices for a position in the + * labyrinth. The result will be typically used by {@code Animal} in + * {@link ch.epfl.maze.physical.Animal#move(Direction[]) move(Direction[])} + * + * @param position + * A position in the maze + * @return An array of all available choices at a position + */ + + public final Direction[] getChoices(Vector2D position) { + // TODO + return null; + } + + /** + * Returns horizontal length of labyrinth. + * + * @return The horizontal length of the labyrinth + */ + + public final int getWidth() { + // TODO + return 0; + } + + /** + * Returns vertical length of labyrinth. + * + * @return The vertical length of the labyrinth + */ + + public final int getHeight() { + // TODO + return 0; + } + + /** + * Returns the entrance of the labyrinth at which animals should begin when + * added. + * + * @return Start position of the labyrinth, null if none. + */ + + public final Vector2D getStart() { + // TODO + return null; + } + + /** + * Returns the exit of the labyrinth at which animals should be removed. + * + * @return Exit position of the labyrinth, null if none. + */ + + public final Vector2D getExit() { + // TODO + return null; + } +} diff --git a/src/ch/epfl/maze/physical/pacman/Blinky.java b/src/ch/epfl/maze/physical/pacman/Blinky.java new file mode 100644 index 0000000..22ef16f --- /dev/null +++ b/src/ch/epfl/maze/physical/pacman/Blinky.java @@ -0,0 +1,39 @@ +package ch.epfl.maze.physical.pacman; + +import ch.epfl.maze.physical.Animal; +import ch.epfl.maze.physical.Daedalus; +import ch.epfl.maze.physical.Predator; +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +/** + * Red ghost from the Pac-Man game, chases directly its target. + * + */ + +public class Blinky extends Predator { + + /** + * Constructs a Blinky with a starting position. + * + * @param position + * Starting position of Blinky in the labyrinth + */ + + public Blinky(Vector2D position) { + super(position); + // TODO + } + + @Override + public Direction move(Direction[] choices, Daedalus daedalus) { + // TODO + return Direction.NONE; + } + + @Override + public Animal copy() { + // TODO + return null; + } +} diff --git a/src/ch/epfl/maze/physical/pacman/Clyde.java b/src/ch/epfl/maze/physical/pacman/Clyde.java new file mode 100644 index 0000000..4da35d8 --- /dev/null +++ b/src/ch/epfl/maze/physical/pacman/Clyde.java @@ -0,0 +1,40 @@ +package ch.epfl.maze.physical.pacman; + +import ch.epfl.maze.physical.Animal; +import ch.epfl.maze.physical.Daedalus; +import ch.epfl.maze.physical.Predator; +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +/** + * Orange ghost from the Pac-Man game, alternates between direct chase if far + * from its target and SCATTER if close. + * + */ + +public class Clyde extends Predator { + + /** + * Constructs a Clyde with a starting position. + * + * @param position + * Starting position of Clyde in the labyrinth + */ + + public Clyde(Vector2D position) { + super(position); + // TODO + } + + @Override + public Direction move(Direction[] choices, Daedalus daedalus) { + // TODO + return Direction.NONE; + } + + @Override + public Animal copy() { + // TODO + return null; + } +} diff --git a/src/ch/epfl/maze/physical/pacman/Inky.java b/src/ch/epfl/maze/physical/pacman/Inky.java new file mode 100644 index 0000000..87d9626 --- /dev/null +++ b/src/ch/epfl/maze/physical/pacman/Inky.java @@ -0,0 +1,40 @@ +package ch.epfl.maze.physical.pacman; + +import ch.epfl.maze.physical.Animal; +import ch.epfl.maze.physical.Daedalus; +import ch.epfl.maze.physical.Predator; +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +/** + * Blue ghost from the Pac-Man game, targets the result of two times the vector + * from Blinky to its target. + * + */ + +public class Inky extends Predator { + + /** + * Constructs a Inky with a starting position. + * + * @param position + * Starting position of Inky in the labyrinth + */ + + public Inky(Vector2D position) { + super(position); + // TODO + } + + @Override + public Direction move(Direction[] choices, Daedalus daedalus) { + // TODO + return Direction.NONE; + } + + @Override + public Animal copy() { + // TODO + return null; + } +} diff --git a/src/ch/epfl/maze/physical/pacman/PacMan.java b/src/ch/epfl/maze/physical/pacman/PacMan.java new file mode 100644 index 0000000..0bdd156 --- /dev/null +++ b/src/ch/epfl/maze/physical/pacman/PacMan.java @@ -0,0 +1,32 @@ +package ch.epfl.maze.physical.pacman; + +import ch.epfl.maze.physical.Animal; +import ch.epfl.maze.physical.Daedalus; +import ch.epfl.maze.physical.Prey; +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +/** + * Pac-Man character, from the famous game of the same name. + * + */ + +public class PacMan extends Prey { + + public PacMan(Vector2D position) { + super(position); + // TODO + } + + @Override + public Direction move(Direction[] choices, Daedalus daedalus) { + // TODO + return Direction.NONE; + } + + @Override + public Animal copy() { + // TODO + return null; + } +} diff --git a/src/ch/epfl/maze/physical/pacman/Pinky.java b/src/ch/epfl/maze/physical/pacman/Pinky.java new file mode 100644 index 0000000..9a64a53 --- /dev/null +++ b/src/ch/epfl/maze/physical/pacman/Pinky.java @@ -0,0 +1,39 @@ +package ch.epfl.maze.physical.pacman; + +import ch.epfl.maze.physical.Animal; +import ch.epfl.maze.physical.Daedalus; +import ch.epfl.maze.physical.Predator; +import ch.epfl.maze.util.Direction; +import ch.epfl.maze.util.Vector2D; + +/** + * Pink ghost from the Pac-Man game, targets 4 squares in front of its target. + * + */ + +public class Pinky extends Predator { + + /** + * Constructs a Pinky with a starting position. + * + * @param position + * Starting position of Pinky in the labyrinth + */ + + public Pinky(Vector2D position) { + super(position); + // TODO + } + + @Override + public Direction move(Direction[] choices, Daedalus daed