Ghost movement working - first rev

This commit is contained in:
Urban Modig
2025-08-17 15:03:54 +02:00
parent 1bbba216d2
commit 05285529c5
10 changed files with 95 additions and 63 deletions

View File

@ -1,5 +1,6 @@
package se.urmo.game.collision;
import lombok.extern.slf4j.Slf4j;
import se.urmo.game.map.GameMap;
import se.urmo.game.util.Direction;
@ -7,6 +8,7 @@ import java.awt.Point;
import java.util.Collections;
import java.util.List;
@Slf4j
public class GhostCollisionChecker {
private final GameMap map;
@ -32,16 +34,20 @@ public class GhostCollisionChecker {
default -> Collections.EMPTY_LIST;
};
System.out.println( direction + " bounderies for " + position + " are " + bounderies);
log.debug("{} bounderies for {} are {}", direction, position, bounderies);
List<Point> normalizedBoundaries = bounderies.stream()
.map(p -> normalizePosition(direction, p, agent_width, agent_height))
.toList();
if (map.isSolid(normalizedBoundaries)) {
if (! map.isSolid(normalizedBoundaries)) {
log.debug("{} is open", direction);
return normalizePosition(direction, position, agent_width, agent_height);
}{
log.debug("{} is blocked", direction);
return null;
}
return null; // Blocked
// Blocked
}
public Point normalizePosition(Direction dir, Point pos, int agent_width, int agent_height) {
@ -57,9 +63,9 @@ public class GhostCollisionChecker {
return new Point(x, y);
}
public List<Direction> isIntersection(Point position) {
List<Direction> intersection = map.isIntersection(position);
System.out.println("Possible travel directions: " + intersection);
public List<Direction> calculateDirectionAlternatives(Point position) {
List<Direction> intersection = map.directionAlternatives(position);
log.info("Possible travel directions: {}", intersection);
return intersection;
}
}

View File

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

View File

@ -1,5 +1,6 @@
package se.urmo.game.entities;
import lombok.extern.slf4j.Slf4j;
import se.urmo.game.collision.GhostCollisionChecker;
import se.urmo.game.main.Game;
import se.urmo.game.map.GameMap;
@ -13,6 +14,7 @@ import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.List;
@Slf4j
public class Ghost {
private static final int COLLISION_BOX_SIZE = 16;
private static final int GHOST_SPEED = 1;
@ -25,19 +27,20 @@ public class Ghost {
private final Game game;
private final GhostCollisionChecker collisionChecker;
private final GhostStrategy strategy;
private Point position;
private boolean moving = true;
private int aniTick = 0;
private int aniIndex = 0;
private BufferedImage[] animation;
private Direction direction = Direction.LEFT;
private int movementTick = 0;
public Ghost(Game game, GhostCollisionChecker collisionChecker, GhostStrategy strategy) {
this.game = game;
this.collisionChecker = collisionChecker;
this.strategy = strategy;
position = new Point(13 * 16 + 8 + GameMap.OFFSET_X, 4 * 16 + GameMap.OFFSET_Y);
loadAnimation();
}
@ -64,36 +67,25 @@ public class Ghost {
g.drawImage(COLLISION_BOX, position.x, position.y, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, null);
}
public void update() {
public void update(PacMan pacman) {
updateAnimationTick();
updatePosition(pacman);
}
private void updatePosition(PacMan pacman) {
if(movementTick >= GHOST_MOVEMENT_UPDATE_FREQUENCY) {
// if intersection - decide direction
// else if direction isPassible
List<Direction> i = collisionChecker.isIntersection(position);
if(!i.isEmpty()){
// Change direction
if(i.contains(Direction.DOWN)) direction = Direction.DOWN;
else if(i.contains(Direction.RIGHT)) direction = Direction.RIGHT;
else if(i.contains(Direction.UP)) direction = Direction.UP;
else direction = Direction.LEFT;
log.info("Evaluating possible directions");
Direction intendedDirection = chooseDirection(
collisionChecker.calculateDirectionAlternatives(position),
strategy.chooseTarget(pacman, null, null));
}
//Point target = strategy.chooseTarget(pacman, this, blinky);
log.info("selecting direction {}", intendedDirection);
Point newPosition = new Point(
position.x += intendedDirection.dx * GHOST_SPEED,
position.y += intendedDirection.dy * GHOST_SPEED);
Point newPosition = switch (direction){
case RIGHT -> new Point(position.x += GHOST_SPEED, position.y);
case LEFT -> new Point(position.x -= GHOST_SPEED, position.y);
case DOWN -> new Point(position.x, position.y += GHOST_SPEED);
case UP -> new Point(position.x, position.y -= GHOST_SPEED);
default -> throw new IllegalStateException("Illegal direction");
};
Point destination = collisionChecker.getValidDestination(direction, newPosition, GHOST_SIZE, GHOST_SIZE);
// if (position.x + direction.dx * GHOST_SPEED < GameMap.OFFSET_X) direction = direction.opposite();
// if (position.x + GHOST_SIZE + (direction.dx * GHOST_SPEED) > GamePanel.SCREEN_WIDTH - GameMap.OFFSET_X)
// direction = direction.opposite();
Point destination = collisionChecker.getValidDestination(intendedDirection, newPosition, COLLISION_BOX_SIZE, COLLISION_BOX_SIZE);
if(destination != null) {
position = destination;

View File

@ -0,0 +1,7 @@
package se.urmo.game.entities;
import java.awt.Point;
public interface GhostStrategy {
Point chooseTarget(PacMan pacman, Ghost self, Ghost blinky);
}

View File

@ -109,4 +109,8 @@ public class PacMan {
public void setDirection(Direction direction) {
this.direction = direction;
}
public Point getTilePosition() {
return position;
}
}

View File

@ -1,5 +1,6 @@
package se.urmo.game.map;
import lombok.extern.slf4j.Slf4j;
import se.urmo.game.main.GamePanel;
import se.urmo.game.util.Direction;
import se.urmo.game.util.LoadSave;
@ -13,6 +14,7 @@ import java.util.Objects;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@Slf4j
public class GameMap {
public static final int MAP_TILESIZE = 16;// 16px from left
public static final int OFFSET_Y = 7 * MAP_TILESIZE; // 160px from top
@ -169,22 +171,28 @@ public class GameMap {
if(tile.getValue() == 0) tile.setImage(null);
}
public List<Direction> isIntersection(Point position) {
public List<Direction> directionAlternatives(Point position) {
int row = (position.y - OFFSET_Y) / MAP_TILESIZE;
int col = (position.x - OFFSET_X) / MAP_TILESIZE;
record DirectionCheck(int rowOffset, int colOffset, Direction direction) {}
log.debug("At [{}][{}]", row, col);
return Stream.of(
new DirectionCheck(0, 1, Direction.RIGHT),
new DirectionCheck(0, -1, Direction.LEFT),
new DirectionCheck(1, 0, Direction.DOWN),
new DirectionCheck(-1, 0, Direction.UP)
)
.filter(dc -> !mapData[row + dc.rowOffset][col + dc.colOffset].isSolid())
.filter(dc -> {
int r = row + dc.rowOffset;
int c = col + dc.colOffset;
MapTile mapTile = mapData[r][c];
boolean solid = mapTile.isSolid();
log.debug("[{}][{}] {} is {} ({})", r, c, dc.direction, solid ? "solid" : " not solid", mapTile.getValue());
return !solid;
})
.map(DirectionCheck::direction)
.toList();
}
public boolean isSolid(List<Point> points) {
@ -196,7 +204,7 @@ public class GameMap {
int col = (x - OFFSET_X) / MAP_TILESIZE;
MapTile mapTile = mapData[row][col];
boolean solid = mapTile.isSolid();
//System.out.println("["+row+"]["+col+"] is " + (solid?"solid":" not solid") + " (" + mapTile.getValue() + ")");
log.debug("["+row+"]["+col+"] is " + (solid?"solid":" not solid") + " (" + mapTile.getValue() + ")");
return solid;
}
}

View File

@ -1,7 +1,12 @@
package se.urmo.game.map;
import lombok.Getter;
import lombok.Setter;
import java.awt.image.BufferedImage;
@Getter
@Setter
public class MapTile {
private final int value;
private BufferedImage image;
@ -11,14 +16,10 @@ public class MapTile {
public MapTile(BufferedImage image, int value) {
this.value = value;
this.image = image;
this.solid = value != 0;
this.solid = value != 0 && value != 99;
this.collisionMask = value != 0 ? createCollisionMask(image) : null;
}
public boolean[][] getCollisionMask() {
return collisionMask;
}
private boolean[][] createCollisionMask(BufferedImage img) {
if(img == null) return null;
int w = img.getWidth();
@ -33,19 +34,4 @@ public class MapTile {
return mask;
}
public BufferedImage getImage() {
return this.image;
}
public boolean isSolid() {
return this.solid;
}
public void setImage(BufferedImage img) {
this.image = img;
}
public int getValue() {
return this.value;
}
}

View File

@ -1,7 +1,9 @@
package se.urmo.game.state;
import lombok.Getter;
import se.urmo.game.collision.CollisionChecker;
import se.urmo.game.collision.GhostCollisionChecker;
import se.urmo.game.entities.BlinkyStrategy;
import se.urmo.game.entities.Ghost;
import se.urmo.game.entities.PacMan;
import se.urmo.game.main.Game;
@ -16,19 +18,20 @@ public class PlayingState implements GameState {
private final GameStateManager gameStateManager;
private final Ghost ghost;
private PacMan pacman;
@Getter
private GameMap map;
public PlayingState(Game game, GameStateManager gameStateManager) {
this.game = game;
this.gameStateManager = gameStateManager;
this.map = new GameMap();
this.pacman = new PacMan(game, new CollisionChecker(map));
this.ghost = new Ghost(game, new GhostCollisionChecker(map), null);
this.ghost = new Ghost(game, new GhostCollisionChecker(map), new BlinkyStrategy());
}
@Override
public void update() {
pacman.update();
ghost.update();
ghost.update(pacman);
}
@Override
@ -58,8 +61,4 @@ public class PlayingState implements GameState {
pacman.setMoving(false);
pacman.setDirection(Direction.NONE);
}
public GameMap getMap() {
return map;
}
}