package ch.epfl.maze.physical; import ch.epfl.maze.util.Direction; import ch.epfl.maze.util.Vector2D; import java.util.ArrayList; import java.util.List; /** * Predator ghost that have two different modes and a home position in the labyrinth. * * @author Pacien TRAN-GIRARD */ abstract public class GhostPredator extends Predator { public enum Mode { CHASE(40), SCATTER(14); public static final Mode DEFAULT = CHASE; public final int duration; /** * Constructs a new Mode with the given duration. * * @param duration The duration in cycles */ Mode(int duration) { this.duration = duration; } /** * Returns the next Mode. * * @return The next Mode */ public Mode getNext() { switch (this) { case CHASE: return SCATTER; case SCATTER: return CHASE; default: return DEFAULT; } } } private static Prey commonPrey; private final Vector2D homePosition; private Mode mode; private int modeCycle; /** * Constructs a predator with a specified position. * * @param position Position of the predator in the labyrinth */ public GhostPredator(Vector2D position) { super(position); this.homePosition = position; this.mode = Mode.DEFAULT; this.modeCycle = 0; } /** * Selects a new random Prey to chase in the Daedalus. * * @param daedalus The Daedalus * @return The Chosen One */ private static Prey selectRandomPrey(Daedalus daedalus) { if (daedalus.getPreys().isEmpty()) return null; int randomPreyIndex = GhostPredator.RANDOM_SOURCE.nextInt(daedalus.getPreys().size()); return daedalus.getPreys().get(randomPreyIndex); } /** * Returns the commonly targeted Prey. * * @param daedalus The Daedalus * @return The common Prey */ private static Prey getPrey(Daedalus daedalus) { if (GhostPredator.commonPrey == null || !daedalus.hasPrey(GhostPredator.commonPrey)) GhostPredator.commonPrey = GhostPredator.selectRandomPrey(daedalus); return GhostPredator.commonPrey; } /** * Returns the commonly targeted Prey's position. * * @param daedalus The Daedalus * @return The position of the Prey */ protected final Vector2D getPreyPosition(Daedalus daedalus) { Prey prey = GhostPredator.getPrey(daedalus); if (prey == null) return this.homePosition; return prey.getPosition(); } /** * Returns the commonly targeted Prey's Direction. * * @param daedalus The Daedalus * @return The Direction the Prey is facing */ protected final Direction getPreyDirection(Daedalus daedalus) { Prey prey = GhostPredator.getPrey(daedalus); if (prey == null) return Direction.NONE; return prey.getCurrentDirection(); } /** * Calculates the Euclidean distance from the adjacent position at the given Direction to the target position. * * @param dir The adjacent Direction * @param targetPosition The targeted position * @return The Euclidean distance between the two positions */ private double calcDistanceFromAdjacentDirectionTo(Direction dir, Vector2D targetPosition) { return this .getPosition() .addDirectionTo(dir) .sub(targetPosition) .dist(); } /** * Selects the best Direction in the given choices by minimizing the Euclidean distance to the targeted position. * * @param targetPosition The targeted position * @param choices An array of Direction choices * @return An array of optimal Direction choices */ private Direction[] selectBestPaths(Vector2D targetPosition, Direction[] choices) { List bestPaths = new ArrayList<>(); double minDist = Double.MAX_VALUE; for (Direction dir : choices) { double dist = this.calcDistanceFromAdjacentDirectionTo(dir, targetPosition); if (dist < minDist) { minDist = dist; bestPaths.clear(); } if (dist <= minDist) bestPaths.add(dir); } return bestPaths.toArray(new Direction[bestPaths.size()]); } /** * Rotates to the next Mode. */ protected void rotateMode() { this.mode = this.mode.getNext(); this.modeCycle = 0; } /** * Increments the cycle counter and rotates to the next Mode if the Mode's duration has been reached. */ private void countCycle() { this.modeCycle += 1; if (this.modeCycle >= this.mode.duration) this.rotateMode(); } @Override public Direction move(Direction[] choices, Daedalus daedalus) { this.countCycle(); Direction[] smartChoices = choices.length > 1 ? this.excludeOrigin(choices) : choices; Direction[] bestPaths = this.selectBestPaths(this.getTargetPosition(daedalus), smartChoices); return this.move(bestPaths); } /** * Returns the current Mode. * * @return The current Mode */ protected Mode getMode(Daedalus daedalus) { return this.mode; } /** * Returns the position to target according to the current Mode. * * @param daedalus The Daedalus * @return The position to target */ protected Vector2D getTargetPosition(Daedalus daedalus) { switch (this.getMode(daedalus)) { case CHASE: return this.getPreyTargetPosition(daedalus); case SCATTER: return this.homePosition; default: return this.homePosition; } } /** * Returns the Prey's projected targeted position. * * @param daedalus The Daedalus * @return The projected position */ protected abstract Vector2D getPreyTargetPosition(Daedalus daedalus); }