From b13f87f77ba78bef8a707ae7e6e228ca21cde6b4 Mon Sep 17 00:00:00 2001 From: Urban Modig Date: Mon, 1 Sep 2025 21:48:34 +0200 Subject: [PATCH] GameOverState WIP --- .../se/urmo/game/state/GameOverState.java | 94 +++++++++++++++++++ .../se/urmo/game/state/GameStateManager.java | 6 +- .../se/urmo/game/state/HighScoreManager.java | 7 ++ .../java/se/urmo/game/state/PlayingState.java | 67 ++++++------- .../java/se/urmo/game/state/RoundPhase.java | 5 +- .../java/se/urmo/game/util/GameFonts.java | 26 +++++ 6 files changed, 168 insertions(+), 37 deletions(-) create mode 100644 src/main/java/se/urmo/game/state/GameOverState.java create mode 100644 src/main/java/se/urmo/game/state/HighScoreManager.java create mode 100644 src/main/java/se/urmo/game/util/GameFonts.java diff --git a/src/main/java/se/urmo/game/state/GameOverState.java b/src/main/java/se/urmo/game/state/GameOverState.java new file mode 100644 index 0000000..e854f2b --- /dev/null +++ b/src/main/java/se/urmo/game/state/GameOverState.java @@ -0,0 +1,94 @@ +package se.urmo.game.state; + +import se.urmo.game.main.GamePanel; +import se.urmo.game.util.GameFonts; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.event.KeyEvent; + +public class GameOverState implements GameState { + private final GameStateManager gsm; + private int finalScore = 0; + private int finalLevel = 0; + private final HighScoreManager highScores; // optional + + private boolean saved = false; + private long startMs = System.currentTimeMillis(); + + public GameOverState(GameStateManager gsm, HighScoreManager highScores) { + this.gsm = gsm; + this.highScores = highScores; + } + + @Override + public void update() { + if (!saved) { + if (highScores != null) highScores.submit(finalScore); + saved = true; + } + // could auto-return to menu after N seconds if you like + } + + @Override + public void render(Graphics2D g) { + // Clear / dim background + g.setColor(new Color(0,0,0,200)); + g.fillRect(0,0, GamePanel.SCREEN_WIDTH, GamePanel.SCREEN_HEIGHT); + + // Title + g.setColor(Color.RED); + g.setFont(GameFonts.arcade(32f)); + drawCentered(g, "GAME OVER", GamePanel.SCREEN_HEIGHT/2 - 40); + + // Stats + g.setColor(Color.WHITE); + g.setFont(GameFonts.arcade(18f)); + drawCentered(g, "SCORE: " + finalScore, GamePanel.SCREEN_HEIGHT/2); + drawCentered(g, "LEVEL: " + finalLevel, GamePanel.SCREEN_HEIGHT/2 + 24); + + // Prompt + g.setFont(GameFonts.arcade(12f)); + drawCentered(g, "Press..", GamePanel.SCREEN_HEIGHT/2 + 64); + drawCentered(g, "ENTER to restart • ESC for menu", GamePanel.SCREEN_HEIGHT/2 + 78); + } + + private void drawCentered(Graphics2D g, String text, int y) { + var fm = g.getFontMetrics(); + int x = (GamePanel.SCREEN_WIDTH - fm.stringWidth(text)) / 2; + g.drawString(text, x, y); + } + + @Override + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_ENTER -> restartNewGame(); + case KeyEvent.VK_ESCAPE -> goToMenu(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + + } + + private void restartNewGame() { + // Build a fresh PlayingState with everything reset + gsm.setState(GameStateType.PLAYING); + } + + private void goToMenu() { +// MenuState menu = new MenuState(gsm); +// gsm.register(StateId.MENU, menu); +// gsm.set(StateId.MENU); + } + + public void setScore(int score) { + this.finalScore = score; + } + + public void setLevel(int level) { + this.finalLevel = level; + } +} + diff --git a/src/main/java/se/urmo/game/state/GameStateManager.java b/src/main/java/se/urmo/game/state/GameStateManager.java index c588939..a211570 100644 --- a/src/main/java/se/urmo/game/state/GameStateManager.java +++ b/src/main/java/se/urmo/game/state/GameStateManager.java @@ -15,8 +15,10 @@ public class GameStateManager { public GameStateManager(Game game) { this.game = game; - states.put(GameStateType.PLAYING, new PlayingState(game, this)); - setState(GameStateType.PLAYING); + GameOverState gameOverState = new GameOverState(this, new HighScoreManager()); + states.put(GameStateType.PLAYING, new PlayingState(game, this, gameOverState)); + states.put(GameStateType.GAME_OVER, gameOverState); + setState(GameStateType.GAME_OVER); } public void setState(GameStateType type) { diff --git a/src/main/java/se/urmo/game/state/HighScoreManager.java b/src/main/java/se/urmo/game/state/HighScoreManager.java new file mode 100644 index 0000000..8ca3a1d --- /dev/null +++ b/src/main/java/se/urmo/game/state/HighScoreManager.java @@ -0,0 +1,7 @@ +package se.urmo.game.state; + +public class HighScoreManager { + public void submit(int finalScore) { + + } +} diff --git a/src/main/java/se/urmo/game/state/PlayingState.java b/src/main/java/se/urmo/game/state/PlayingState.java index 3cea5b6..0ffb08d 100644 --- a/src/main/java/se/urmo/game/state/PlayingState.java +++ b/src/main/java/se/urmo/game/state/PlayingState.java @@ -12,17 +12,17 @@ import se.urmo.game.map.GameMap; import se.urmo.game.map.MapTile; import se.urmo.game.map.TileType; import se.urmo.game.util.Direction; +import se.urmo.game.util.GameFonts; import java.awt.*; import java.awt.event.KeyEvent; -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 Font arcadeFont; + private final GameOverState gameOverState; // Core components private final GhostManager ghostManager; @@ -34,9 +34,9 @@ public class PlayingState implements GameState { private GameMap map; // 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; + 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; @@ -48,9 +48,10 @@ public class PlayingState implements GameState { private long phaseStartMs = System.currentTimeMillis(); private boolean deathInProgress; - public PlayingState(Game game, GameStateManager gameStateManager) { + public PlayingState(Game game, GameStateManager gameStateManager, GameOverState gameOverState) { this.game = game; this.gameStateManager = gameStateManager; + this.gameOverState = gameOverState; this.map = new GameMap("maps/map1.csv"); this.animationManager = new AnimationManager(); this.levelManager = new LevelManager(); @@ -58,7 +59,6 @@ public class PlayingState implements GameState { this.animationManager.register(pacman); this.ghostManager = new GhostManager(new GhostCollisionChecker(map), animationManager, levelManager); this.fruitManager = new FruitManager(levelManager); - this.arcadeFont = loadArcadeFont(); } @Override @@ -88,16 +88,20 @@ public class PlayingState implements GameState { case LIFE_LOST -> { // Freeze, then reset round (keep dot state) if (phaseElapsed() >= LIFE_LOST_MS) { + deathInProgress = false; resetAfterLifeLost(); setPhase(RoundPhase.READY); + if (lives <= 0) { + endGame(); + } } } } } private void resetAfterLifeLost() { - pacman.reset(); // to start tile, direction stopped - ghostManager.reset(); // to house + pacman.reset(); // to start tile, direction stopped + ghostManager.reset(); // to house } private void advanceLevel() { @@ -122,14 +126,14 @@ public class PlayingState implements GameState { Point pacmanScreenPos = pacman.getPosition(); MapTile tile = map.getTile(pacmanScreenPos); boolean wasRemoved = map.removeTileImage(pacmanScreenPos); - if(wasRemoved && tile.getTileType() == TileType.LARGE_PELLET){ + if (wasRemoved && tile.getTileType() == TileType.LARGE_PELLET) { ghostManager.setFrightMode(); } - if(wasRemoved){ + if (wasRemoved) { dotsEaten++; fruitManager.dotEaten(dotsEaten); - score+=tile.getTileType().getScore(); - if(dotsEaten == map.numberOfDots()){ + score += tile.getTileType().getScore(); + if (dotsEaten == map.numberOfDots()) { setPhase(RoundPhase.LEVEL_COMPLETE); } } @@ -153,17 +157,17 @@ public class PlayingState implements GameState { } private void drawCenterText(Graphics2D g, String text) { - g.setFont(arcadeFont.deriveFont(18F)); + g.setFont(GameFonts.arcade(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); + 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) { g.setColor(Color.WHITE); - g.setFont(arcadeFont); + g.setFont(GameFonts.arcade(18F)); // Score (above map, left) g.drawString("Your Score", 48, 48); @@ -202,6 +206,7 @@ public class PlayingState implements GameState { private void checkCollisions() { for (Ghost ghost : ghostManager.getGhosts()) { if (deathInProgress) return; // guard + //if(overlap(pacman, ghost) double dist = pacman.distanceTo(ghost.getPosition()); if (dist < GameMap.MAP_TILESIZE / 2.0) { if (ghost.isFrightened()) { @@ -213,26 +218,24 @@ public class PlayingState implements GameState { deathInProgress = true; // Pac-Man loses a life lives--; - if(lives >= REMAINING_LIVES) - setPhase(RoundPhase.LIFE_LOST); - else - endGame(); + setPhase(RoundPhase.LIFE_LOST); } } } } - private void endGame() { - setPhase(RoundPhase.GAME_OVER); - gameStateManager.setState(GameStateType.GAME_OVER); - } +// private boolean overlaps(PacMan p, Ghost g) { +// // center-distance or AABB; center distance keeps the arcade feel +// double dx = p.getCenterX() - g.getCenterX(); +// double dy = p.getCenterY() - g.getCenterY(); +// double r = map.getTileSize() * 0.45; // tune threshold +// return (dx*dx + dy*dy) <= r*r; +// } - private Font loadArcadeFont() { - try (InputStream is = getClass().getResourceAsStream("/fonts/PressStart2P-Regular.ttf")) { - return Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(16f); - } catch (Exception e) { - return new Font("Monospaced", Font.BOLD, 16); - } + private void endGame() { + gameOverState.setScore(score); + gameOverState.setLevel(levelManager.getCurrentLevel().getLevel()); + gameStateManager.setState(GameStateType.GAME_OVER); } public void setScore(int score) { diff --git a/src/main/java/se/urmo/game/state/RoundPhase.java b/src/main/java/se/urmo/game/state/RoundPhase.java index 1bc6145..a2941e1 100644 --- a/src/main/java/se/urmo/game/state/RoundPhase.java +++ b/src/main/java/se/urmo/game/state/RoundPhase.java @@ -4,6 +4,5 @@ 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 -} + LIFE_LOST // Pac-Man hit; freeze briefly then reset positions} +} \ No newline at end of file diff --git a/src/main/java/se/urmo/game/util/GameFonts.java b/src/main/java/se/urmo/game/util/GameFonts.java new file mode 100644 index 0000000..7a30da9 --- /dev/null +++ b/src/main/java/se/urmo/game/util/GameFonts.java @@ -0,0 +1,26 @@ +package se.urmo.game.util; + +import java.awt.Font; +import java.io.InputStream; + +public enum GameFonts { + ARCADE_FONT("/fonts/PressStart2P-Regular.ttf"); + + private final Font font; + + GameFonts(String s) { + font = loadArcadeFont(s); + } + + private static Font loadArcadeFont(String name) { + try (InputStream is = GameFonts.class.getResourceAsStream(name)) { + return Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(16f); + } catch (Exception e) { + return new Font("Monospaced", Font.BOLD, 16); + } + } + + public static Font arcade(float textSize) { + return ARCADE_FONT.font.deriveFont(textSize); + } +}