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 }