Average Density: 0.04
  1 package nl.tudelft.jpacman.npc.ghost;
  2 
  3 import java.util.List;
  4 import java.util.Map;
  5 import java.util.Optional;
  6 
  7 import nl.tudelft.jpacman.board.Direction;
  8 import nl.tudelft.jpacman.board.Square;
  9 import nl.tudelft.jpacman.board.Unit;
 10 import nl.tudelft.jpacman.level.Player;
 11 import nl.tudelft.jpacman.npc.Ghost;
 12 import nl.tudelft.jpacman.sprite.Sprite;
 13 
 14 /**
 15  * <p>
 16  * An implementation of the classic Pac-Man ghost Speedy.
 17  * </p>
 18  * <p>
 19  * Nickname: Pinky. Speedy gets his name for an unusual reason. Speedy appears
 20  * to try to outsmart Pac-Man and crash into Pac-Man from the opposite
 21  * direction. The truth behind this is that when Speedy isn't patrolling the
 22  * top-left corner of the maze, he tries to attack Pac-Man by moving to where he
 23  * is going to be (that is, a few spaces ahead of Pac-Man's current direction)
 24  * instead of right where he is, as Blinky does. It's difficult to use this to
 25  * your advantage, but it's possible. If Pinky is coming at you and you face a
 26  * different direction, even briefly, he may just turn away and attempt to cut
 27  * you off in the new direction while you return to your original direction. In
 28  * the original Japanese version, his name is Machibuse/Pinky.
 29  * </p>
 30  * <p>
 31  * <b>AI:</b> When the ghosts are not patrolling their home corners, Pinky wants
 32  * to go to the place that is four grid spaces ahead of Pac-Man in the direction
 33  * that Pac-Man is facing. If Pac-Man is facing down, Pinky wants to go to the
 34  * location exactly four spaces below Pac-Man. Moving towards this place uses
 35  * the same logic that Blinky uses to find Pac-Man's exact location. Pinky is
 36  * affected by a targeting bug if Pac-Man is facing up - when he moves or faces
 37  * up, Pinky tries moving towards a point up, and left, four spaces.
 38  * </p>
 39  * <p>
 40  * <i>Note: In the original arcade series, the ghosts' genders are unspecified
 41  * and assumed to be male. In 1999, the USA division of Namco and Namco Hometech
 42  * developed the Pac-Man World series and declared Pinky to be female.</i>
 43  * </p>
 44  * <p>
 45  * Source: http://strategywiki.org/wiki/Pac-Man/Getting_Started
 46  * </p>
 47  *
 48  * @author Jeroen Roosen 
 49  *
 50  */
 51 public class Pinky extends Ghost {
 52 
 53     private static final int SQUARES_AHEAD = 4;
 54 
 55     /**
 56      * The variation in intervals, this makes the ghosts look more dynamic and
 57      * less predictable.
 58      */
 59     private static final int INTERVAL_VARIATION = 50;
 60 
 61     /**
 62      * The base movement interval.
 63      */
 64     private static final int MOVE_INTERVAL = 200;
 65 
 66     /**
 67      * Creates a new "Pinky", a.k.a. "Speedy".
 68      *
 69      * @param spriteMap
 70      *            The sprites for this ghost.
 71      */
 72     public Pinky(Map<Direction, Sprite> spriteMap) {
 73         super(spriteMap, MOVE_INTERVAL, INTERVAL_VARIATION);
 74     }
 75 
 76     /**
 77      * {@inheritDoc}
 78      *
 79      * <p>
 80      * When the ghosts are not patrolling their home corners, Pinky wants to go
 81      * to the place that is four grid spaces ahead of Pac-Man in the direction
 82      * that Pac-Man is facing. If Pac-Man is facing down, Pinky wants to go to
 83      * the location exactly four spaces below Pac-Man. Moving towards this place
 84      * uses the same logic that Blinky uses to find Pac-Man's exact location.
 85      * Pinky is affected by a targeting bug if Pac-Man is facing up - when he
 86      * moves or faces up, Pinky tries moving towards a point up, and left, four
 87      * spaces.
 88      * </p>
 89      */
 90     @Override
 91     public Optional<Direction> nextAiMove() {
 92         assert hasSquare();
 93 
 94         Unit player = Navigation.findNearest(Player.class, getSquare());
 95         if (player == null) {
 96             return Optional.empty();
 97         }
 98         assert player.hasSquare();
 99         Square destination = player.squaresAheadOf(SQUARES_AHEAD);
100 
101         List<Direction> path = Navigation.shortestPath(getSquare(), destination, this);
102         if (path != null && !path.isEmpty()) {
103             return Optional.ofNullable(path.get(0));
104         }
105         return Optional.empty();
106     }
107 }