package ch.epfl.maze.physical.pacman; import ch.epfl.maze.physical.Daedalus; import ch.epfl.maze.physical.Predator; import ch.epfl.maze.physical.Prey; import ch.epfl.maze.physical.stragegies.picker.RandomPicker; import ch.epfl.maze.physical.stragegies.reducer.BackwardReducer; import ch.epfl.maze.physical.stragegies.reducer.CostReducer; import ch.epfl.maze.util.Direction; import ch.epfl.maze.util.Vector2D; import java.util.EnumSet; import java.util.Set; /** * Predator ghost that have two different modes and a home position in the labyrinth. * * @author Pacien TRAN-GIRARD */ abstract public class Ghost extends Predator implements BackwardReducer, CostReducer, RandomPicker { 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 Ghost(Vector2D position) { super(position); this.homePosition = position; this.mode = Mode.DEFAULT; this.modeCycle = 0; } /** * Returns the cost to reach the target by choosing the given Direction by calculating the Euclidean distance. * * @param choice The Direction choice * @param daedalus The Daedalus * @return The Euclidean distance cost */ @Override public int getChoiceCost(Direction choice, Daedalus daedalus) { Vector2D target = this.getTargetPosition(daedalus); return (int) (this.getDistanceTo(choice, target) * 100); } @Override public Set reduce(Set choices) { return EnumSet.noneOf(Direction.class); } /** * Selects the best Direction in the given choices by minimizing the Euclidean distance to the targeted position * after excluding the provenance if possible. * * @param choices A set of Direction choices * @param daedalus The Daedalus * @return A set of optimal Direction choices */ @Override public Set reduce(Set choices, Daedalus daedalus) { Set forwardChoices = choices.size() > 1 ? BackwardReducer.super.reduce(choices) : choices; return CostReducer.super.reduce(forwardChoices, daedalus); } @Override public Direction move(Set choices, Daedalus daedalus) { this.countCycle(); Set bestChoices = this.reduce(choices, daedalus); return this.pick(bestChoices); } /** * Returns the Prey's projected targeted position. * * @param daedalus The Daedalus * @return The projected position */ abstract protected Vector2D getPreyTargetPosition(Daedalus daedalus); /** * Returns the current Mode. * * @param daedalus The Daedalus * @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; } } /** * Selects a new random Prey to chase in the Daedalus. * * @param daedalus The Daedalus * @return The Chosen One */ private static Prey selectAnyPrey(Daedalus daedalus) { if (daedalus.getPreySet().isEmpty()) return null; return daedalus.getPreySet().stream().findAny().get(); } /** * Sets a random Prey as the common Pre. * * @param daedalus The Daedalus */ private static synchronized void setAnyPrey(Daedalus daedalus) { Ghost.commonPrey = Ghost.selectAnyPrey(daedalus); } /** * Returns the commonly targeted Prey. * * @param daedalus The Daedalus * @return The common Prey */ private static Prey getPrey(Daedalus daedalus) { if (Ghost.commonPrey == null || !daedalus.hasPrey(Ghost.commonPrey)) Ghost.setAnyPrey(daedalus); return Ghost.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 = Ghost.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 = Ghost.getPrey(daedalus); if (prey == null) return Direction.NONE; return prey.getDirection(); } /** * 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 getDistanceTo(Direction dir, Vector2D targetPosition) { return this .getPosition() .addDirectionTo(dir) .sub(targetPosition) .dist(); } /** * 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(); } }