Average Density: 0.06
  1 package nl.tudelft.jpacman.npc.ghost;
  2 
  3 import nl.tudelft.jpacman.board.Direction;
  4 import nl.tudelft.jpacman.board.Square;
  5 import nl.tudelft.jpacman.board.Unit;
  6 import nl.tudelft.jpacman.level.Player;
  7 import nl.tudelft.jpacman.npc.Ghost;
  8 import nl.tudelft.jpacman.sprite.Sprite;
  9 
 10 import java.util.List;
 11 import java.util.Map;
 12 import java.util.Optional;
 13 
 14 /**
 15  * <p>
 16  * An implementation of the classic Pac-Man ghost Inky.
 17  * </p>
 18  * <b>AI:</b> Inky has the most complicated AI of all. Inky considers two things: Blinky's
 19  * location, and the location two grid spaces ahead of Pac-Man. Inky draws a
 20  * line from Blinky to the spot that is two squares in front of Pac-Man and
 21  * extends that line twice as far. Therefore, if Inky is alongside Blinky
 22  * when they are behind Pac-Man, Inky will usually follow Blinky the whole
 23  * time. But if Inky is in front of Pac-Man when Blinky is far behind him,
 24  * Inky tends to want to move away from Pac-Man (in reality, to a point very
 25  * far ahead of Pac-Man). Inky is affected by a similar targeting bug that
 26  * affects Speedy. When Pac-Man is moving or facing up, the spot Inky uses to
 27  * draw the line is two squares above and left of Pac-Man.
 28  * <p>
 29  * Source: http://strategywiki.org/wiki/Pac-Man/Getting_Started
 30  * </p>
 31  *
 32  * @author Jeroen Roosen
 33  */
 34 public class Inky extends Ghost {
 35 
 36     private static final int SQUARES_AHEAD = 2;
 37 
 38     /**
 39      * The variation in intervals, this makes the ghosts look more dynamic and
 40      * less predictable.
 41      */
 42     private static final int INTERVAL_VARIATION = 50;
 43 
 44     /**
 45      * The base movement interval.
 46      */
 47     private static final int MOVE_INTERVAL = 250;
 48 
 49     /**
 50      * Creates a new "Inky".
 51      *
 52      * @param spriteMap The sprites for this ghost.
 53      */
 54     public Inky(Map<Direction, Sprite> spriteMap) {
 55         super(spriteMap, MOVE_INTERVAL, INTERVAL_VARIATION);
 56     }
 57 
 58     /**
 59      * {@inheritDoc}
 60      *
 61      * <p>
 62      * Inky has the most complicated AI of all. Inky considers two things: Blinky's
 63      * location, and the location two grid spaces ahead of Pac-Man. Inky
 64      * draws a line from Blinky to the spot that is two squares in front of
 65      * Pac-Man and extends that line twice as far. Therefore, if Inky is
 66      * alongside Blinky when they are behind Pac-Man, Inky will usually
 67      * follow Blinky the whole time. But if Inky is in front of Pac-Man when
 68      * Blinky is far behind him, Inky tends to want to move away from Pac-Man
 69      * (in reality, to a point very far ahead of Pac-Man). Inky is affected
 70      * by a similar targeting bug that affects Speedy. When Pac-Man is moving or
 71      * facing up, the spot Inky uses to draw the line is two squares above
 72      * and left of Pac-Man.
 73      * </p>
 74      *
 75      * <p>
 76      * <b>Implementation:</b>
 77      * To actually implement this in jpacman we have the following approximation:
 78      * first determine the square of Blinky (A) and the square 2
 79      * squares away from Pac-Man (B). Then determine the shortest path from A to
 80      * B regardless of terrain and walk that same path from B. This is the
 81      * destination.
 82      * </p>
 83      */
 84     @Override
 85     public Optional<Direction> nextAiMove() {
 86         assert hasSquare();
 87         Unit blinky = Navigation.findNearest(Blinky.class, getSquare());
 88         Unit player = Navigation.findNearest(Player.class, getSquare());
 89 
 90         if (blinky == null || player == null) {
 91             return Optional.empty();
 92         }
 93 
 94         assert player.hasSquare();
 95         Square playerDestination = player.squaresAheadOf(SQUARES_AHEAD);
 96 
 97         List<Direction> firstHalf = Navigation.shortestPath(blinky.getSquare(),
 98             playerDestination, null);
 99 
100         if (firstHalf == null) {
101             return Optional.empty();
102         }
103 
104         Square destination = followPath(firstHalf, playerDestination);
105         List<Direction> path = Navigation.shortestPath(getSquare(),
106             destination, this);
107 
108         if (path != null && !path.isEmpty()) {
109             return Optional.ofNullable(path.get(0));
110         }
111         return Optional.empty();
112     }
113 
114 
115     private Square followPath(List<Direction> directions, Square start) {
116         Square destination = start;
117 
118         for (Direction d : directions) {
119             destination = destination.getSquareAt(d);
120         }
121 
122         return destination;
123     }
124 }