Added Scatter-strategy

This commit is contained in:
Urban Modig
2025-08-19 21:17:36 +02:00
parent 64dcba2584
commit 9f316e5b43
13 changed files with 183 additions and 80 deletions

View File

@ -1,10 +1,12 @@
package se.urmo.game.entities;
import se.urmo.game.map.GameMap;
import java.awt.Point;
public class BlinkyStrategy implements GhostStrategy {
@Override
public Point chooseTarget(PacMan pacman) {
public Point chooseTarget(PacMan pacman, GameMap map) {
return pacman.getTilePosition();
}
}

View File

@ -11,7 +11,11 @@ import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
public class Ghost {
@ -30,22 +34,25 @@ public class Ghost {
private boolean moving = true;
private int aniTick = 0;
private int aniIndex = 0;
private final GhostStrategy scaterStrategy;
private GhostStrategy currentStrategy;
private final BufferedImage[] animation;
private int movementTick = 0;
private Direction direction;
private Direction prevDirection;
private GhostMode mode;
private GhostStrategy currentStrategy;
public Ghost(GhostCollisionChecker collisionChecker, GhostStrategy strategy, BufferedImage[] animation) {
public Ghost(GhostCollisionChecker collisionChecker, GhostStrategy strategy, GhostStrategy scaterStrategy, BufferedImage[] animation) {
this.collisionChecker = collisionChecker;
this.chaseStrategy = strategy;
this.currentStrategy = strategy;
this.scaterStrategy = scaterStrategy;
this.animation = animation;
position = new Point(
13 * GameMap.MAP_TILESIZE + (GameMap.MAP_TILESIZE / 2) + GameMap.OFFSET_X,
4 * GameMap.MAP_TILESIZE + (GameMap.MAP_TILESIZE / 2) + GameMap.OFFSET_Y);
this.currentStrategy = chaseStrategy;
}
public void draw(Graphics g) {
@ -62,20 +69,19 @@ public class Ghost {
COLLISION_BOX_SIZE, null);
}
public void update(PacMan pacman) {
public void update(PacMan pacman, GameMap map) {
updateAnimationTick();
updatePosition(pacman);
updatePosition(pacman, map);
}
private void updatePosition(PacMan pacman) {
private void updatePosition(PacMan pacman, GameMap map) {
if (movementTick >= GHOST_MOVEMENT_UPDATE_FREQUENCY) {
if (isAlligned(position)) {
log.info("Evaluating possible directions");
prevDirection = direction;
direction = chooseDirection(
collisionChecker.calculateDirectionAlternatives(position),
currentStrategy.chooseTarget(pacman),
prevDirection);
prioritize(collisionChecker.calculateDirectionAlternatives(position)),
currentStrategy.chooseTarget(pacman, map));
log.info("selecting direction {}", direction);
}
@ -93,17 +99,36 @@ public class Ghost {
} else movementTick++;
}
private Map<Direction, Integer> prioritize(List<Direction> directions) {
return directions.stream()
.filter(d -> d != Direction.NONE)
.collect(Collectors.toMap(
d -> d,
d -> (prevDirection != null && d == prevDirection.opposite()) ? 2 : 1
));
}
private boolean isAlligned(Point pos) {
int row = pos.x % GameMap.MAP_TILESIZE;
int col = pos.y % GameMap.MAP_TILESIZE;
return row == GameMap.MAP_TILESIZE / 2 && col == GameMap.MAP_TILESIZE / 2;
}
private Direction chooseDirection(List<Direction> options, Point target, Direction prevDirection) {
List<Direction> l = options.stream()
// remove any option to go back
.filter(d -> !(prevDirection != null && d.equals(prevDirection.opposite())))
private Direction chooseDirection(Map<Direction, Integer> options, Point target) {
// Find the lowest priority
int lowestPriority = options.values().stream()
.mapToInt(Integer::intValue)
.min()
.orElse(Integer.MAX_VALUE);
// Return all directions that have this priority
List<Direction> l = options.entrySet().stream()
.filter(entry -> entry.getValue() == lowestPriority)
.map(Map.Entry::getKey)
.toList();
Direction best = l.getFirst();
double bestDist = Double.MAX_VALUE;
@ -137,7 +162,7 @@ public class Ghost {
this.mode = mode;
switch (mode) {
case CHASE -> currentStrategy = chaseStrategy;
case SCATTER -> currentStrategy = null;
case SCATTER -> currentStrategy = scaterStrategy;
case FRIGHTENED -> currentStrategy = null;
case EATEN -> currentStrategy = null;
}

View File

@ -1,7 +1,9 @@
package se.urmo.game.entities;
import se.urmo.game.map.GameMap;
import java.awt.Point;
public interface GhostStrategy {
Point chooseTarget(PacMan pacman);
Point chooseTarget(PacMan pacman, GameMap map);
}

View File

@ -18,19 +18,20 @@ import java.util.Arrays;
@Slf4j
public class PacMan {
public static final int PACMAN_SIZE = 32;
public static final int PACMAN_OFFSET = PACMAN_SIZE / 2;
private static final int COLLISION_BOX_SIZE = 16;
private static final int COLLISION_BOX_OFFSET = (PACMAN_SIZE - COLLISION_BOX_SIZE) / 2;
private final Game game;
private int aniTick = 0;
private int aniIndex = 0;
private static final int ANIMATION_UPDATE_FREQUENCY = 25;
private static final int ANIMATION_UPDATE_FREQUENCY = 10;
private int speed = 1;
@Setter
private boolean moving;
private final BufferedImage[][] movmentImages = new BufferedImage[4][4];
private Point position;
private static final BufferedImage COLLISION_BOX = MiscUtil.createOutlinedBox(COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, Color.yellow, 2);
private CollisionChecker collisionChecker;
private final CollisionChecker collisionChecker;
@Setter
@Getter
private Direction direction = Direction.NONE;
@ -65,17 +66,16 @@ public class PacMan {
.toArray(BufferedImage[]::new);
}
public void draw(Graphics g) {
g.drawImage(
movmentImages[direction==Direction.NONE?0:direction.ordinal()][aniIndex],
position.x - PACMAN_SIZE / 2,
position.y - PACMAN_SIZE / 2,
movmentImages[direction==Direction.NONE ? 0 : direction.ordinal()][aniIndex],
position.x - PACMAN_OFFSET,
position.y - PACMAN_OFFSET,
PACMAN_SIZE,
PACMAN_SIZE, null);
g.drawImage(COLLISION_BOX, position.x - COLLISION_BOX_OFFSET, position.y - COLLISION_BOX_OFFSET, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, null);
g.setColor(Color.BLUE);
g.fillRect(position.x-1, position.y-1, 3, 3);
//g.fillRect(position.x-1, position.y-1, 3, 3);
}
public void update() {
@ -98,7 +98,6 @@ public class PacMan {
}
}
private void updateAnimationTick() {
if (moving) {
aniTick++;

View File

@ -1,21 +1,21 @@
package se.urmo.game.entities;
import se.urmo.game.map.GameMap;
import se.urmo.game.util.Direction;
import java.awt.Point;
public class PinkyStrategy implements GhostStrategy{
@Override
public Point chooseTarget(PacMan pacman) {
public Point chooseTarget(PacMan pacman, GameMap map) {
Direction pacmanDir = pacman.getDirection();
Point pacmanPos = pacman.getTilePosition();
return switch (pacmanDir){
case RIGHT -> new Point(pacmanPos.x + 4 * 16, pacmanPos.y);
case LEFT -> new Point(pacmanPos.x - 4 * 16, pacmanPos.y);
case DOWN -> new Point(pacmanPos.x, pacmanPos.y + 4 * 16);
case UP -> new Point(pacmanPos.x, pacmanPos.y - 4 * 16);
case RIGHT -> new Point(pacmanPos.x + 4 * GameMap.MAP_TILESIZE, pacmanPos.y);
case LEFT -> new Point(pacmanPos.x - 4 * GameMap.MAP_TILESIZE, pacmanPos.y);
case DOWN -> new Point(pacmanPos.x, pacmanPos.y + 4 * GameMap.MAP_TILESIZE);
case UP -> new Point(pacmanPos.x, pacmanPos.y - 4 * GameMap.MAP_TILESIZE);
case NONE -> pacmanPos;
default -> throw new IllegalStateException("Illegal direction");
};
}
}

View File

@ -0,0 +1,12 @@
package se.urmo.game.entities;
import se.urmo.game.map.GameMap;
import java.awt.Point;
public class ScatterToTopRight implements GhostStrategy{
@Override
public Point chooseTarget(PacMan pacman, GameMap map) {
return new Point((map.columns() -1) * GameMap.MAP_TILESIZE, 0);
}
}