diff --git a/src/main/java/se/urmo/game/collision/GhostCollisionChecker.java b/src/main/java/se/urmo/game/collision/GhostCollisionChecker.java index 88d5593..b6be096 100644 --- a/src/main/java/se/urmo/game/collision/GhostCollisionChecker.java +++ b/src/main/java/se/urmo/game/collision/GhostCollisionChecker.java @@ -1,9 +1,10 @@ package se.urmo.game.collision; import lombok.extern.slf4j.Slf4j; -import se.urmo.game.util.MyPoint; +import se.urmo.game.entities.ghost.Ghost; import se.urmo.game.map.GameMap; import se.urmo.game.util.Direction; +import se.urmo.game.util.MyPoint; import java.util.List; @@ -15,19 +16,16 @@ public class GhostCollisionChecker { this.map = map; } - public List calculateDirectionAlternatives(MyPoint position) { - List intersection = map.directionAlternatives((int) position.x, (int) position.y); + public List calculateDirectionAlternatives(Ghost ghost, MyPoint position) { + List intersection = map.directionAlternatives(ghost, (int) position.x, (int) position.y); log.info("Possible travel directions: {}", intersection); return intersection; } - - public MyPoint canMoveTo(Direction dir, double x, double y) { + public MyPoint canMoveTo(Ghost ghost, Direction dir, double x, double y) { // -1 is because else we endup in next tile - //Point pp = new Point((int) (x + dir.dx * (GameMap.MAP_TILESIZE/2 - 1)), (int) (y + dir.dy * (GameMap.MAP_TILESIZE/2 -1))); - - return ! map.isSolidXY( + return !map.isSolidXY(ghost, (int) (x) + dir.dx * (GameMap.MAP_TILESIZE/2 - 1), (int) (y) + dir.dy * (GameMap.MAP_TILESIZE/2 - 1)) ? new MyPoint(x,y) : null; } diff --git a/src/main/java/se/urmo/game/entities/ghost/Ghost.java b/src/main/java/se/urmo/game/entities/ghost/Ghost.java index db59b49..fb5b5af 100644 --- a/src/main/java/se/urmo/game/entities/ghost/Ghost.java +++ b/src/main/java/se/urmo/game/entities/ghost/Ghost.java @@ -58,6 +58,10 @@ public class Ghost extends BaseAnimated { private static final MyPoint startPosition = new MyPoint( GameMap.colToScreen(13) + ((double) GameMap.MAP_TILESIZE / 2), GameMap.rowToScreen(12) + ((double) GameMap.MAP_TILESIZE / 2)); + @Getter + private static final MyPoint houseEntrance = new MyPoint( + GameMap.colToScreen(13) + ((double) GameMap.MAP_TILESIZE / 2), + GameMap.rowToScreen(10) + ((double) GameMap.MAP_TILESIZE / 2)); public Ghost(GhostCollisionChecker collisionChecker, GhostStrategy chaseStrategy, GhostStrategy scaterStrategy, int animation, LevelManager levelManager) { super(ANIMATION_UPDATE_FREQUENCY, GhostManager.MAX_SPRITE_FRAMES); @@ -81,6 +85,9 @@ public class Ghost extends BaseAnimated { (int) position.y - GHOST_SIZE / 2, GHOST_SIZE, GHOST_SIZE, null); +// g.setColor(Color.YELLOW); +// g.fillRect((int) Ghost.startPosition.x, (int) Ghost.startPosition.y, 2, 2); +// g.fillRect((int) Ghost.houseEntrance.x, (int) Ghost.houseEntrance.y, 2, 2); } public void update(PacMan pacman, GameMap map) { @@ -94,6 +101,15 @@ public class Ghost extends BaseAnimated { } } + /** + * Used by a state itself to request a transition to any other state + * This bypasses the priority system since the state itself is requesting the change + */ + public void requestModeChange(GhostMode mode) { + currentState = states.get(mode); + } + + public boolean isFrightened() { return states.get(GhostMode.FRIGHTENED) == currentState; } diff --git a/src/main/java/se/urmo/game/entities/ghost/mode/AbstractGhostModeImpl.java b/src/main/java/se/urmo/game/entities/ghost/mode/AbstractGhostModeImpl.java index 4635c5d..0075bc2 100644 --- a/src/main/java/se/urmo/game/entities/ghost/mode/AbstractGhostModeImpl.java +++ b/src/main/java/se/urmo/game/entities/ghost/mode/AbstractGhostModeImpl.java @@ -13,6 +13,7 @@ import se.urmo.game.util.MyPoint; import java.awt.Point; import java.awt.image.BufferedImage; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -61,7 +62,7 @@ public abstract class AbstractGhostModeImpl implements GhostState { ghost.setDirection(chooseDirection( ghost, prioritizeDirections( - ghost.getCollisionChecker().calculateDirectionAlternatives(getPosition())), + ghost.getCollisionChecker().calculateDirectionAlternatives(ghost, getPosition())), getStrategy().chooseTarget(ghost, pacman, map))); log.debug("Ghost moving to {}", getPosition()); @@ -83,7 +84,7 @@ public abstract class AbstractGhostModeImpl implements GhostState { } private void moveTo(Ghost ghost, MyPoint newPosition) { - MyPoint destination = ghost.getCollisionChecker().canMoveTo( + MyPoint destination = ghost.getCollisionChecker().canMoveTo(ghost, getDirection(), newPosition.x, newPosition.y); if (destination != null) { ghost.setPosition(destination); @@ -113,23 +114,31 @@ public abstract class AbstractGhostModeImpl implements GhostState { .map(Map.Entry::getKey) .toList(); - // Calculate the direction that has the lowest distance to the target - Direction best = directions.getFirst(); - double bestDist = Double.MAX_VALUE; - MyPoint position = getPosition(); - for (Direction d : directions) { - double nx = position.x + d.dx * GameMap.MAP_TILESIZE; - double ny = position.y + d.dy * GameMap.MAP_TILESIZE; - double dist = target.distance(nx, ny); - if (dist < bestDist) { - bestDist = dist; - best = d; - } + // Create a record to hold direction and distance + record DirectionDistance(Direction direction, double distance) { } - log.debug("Ghost coming from {}, choosing {}, from {} (dist={})", - ghost.getPrevDirection(), best, directions, bestDist); + // Stream through directions and find the one with the minimum distance to the target + List dd = directions.stream() + .map(d -> { + double nx = position.x + d.dx * GameMap.MAP_TILESIZE; + double ny = position.y + d.dy * GameMap.MAP_TILESIZE; + double dist = target.distance(nx, ny); + return new DirectionDistance(d, dist); + }) + .toList(); + + log.debug("Target: {}, Position: {}", target, getPosition()); + log.debug("Directions: {}", dd); + + Direction best = dd.stream() + .min(Comparator.comparingDouble(DirectionDistance::distance)) + .map(DirectionDistance::direction) + .orElse(directions.getFirst()); // Fallback to first direction if stream is empty + + log.debug("Ghost coming from {}, choosing {}, from {}", + ghost.getPrevDirection(), best, directions); return best; } diff --git a/src/main/java/se/urmo/game/entities/ghost/mode/EatenGhostMode.java b/src/main/java/se/urmo/game/entities/ghost/mode/EatenGhostMode.java index 33a47a0..974977f 100644 --- a/src/main/java/se/urmo/game/entities/ghost/mode/EatenGhostMode.java +++ b/src/main/java/se/urmo/game/entities/ghost/mode/EatenGhostMode.java @@ -18,25 +18,19 @@ public class EatenGhostMode extends AbstractGhostModeImpl { public EatenGhostMode(Ghost ghost) { super(ghost, new EatenStrategy(), ghost.getLevelManager(), 9); - // Eaten mode uses a specific strategy to return home } @Override public void update(Ghost ghost, PacMan pacman, GameMap map) { - // Check if ghost has reached its starting position - if (getPosition().asPoint().distance(Ghost.getStartPosition().asPoint()) < 10) { + if (getPosition().asPoint().distance(Ghost.getHouseEntrance().asPoint()) < 10) { log.debug("Ghost reached home, returning to chase mode"); - ghost.setMode(GhostMode.CHASE); + ghost.requestModeChange(GhostMode.CHASE); return; } - // Update position using eaten strategy updatePosition(ghost, pacman, map); } -// @Override -// public void enter(Ghost ghost) {} - @Override public BufferedImage[] getAnimation() { return EATEN_ANIMATION; diff --git a/src/main/java/se/urmo/game/entities/ghost/mode/FrightenedGhostMode.java b/src/main/java/se/urmo/game/entities/ghost/mode/FrightenedGhostMode.java index e79989a..a115022 100644 --- a/src/main/java/se/urmo/game/entities/ghost/mode/FrightenedGhostMode.java +++ b/src/main/java/se/urmo/game/entities/ghost/mode/FrightenedGhostMode.java @@ -57,7 +57,7 @@ public class FrightenedGhostMode extends AbstractGhostModeImpl { // Check if frightened mode should end if (frightenedTimer <= 0) { log.debug("Frightened mode ended"); - ghost.setMode(GhostMode.CHASE); + ghost.requestModeChange(GhostMode.CHASE); frightenedTimer = FRIGHTENED_DURATION_TICKS; isBlinking = false; } diff --git a/src/main/java/se/urmo/game/entities/ghost/mode/GhostMode.java b/src/main/java/se/urmo/game/entities/ghost/mode/GhostMode.java index 99ec128..222726f 100644 --- a/src/main/java/se/urmo/game/entities/ghost/mode/GhostMode.java +++ b/src/main/java/se/urmo/game/entities/ghost/mode/GhostMode.java @@ -1,10 +1,12 @@ package se.urmo.game.entities.ghost.mode; public enum GhostMode { - FRIGHTENED, - EATEN, - HOUSE, - FROZEN, - CHASE, - SCATTER + // Highest priority first + EATEN, // 0: Ghost was eaten - highest priority + FRIGHTENED,// 1: Ghost is frightened by power pellet + FROZEN, // 2: Game is paused/frozen + HOUSE, // 3: Ghost is in the house + SCATTER, // 4: Ghost is scattering to corners + CHASE // 5: Ghost is chasing Pacman - lowest priority + } diff --git a/src/main/java/se/urmo/game/entities/ghost/mode/HouseGhostMode.java b/src/main/java/se/urmo/game/entities/ghost/mode/HouseGhostMode.java index cf0a820..55afb2e 100644 --- a/src/main/java/se/urmo/game/entities/ghost/mode/HouseGhostMode.java +++ b/src/main/java/se/urmo/game/entities/ghost/mode/HouseGhostMode.java @@ -6,8 +6,6 @@ import se.urmo.game.entities.ghost.strategy.HouseStrategy; import se.urmo.game.entities.pacman.PacMan; import se.urmo.game.map.GameMap; -import java.awt.Point; - @Slf4j public class HouseGhostMode extends AbstractGhostModeImpl { public HouseGhostMode(Ghost ghost) { @@ -16,10 +14,9 @@ public class HouseGhostMode extends AbstractGhostModeImpl { @Override public void update(Ghost ghost, PacMan pacman, GameMap map) { - if (getPosition().asPoint().distance(new Point(232, 154)) < 1) { + if (getPosition().asPoint().distance(Ghost.getHouseEntrance().asPoint()) < 15) { log.debug("Ghost left home, switching to chase mode"); - ghost.setMode(GhostMode.CHASE); - return; + ghost.requestModeChange(GhostMode.CHASE); } updatePosition(ghost, pacman, map); } diff --git a/src/main/java/se/urmo/game/entities/ghost/mode/ScatterGhostMode.java b/src/main/java/se/urmo/game/entities/ghost/mode/ScatterGhostMode.java index 394597a..0280e78 100644 --- a/src/main/java/se/urmo/game/entities/ghost/mode/ScatterGhostMode.java +++ b/src/main/java/se/urmo/game/entities/ghost/mode/ScatterGhostMode.java @@ -34,7 +34,7 @@ public class ScatterGhostMode extends AbstractGhostModeImpl { scatterTimer--; if (scatterTimer <= 0) { log.debug("Scatter mode timed out, returning to chase"); - ghost.setMode(GhostMode.CHASE); + ghost.requestModeChange(GhostMode.CHASE); scatterTimer = SCATTER_DURATION; } } diff --git a/src/main/java/se/urmo/game/entities/ghost/strategy/EatenStrategy.java b/src/main/java/se/urmo/game/entities/ghost/strategy/EatenStrategy.java index 58ee49c..b7b22fe 100644 --- a/src/main/java/se/urmo/game/entities/ghost/strategy/EatenStrategy.java +++ b/src/main/java/se/urmo/game/entities/ghost/strategy/EatenStrategy.java @@ -9,8 +9,6 @@ import java.awt.Point; public class EatenStrategy implements GhostStrategy { @Override public Point chooseTarget(Ghost ghost, PacMan pacman, GameMap map) { - return new Point( - 13 * GameMap.MAP_TILESIZE + GameMap.OFFSET_X + (GameMap.MAP_TILESIZE / 2), - 4 * GameMap.MAP_TILESIZE + GameMap.OFFSET_Y + (GameMap.MAP_TILESIZE / 2)); + return Ghost.getHouseEntrance().asPoint(); } } diff --git a/src/main/java/se/urmo/game/entities/ghost/strategy/FearStrategy.java b/src/main/java/se/urmo/game/entities/ghost/strategy/FearStrategy.java index 6c1c179..74a7980 100644 --- a/src/main/java/se/urmo/game/entities/ghost/strategy/FearStrategy.java +++ b/src/main/java/se/urmo/game/entities/ghost/strategy/FearStrategy.java @@ -17,7 +17,7 @@ public class FearStrategy implements GhostStrategy { // Frightened ghosts do not target Pacman. // Instead, they pick a random adjacent valid tile. Point ghostPos = ghost.getPosition().asPoint(); - List neighbors = map.directionAlternatives(ghostPos.x, ghostPos.y); + List neighbors = map.directionAlternatives(ghost, ghostPos.x, ghostPos.y); if (neighbors.isEmpty()) { return ghost.getPosition().asPoint(); // stuck diff --git a/src/main/java/se/urmo/game/entities/ghost/strategy/HouseStrategy.java b/src/main/java/se/urmo/game/entities/ghost/strategy/HouseStrategy.java index c0c2bcc..daae2c6 100644 --- a/src/main/java/se/urmo/game/entities/ghost/strategy/HouseStrategy.java +++ b/src/main/java/se/urmo/game/entities/ghost/strategy/HouseStrategy.java @@ -9,6 +9,6 @@ import java.awt.Point; public class HouseStrategy implements GhostStrategy { @Override public Point chooseTarget(Ghost ghost, PacMan pacman, GameMap map) { - return new Point(232, 154); + return new Point(232, 280); } } diff --git a/src/main/java/se/urmo/game/map/GameMap.java b/src/main/java/se/urmo/game/map/GameMap.java index 1b1c5ff..11277c0 100644 --- a/src/main/java/se/urmo/game/map/GameMap.java +++ b/src/main/java/se/urmo/game/map/GameMap.java @@ -1,6 +1,8 @@ package se.urmo.game.map; import lombok.extern.slf4j.Slf4j; +import se.urmo.game.entities.BaseAnimated; +import se.urmo.game.entities.ghost.Ghost; import se.urmo.game.util.Direction; import java.awt.Graphics; @@ -114,18 +116,35 @@ public class GameMap { return b; } - public boolean isSolidXY(int screenX, int screenY) { - return isSolid(screenToRow(screenY), screenToCol(screenX)); + public boolean isSolidXY(Ghost ghost, int screenX, int screenY) { + return isSolid(ghost, screenToRow(screenY), screenToCol(screenX)); } - public boolean isSolid(int row, int col) { + public boolean isSolid(BaseAnimated entity, int row, int col) { + // Check for out of bounds if (col >= columns() || col < 0) return true; if (row >= rows() || row < 0) return true; + + // Get the tile information MapTile mapTile = mapData[row][col]; boolean solid = mapTile.isSolid(); + + // Special case: If the entity is a Ghost, it can pass through certain solid tiles + // This allows ghosts to move through areas that would be solid for other entities + if (entity instanceof Ghost) { + // For Ghost entities, check if this is a ghost-passable tile + // You might want to refine this logic based on specific tile types or ghost states + + // Example: Allow ghosts to pass through ghost house door + if (mapTile.getTileType() == TileType.DOOR) { + return false; // Not solid for ghosts + } + } + //log.debug("[{}][{}] {}", row, col, mapTile.getTileType()); return solid; + } public boolean removeTileImage(Point screen) { @@ -220,13 +239,13 @@ public class GameMap { } - public List directionAlternatives(int screenX, int screenY) { + public List directionAlternatives(BaseAnimated entity, int screenX, int screenY) { int row = (screenY - GameMap.OFFSET_Y) / GameMap.MAP_TILESIZE; int col = (screenX - GameMap.OFFSET_X) / GameMap.MAP_TILESIZE; record DirectionCheck(int rowOffset, int colOffset, Direction direction) { } - log.debug("At [{}][{}]", row, col); + log.debug("At ({},{}), [{}][{}]", screenX, screenY, row, col); return Stream.of( new DirectionCheck(0, 1, Direction.RIGHT), new DirectionCheck(0, -1, Direction.LEFT), @@ -236,7 +255,7 @@ public class GameMap { .filter(dc -> { int r = row + dc.rowOffset; int c = col + dc.colOffset; - boolean solid = isSolid(r, c); + boolean solid = isSolid(entity, r, c); log.debug("[{}][{}] {} is {}", r, c, dc.direction, solid ? "solid" : " not solid"); return !solid; }) diff --git a/src/main/java/se/urmo/game/map/TileType.java b/src/main/java/se/urmo/game/map/TileType.java index 96aaa09..d7202f8 100644 --- a/src/main/java/se/urmo/game/map/TileType.java +++ b/src/main/java/se/urmo/game/map/TileType.java @@ -43,7 +43,7 @@ public enum TileType { TILE_35(35, true, SpriteLocation.MAP, 3, 1, false ,0), TILE_36(36, true, SpriteLocation.MAP, 3, 2, false ,0), TILE_37(37, true, SpriteLocation.MAP, 3, 3, false ,0), - TILE_38(38, true, SpriteLocation.MAP, 3, 4, false ,0), + DOOR(38, true, SpriteLocation.MAP, 3, 4, false, 0), TILE_39(39, true, SpriteLocation.MAP, 3, 5, false ,0), TILE_40(40, true, SpriteLocation.MAP, 3, 6, false ,0), TILE_41(41, true, SpriteLocation.MAP, 3, 7, false ,0),