Average Density: 0.04
  1 package nl.tudelft.jpacman.npc.ghost;
  2 
  3 import java.util.EnumMap;
  4 import java.util.List;
  5 import java.util.Map;
  6 import java.util.Optional;
  7 
  8 import nl.tudelft.jpacman.board.Direction;
  9 import nl.tudelft.jpacman.board.Square;
 10 import nl.tudelft.jpacman.board.Unit;
 11 import nl.tudelft.jpacman.level.Player;
 12 import nl.tudelft.jpacman.npc.Ghost;
 13 import nl.tudelft.jpacman.sprite.Sprite;
 14 
 15 /**
 16  * <p>
 17  * An implementation of the classic Pac-Man ghost Clyde.
 18  * </p>
 19  * <p>
 20  * Pokey needs a new nickname because out of all the ghosts,
 21  * Clyde is the least likely to "C'lyde" with Pac-Man. Clyde is always the last
 22  * ghost out of the regenerator, and the loner of the gang, usually off doing
 23  * his own thing when not patrolling the bottom-left corner of the maze. His
 24  * behavior is very random, so while he's not likely to be following you in hot
 25  * pursuit with the other ghosts, he is a little less predictable, and still a
 26  * danger.
 27  * </p>
 28  * <p>
 29  * <b>AI:</b> Clyde has two basic AIs, one for when he's far from Pac-Man, and
 30  * one for when he is near to Pac-Man. 
 31  * When Clyde is far away from Pac-Man (beyond eight grid spaces),
 32  * Clyde behaves very much like Blinky, trying to move to Pac-Man's exact
 33  * location. However, when Clyde gets within eight grid spaces of Pac-Man, he
 34  * automatically changes his behavior and runs away.
 35  * </p>
 36  * <p>
 37  * Source: http://strategywiki.org/wiki/Pac-Man/Getting_Started
 38  * </p>
 39  *
 40  * @author Jeroen Roosen
 41  */
 42 public class Clyde extends Ghost {
 43 
 44     /**
 45      * The amount of cells Clyde wants to stay away from Pac Man.
 46      */
 47     private static final int SHYNESS = 8;
 48 
 49     /**
 50      * The variation in intervals, this makes the ghosts look more dynamic and
 51      * less predictable.
 52      */
 53     private static final int INTERVAL_VARIATION = 50;
 54 
 55     /**
 56      * The base movement interval.
 57      */
 58     private static final int MOVE_INTERVAL = 250;
 59 
 60     /**
 61      * A map of opposite directions.
 62      */
 63     private static final Map<Direction, Direction> OPPOSITES = new EnumMap<>(Direction.class);
 64 
 65     static {
 66         OPPOSITES.put(Direction.NORTH, Direction.SOUTH);
 67         OPPOSITES.put(Direction.SOUTH, Direction.NORTH);
 68         OPPOSITES.put(Direction.WEST, Direction.EAST);
 69         OPPOSITES.put(Direction.EAST, Direction.WEST);
 70     }
 71 
 72     /**
 73      * Creates a new "Clyde", a.k.a. "Pokey".
 74      *
 75      * @param spriteMap The sprites for this ghost.
 76      */
 77     public Clyde(Map<Direction, Sprite> spriteMap) {
 78         super(spriteMap, MOVE_INTERVAL, INTERVAL_VARIATION);
 79     }
 80 
 81     /**
 82      * {@inheritDoc}
 83      *
 84      * <p>
 85      * Clyde has two basic AIs, one for when he's far from Pac-Man, and one for
 86      * when he is near to Pac-Man. 
 87      * When Clyde is far away from Pac-Man (beyond eight grid spaces),
 88      * Clyde behaves very much like Blinky, trying to move to Pac-Man's exact
 89      * location. However, when Clyde gets within eight grid spaces of Pac-Man,
 90      * he automatically changes his behavior and runs away
 91      * </p>
 92      */
 93     @Override
 94     public Optional<Direction> nextAiMove() {
 95         assert hasSquare();
 96 
 97         Unit nearest = Navigation.findNearest(Player.class, getSquare());
 98         if (nearest == null) {
 99             return Optional.empty();
100         }
101         assert nearest.hasSquare();
102         Square target = nearest.getSquare();
103 
104         List<Direction> path = Navigation.shortestPath(getSquare(), target, this);
105         if (path != null && !path.isEmpty()) {
106             Direction direction = path.get(0);
107             if (path.size() <= SHYNESS) {
108                 return Optional.ofNullable(OPPOSITES.get(direction));
109             }
110             return Optional.of(direction);
111         }
112         return Optional.empty();
113     }
114 }