From 55ed9f5c8b83cc30a5bf111c93a9f4c5b8cc0d91 Mon Sep 17 00:00:00 2001 From: Ahmed Alwahedi Date: Sat, 26 Oct 2024 17:15:20 -0400 Subject: [PATCH] Ahmed disappointing submission --- assets/saves/oct26-25/best-calculator-22.json | 1 + .../java/com/buaisociety/pacman/Main.java | 102 ++++++------- .../java/com/buaisociety/pacman/Searcher.java | 136 ++++++++++++++++++ .../pacman/SpecialTrainingConditions.java | 2 +- .../com/buaisociety/pacman/Tournament.java | 2 +- .../entity/behavior/NeatPacmanBehavior.java | 130 ++++++++++++++++- .../entity/behavior/TournamentBehavior.java | 78 +++++++++- .../com/buaisociety/pacman/maze/Tile.java | 14 ++ 8 files changed, 405 insertions(+), 60 deletions(-) create mode 100644 assets/saves/oct26-25/best-calculator-22.json create mode 100644 core/src/main/java/com/buaisociety/pacman/Searcher.java diff --git a/assets/saves/oct26-25/best-calculator-22.json b/assets/saves/oct26-25/best-calculator-22.json new file mode 100644 index 0000000..f893cea --- /dev/null +++ b/assets/saves/oct26-25/best-calculator-22.json @@ -0,0 +1 @@ +{"isAddBias":true,"inputs":[{"id":0,"x":0.1,"incoming":[]},{"id":1,"x":0.1,"incoming":[]},{"id":2,"x":0.1,"incoming":[]},{"id":3,"x":0.1,"incoming":[]},{"id":4,"x":0.1,"incoming":[]},{"id":5,"x":0.1,"incoming":[]},{"id":6,"x":0.1,"incoming":[]},{"id":7,"x":0.1,"incoming":[]},{"id":8,"x":0.1,"incoming":[]},{"id":9,"x":0.1,"incoming":[]},{"id":10,"x":0.1,"incoming":[]},{"id":11,"x":0.1,"incoming":[]},{"id":12,"x":0.1,"incoming":[]}],"hidden":[{"id":36,"x":0.49999997,"incoming":[{"fromId":11,"weight":-0.62742525,"enabled":true},{"fromId":0,"weight":-0.46833527,"enabled":true},{"fromId":12,"weight":-2.0767417,"enabled":true}]},{"id":37,"x":0.49999997,"incoming":[{"fromId":3,"weight":-0.63522226,"enabled":true},{"fromId":0,"weight":-0.26396576,"enabled":true},{"fromId":9,"weight":1.2500377,"enabled":true},{"fromId":7,"weight":-0.39467263,"enabled":true},{"fromId":11,"weight":-0.10830047,"enabled":true},{"fromId":8,"weight":-1.3098693,"enabled":true}]},{"id":50,"x":0.49999997,"incoming":[{"fromId":12,"weight":2.3026857,"enabled":true},{"fromId":0,"weight":-1.6496298,"enabled":true},{"fromId":4,"weight":0.79640305,"enabled":true},{"fromId":10,"weight":0.011615746,"enabled":true}]}],"outputs":[{"id":13,"x":0.9,"incoming":[{"fromId":0,"weight":-0.6293719,"enabled":true},{"fromId":1,"weight":0.91749305,"enabled":true},{"fromId":2,"weight":0.8695058,"enabled":true},{"fromId":3,"weight":-0.05514489,"enabled":true},{"fromId":4,"weight":0.81233287,"enabled":true},{"fromId":5,"weight":1.4128559,"enabled":true},{"fromId":6,"weight":1.4513752,"enabled":true},{"fromId":7,"weight":0.028820913,"enabled":true},{"fromId":8,"weight":-0.37260377,"enabled":true},{"fromId":9,"weight":0.23969495,"enabled":true},{"fromId":10,"weight":0.5924212,"enabled":true},{"fromId":11,"weight":-1.0123627,"enabled":true},{"fromId":12,"weight":-0.30324888,"enabled":true}]},{"id":14,"x":0.9,"incoming":[{"fromId":0,"weight":0.61271966,"enabled":true},{"fromId":1,"weight":-0.43170908,"enabled":true},{"fromId":2,"weight":0.63351446,"enabled":true},{"fromId":3,"weight":0.39771095,"enabled":true},{"fromId":4,"weight":-0.23856583,"enabled":true},{"fromId":5,"weight":0.9336664,"enabled":true},{"fromId":6,"weight":0.09452582,"enabled":true},{"fromId":7,"weight":0.06078539,"enabled":true},{"fromId":8,"weight":-0.18390569,"enabled":true},{"fromId":9,"weight":-0.4636197,"enabled":true},{"fromId":10,"weight":-0.6440598,"enabled":true},{"fromId":11,"weight":-1.955276,"enabled":true},{"fromId":12,"weight":0.13735065,"enabled":true},{"fromId":37,"weight":0.23361547,"enabled":true}]},{"id":15,"x":0.9,"incoming":[{"fromId":0,"weight":-1.7581626,"enabled":true},{"fromId":1,"weight":-0.41642317,"enabled":true},{"fromId":2,"weight":0.08641729,"enabled":true},{"fromId":3,"weight":0.24208727,"enabled":true},{"fromId":4,"weight":2.0819035,"enabled":true},{"fromId":5,"weight":0.76017636,"enabled":true},{"fromId":6,"weight":-0.20650533,"enabled":true},{"fromId":7,"weight":1.0411346,"enabled":true},{"fromId":8,"weight":-0.63636374,"enabled":true},{"fromId":9,"weight":1.2254355,"enabled":true},{"fromId":10,"weight":0.29987216,"enabled":true},{"fromId":11,"weight":-0.19834638,"enabled":false},{"fromId":12,"weight":0.44649446,"enabled":true},{"fromId":36,"weight":-1.0254033,"enabled":true},{"fromId":37,"weight":-0.13361458,"enabled":true}]},{"id":16,"x":0.9,"incoming":[{"fromId":0,"weight":1.1678987,"enabled":true},{"fromId":1,"weight":-0.4645596,"enabled":true},{"fromId":2,"weight":-0.2257801,"enabled":true},{"fromId":3,"weight":0.4425527,"enabled":true},{"fromId":4,"weight":-0.4424284,"enabled":true},{"fromId":5,"weight":0.3860943,"enabled":true},{"fromId":6,"weight":-0.22475135,"enabled":true},{"fromId":7,"weight":0.52331454,"enabled":true},{"fromId":8,"weight":1.3660479,"enabled":true},{"fromId":9,"weight":0.019432902,"enabled":true},{"fromId":10,"weight":-0.7152641,"enabled":true},{"fromId":11,"weight":0.22664385,"enabled":true},{"fromId":12,"weight":0.22737403,"enabled":true},{"fromId":50,"weight":-0.3173502,"enabled":true}]}]} \ No newline at end of file diff --git a/core/src/main/java/com/buaisociety/pacman/Main.java b/core/src/main/java/com/buaisociety/pacman/Main.java index 29113e7..17a9c87 100644 --- a/core/src/main/java/com/buaisociety/pacman/Main.java +++ b/core/src/main/java/com/buaisociety/pacman/Main.java @@ -49,7 +49,7 @@ public class Main extends ApplicationAdapter { private OrthographicCamera camera; private final @NotNull EventSystem events = new EventSystem(); - private final @NotNull Vector2i visibleGames = new Vector2i(4, 2); + private final @NotNull Vector2i visibleGames = new Vector2i(4, 4); private final @NotNull List managers = new ArrayList<>(); private final int totalGames = 250; private GameLoop secondLoop; // 1 update per second @@ -69,13 +69,14 @@ public void create() { camera = new OrthographicCamera(); batch = new SpriteBatch(); camera.setToOrtho(false, 8 * 28 * visibleGames.x, 8 * 36 * visibleGames.y); + Gdx.graphics.setWindowedMode(8 * 28 * 4, 8 * 36 * 4); neat = createNeat(); neatPrinter = new NeatPrinter(neat); neatSaver = new NeatSaver(neat, getSaveFolder()); secondLoop = new GameLoop(1); int processors = Runtime.getRuntime().availableProcessors(); - threadPool = Executors.newFixedThreadPool(processors); + threadPool = Executors.newFixedThreadPool(processors * 2); System.out.println("Using " + processors + " threads"); // When all games have ended, reset @@ -105,7 +106,7 @@ public void create() { // Change this to true/false as needed, if you want to load from file if (false) { // TODO: Change this to the exact file you want to load - File exactFile = new File("saves" + File.separator + "oct26-4" + File.separator + "generation-51.json"); + File exactFile = new File("saves" + File.separator + "oct25-12" + File.separator + "generation-102.json"); // load exactFile contents to string String json; try { @@ -115,7 +116,7 @@ public void create() { } NeatImpl impl = NeatImpl.fromJson(json); // modify this as needed - //impl.updateNodeCounts(8, 4); // Add 4 new inputs + //impl.updateNodeCounts(6, 4); // Add 4 new inputs //impl.updateClients(200); // have 200 pacman games at once return impl; } else { @@ -125,7 +126,7 @@ public void create() { neatParameters.setTargetClientsPerSpecies(12); // targeting ~12 clients per species neatParameters.setStagnationLimit(10); // lower stagnation limit neatParameters.setUseBiasNode(true); // use bias node - return new NeatImpl(4, 4, totalGames, neatParameters); + return new NeatImpl(12, 4, totalGames, neatParameters); } } @@ -191,37 +192,41 @@ public void reset() { } @Override - public void render() { +public void render() { + paused ^= Gdx.input.isKeyJustPressed(Input.Keys.SPACE); + showNetworks ^= Gdx.input.isKeyJustPressed(Input.Keys.TAB); - paused ^= Gdx.input.isKeyJustPressed(Input.Keys.SPACE); - showNetworks ^= Gdx.input.isKeyJustPressed(Input.Keys.TAB); + frames++; + fps++; - frames++; - fps++; - - if (secondLoop.update()) { - System.out.println("FPS: " + fps + ", Frames: " + frames); - fps = 0; - } + if (secondLoop.update()) { + System.out.println("FPS: " + fps + ", Frames: " + frames); + fps = 0; + } - // If all games are complete, reset - if (managers.stream().map(PacmanNeatClient::getGameCompleteFuture).allMatch(CompletableFuture::isDone)) { - reset(); - System.out.println(neatPrinter.render()); - neatSaver.save(); - neat.evolve(); - } + // If all games are complete, reset + if (managers.stream().map(PacmanNeatClient::getGameCompleteFuture).allMatch(CompletableFuture::isDone)) { + reset(); + System.out.println(neatPrinter.render()); + neatSaver.save(); + neat.evolve(); + } - // Update games + // Update games in batches (e.g., 100 games at a time) + int batchSize = 100; // Customize this to control batch size + for (int i = 0; i < totalGames; i += batchSize) { List> futures = new ArrayList<>(); List updatedManagers = new ArrayList<>(); - for (PacmanNeatClient manager : managers) { + + // Process a batch of games in parallel + for (int j = i; j < Math.min(i + batchSize, totalGames); j++) { + PacmanNeatClient manager = managers.get(j); manager.setRenderNetwork(showNetworks); if (manager.getGameCompleteFuture().isDone()) continue; if (!paused) { - // Submit the update task and add to updatedManagers + // Submit the update task for the current batch Future future = threadPool.submit(() -> { manager.getGameManager().update(); }); @@ -230,7 +235,7 @@ public void render() { } } - // Wait for all games to be updated + // Wait for all games in the batch to be updated for (Future future : futures) { try { future.get(); @@ -243,31 +248,32 @@ public void render() { for (PacmanNeatClient manager : updatedManagers) { manager.getGameManager().postUpdate(); } + } - // Render everything - ScreenUtils.clear(0, 0, 0, 1); - batch.begin(); - - // Get a copy of the managers list and sort by score so the best are rendered first - List sortedManagers = new ArrayList<>(managers); - sortedManagers.sort(Comparator.comparingInt(manager -> -manager.getGameManager().getScore())); - - int renderCount = 0; - for (PacmanNeatClient manager : managers) { - if (manager.getGameCompleteFuture().isDone()) - continue; - if (renderCount >= visibleGames.x * visibleGames.y) - break; + // Render everything + ScreenUtils.clear(0, 0, 0, 1); + batch.begin(); + + // Render the managers (you can sort by score as in the original code) + int renderCount = 0; + for (PacmanNeatClient manager : managers) { + if (manager.getGameCompleteFuture().isDone()) + continue; + if (renderCount >= visibleGames.x * visibleGames.y) + break; + + // Render the game objects (e.g., Pacman, ghosts) + int gameX = renderCount % visibleGames.x; + int gameY = renderCount / visibleGames.x; + renderCount++; + + batch.setProjectionMatrix(camera.combined.cpy().translate(gameX * 8 * 28, gameY * 8 * 36, 0)); + manager.render(batch); + } - int gameX = renderCount % visibleGames.x; - int gameY = renderCount / visibleGames.x; - renderCount++; + batch.end(); +} - batch.setProjectionMatrix(camera.combined.cpy().translate(gameX * 8 * 28, gameY * 8 * 36, 0)); - manager.render(batch); - } - batch.end(); - } @Override public void dispose() { diff --git a/core/src/main/java/com/buaisociety/pacman/Searcher.java b/core/src/main/java/com/buaisociety/pacman/Searcher.java new file mode 100644 index 0000000..ea6ef03 --- /dev/null +++ b/core/src/main/java/com/buaisociety/pacman/Searcher.java @@ -0,0 +1,136 @@ +package com.buaisociety.pacman; + +import com.buaisociety.pacman.entity.Direction; +import com.buaisociety.pacman.maze.Maze; +import com.buaisociety.pacman.maze.Tile; +import com.buaisociety.pacman.maze.TileState; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.function.Predicate; +import java.util.Map; + +public class Searcher { + + /** + * Stores the result of the BFS search, including the found tile and the distance from the start tile. + */ + public static class SearchResult { + private final Tile tile; + private final int distance; + + public SearchResult(Tile tile, int distance) { + this.tile = tile; + this.distance = distance; + } + + public Tile getTile() { + return tile; + } + + public int getDistance() { + return distance; + } + } + + /** + * Performs a BFS search in each of the four directions to find tiles matching the predicate. + * + * @param startTile The starting tile for the BFS. + * @param predicate The predicate to test each tile. + * @return A Map containing the first matching tile and distance for each direction. + */ + public static Map findTileInAllDirections(@NotNull Tile startTile, @NotNull Predicate predicate) { + Map results = new EnumMap<>(Direction.class); + + for (Direction direction : Direction.values()) { + SearchResult result = findTileWithBFS(startTile, predicate, direction); + if (result != null) { + results.put(direction, result); + } + } + + return results; + } + + /** + * Performs a BFS to find the closest tile in the specified direction that matches the predicate. + * + * @param startTile The starting tile for the BFS. + * @param predicate The predicate to test each tile. + * @param direction The initial direction for the search. + * @return The SearchResult containing the tile and distance, or null if no matching tile is found. + */ + private static SearchResult findTileWithBFS(@NotNull Tile startTile, @NotNull Predicate predicate, @NotNull Direction direction) { + Queue queue = new ArrayDeque<>(); + Set visited = new HashSet<>(); + Queue distances = new ArrayDeque<>(); + + Vector2i startPosition = new Vector2i(startTile.getPosition()).add(getDirectionOffset(direction)); + queue.add(startPosition); + visited.add(startPosition); + distances.add(1); + + while (!queue.isEmpty()) { + Vector2ic currentPos = queue.poll(); + int currentDistance = distances.poll(); + Tile currentTile = startTile.getMaze().getTile(new Vector2i(currentPos)); + + if (predicate.test(currentTile)) { + return new SearchResult(currentTile, currentDistance); + } + + // Enqueue neighboring tiles only in the initial search direction + for (Vector2i neighbor : getNeighborsInDirection(new Vector2i(currentPos), direction)) { + Tile neighborTile = startTile.getMaze().getTile(neighbor); + if (neighborTile.getState() == TileState.WALL) continue; + + if (!visited.contains(neighbor)) { + visited.add(neighbor); + queue.add(neighbor); + distances.add(currentDistance + 1); + } + } + } + + return null; // No tile matching the predicate was found + } + + /** + * Returns the offset vector based on the initial direction. + * + * @param direction The direction for the offset. + * @return The vector offset for moving in that direction. + */ + private static Vector2i getDirectionOffset(Direction direction) { + return switch (direction) { + case UP -> new Vector2i(0, 1); + case DOWN -> new Vector2i(0, -1); + case LEFT -> new Vector2i(-1, 0); + case RIGHT -> new Vector2i(1, 0); + }; + } + + /** + * Returns neighboring tiles based on the given initial direction. + * + * @param position The tile position for which to get neighbors. + * @param direction The primary direction for the search. + * @return A list of neighboring tile positions limited to the specified direction. + */ + private static List getNeighborsInDirection(Vector2i position, Direction direction) { + List neighbors = new ArrayList<>(); + Vector2i offset = getDirectionOffset(direction); + neighbors.add(new Vector2i(position.x + offset.x, position.y + offset.y)); + return neighbors; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java index d9d5684..c77e48a 100644 --- a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java +++ b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java @@ -28,7 +28,7 @@ private SpecialTrainingConditions() { public static @NotNull EventListener onEntityPreSpawn() { return event -> { - // Prevent ghosts from spawning during training + // // Prevent ghosts from spawning during training if (event.getEntityType() == EntityType.GHOST) { event.setCancelled(true); } diff --git a/core/src/main/java/com/buaisociety/pacman/Tournament.java b/core/src/main/java/com/buaisociety/pacman/Tournament.java index 697fa74..624c2b1 100644 --- a/core/src/main/java/com/buaisociety/pacman/Tournament.java +++ b/core/src/main/java/com/buaisociety/pacman/Tournament.java @@ -47,7 +47,7 @@ public class Tournament extends ApplicationAdapter { */ public Behavior setupBehavior() { // TODO: Choose your best client here - File file = new File("saves" + File.separator + "oct20-4" + File.separator + "best-calculator-127.json"); + File file = new File("saves" + File.separator + "oct26-25" + File.separator + "best-calculator-22.json"); if (!file.exists()) { System.err.println("Could not find the file: " + file.getAbsolutePath()); return null; diff --git a/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java b/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java index d3fd62f..d06b32b 100644 --- a/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java +++ b/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java @@ -2,29 +2,54 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import java.util.Map; import com.buaisociety.pacman.maze.Maze; +import com.buaisociety.pacman.maze.Tile; +import com.buaisociety.pacman.maze.TileState; import com.buaisociety.pacman.sprite.DebugDrawing; import com.cjcrafter.neat.Client; +import com.buaisociety.pacman.Searcher; import com.buaisociety.pacman.entity.Direction; import com.buaisociety.pacman.entity.Entity; import com.buaisociety.pacman.entity.PacmanEntity; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; +import org.joml.Vector2f; +import org.joml.Vector2i; +import org.joml.Vector2ic; +import java.util.EnumMap; public class NeatPacmanBehavior implements Behavior { private final @NotNull Client client; private @Nullable PacmanEntity pacman; + private Vector2i lastTilePosition; // Track the last tile Pac-Man was on + private int updatesSinceLastTileChange = 0; // Counter for how long Pac-Man has been on the same tile + // Score modifiers help us maintain "multiple pools" of points. // This is great for training, because we can take away points from // specific pools of points instead of subtracting from all. private int scoreModifier = 0; + // new variables!! :) + private int lastScore = 0; + private int updatesSinceLastScore = 0; + // New class variable to store the direction weight (more pellets found, higher weight) + // New class variable to store the direction weight (more pellets found, higher weight) + private Map directionWeights = new EnumMap<>(Direction.class); + + // Initialize the direction weights public NeatPacmanBehavior(@NotNull Client client) { this.client = client; + for (Direction direction : Direction.values()) { + directionWeights.put(direction, 0); // Start all directions with weight 0 + } } + + /** * Returns the desired direction that the entity should move towards. * @@ -39,7 +64,16 @@ public Direction getDirection(@NotNull Entity entity) { } // SPECIAL TRAINING CONDITIONS - // TODO: Make changes here to help with your training... + int newScore = pacman.getMaze().getLevelManager().getScore(); + if (newScore > lastScore) { + lastScore = newScore; + updatesSinceLastScore = 0; + } + + if (updatesSinceLastScore++ > 60 * 15) { + pacman.kill(); + return Direction.UP; + } // END OF SPECIAL TRAINING CONDITIONS // We are going to use these directions a lot for different inputs. Get them all once for clarity and brevity @@ -47,18 +81,72 @@ public Direction getDirection(@NotNull Entity entity) { Direction left = pacman.getDirection().left(); Direction right = pacman.getDirection().right(); Direction behind = pacman.getDirection().behind(); + Tile currentTile = pacman.getMaze().getTile(pacman.getTilePosition()); + currentTile.setVisited(true); + Map nearestPellets = Searcher.findTileInAllDirections(currentTile, tile -> tile.getState() == TileState.PELLET); + + // Find the direction with the closest pellet + Direction bestDirection = null; + int closestDistance = Integer.MAX_VALUE; + + for (Direction direction : nearestPellets.keySet()) { + Searcher.SearchResult result = nearestPellets.get(direction); + if (result != null && result.getDistance() < closestDistance) { + closestDistance = result.getDistance(); + bestDirection = direction; + } + } + + // Move toward the closest pellet + if (bestDirection != null && pacman.canMove(bestDirection)) { + return bestDirection; + } + + + // Initialize maxDistance + int maxDistance = -1; + + // Recalculate maxDistance to normalize the distances + for (Searcher.SearchResult result : nearestPellets.values()) { + if (result != null) { + maxDistance = Math.max(maxDistance, result.getDistance()); + } + } + + // Make sure maxDistance is valid to avoid division by zero + if (maxDistance <= 0) { + maxDistance = 1; + } + + // Calculate the normalized distances for each direction + float nearestPelletForward = nearestPellets.get(forward) != null ? 1 - (float) nearestPellets.get(forward).getDistance() / maxDistance : 0; + float nearestPelletLeft = nearestPellets.get(left) != null ? 1 - (float) nearestPellets.get(left).getDistance() / maxDistance : 0; + float nearestPelletRight = nearestPellets.get(right) != null ? 1 - (float) nearestPellets.get(right).getDistance() / maxDistance : 0; + float nearestPelletBehind = nearestPellets.get(behind) != null ? 1 - (float) nearestPellets.get(behind).getDistance() / maxDistance : 0; - // Input nodes 1, 2, 3, and 4 show if the pacman can move in the forward, left, right, and behind directions boolean canMoveForward = pacman.canMove(forward); boolean canMoveLeft = pacman.canMove(left); boolean canMoveRight = pacman.canMove(right); boolean canMoveBehind = pacman.canMove(behind); + boolean wallForward = !pacman.canMove(forward); // There's a wall if Pac-Man can't move forward + boolean wallLeft = !pacman.canMove(left); + boolean wallRight = !pacman.canMove(right); + boolean wallBehind = !pacman.canMove(behind); + float[] outputs = client.getCalculator().calculate(new float[]{ canMoveForward ? 1f : 0f, canMoveLeft ? 1f : 0f, canMoveRight ? 1f : 0f, canMoveBehind ? 1f : 0f, + nearestPelletForward, + nearestPelletLeft, + nearestPelletRight, + nearestPelletBehind, + wallForward ? 1f : 0f, // New input for wall in front + wallLeft ? 1f : 0f, // New input for wall to the left + wallRight ? 1f : 0f, // New input for wall to the right + wallBehind ? 1f : 0f // New input for wall behind }).join(); int index = 0; @@ -78,18 +166,48 @@ public Direction getDirection(@NotNull Entity entity) { default -> throw new IllegalStateException("Unexpected value: " + index); }; - client.setScore(pacman.getMaze().getLevelManager().getScore() + scoreModifier); + if (!pacman.canMove(newDirection)) { + client.setScore(client.getScore() - 10); // Penalize for trying to move into a wall + } + + int pelletReward = 0; + for (Direction direction : nearestPellets.keySet()) { + Searcher.SearchResult result = nearestPellets.get(direction); + if (result != null && result.getDistance() <= 5) { // Close distance pellet bonus + pelletReward += 5 - result.getDistance(); // Reward based on proximity to pellet + } + } + + // Check if Pac-Man moved to a new tile + if (lastTilePosition == null || !lastTilePosition.equals(pacman.getTilePosition())) { + // Pac-Man has moved to a new tile, reset the counter + lastTilePosition = new Vector2i(pacman.getTilePosition()); + updatesSinceLastTileChange = 0; + } else { + // Pac-Man is still on the same tile, increment the counter + updatesSinceLastTileChange++; + } + + // Penalize if Pac-Man stays too long on the same tile + int timeOnSameTilePenalty = 0; + if (updatesSinceLastTileChange > 20) { // Adjust 20 as needed + timeOnSameTilePenalty = -5; + } + + int timePenalty = updatesSinceLastScore / 10; + int tileVisitPenalty = currentTile.isVisited() ? -5 : 0; + client.setScore(pacman.getMaze().getLevelManager().getScore() + scoreModifier + pelletReward + tileVisitPenalty - timePenalty + timeOnSameTilePenalty); return newDirection; } @Override public void render(@NotNull SpriteBatch batch) { // TODO: You can render debug information here - /* + if (pacman != null) { DebugDrawing.outlineTile(batch, pacman.getMaze().getTile(pacman.getTilePosition()), Color.RED); DebugDrawing.drawDirection(batch, pacman.getTilePosition().x() * Maze.TILE_SIZE, pacman.getTilePosition().y() * Maze.TILE_SIZE, pacman.getDirection(), Color.RED); } - */ + } -} +} \ No newline at end of file diff --git a/core/src/main/java/com/buaisociety/pacman/entity/behavior/TournamentBehavior.java b/core/src/main/java/com/buaisociety/pacman/entity/behavior/TournamentBehavior.java index 22cab50..e72f21f 100644 --- a/core/src/main/java/com/buaisociety/pacman/entity/behavior/TournamentBehavior.java +++ b/core/src/main/java/com/buaisociety/pacman/entity/behavior/TournamentBehavior.java @@ -1,11 +1,15 @@ package com.buaisociety.pacman.entity.behavior; +import com.buaisociety.pacman.Searcher; import com.buaisociety.pacman.entity.Direction; import com.buaisociety.pacman.entity.Entity; import com.buaisociety.pacman.entity.PacmanEntity; +import com.buaisociety.pacman.maze.Tile; +import com.buaisociety.pacman.maze.TileState; import com.cjcrafter.neat.compute.Calculator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Map; public class TournamentBehavior implements Behavior { @@ -48,11 +52,77 @@ public Direction getDirection(@NotNull Entity entity) { // --- END OF DO NOT REMOVE --- // TODO: Put all your code for info into the neural network here + Direction forward = pacman.getDirection(); + Direction left = pacman.getDirection().left(); + Direction right = pacman.getDirection().right(); + Direction behind = pacman.getDirection().behind(); + Tile currentTile = pacman.getMaze().getTile(pacman.getTilePosition()); + currentTile.setVisited(true); + Map nearestPellets = Searcher.findTileInAllDirections(currentTile, tile -> tile.getState() == TileState.PELLET); - float[] inputs = new float[] { - // TODO: Add your inputs here - }; - float[] outputs = calculator.calculate(inputs).join(); + // Find the direction with the closest pellet + Direction bestDirection = null; + int closestDistance = Integer.MAX_VALUE; + + for (Direction direction : nearestPellets.keySet()) { + Searcher.SearchResult result = nearestPellets.get(direction); + if (result != null && result.getDistance() < closestDistance) { + closestDistance = result.getDistance(); + bestDirection = direction; + } + } + + // Move toward the closest pellet + if (bestDirection != null && pacman.canMove(bestDirection)) { + return bestDirection; + } + + + // Initialize maxDistance + int maxDistance = -1; + + // Recalculate maxDistance to normalize the distances + for (Searcher.SearchResult result : nearestPellets.values()) { + if (result != null) { + maxDistance = Math.max(maxDistance, result.getDistance()); + } + } + + // Make sure maxDistance is valid to avoid division by zero + if (maxDistance <= 0) { + maxDistance = 1; + } + + // Calculate the normalized distances for each direction + float nearestPelletForward = nearestPellets.get(forward) != null ? 1 - (float) nearestPellets.get(forward).getDistance() / maxDistance : 0; + float nearestPelletLeft = nearestPellets.get(left) != null ? 1 - (float) nearestPellets.get(left).getDistance() / maxDistance : 0; + float nearestPelletRight = nearestPellets.get(right) != null ? 1 - (float) nearestPellets.get(right).getDistance() / maxDistance : 0; + float nearestPelletBehind = nearestPellets.get(behind) != null ? 1 - (float) nearestPellets.get(behind).getDistance() / maxDistance : 0; + + boolean canMoveForward = pacman.canMove(forward); + boolean canMoveLeft = pacman.canMove(left); + boolean canMoveRight = pacman.canMove(right); + boolean canMoveBehind = pacman.canMove(behind); + + boolean wallForward = !pacman.canMove(forward); // There's a wall if Pac-Man can't move forward + boolean wallLeft = !pacman.canMove(left); + boolean wallRight = !pacman.canMove(right); + boolean wallBehind = !pacman.canMove(behind); + + float[] outputs = calculator.calculate(new float[]{ + canMoveForward ? 1f : 0f, + canMoveLeft ? 1f : 0f, + canMoveRight ? 1f : 0f, + canMoveBehind ? 1f : 0f, + nearestPelletForward, + nearestPelletLeft, + nearestPelletRight, + nearestPelletBehind, + wallForward ? 1f : 0f, // New input for wall in front + wallLeft ? 1f : 0f, // New input for wall to the left + wallRight ? 1f : 0f, // New input for wall to the right + wallBehind ? 1f : 0f // New input for wall behind + }).join(); // Chooses the maximum output as the direction to go... feel free to change this ofc! // Adjust this to whatever you used in the NeatPacmanBehavior.class diff --git a/core/src/main/java/com/buaisociety/pacman/maze/Tile.java b/core/src/main/java/com/buaisociety/pacman/maze/Tile.java index 1de2ede..983e27b 100644 --- a/core/src/main/java/com/buaisociety/pacman/maze/Tile.java +++ b/core/src/main/java/com/buaisociety/pacman/maze/Tile.java @@ -21,6 +21,20 @@ public Tile(@NotNull Maze maze, @NotNull Vector2i position, @NotNull TileState s this.position = position; this.state = state; } + public boolean isPassable() { + return this.state == TileState.PELLET || this.state == TileState.POWER_PELLET || this.state == TileState.SPACE; + } + private boolean visited = false; // New field to track visited status + + // Call this method when Pac-Man visits the tile + public void setVisited(boolean visited) { + this.visited = visited; + } + + // Check if Pac-Man has visited this tile + public boolean isVisited() { + return visited; + } /** * Returns the maze that this tile is a part of.