Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
Python
SQL
Shell
Markdown
YAML
JSON
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
TypeScript
Perl
Lua
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
package com.dawciobiel.pacman; import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.input.KeyCode; import javafx.scene.layout.BorderPane; import javafx.scene.paint.Color; import javafx.scene.shape.ArcType; import javafx.scene.text.Font; import javafx.stage.Stage; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random; import static com.dawciobiel.pacman.PacmanGame.BOARD_COLS; import static com.dawciobiel.pacman.PacmanGame.BOARD_ROWS; import static com.dawciobiel.pacman.PacmanGame.SCATTER_TARGETS; enum Direction { UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0); public final int dx; public final int dy; Direction(int dx, int dy) { this.dx = dx; this.dy = dy; } } enum GhostMode { NORMAL, FRIGHTENED, SCATTER, CHASE, DEAD } public class PacmanGame extends Application { static final int TILE_SIZE = 50; static final double MOVEMENT_SPEED = 0.1; // OPTYMALIZACJA: Re-używalne obiekty static final Random SHARED_RANDOM = new Random(); private static final int BOARD_WIDTH = 1200; static final int BOARD_COLS = BOARD_WIDTH / TILE_SIZE; private static final int BOARD_HEIGHT = 800; static final int BOARD_ROWS = BOARD_HEIGHT / TILE_SIZE; // Pozycje rogów dla scatter mode protected static final int[][] SCATTER_TARGETS = {{BOARD_COLS - 1, 0}, // czerwony — prawy górny {0, 0}, // różowy — lewy górny {BOARD_COLS - 1, BOARD_ROWS - 1}, // cyan — prawy dolny {0, BOARD_ROWS - 1} // pomarańczowy — lewy dolny }; private static final Font UI_FONT = new Font("Arial", 20); private static final Font GAME_OVER_FONT = new Font("Arial", 40); // Power pellet settings private static final int POWER_PELLET_DURATION = 10000; // 10 sekund w milisekundach private static final int GHOST_POINTS = 200; // Punkty za zjedzenie ducha // OPTYMALIZACJA: Object pooling dla kierunków private static final Direction[] DIRECTION_VALUES = Direction.values(); private static final int[][] MAZE_DIRECTIONS = {{0, -2}, {0, 2}, {-2, 0}, {2, 0}}; // OPTYMALIZACJA: Bufory do wielokrotnego użytku private final List
stackBuffer = new ArrayList<>(100); private final List
neighborsBuffer = new ArrayList<>(4); private final List
validDirectionsBuffer = new ArrayList<>(4); private SoundManager soundManager; private Canvas canvas; private GraphicsContext gc; private AnimationTimer gameLoop; private Player pacman; private List
ghosts; private List
foods; private List
powerPellets; private boolean[][] walls; private int score = 0; private int lives = 3; private boolean gameOver = false; private boolean gameWon = false; private long powerPelletStartTime = 0; private boolean isPowerPelletActive = false; private int ghostMultiplier = 1; // Mnożnik punktów za duchy public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { BorderPane root = new BorderPane(); canvas = new Canvas(BOARD_WIDTH, BOARD_HEIGHT); gc = canvas.getGraphicsContext2D(); root.setCenter(canvas); Scene scene = new Scene(root, BOARD_WIDTH, BOARD_HEIGHT); scene.setOnKeyPressed(e -> handleKeyPress(e.getCode())); primaryStage.setTitle("Pacman Game - Enhanced"); primaryStage.setScene(scene); primaryStage.setResizable(false); primaryStage.show(); canvas.requestFocus(); initializeGame(); soundManager = SoundManager.getInstance(); startGameLoop(); } private void initializeGame() { // OPTYMALIZACJA: Wyczyść istniejące listy, zamiast tworzyć nowe if (ghosts != null) { ghosts.clear(); } else { ghosts = new ArrayList<>(4); } if (foods != null) { foods.clear(); } else { foods = new ArrayList<>(200); } if (powerPellets != null) { powerPellets.clear(); } else { powerPellets = new ArrayList<>(4); } // Inicjalizacja ścian - reużywanie tablicy jeśli możliwe if (walls == null) { walls = new boolean[BOARD_COLS][BOARD_ROWS]; } createMaze(); pacman = new Player(1, 1); // Dodaj duchy z predefiniowanymi kolorami ghosts.add(new Ghost(BOARD_COLS - 2, 1, Color.RED)); ghosts.add(new Ghost(BOARD_COLS - 2, BOARD_ROWS - 2, Color.PINK)); ghosts.add(new Ghost(1, BOARD_ROWS - 2, Color.CYAN)); createFood(); createPowerPellets(); // Reset power pellet state isPowerPelletActive = false; powerPelletStartTime = 0; ghostMultiplier = 1; } private void createMaze() { // OPTYMALIZACJA: Zresetuj tablicę zamiast tworzyć nową for (int x = 0; x < BOARD_COLS; x++) { for (int y = 0; y < BOARD_ROWS; y++) { walls[x][y] = true; } } generateMaze(1, 1); int middleY = BOARD_ROWS / 2; walls[0][middleY] = false; walls[BOARD_COLS - 1][middleY] = false; ensureTunnelAccess(middleY); clearArea(1, 1, 2); clearArea(BOARD_COLS - 2, 1, 1); clearArea(BOARD_COLS - 2, BOARD_ROWS - 2, 1); clearArea(1, BOARD_ROWS - 2, 1); createOpenAreasAsCross(); } private void ensureTunnelAccess(int tunnelRow) { walls[1][tunnelRow] = false; walls[BOARD_COLS - 2][tunnelRow] = false; } private void generateMaze(int startX, int startY) { // OPTYMALIZACJA: Re-używanie buforów stackBuffer.clear(); boolean[][] visited = new boolean[BOARD_COLS][BOARD_ROWS]; walls[startX][startY] = false; visited[startX][startY] = true; stackBuffer.add(new int[]{startX, startY}); while (!stackBuffer.isEmpty()) { int[] current = stackBuffer.get(stackBuffer.size() - 1); int currentX = current[0]; int currentY = current[1]; // OPTYMALIZACJA: Reużywanie bufora sąsiadów neighborsBuffer.clear(); for (int[] dir : MAZE_DIRECTIONS) { int newX = currentX + dir[0]; int newY = currentY + dir[1]; if (newX > 0 && newX < BOARD_COLS - 1 && newY > 0 && newY < BOARD_ROWS - 1 && !visited[newX][newY]) { neighborsBuffer.add(new int[]{newX, newY}); } } if (!neighborsBuffer.isEmpty()) { int[] chosen = neighborsBuffer.get(SHARED_RANDOM.nextInt(neighborsBuffer.size())); int chosenX = chosen[0]; int chosenY = chosen[1]; int wallX = currentX + (chosenX - currentX) / 2; int wallY = currentY + (chosenY - currentY) / 2; walls[chosenX][chosenY] = false; walls[wallX][wallY] = false; visited[chosenX][chosenY] = true; stackBuffer.add(chosen); } else { stackBuffer.remove(stackBuffer.size() - 1); } } } private void clearArea(int centerX, int centerY, int radius) { for (int x = centerX - radius; x <= centerX + radius; x++) { for (int y = centerY - radius; y <= centerY + radius; y++) { if (x > 0 && x < BOARD_COLS - 1 && y > 0 && y < BOARD_ROWS - 1) { walls[x][y] = false; } } } } private void createOpenAreasAsCross() { int numAreas = 3 + SHARED_RANDOM.nextInt(3); for (int i = 0; i < numAreas; i++) { int centerX = 3 + SHARED_RANDOM.nextInt(BOARD_COLS - 6); int centerY = 3 + SHARED_RANDOM.nextInt(BOARD_ROWS - 6); walls[centerX][centerY] = false; walls[centerX - 1][centerY] = false; walls[centerX + 1][centerY] = false; walls[centerX][centerY - 1] = false; walls[centerX][centerY + 1] = false; } } private void createFood() { for (int x = 1; x < BOARD_COLS - 1; x++) { for (int y = 1; y < BOARD_ROWS - 1; y++) { if (!walls[x][y] && !(x == 1 && y == 1)) { foods.add(new Food(x, y)); } } } } private void createPowerPellets() { // Dodaj power pellets w rogach labiryntu List
corners = new ArrayList<>(); corners.add(new int[]{3, 3}); corners.add(new int[]{BOARD_COLS - 4, 3}); corners.add(new int[]{3, BOARD_ROWS - 4}); corners.add(new int[]{BOARD_COLS - 4, BOARD_ROWS - 4}); for (int[] corner : corners) { int x = corner[0]; int y = corner[1]; // Znajdź najbliższe wolne miejsce for (int radius = 0; radius < 5; radius++) { for (int dx = -radius; dx <= radius; dx++) { for (int dy = -radius; dy <= radius; dy++) { int checkX = x + dx; int checkY = y + dy; if (checkX > 0 && checkX < BOARD_COLS - 1 && checkY > 0 && checkY < BOARD_ROWS - 1 && !walls[checkX][checkY]) { powerPellets.add(new PowerPellet(checkX, checkY)); // Usuń zwykłe jedzenie z tego miejsca foods.removeIf(food -> food.x() == checkX && food.y() == checkY); radius = 5; // Przerwij pętle dx = radius + 1; dy = radius + 1; } } } } } } private void handleKeyPress(KeyCode keyCode) { if (gameOver || gameWon) { if (keyCode == KeyCode.R) { restartGame(); } return; } if (keyCode == KeyCode.M) { soundManager.setSoundEnabled(!soundManager.isSoundEnabled()); return; } Direction newDirection = switch (keyCode) { case UP, W -> Direction.UP; case DOWN, S -> Direction.DOWN; case LEFT, A -> Direction.LEFT; case RIGHT, D -> Direction.RIGHT; default -> null; }; if (newDirection != null) { pacman.queueDirection(newDirection); } } private void restartGame() { score = 0; lives = 3; gameOver = false; gameWon = false; initializeGame(); if (gameLoop != null) { gameLoop.stop(); } startGameLoop(); } private void startGameLoop() { gameLoop = new AnimationTimer() { @Override public void handle(long now) { update(now); render(); } }; gameLoop.start(); } private void update(long currentTime) { if (gameOver || gameWon) return; // Sprawdź, czy power pellet wygasł if (isPowerPelletActive && currentTime - powerPelletStartTime > POWER_PELLET_DURATION * 1_000_000L) { isPowerPelletActive = false; ghostMultiplier = 1; // Przywróć normalny tryb duchów for (Ghost ghost : ghosts) { ghost.setMode(GhostMode.NORMAL); } } pacman.update(walls); for (Ghost ghost : ghosts) { ghost.update(walls, pacman.getTileX(), pacman.getTileY(), isPowerPelletActive, pacman.getDirection()); } // OPTYMALIZACJA: Sprawdź kolizje z jedzeniem tylko, gdy Pacman jest w centrum if (pacman.isAtTileCenter()) { // Sprawdź kolizje ze zwykłym jedzeniem Iterator
foodIterator = foods.iterator(); while (foodIterator.hasNext()) { Food food = foodIterator.next(); if (food.x() == pacman.getTileX() && food.y() == pacman.getTileY()) { score += 10; foodIterator.remove(); soundManager.playSound("chomp"); // DODANE } } // Sprawdź kolizje z power pellets Iterator
powerPelletIterator = powerPellets.iterator(); while (powerPelletIterator.hasNext()) { PowerPellet powerPellet = powerPelletIterator.next(); if (powerPellet.x() == pacman.getTileX() && powerPellet.y() == pacman.getTileY()) { score += 50; powerPelletIterator.remove(); soundManager.playSound("power_pellet"); // Aktywuj power pellet isPowerPelletActive = true; powerPelletStartTime = currentTime; ghostMultiplier = 1; // Zmień tryb duchów na frightened for (Ghost ghost : ghosts) { ghost.setMode(GhostMode.FRIGHTENED); } } } } // Sprawdź kolizje z duchami Iterator
ghostIterator = ghosts.iterator(); while (ghostIterator.hasNext()) { Ghost ghost = ghostIterator.next(); double dx = ghost.getDrawX() - pacman.getDrawX(); double dy = ghost.getDrawY() - pacman.getDrawY(); double distanceSquared = dx * dx + dy * dy; if (distanceSquared < (TILE_SIZE * 0.8) * (TILE_SIZE * 0.8)) { if (isPowerPelletActive && ghost.getMode() == GhostMode.FRIGHTENED) { // Zjadanie ducha score += GHOST_POINTS * ghostMultiplier; ghostMultiplier *= 2; // Podwajaj punkty za kolejne duchy soundManager.playSound("ghost_eaten"); ghost.setModeDead(); } else if (ghost.getMode() != GhostMode.DEAD) { // DODANE: Nie zabijaj jeśli duch jest martwy // Pacman zostaje zjedzony lives--; soundManager.playSound("death"); if (lives <= 0) { gameOver = true; soundManager.playSound("game_over"); gameLoop.stop(); return; } else { // Restart pozycji pacman.reset(1, 1); isPowerPelletActive = false; ghostMultiplier = 1; // Przywróć normalny tryb duchów for (Ghost g : ghosts) { g.setMode(GhostMode.SCATTER); } } } } } // Sprawdź warunek wygranej if (foods.isEmpty() && powerPellets.isEmpty()) { gameWon = true; soundManager.playSound("level_complete"); gameLoop.stop(); } } private void render() { // Wyczyść ekran gc.setFill(Color.BLACK); gc.fillRect(0, 0, BOARD_WIDTH, BOARD_HEIGHT); // Narysuj ściany gc.setFill(Color.BLUE); for (int x = 0; x < BOARD_COLS; x++) { for (int y = 0; y < BOARD_ROWS; y++) { if (walls[x][y]) { gc.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); } } } // Narysuj jedzenie gc.setFill(Color.GHOSTWHITE); for (Food food : foods) { double foodX = food.x() * TILE_SIZE + TILE_SIZE * 0.25; double foodY = food.y() * TILE_SIZE + TILE_SIZE * 0.25; double foodSize = TILE_SIZE * 0.5; gc.fillOval(foodX, foodY, foodSize, foodSize); } // Narysuj power pellets gc.setFill(Color.YELLOW); for (PowerPellet powerPellet : powerPellets) { double pelletX = powerPellet.x() * TILE_SIZE + TILE_SIZE * 0.15; double pelletY = powerPellet.y() * TILE_SIZE + TILE_SIZE * 0.15; double pelletSize = TILE_SIZE * 0.7; gc.fillOval(pelletX, pelletY, pelletSize, pelletSize); } pacman.draw(gc); // Narysuj duchy for (Ghost ghost : ghosts) { Color ghostColor; if (ghost.getMode() == GhostMode.FRIGHTENED) { ghostColor = Color.BLUE; } else if (ghost.getMode() == GhostMode.DEAD) { ghostColor = Color.GRAY; // Inny kolor dla martwych duchów } else { ghostColor = ghost.getColor(); } gc.setFill(ghostColor); double ghostX = ghost.getDrawX() - TILE_SIZE * 0.5 + 2; double ghostY = ghost.getDrawY() - TILE_SIZE * 0.5 + 2; double ghostSize = TILE_SIZE - 4; gc.fillRect(ghostX, ghostY, ghostSize, ghostSize); } // UI gc.setFill(Color.WHITE); gc.setFont(UI_FONT); gc.fillText("Wynik: " + score, 10, 25); gc.fillText("Życia: " + lives, 10, 50); if (isPowerPelletActive) { gc.fillText("POWER MODE!", 10, 75); } if (gameOver) { gc.setFont(GAME_OVER_FONT); gc.fillText("GAME OVER!", BOARD_WIDTH * 0.5 - 120, BOARD_HEIGHT * 0.5); gc.setFont(UI_FONT); gc.fillText("Naciśnij R aby zagrać ponownie", BOARD_WIDTH * 0.5 - 120, BOARD_HEIGHT * 0.5 + 40); } else if (gameWon) { gc.setFont(GAME_OVER_FONT); gc.fillText("WYGRAŁEŚ!", BOARD_WIDTH * 0.5 - 100, BOARD_HEIGHT * 0.5); gc.setFont(UI_FONT); gc.fillText("Naciśnij R aby zagrać ponownie", BOARD_WIDTH * 0.5 - 120, BOARD_HEIGHT * 0.5 + 40); } } @Override public void stop() throws Exception { if (gameLoop != null) { gameLoop.stop(); } if (soundManager != null) { soundManager.cleanup(); // DODANE } super.stop(); } } class Player { private static final double ANIMATION_SPEED = 0.15; private final int startX; private final int startY; private double x; private double y; private int tileX; private int tileY; private Direction direction = Direction.RIGHT; private Direction queuedDirection = null; private boolean isMoving = false; private double moveProgress = 0.0; private double animationTime = 0.0; public Player(int startX, int startY) { this.startX = startX; this.startY = startY; this.tileX = startX; this.tileY = startY; this.x = startX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; this.y = startY * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; } public void reset(int newX, int newY) { this.tileX = newX; this.tileY = newY; this.x = newX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; this.y = newY * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; this.direction = Direction.RIGHT; this.queuedDirection = null; this.isMoving = false; this.moveProgress = 0.0; this.animationTime = 0.0; } public void draw(GraphicsContext gc) { double startAngle = switch (direction) { case RIGHT -> 0; case DOWN -> 270; case LEFT -> 180; case UP -> 90; }; double mouthAngle = 45 * Math.sin(animationTime * Math.PI * 2); if (mouthAngle < 0) mouthAngle = 0; double drawX = getDrawX() - PacmanGame.TILE_SIZE * 0.5 + 2; double drawY = getDrawY() - PacmanGame.TILE_SIZE * 0.5 + 2; double size = PacmanGame.TILE_SIZE - 4; gc.setFill(Color.YELLOW); if (mouthAngle > 0) { gc.fillArc(drawX, drawY, size, size, startAngle + mouthAngle * 0.5, 360 - mouthAngle, ArcType.ROUND); } else { gc.fillOval(drawX, drawY, size, size); } } public void update(boolean[][] walls) { if (isMoving) { animationTime += ANIMATION_SPEED; if (animationTime >= 1.0) { animationTime = 0.0; } } if (queuedDirection != null && !isMoving) { if (canMove(queuedDirection, walls)) { direction = queuedDirection; queuedDirection = null; startMoving(); } } if (!isMoving && canMove(direction, walls)) { startMoving(); } if (isMoving) { moveProgress += PacmanGame.MOVEMENT_SPEED; if (moveProgress >= 1.0) { completeMove(); moveProgress = 0.0; isMoving = false; handleTunnels(); } else { updateInterpolatedPosition(); } } } private void startMoving() { isMoving = true; moveProgress = 0.0; } private void completeMove() { tileX += direction.dx; tileY += direction.dy; x = tileX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; y = tileY * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; } private void updateInterpolatedPosition() { double startX = tileX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; double startY = tileY * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; double targetX = startX + direction.dx * PacmanGame.TILE_SIZE; double targetY = startY + direction.dy * PacmanGame.TILE_SIZE; x = startX + (targetX - startX) * moveProgress; y = startY + (targetY - startY) * moveProgress; } private boolean canMove(Direction dir, boolean[][] walls) { int nextX = tileX + dir.dx; int nextY = tileY + dir.dy; if (nextX < 0 || nextX >= walls.length || nextY < 0 || nextY >= walls[0].length) { return (nextX < 0 || nextX >= walls.length) && nextY == BOARD_ROWS / 2; } return !walls[nextX][nextY]; } private void handleTunnels() { if (tileX < 0) { tileX = BOARD_COLS - 1; x = tileX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; } else if (tileX >= BOARD_COLS) { tileX = 0; x = tileX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; } } public void queueDirection(Direction newDirection) { this.queuedDirection = newDirection; } public boolean isAtTileCenter() { return !isMoving; } public double getDrawX() { return x; } public double getDrawY() { return y; } public int getTileX() { return tileX; } public int getTileY() { return tileY; } public Direction getDirection() { return direction; } } class Ghost { private static final int SCATTER_DURATION = 420; // 7 sekund * 60 FPS private static final int CHASE_DURATION = 1200; // 20 sekund * 60 FPS private static final int HOME_X = BOARD_COLS / 2; private static final int HOME_Y = BOARD_ROWS / 2; private final Color originalColor; private final List
validDirections = new ArrayList<>(4); private final int startX; private final int startY; private final int scatterTargetX; private final int scatterTargetY; private final int ghostIndex; // 0-3 dla różnych strategii private double x; private double y; private int tileX; private int tileY; private Direction direction; private boolean isMoving = false; private double moveProgress = 0.0; private GhostMode mode = GhostMode.SCATTER; private int modeTimer = 0; public Ghost(int startX, int startY, Color color) { this.startX = startX; this.startY = startY; this.tileX = startX; this.tileY = startY; this.x = startX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; this.y = startY * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; this.originalColor = color; this.direction = Direction.values()[PacmanGame.SHARED_RANDOM.nextInt(4)]; // Przypisz indeks na podstawie koloru if (color.equals(Color.RED)) this.ghostIndex = 0; else if (color.equals(Color.PINK)) this.ghostIndex = 1; else if (color.equals(Color.CYAN)) this.ghostIndex = 2; else this.ghostIndex = 3; // Przypisz cel scatter this.scatterTargetX = SCATTER_TARGETS[ghostIndex][0]; this.scatterTargetY = SCATTER_TARGETS[ghostIndex][1]; } // 4. Nowa metoda do zarządzania stanami (dodaj do klasy Ghost) private void updateMode() { if (mode == GhostMode.FRIGHTENED || mode == GhostMode.DEAD) { return; // Nie zmieniaj automatycznie w tych trybach } modeTimer++; if (mode == GhostMode.SCATTER && modeTimer >= SCATTER_DURATION) { mode = GhostMode.CHASE; modeTimer = 0; } else if (mode == GhostMode.CHASE && modeTimer >= CHASE_DURATION) { mode = GhostMode.SCATTER; modeTimer = 0; } } // 5. Nowa metoda do wybierania celu (dodaj do klasy Ghost) private int[] getTarget(int pacmanX, int pacmanY, Direction pacmanDirection) { switch (mode) { case SCATTER: return new int[]{scatterTargetX, scatterTargetY}; case CHASE: return getChaseTarget(pacmanX, pacmanY, pacmanDirection); case FRIGHTENED: return null; // Losowy ruch case DEAD: return new int[]{HOME_X, HOME_Y}; default: return new int[]{scatterTargetX, scatterTargetY}; } } // 6. Metoda do strategii ścigania (dodaj do klasy Ghost) private int[] getChaseTarget(int pacmanX, int pacmanY, Direction pacmanDirection) { switch (ghostIndex) { case 0: // Czerwony (Blinky) - bezpośrednio do Pacmana return new int[]{pacmanX, pacmanY}; case 1: // Różowy (Pinky) - 4 pola przed Pacmanem int targetX = pacmanX; int targetY = pacmanY; switch (pacmanDirection) { case LEFT: targetX -= 4; break; case RIGHT: targetX += 4; break; case UP: targetY -= 4; break; case DOWN: targetY += 4; break; } return new int[]{targetX, targetY}; case 2: // Cyan (Inky) - 2 pola przed Pacmanem targetX = pacmanX; targetY = pacmanY; switch (pacmanDirection) { case LEFT: targetX -= 2; break; case RIGHT: targetX += 2; break; case UP: targetY -= 2; break; case DOWN: targetY += 2; break; } return new int[]{targetX, targetY}; case 3: // Pomarańczowy (Clyde) - jeśli daleko to jak Blinky, jeśli blisko to scatter double distance = Math.sqrt((tileX - pacmanX) * (tileX - pacmanX) + (tileY - pacmanY) * (tileY - pacmanY)); if (distance > 8) { return new int[]{pacmanX, pacmanY}; } else { return new int[]{scatterTargetX, scatterTargetY}; } default: return new int[]{pacmanX, pacmanY}; } } // 7. Nowa metoda do ruchu w kierunku celu (dodaj do klasy Ghost) private Direction getBestDirectionToTarget(int[] target, boolean[][] walls) { if (target == null) return null; Direction bestDirection = direction; double shortestDistance = Double.MAX_VALUE; for (Direction dir : Direction.values()) { // Nie cofaj się (chyba że musisz) if (getOppositeDirection(dir) == direction && validDirections.size() > 1) { continue; } if (canMove(dir, walls)) { int nextX = tileX + dir.dx; int nextY = tileY + dir.dy; double distance = Math.sqrt((nextX - target[0]) * (nextX - target[0]) + (nextY - target[1]) * (nextY - target[1])); if (distance < shortestDistance) { shortestDistance = distance; bestDirection = dir; } } } return bestDirection; } // 8. Metoda pomocnicza dla kierunków przeciwnych (dodaj do klasy Ghost) private Direction getOppositeDirection(Direction dir) { switch (dir) { case UP: return Direction.DOWN; case DOWN: return Direction.UP; case LEFT: return Direction.RIGHT; case RIGHT: return Direction.LEFT; default: return dir; } } public void respawn(int newX, int newY) { this.tileX = newX; this.tileY = newY; this.x = newX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; this.y = newY * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; this.isMoving = false; this.moveProgress = 0.0; this.mode = GhostMode.SCATTER; this.modeTimer = 0; } public void update(boolean[][] walls, int pacmanX, int pacmanY, boolean isPowerPelletActive, Direction pacmanDirection) { // Aktualizuj tryb tylko jeśli nie jest frightened if (mode != GhostMode.FRIGHTENED) { updateMode(); } // Sprawdź, czy duch w trybie DEAD dotarł do domu if (mode == GhostMode.DEAD) { if (Math.abs(tileX - HOME_X) <= 1 && Math.abs(tileY - HOME_Y) <= 1) { mode = GhostMode.SCATTER; modeTimer = 0; } } if (!isMoving) { chooseDirection(walls, pacmanX, pacmanY, pacmanDirection); if (canMove(direction, walls)) { startMoving(); } } if (isMoving) { moveProgress += PacmanGame.MOVEMENT_SPEED; if (moveProgress >= 1.0) { completeMove(); moveProgress = 0.0; isMoving = false; handleTunnels(); } else { updateInterpolatedPosition(); } } } public void setModeDead() { this.mode = GhostMode.DEAD; this.modeTimer = 0; } private void chooseDirection(boolean[][] walls, int pacmanX, int pacmanY, Direction pacmanDirection) { validDirections.clear(); for (Direction dir : Direction.values()) { if (canMove(dir, walls)) { validDirections.add(dir); } } if (!validDirections.isEmpty()) { if (mode == GhostMode.FRIGHTENED) { // W trybie frightened, unikaj Pacmana (istniejący kod) Direction bestDirection = null; double maxDistance = -1; for (Direction dir : validDirections) { int nextX = tileX + dir.dx; int nextY = tileY + dir.dy; double distance = Math.sqrt((nextX - pacmanX) * (nextX - pacmanX) + (nextY - pacmanY) * (nextY - pacmanY)); if (distance > maxDistance) { maxDistance = distance; bestDirection = dir; } } if (bestDirection != null) { direction = bestDirection; } else { direction = validDirections.get(PacmanGame.SHARED_RANDOM.nextInt(validDirections.size())); } } else { // NOWE: Scatter, Chase, Dead modes int[] target = getTarget(pacmanX, pacmanY, pacmanDirection); Direction bestDirection = getBestDirectionToTarget(target, walls); if (bestDirection != null && validDirections.contains(bestDirection)) { direction = bestDirection; } else { // Fallback — kontynuuj obecny kierunek lub wybierz losowy if (PacmanGame.SHARED_RANDOM.nextInt(10) < 7 && validDirections.contains(direction)) { // Kontynuuj obecny kierunek } else { direction = validDirections.get(PacmanGame.SHARED_RANDOM.nextInt(validDirections.size())); } } } } } private void startMoving() { isMoving = true; moveProgress = 0.0; } private void completeMove() { tileX += direction.dx; tileY += direction.dy; x = tileX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; y = tileY * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; } private void updateInterpolatedPosition() { double startX = tileX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; double startY = tileY * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; double targetX = startX + direction.dx * PacmanGame.TILE_SIZE; double targetY = startY + direction.dy * PacmanGame.TILE_SIZE; x = startX + (targetX - startX) * moveProgress; y = startY + (targetY - startY) * moveProgress; } private boolean canMove(Direction dir, boolean[][] walls) { int nextX = tileX + dir.dx; int nextY = tileY + dir.dy; if (nextX < 0 || nextX >= walls.length || nextY < 0 || nextY >= walls[0].length) { return (nextX < 0 || nextX >= walls.length) && nextY == BOARD_ROWS / 2; } return !walls[nextX][nextY]; } private void handleTunnels() { if (tileX < 0) { tileX = BOARD_COLS - 1; x = tileX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; } else if (tileX >= BOARD_COLS) { tileX = 0; x = tileX * PacmanGame.TILE_SIZE + PacmanGame.TILE_SIZE * 0.5; } } public GhostMode getMode() { return mode; } public void setMode(GhostMode newMode) { this.mode = newMode; } public double getDrawX() { return x; } public double getDrawY() { return y; } public int getTileX() { return tileX; } public int getTileY() { return tileY; } public Color getColor() { return originalColor; } } record Food(int x, int y) { } record PowerPellet(int x, int y) { }
Mark as private
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year