Added Scatter-strategy
This commit is contained in:
@ -2,6 +2,7 @@ package se.urmo.game.collision;
|
|||||||
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import se.urmo.game.main.GamePanel;
|
||||||
import se.urmo.game.util.Direction;
|
import se.urmo.game.util.Direction;
|
||||||
import se.urmo.game.map.GameMap;
|
import se.urmo.game.map.GameMap;
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ public class CollisionChecker {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies specific rules to movement
|
* Applies specific rules to movement
|
||||||
* This for instance makes sure the tunnel left/right works.
|
* This, for instance, makes sure the tunnel left/right works.
|
||||||
*
|
*
|
||||||
* @param dir
|
* @param dir
|
||||||
* @param pos
|
* @param pos
|
||||||
@ -55,7 +56,7 @@ public class CollisionChecker {
|
|||||||
public Point normalizePosition(Direction dir, Point pos, int agent_width, int agent_height) {
|
public Point normalizePosition(Direction dir, Point pos, int agent_width, int agent_height) {
|
||||||
int x = pos.x;
|
int x = pos.x;
|
||||||
int y = pos.y;
|
int y = pos.y;
|
||||||
int width = map.getWidth();
|
int width = GamePanel.SCREEN_WIDTH;
|
||||||
int height = map.getHeight();
|
int height = map.getHeight();
|
||||||
|
|
||||||
// tunnel
|
// tunnel
|
||||||
|
|||||||
@ -2,10 +2,12 @@ package se.urmo.game.collision;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import se.urmo.game.map.GameMap;
|
import se.urmo.game.map.GameMap;
|
||||||
|
import se.urmo.game.map.MapTile;
|
||||||
import se.urmo.game.util.Direction;
|
import se.urmo.game.util.Direction;
|
||||||
|
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GhostCollisionChecker {
|
public class GhostCollisionChecker {
|
||||||
@ -16,15 +18,38 @@ public class GhostCollisionChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<Direction> calculateDirectionAlternatives(Point position) {
|
public List<Direction> calculateDirectionAlternatives(Point position) {
|
||||||
List<Direction> intersection = map.directionAlternatives(position);
|
List<Direction> intersection = directionAlternatives(position);
|
||||||
log.info("Possible travel directions: {}", intersection);
|
log.info("Possible travel directions: {}", intersection);
|
||||||
return intersection;
|
return intersection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Direction> directionAlternatives(Point position) {
|
||||||
|
int row = (position.y - GameMap.OFFSET_Y) / GameMap.MAP_TILESIZE;
|
||||||
|
int col = (position.x - GameMap.OFFSET_X) / GameMap.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 -> {
|
||||||
|
int r = row + dc.rowOffset;
|
||||||
|
int c = col + dc.colOffset;
|
||||||
|
boolean solid = map.isSolid(r, c);
|
||||||
|
log.debug("[{}][{}] {} is {}", r, c, dc.direction, solid ? "solid" : " not solid");
|
||||||
|
return !solid;
|
||||||
|
})
|
||||||
|
.map(DirectionCheck::direction)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
public Point canMoveTo(Direction dir, Point pos) {
|
public Point canMoveTo(Direction dir, Point pos) {
|
||||||
// -1 is because else we endup in next tile
|
// -1 is because else we endup in next tile
|
||||||
Point pp = new Point(pos.x + dir.dx * (GameMap.MAP_TILESIZE/2 - 1), pos.y + dir.dy * (GameMap.MAP_TILESIZE/2 -1) );
|
Point pp = new Point(pos.x + dir.dx * (GameMap.MAP_TILESIZE/2 - 1), pos.y + dir.dy * (GameMap.MAP_TILESIZE/2 -1) );
|
||||||
|
|
||||||
return ! map.isSolid(pp.x, pp.y) ? pos : null;
|
return ! map.isSolid(pp) ? pos : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
package se.urmo.game.entities;
|
package se.urmo.game.entities;
|
||||||
|
|
||||||
|
import se.urmo.game.map.GameMap;
|
||||||
|
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
|
|
||||||
public class BlinkyStrategy implements GhostStrategy {
|
public class BlinkyStrategy implements GhostStrategy {
|
||||||
@Override
|
@Override
|
||||||
public Point chooseTarget(PacMan pacman) {
|
public Point chooseTarget(PacMan pacman, GameMap map) {
|
||||||
return pacman.getTilePosition();
|
return pacman.getTilePosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,11 @@ import java.awt.Color;
|
|||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Ghost {
|
public class Ghost {
|
||||||
@ -30,22 +34,25 @@ public class Ghost {
|
|||||||
private boolean moving = true;
|
private boolean moving = true;
|
||||||
private int aniTick = 0;
|
private int aniTick = 0;
|
||||||
private int aniIndex = 0;
|
private int aniIndex = 0;
|
||||||
|
private final GhostStrategy scaterStrategy;
|
||||||
|
private GhostStrategy currentStrategy;
|
||||||
private final BufferedImage[] animation;
|
private final BufferedImage[] animation;
|
||||||
private int movementTick = 0;
|
private int movementTick = 0;
|
||||||
private Direction direction;
|
private Direction direction;
|
||||||
private Direction prevDirection;
|
private Direction prevDirection;
|
||||||
private GhostMode mode;
|
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.collisionChecker = collisionChecker;
|
||||||
this.chaseStrategy = strategy;
|
this.chaseStrategy = strategy;
|
||||||
this.currentStrategy = strategy;
|
this.scaterStrategy = scaterStrategy;
|
||||||
this.animation = animation;
|
this.animation = animation;
|
||||||
position = new Point(
|
position = new Point(
|
||||||
13 * GameMap.MAP_TILESIZE + (GameMap.MAP_TILESIZE / 2) + GameMap.OFFSET_X,
|
13 * GameMap.MAP_TILESIZE + (GameMap.MAP_TILESIZE / 2) + GameMap.OFFSET_X,
|
||||||
4 * GameMap.MAP_TILESIZE + (GameMap.MAP_TILESIZE / 2) + GameMap.OFFSET_Y);
|
4 * GameMap.MAP_TILESIZE + (GameMap.MAP_TILESIZE / 2) + GameMap.OFFSET_Y);
|
||||||
|
|
||||||
|
this.currentStrategy = chaseStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(Graphics g) {
|
public void draw(Graphics g) {
|
||||||
@ -62,20 +69,19 @@ public class Ghost {
|
|||||||
COLLISION_BOX_SIZE, null);
|
COLLISION_BOX_SIZE, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(PacMan pacman) {
|
public void update(PacMan pacman, GameMap map) {
|
||||||
updateAnimationTick();
|
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 (movementTick >= GHOST_MOVEMENT_UPDATE_FREQUENCY) {
|
||||||
if (isAlligned(position)) {
|
if (isAlligned(position)) {
|
||||||
log.info("Evaluating possible directions");
|
log.info("Evaluating possible directions");
|
||||||
prevDirection = direction;
|
prevDirection = direction;
|
||||||
direction = chooseDirection(
|
direction = chooseDirection(
|
||||||
collisionChecker.calculateDirectionAlternatives(position),
|
prioritize(collisionChecker.calculateDirectionAlternatives(position)),
|
||||||
currentStrategy.chooseTarget(pacman),
|
currentStrategy.chooseTarget(pacman, map));
|
||||||
prevDirection);
|
|
||||||
log.info("selecting direction {}", direction);
|
log.info("selecting direction {}", direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,17 +99,36 @@ public class Ghost {
|
|||||||
} else movementTick++;
|
} 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) {
|
private boolean isAlligned(Point pos) {
|
||||||
int row = pos.x % GameMap.MAP_TILESIZE;
|
int row = pos.x % GameMap.MAP_TILESIZE;
|
||||||
int col = pos.y % GameMap.MAP_TILESIZE;
|
int col = pos.y % GameMap.MAP_TILESIZE;
|
||||||
return row == GameMap.MAP_TILESIZE / 2 && col == GameMap.MAP_TILESIZE / 2;
|
return row == GameMap.MAP_TILESIZE / 2 && col == GameMap.MAP_TILESIZE / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Direction chooseDirection(List<Direction> options, Point target, Direction prevDirection) {
|
private Direction chooseDirection(Map<Direction, Integer> options, Point target) {
|
||||||
List<Direction> l = options.stream()
|
|
||||||
// remove any option to go back
|
// Find the lowest priority
|
||||||
.filter(d -> !(prevDirection != null && d.equals(prevDirection.opposite())))
|
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();
|
.toList();
|
||||||
|
|
||||||
Direction best = l.getFirst();
|
Direction best = l.getFirst();
|
||||||
double bestDist = Double.MAX_VALUE;
|
double bestDist = Double.MAX_VALUE;
|
||||||
|
|
||||||
@ -137,7 +162,7 @@ public class Ghost {
|
|||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case CHASE -> currentStrategy = chaseStrategy;
|
case CHASE -> currentStrategy = chaseStrategy;
|
||||||
case SCATTER -> currentStrategy = null;
|
case SCATTER -> currentStrategy = scaterStrategy;
|
||||||
case FRIGHTENED -> currentStrategy = null;
|
case FRIGHTENED -> currentStrategy = null;
|
||||||
case EATEN -> currentStrategy = null;
|
case EATEN -> currentStrategy = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
package se.urmo.game.entities;
|
package se.urmo.game.entities;
|
||||||
|
|
||||||
|
import se.urmo.game.map.GameMap;
|
||||||
|
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
|
|
||||||
public interface GhostStrategy {
|
public interface GhostStrategy {
|
||||||
Point chooseTarget(PacMan pacman);
|
Point chooseTarget(PacMan pacman, GameMap map);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,19 +18,20 @@ import java.util.Arrays;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class PacMan {
|
public class PacMan {
|
||||||
public static final int PACMAN_SIZE = 32;
|
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_SIZE = 16;
|
||||||
private static final int COLLISION_BOX_OFFSET = (PACMAN_SIZE - COLLISION_BOX_SIZE) / 2;
|
private static final int COLLISION_BOX_OFFSET = (PACMAN_SIZE - COLLISION_BOX_SIZE) / 2;
|
||||||
private final Game game;
|
private final Game game;
|
||||||
private int aniTick = 0;
|
private int aniTick = 0;
|
||||||
private int aniIndex = 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;
|
private int speed = 1;
|
||||||
@Setter
|
@Setter
|
||||||
private boolean moving;
|
private boolean moving;
|
||||||
private final BufferedImage[][] movmentImages = new BufferedImage[4][4];
|
private final BufferedImage[][] movmentImages = new BufferedImage[4][4];
|
||||||
private Point position;
|
private Point position;
|
||||||
private static final BufferedImage COLLISION_BOX = MiscUtil.createOutlinedBox(COLLISION_BOX_SIZE, COLLISION_BOX_SIZE, Color.yellow, 2);
|
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
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
private Direction direction = Direction.NONE;
|
private Direction direction = Direction.NONE;
|
||||||
@ -65,17 +66,16 @@ public class PacMan {
|
|||||||
.toArray(BufferedImage[]::new);
|
.toArray(BufferedImage[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void draw(Graphics g) {
|
public void draw(Graphics g) {
|
||||||
g.drawImage(
|
g.drawImage(
|
||||||
movmentImages[direction==Direction.NONE ? 0 : direction.ordinal()][aniIndex],
|
movmentImages[direction==Direction.NONE ? 0 : direction.ordinal()][aniIndex],
|
||||||
position.x - PACMAN_SIZE / 2,
|
position.x - PACMAN_OFFSET,
|
||||||
position.y - PACMAN_SIZE / 2,
|
position.y - PACMAN_OFFSET,
|
||||||
PACMAN_SIZE,
|
PACMAN_SIZE,
|
||||||
PACMAN_SIZE, null);
|
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.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.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() {
|
public void update() {
|
||||||
@ -98,7 +98,6 @@ public class PacMan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void updateAnimationTick() {
|
private void updateAnimationTick() {
|
||||||
if (moving) {
|
if (moving) {
|
||||||
aniTick++;
|
aniTick++;
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
package se.urmo.game.entities;
|
package se.urmo.game.entities;
|
||||||
|
|
||||||
|
import se.urmo.game.map.GameMap;
|
||||||
import se.urmo.game.util.Direction;
|
import se.urmo.game.util.Direction;
|
||||||
|
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
|
|
||||||
public class PinkyStrategy implements GhostStrategy{
|
public class PinkyStrategy implements GhostStrategy{
|
||||||
@Override
|
@Override
|
||||||
public Point chooseTarget(PacMan pacman) {
|
public Point chooseTarget(PacMan pacman, GameMap map) {
|
||||||
Direction pacmanDir = pacman.getDirection();
|
Direction pacmanDir = pacman.getDirection();
|
||||||
Point pacmanPos = pacman.getTilePosition();
|
Point pacmanPos = pacman.getTilePosition();
|
||||||
return switch (pacmanDir){
|
return switch (pacmanDir){
|
||||||
case RIGHT -> new Point(pacmanPos.x + 4 * 16, pacmanPos.y);
|
case RIGHT -> new Point(pacmanPos.x + 4 * GameMap.MAP_TILESIZE, pacmanPos.y);
|
||||||
case LEFT -> new Point(pacmanPos.x - 4 * 16, pacmanPos.y);
|
case LEFT -> new Point(pacmanPos.x - 4 * GameMap.MAP_TILESIZE, pacmanPos.y);
|
||||||
case DOWN -> new Point(pacmanPos.x, pacmanPos.y + 4 * 16);
|
case DOWN -> new Point(pacmanPos.x, pacmanPos.y + 4 * GameMap.MAP_TILESIZE);
|
||||||
case UP -> new Point(pacmanPos.x, pacmanPos.y - 4 * 16);
|
case UP -> new Point(pacmanPos.x, pacmanPos.y - 4 * GameMap.MAP_TILESIZE);
|
||||||
case NONE -> pacmanPos;
|
case NONE -> pacmanPos;
|
||||||
default -> throw new IllegalStateException("Illegal direction");
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/main/java/se/urmo/game/entities/ScatterToTopRight.java
Normal file
12
src/main/java/se/urmo/game/entities/ScatterToTopRight.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,6 @@ package se.urmo.game.map;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import se.urmo.game.main.GamePanel;
|
import se.urmo.game.main.GamePanel;
|
||||||
import se.urmo.game.util.Direction;
|
|
||||||
import se.urmo.game.util.LoadSave;
|
import se.urmo.game.util.LoadSave;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@ -12,7 +11,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GameMap {
|
public class GameMap {
|
||||||
@ -127,9 +125,7 @@ public class GameMap {
|
|||||||
public void draw(Graphics g) {
|
public void draw(Graphics g) {
|
||||||
for (int row = 0; row < mapData.length; row++) {
|
for (int row = 0; row < mapData.length; row++) {
|
||||||
for (int col = 0; col < mapData[row].length; col++) {
|
for (int col = 0; col < mapData[row].length; col++) {
|
||||||
MapTile tile = mapData[row][col];
|
BufferedImage tileImage = mapData[row][col].getImage();
|
||||||
|
|
||||||
BufferedImage tileImage = tile.getImage();
|
|
||||||
|
|
||||||
if (tileImage != null) {
|
if (tileImage != null) {
|
||||||
int x = OFFSET_X + col * MAP_TILESIZE;
|
int x = OFFSET_X + col * MAP_TILESIZE;
|
||||||
@ -144,8 +140,8 @@ public class GameMap {
|
|||||||
return list.stream().allMatch(p -> isPassable(p.x, p.y));
|
return list.stream().allMatch(p -> isPassable(p.x, p.y));
|
||||||
}
|
}
|
||||||
public boolean isPassable(int x, int y) {
|
public boolean isPassable(int x, int y) {
|
||||||
int row = (y - OFFSET_Y) / MAP_TILESIZE;
|
int row = getRow(y);
|
||||||
int col = (x - OFFSET_X) / MAP_TILESIZE;
|
int col = getCol(x);
|
||||||
int tileY = (y - OFFSET_Y) % MAP_TILESIZE;
|
int tileY = (y - OFFSET_Y) % MAP_TILESIZE;
|
||||||
int tileX = (x - OFFSET_X) % MAP_TILESIZE;
|
int tileX = (x - OFFSET_X) % MAP_TILESIZE;
|
||||||
log.trace("Point[x="+x+",y="+y+"] is row="+ row + ", col=" + col + " with reminder x=" +tileX+",y=" +tileY);
|
log.trace("Point[x="+x+",y="+y+"] is row="+ row + ", col=" + col + " with reminder x=" +tileX+",y=" +tileY);
|
||||||
@ -156,8 +152,12 @@ public class GameMap {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth() {
|
private static int getCol(int x) {
|
||||||
return GamePanel.SCREEN_WIDTH;
|
return (x - OFFSET_X) / MAP_TILESIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getRow(int y) {
|
||||||
|
return (y - OFFSET_Y) / MAP_TILESIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
@ -165,42 +165,36 @@ public class GameMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void removeTileImage(Point destination) {
|
public void removeTileImage(Point destination) {
|
||||||
int row = (destination.y - OFFSET_Y) / MAP_TILESIZE;
|
int row = getRow(destination);
|
||||||
int col = (destination.x - OFFSET_X) / MAP_TILESIZE;
|
int col = getCol(destination);
|
||||||
MapTile tile = mapData[row][col];
|
MapTile tile = mapData[row][col];
|
||||||
if(tile.getValue() == 0) tile.setImage(null);
|
if(tile.getValue() == 0) tile.setImage(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Direction> directionAlternatives(Point position) {
|
private static int getCol(Point point) {
|
||||||
int row = (position.y - OFFSET_Y) / MAP_TILESIZE;
|
return getCol(point.x);
|
||||||
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 -> {
|
|
||||||
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(int x, int y) {
|
private static int getRow(Point point) {
|
||||||
int row = (y - OFFSET_Y) / MAP_TILESIZE;
|
return getRow(point.y);
|
||||||
int col = (x - OFFSET_X) / MAP_TILESIZE;
|
}
|
||||||
|
|
||||||
|
public boolean isSolid(Point pos) {
|
||||||
|
int row = getRow(pos);
|
||||||
|
int col = getCol(pos);
|
||||||
|
return isSolid(row,col);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isSolid(int row, int col) {
|
||||||
|
if (col >= columns() || col < 0 ) return true;
|
||||||
MapTile mapTile = mapData[row][col];
|
MapTile mapTile = mapData[row][col];
|
||||||
boolean solid = mapTile.isSolid();
|
boolean solid = mapTile.isSolid();
|
||||||
log.debug("[{}][{}] is {} ({})", row, col, solid ? "solid" : " not solid", mapTile.getValue());
|
log.debug("[{}][{}] is {} ({})", row, col, solid ? "solid" : " not solid", mapTile.getValue());
|
||||||
return solid;
|
return solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int columns() {
|
||||||
|
return (GamePanel.SCREEN_WIDTH - 2 * OFFSET_X) / MAP_TILESIZE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package se.urmo.game.state;
|
package se.urmo.game.state;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import se.urmo.game.main.Game;
|
import se.urmo.game.main.Game;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@ -9,6 +10,7 @@ import java.util.Map;
|
|||||||
public class GameStateManager {
|
public class GameStateManager {
|
||||||
private final Game game;
|
private final Game game;
|
||||||
private Map<GameStateType, GameState> states = new HashMap<>();
|
private Map<GameStateType, GameState> states = new HashMap<>();
|
||||||
|
@Getter
|
||||||
private GameState currentState;
|
private GameState currentState;
|
||||||
|
|
||||||
public GameStateManager(Game game) {
|
public GameStateManager(Game game) {
|
||||||
@ -28,8 +30,4 @@ public class GameStateManager {
|
|||||||
public void render(Graphics2D g) {
|
public void render(Graphics2D g) {
|
||||||
currentState.render(g);
|
currentState.render(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameState getCurrentState() {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
package se.urmo.game.state;
|
package se.urmo.game.state;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import se.urmo.game.collision.GhostCollisionChecker;
|
import se.urmo.game.collision.GhostCollisionChecker;
|
||||||
import se.urmo.game.entities.BlinkyStrategy;
|
import se.urmo.game.entities.BlinkyStrategy;
|
||||||
import se.urmo.game.entities.Ghost;
|
import se.urmo.game.entities.Ghost;
|
||||||
import se.urmo.game.entities.GhostMode;
|
import se.urmo.game.entities.GhostMode;
|
||||||
import se.urmo.game.entities.PacMan;
|
import se.urmo.game.entities.PacMan;
|
||||||
import se.urmo.game.entities.PinkyStrategy;
|
import se.urmo.game.entities.PinkyStrategy;
|
||||||
|
import se.urmo.game.entities.ScatterToTopRight;
|
||||||
|
import se.urmo.game.map.GameMap;
|
||||||
import se.urmo.game.util.LoadSave;
|
import se.urmo.game.util.LoadSave;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
@ -14,21 +17,34 @@ import java.awt.image.BufferedImage;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class GhostManager {
|
public class GhostManager {
|
||||||
public static final int SPRITE_SHEET_ROWS = 10;
|
public static final int SPRITE_SHEET_ROWS = 10;
|
||||||
public static final int MAX_SPRITE_FRAMES = 4;
|
public static final int MAX_SPRITE_FRAMES = 4;
|
||||||
@Getter
|
@Getter
|
||||||
private final List<Ghost> ghosts = new ArrayList<>();
|
private final List<Ghost> ghosts = new ArrayList<>();
|
||||||
private BufferedImage[][] image;
|
private BufferedImage[][] image;
|
||||||
private GhostMode globalMode = GhostMode.CHASE;
|
private GhostMode globalMode;
|
||||||
|
|
||||||
|
private long lastModeSwitchTime;
|
||||||
|
private int phaseIndex = 0;
|
||||||
|
|
||||||
|
// cycle in milliseconds: {scatter, chase, scatter, chase, ...}
|
||||||
|
private final int[] cycleDurations = {
|
||||||
|
7000, 20000, // scatter 7s, chase 20s
|
||||||
|
7000, 20000, // scatter 7s, chase 20s
|
||||||
|
5000, 20000, // scatter 5s, chase 20s
|
||||||
|
5000, Integer.MAX_VALUE // scatter 5s, then chase forever
|
||||||
|
};
|
||||||
|
|
||||||
public GhostManager(GhostCollisionChecker ghostCollisionChecker) {
|
public GhostManager(GhostCollisionChecker ghostCollisionChecker) {
|
||||||
loadAnimation();
|
loadAnimation();
|
||||||
// Create ghosts with their strategies
|
// Create ghosts with their strategies
|
||||||
//ghosts.add(new Ghost(ghostCollisionChecker, new BlinkyStrategy(),image[0]));
|
ghosts.add(new Ghost(ghostCollisionChecker, new BlinkyStrategy(),new ScatterToTopLeft(), image[0]));
|
||||||
//ghosts.add(new Ghost(ghostCollisionChecker, new PinkyStrategy(), image[1]));
|
ghosts.add(new Ghost(ghostCollisionChecker, new PinkyStrategy(),new ScatterToTopRight(), image[1]));
|
||||||
//ghosts.add(new Ghost(240, 200, new InkyStrategy(), loader.getSprite("inky")));
|
//ghosts.add(new Ghost(240, 200, new InkyStrategy(), loader.getSprite("inky")));
|
||||||
//ghosts.add(new Ghost(260, 200, new ClydeStrategy(), loader.getSprite("clyde")));
|
//ghosts.add(new Ghost(260, 200, new ClydeStrategy(), loader.getSprite("clyde")));
|
||||||
|
setMode(GhostMode.CHASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAnimation() {
|
private void loadAnimation() {
|
||||||
@ -44,14 +60,28 @@ public class GhostManager {
|
|||||||
|
|
||||||
public void setMode(GhostMode mode) {
|
public void setMode(GhostMode mode) {
|
||||||
this.globalMode = mode;
|
this.globalMode = mode;
|
||||||
|
log.debug("Mode changed to {}", globalMode);
|
||||||
for (Ghost g : ghosts) {
|
for (Ghost g : ghosts) {
|
||||||
g.setMode(mode);
|
g.setMode(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(PacMan pacman) {
|
public void update(PacMan pacman, GameMap map) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (phaseIndex < cycleDurations.length) {
|
||||||
|
int duration = cycleDurations[phaseIndex];
|
||||||
|
if (now - lastModeSwitchTime >= duration) {
|
||||||
|
phaseIndex++;
|
||||||
|
if (phaseIndex < cycleDurations.length) {
|
||||||
|
GhostMode newMode = (phaseIndex % 2 == 0) ? GhostMode.SCATTER : GhostMode.CHASE;
|
||||||
|
setMode(newMode);
|
||||||
|
}
|
||||||
|
lastModeSwitchTime = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (Ghost g : ghosts) {
|
for (Ghost g : ghosts) {
|
||||||
g.update(pacman);
|
g.update(pacman, map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ public class PlayingState implements GameState {
|
|||||||
@Override
|
@Override
|
||||||
public void update() {
|
public void update() {
|
||||||
pacman.update();
|
pacman.update();
|
||||||
ghostManager.update(pacman);
|
ghostManager.update(pacman, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
15
src/main/java/se/urmo/game/state/ScatterToTopLeft.java
Normal file
15
src/main/java/se/urmo/game/state/ScatterToTopLeft.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package se.urmo.game.state;
|
||||||
|
|
||||||
|
import se.urmo.game.entities.GhostStrategy;
|
||||||
|
import se.urmo.game.entities.PacMan;
|
||||||
|
import se.urmo.game.map.GameMap;
|
||||||
|
|
||||||
|
import java.awt.Point;
|
||||||
|
|
||||||
|
public class ScatterToTopLeft implements GhostStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Point chooseTarget(PacMan pacman, GameMap map) {
|
||||||
|
return new Point(1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user