package ch.epfl.maze.physical.zoo; import ch.epfl.maze.physical.Animal; import ch.epfl.maze.physical.stragegies.picker.RandomPicker; import ch.epfl.maze.physical.stragegies.reducer.BackwardReducer; import ch.epfl.maze.util.Direction; import ch.epfl.maze.util.Trail; import ch.epfl.maze.util.Vector2D; import java.util.EnumSet; import java.util.Set; import java.util.stream.Collectors; /** * Panda A.I. that implements Trémeaux's Algorithm. * * @author EPFL * @author Pacien TRAN-GIRARD */ public class Panda extends Animal implements BackwardReducer, RandomPicker { private final Trail trail; /** * Constructs a panda with a starting position. * * @param position Starting position of the panda in the labyrinth */ public Panda(Vector2D position) { super(position); this.trail = new Trail(); } /** * Reduces the set of Direction choice using Trémeaux's Algorithm and, * if possible, avoiding going backward. * * @param choices A set of possible choices * @return A subset of choices */ @Override public Set reduce(Set choices) { Set bestChoices = this.selectBestDirections(choices); return bestChoices.size() > 1 ? BackwardReducer.super.reduce(bestChoices) : bestChoices; } /** * Moves according to Trémeaux's Algorithm: when the panda * moves, it will mark the ground at most two times (with two different * colors). It will prefer taking the least marked paths. Special cases * have to be handled, especially when the panda is at an intersection. */ @Override public Direction move(Set choices) { Direction choice = this.pick(this.reduce(choices)); if (this.shouldMarkCurrentPosition(choices, choice)) this.markCurrentPosition(choices); return choice; } @Override public Animal copy() { return new Panda(this.getPosition()); } /** * Checks if the current position is an intersection given the possible choices. * * @param choices A set of possible Directions * @return T(The set of choices corresponds to an intersection) */ private boolean onIntersection(Set choices) { return choices.size() > 2; } /** * Checks if the current position is a dead end. * * @param choices A set of possible Directions * @return T(The set of choices and their Marking corresponds to a dead end) */ private boolean inDeadEnd(Set choices) { return choices.size() == 1 && this.allChoicesLeadingTo(choices, Trail.Marking.AVOID_MARKING); } /** * Get the Marking at the adjacent position given the Direction. * * @param dir The Direction from the current position * @return The Marking */ private Trail.Marking getMarkingAtDirection(Direction dir) { Vector2D pos = this.getPosition().addDirectionTo(dir); return this.trail.getMarking(pos); } /** * Determines if the current position should be marked according to the rules of the Trémeaux's Algorithm, * avoiding intersections over-marking. * * @param choices A set of possible Directions * @param choice The selected Direction * @return T(the current position should be marked) */ private boolean shouldMarkCurrentPosition(Set choices, Direction choice) { return !(this.onIntersection(choices) && this.trail.getMarking(this.getPosition()) == Trail.Marking.AVOID_MARKING && this.getMarkingAtDirection(choice) == Trail.Marking.NO_MARKING); } /** * Marks the current position according to the rules of the Trémeaux's Algorithm. * * @param choices A set of possible Direction to take */ private void markCurrentPosition(Set choices) { if (this.inDeadEnd(choices)) this.trail.markPosition(this.getPosition(), Trail.Marking.NO_GO_MARKING); else this.trail.markPosition(this.getPosition()); } /** * Checks if all Direction choices are leading to the given Marking. * * @param choices A set of possible Directions * @param marking The Marking * @return T(all choices are leading to positions with the given Marking) */ private boolean allChoicesLeadingTo(Set choices, Trail.Marking marking) { return choices .stream() .allMatch(dir -> this.getMarkingAtDirection(dir) == marking); } /** * Selects the Direction choices leading to the given Marking. * * @param choices A set of possible Directions * @param marking The Marking * @return A set of choices leading to the given Marking */ private Set selectDirectionsWithMarking(Set choices, Trail.Marking marking) { return choices .stream() .filter(dir -> this.getMarkingAtDirection(dir) == marking) .collect(Collectors.toSet()); } /** * Selects the best choices according to the neighbours Markings. * * @param choices A set of possible Directions * @return A set of smart choices */ private Set selectBestDirections(Set choices) { // special case if (this.onIntersection(choices) && this.allChoicesLeadingTo(choices, Trail.Marking.AVOID_MARKING)) return EnumSet.of(this.getDirection().reverse()); // general case for (Trail.Marking mark : Trail.Marking.ALL) { Set smartChoices = this.selectDirectionsWithMarking(choices, mark); if (!smartChoices.isEmpty()) return smartChoices; } // panda is trapped :( return EnumSet.noneOf(Direction.class); } }