package ch.epfl.maze.physical.zoo;
import ch.epfl.maze.physical.Animal;
import ch.epfl.maze.physical.ProbabilisticAnimal;
import ch.epfl.maze.util.Direction;
import ch.epfl.maze.util.Trail;
import ch.epfl.maze.util.Vector2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* Panda A.I. that implements Trémeaux's Algorithm.
*
* @author Pacien TRAN-GIRARD
*/
public class Panda extends ProbabilisticAnimal {
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();
}
/**
* Checks if the current position is an intersection given the possible choices.
*
* @param choices An array of possible Directions
* @return T(the current position is an intersection)
*/
private boolean isIntersection(Direction[] choices) {
return choices.length > 2;
}
/**
* 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);
}
/**
* Checks if all Direction choices are leading to the given Marking.
*
* @param choices An array of possible Directions
* @param marking The Marking
* @return T(all choices are leading to positions with the given Marking)
*/
private boolean allChoicesLeadingTo(Direction[] choices, Trail.Marking marking) {
return (new ArrayList<>(Arrays.asList(choices)))
.stream()
.allMatch(dir -> this.getMarkingAtDirection(dir) == marking);
}
/**
* Selects the Direction choices leading to the given Marking.
*
* @param choices An array of possible Directions
* @param marking The Marking
* @return An array of choices leading to the given Marking
*/
private Direction[] selectDirectionsWithMarking(Direction[] choices, Trail.Marking marking) {
return (new ArrayList<>(Arrays.asList(choices)))
.stream()
.filter(dir -> this.getMarkingAtDirection(dir) == marking)
.collect(Collectors.toList())
.toArray(new Direction[0]);
}
/**
* Selects the best choices according to the neighbours Markings.
*
* @param choices An array of possible Directions
* @return An array of smart choices
*/
private Direction[] selectBestDirections(Direction[] choices) {
// special case
if (this.isIntersection(choices) && this.allChoicesLeadingTo(choices, Trail.Marking.AVOID_MARKING))
return new Direction[]{this.getCurrentDirection().reverse()};
// general case
for (Trail.Marking mark : Trail.Marking.ALL) {
Direction[] smartChoices = this.selectDirectionsWithMarking(choices, mark);
if (smartChoices.length > 0) return smartChoices;
}
// panda is trapped :(
return new Direction[]{};
}
/**
* Determines if the current position should be marked according to the rules of the Trémeaux's Algorithm,
* avoiding intersections over-marking.
*
* @param choices An array of possible Directions
* @param choice The selected Direction
* @return T(the current position should be marked)
*/
private boolean shouldMarkCurrentPosition(Direction[] choices, Direction choice) {
return !(this.isIntersection(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 An array of possible Direction to take
*/
private void markCurrentPosition(Direction[] choices) {
if (choices.length == 1 && this.allChoicesLeadingTo(choices, Trail.Marking.AVOID_MARKING)) // dead end
this.trail.markPosition(this.getPosition(), Trail.Marking.NO_GO_MARKING);
else
this.trail.markPosition(this.getPosition());
}
/**
* 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(Direction[] choices) {
Direction[] smartChoices = this.selectBestDirections(choices);
Direction choice = super.move(smartChoices);
if (this.shouldMarkCurrentPosition(choices, choice))
this.markCurrentPosition(choices);
return choice;
}
@Override
public Animal copy() {
return new Panda(this.getPosition());
}
}