From fcd686fad0889b6644e949c31607dc4b63080ddf Mon Sep 17 00:00:00 2001 From: Urban Modig Date: Mon, 1 Sep 2025 20:36:54 +0200 Subject: [PATCH] Added visuals to RoundPhase --- .../se/urmo/game/entities/ghost/Ghost.java | 4 + .../se/urmo/game/entities/pacman/PacMan.java | 5 + src/main/java/se/urmo/game/map/GameMap.java | 2 +- .../se/urmo/game/state/GameStateManager.java | 2 +- .../se/urmo/game/state/GameStateType.java | 2 +- .../java/se/urmo/game/state/GhostManager.java | 5 +- .../java/se/urmo/game/state/PlayingState.java | 116 +++++++++++++++--- .../java/se/urmo/game/state/RoundPhase.java | 9 ++ 8 files changed, 124 insertions(+), 21 deletions(-) create mode 100644 src/main/java/se/urmo/game/state/RoundPhase.java 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 7e16399..526194d 100644 --- a/src/main/java/se/urmo/game/entities/ghost/Ghost.java +++ b/src/main/java/se/urmo/game/entities/ghost/Ghost.java @@ -219,4 +219,8 @@ public class Ghost extends BaseAnimated { public Point getPosition() { return new Point((int) position.x, (int) position.y); } + + public void resetModes() { + mode = GhostMode.CHASE; + } } diff --git a/src/main/java/se/urmo/game/entities/pacman/PacMan.java b/src/main/java/se/urmo/game/entities/pacman/PacMan.java index 0f3c88d..2675ef3 100644 --- a/src/main/java/se/urmo/game/entities/pacman/PacMan.java +++ b/src/main/java/se/urmo/game/entities/pacman/PacMan.java @@ -109,6 +109,11 @@ public class PacMan extends BaseAnimated { position = startPosition; } + public void reset() { + resetPosition(); + aniIndex = 0; // reset animation to start + } + public Image getLifeIcon() { return spriteSheets[0][1]; } diff --git a/src/main/java/se/urmo/game/map/GameMap.java b/src/main/java/se/urmo/game/map/GameMap.java index e83fb67..4e15dd0 100644 --- a/src/main/java/se/urmo/game/map/GameMap.java +++ b/src/main/java/se/urmo/game/map/GameMap.java @@ -253,7 +253,7 @@ public class GameMap { public long numberOfDots() { //return this.numberOfDots; - return 10; + return 50; } public void reset() { diff --git a/src/main/java/se/urmo/game/state/GameStateManager.java b/src/main/java/se/urmo/game/state/GameStateManager.java index 04b07ad..c588939 100644 --- a/src/main/java/se/urmo/game/state/GameStateManager.java +++ b/src/main/java/se/urmo/game/state/GameStateManager.java @@ -19,7 +19,7 @@ public class GameStateManager { setState(GameStateType.PLAYING); } - private void setState(GameStateType type) { + public void setState(GameStateType type) { currentState = states.get(type); } diff --git a/src/main/java/se/urmo/game/state/GameStateType.java b/src/main/java/se/urmo/game/state/GameStateType.java index 4dcd68b..9277928 100644 --- a/src/main/java/se/urmo/game/state/GameStateType.java +++ b/src/main/java/se/urmo/game/state/GameStateType.java @@ -1,5 +1,5 @@ package se.urmo.game.state; public enum GameStateType { - PLAYING, + PLAYING, GAME_OVER, } diff --git a/src/main/java/se/urmo/game/state/GhostManager.java b/src/main/java/se/urmo/game/state/GhostManager.java index 976d1dd..000928d 100644 --- a/src/main/java/se/urmo/game/state/GhostManager.java +++ b/src/main/java/se/urmo/game/state/GhostManager.java @@ -22,7 +22,6 @@ import java.util.List; @Slf4j public class GhostManager { - public static final int SPRITE_SHEET_ROWS = 10; public static final int MAX_SPRITE_FRAMES = 4; public static final int BLINKY_ANIMATION = 0; public static final int PINKY_ANIMATION = 2; @@ -90,10 +89,12 @@ public class GhostManager { } public void setFrightMode() { - this.setMode(GhostMode.FRIGHTENED); + setMode(GhostMode.FRIGHTENED); } public void reset() { + phaseIndex = 0; + setMode(GhostMode.SCATTER); ghosts.forEach(Ghost::resetPosition); } } diff --git a/src/main/java/se/urmo/game/state/PlayingState.java b/src/main/java/se/urmo/game/state/PlayingState.java index 920b677..3cea5b6 100644 --- a/src/main/java/se/urmo/game/state/PlayingState.java +++ b/src/main/java/se/urmo/game/state/PlayingState.java @@ -19,20 +19,35 @@ import java.io.InputStream; @Slf4j public class PlayingState implements GameState { + public static final int REMAINING_LIVES = 0; private final Game game; private final GameStateManager gameStateManager; - private final GhostManager ghostManager; private final Font arcadeFont; + + // Core components + private final GhostManager ghostManager; private final FruitManager fruitManager; private final LevelManager levelManager; private final AnimationManager animationManager; private PacMan pacman; @Getter private GameMap map; - private int score; + + // Durations (tune to taste) + private static final int READY_MS = 1500; + private static final int LEVEL_COMPLETE_MS= 1500; + private static final int LIFE_LOST_MS = 2000; + + // Score/Lives + private int score = 0; private int lives = 3; private int dotsEaten = 0; + // Phase + timers + private RoundPhase phase = RoundPhase.PLAYING; + private long phaseStartMs = System.currentTimeMillis(); + private boolean deathInProgress; + public PlayingState(Game game, GameStateManager gameStateManager) { this.game = game; this.gameStateManager = gameStateManager; @@ -48,12 +63,59 @@ public class PlayingState implements GameState { @Override public void update() { - animationManager.updateAll(); - pacman.update(); - ghostManager.update(pacman, map); - fruitManager.update(pacman, this); - checkCollisions(); - handleDots(); + switch (phase) { + case READY -> { + // Freeze everything during READY + if (phaseElapsed() >= READY_MS) { + setPhase(RoundPhase.PLAYING); + } + } + case PLAYING -> { + animationManager.updateAll(); + pacman.update(); + ghostManager.update(pacman, map); + fruitManager.update(pacman, this); + checkCollisions(); + handleDots(); + } + case LEVEL_COMPLETE -> { + // Freeze, then advance level + if (phaseElapsed() >= LEVEL_COMPLETE_MS) { + advanceLevel(); + setPhase(RoundPhase.READY); + } + } + case LIFE_LOST -> { + // Freeze, then reset round (keep dot state) + if (phaseElapsed() >= LIFE_LOST_MS) { + resetAfterLifeLost(); + setPhase(RoundPhase.READY); + } + } + } + } + + private void resetAfterLifeLost() { + pacman.reset(); // to start tile, direction stopped + ghostManager.reset(); // to house + } + + private void advanceLevel() { + levelManager.nextLevel(); + map.reset(); + ghostManager.reset(); + fruitManager.reset(); + pacman.reset(); + dotsEaten = 0; + } + + private void setPhase(RoundPhase next) { + phase = next; + phaseStartMs = System.currentTimeMillis(); + } + + private long phaseElapsed() { + return System.currentTimeMillis() - phaseStartMs; } private void handleDots() { @@ -68,12 +130,7 @@ public class PlayingState implements GameState { fruitManager.dotEaten(dotsEaten); score+=tile.getTileType().getScore(); if(dotsEaten == map.numberOfDots()){ - levelManager.nextLevel(); - map.reset(); - ghostManager.reset(); - fruitManager.reset(); - pacman.resetPosition(); - dotsEaten = 0; + setPhase(RoundPhase.LEVEL_COMPLETE); } } } @@ -85,6 +142,23 @@ public class PlayingState implements GameState { ghostManager.draw(g); fruitManager.draw(g); drawUI(g); + + // Phase overlays + switch (phase) { + case READY -> drawCenterText(g, "READY!"); + case LEVEL_COMPLETE -> drawCenterText(g, "LEVEL COMPLETE!"); + case LIFE_LOST -> drawCenterText(g, "LIFE LOST"); + default -> { /* no overlay */ } + } + } + + private void drawCenterText(Graphics2D g, String text) { + g.setFont(arcadeFont.deriveFont(18F)); + g.setColor(Color.YELLOW); + var fm = g.getFontMetrics(); + int cx = GameMap.OFFSET_X + map.columns() * GameMap.MAP_TILESIZE/2; + int cy = GameMap.OFFSET_Y + map.rows() * GameMap.MAP_TILESIZE/2; + g.drawString(text, cx - fm.stringWidth(text)/2, cy); } private void drawUI(Graphics2D g) { @@ -106,6 +180,7 @@ public class PlayingState implements GameState { @Override public void keyPressed(KeyEvent e) { + if (phase != RoundPhase.PLAYING) return; switch (e.getKeyCode()) { case KeyEvent.VK_W -> move(Direction.UP); case KeyEvent.VK_S -> move(Direction.DOWN); @@ -126,6 +201,7 @@ public class PlayingState implements GameState { private void checkCollisions() { for (Ghost ghost : ghostManager.getGhosts()) { + if (deathInProgress) return; // guard double dist = pacman.distanceTo(ghost.getPosition()); if (dist < GameMap.MAP_TILESIZE / 2.0) { if (ghost.isFrightened()) { @@ -134,15 +210,23 @@ public class PlayingState implements GameState { ghost.resetPosition(); ghost.setMode(GhostMode.CHASE); // end frightend } else { + deathInProgress = true; // Pac-Man loses a life lives--; - if(lives == 0)System.exit(1); - else pacman.resetPosition(); + if(lives >= REMAINING_LIVES) + setPhase(RoundPhase.LIFE_LOST); + else + endGame(); } } } } + private void endGame() { + setPhase(RoundPhase.GAME_OVER); + gameStateManager.setState(GameStateType.GAME_OVER); + } + private Font loadArcadeFont() { try (InputStream is = getClass().getResourceAsStream("/fonts/PressStart2P-Regular.ttf")) { return Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(16f); diff --git a/src/main/java/se/urmo/game/state/RoundPhase.java b/src/main/java/se/urmo/game/state/RoundPhase.java new file mode 100644 index 0000000..1bc6145 --- /dev/null +++ b/src/main/java/se/urmo/game/state/RoundPhase.java @@ -0,0 +1,9 @@ +package se.urmo.game.state; + +public enum RoundPhase { + READY, // "READY!" shown briefly before play starts + PLAYING, // normal gameplay + LEVEL_COMPLETE, // all dots eaten; freeze briefly then advance level + LIFE_LOST, // Pac-Man hit; freeze briefly then reset positions + GAME_OVER +}