From b236d07d0efbe5c10cc08acb32085001747e80db Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Fri, 25 Oct 2024 23:43:40 -0400 Subject: [PATCH 01/17] Added basic network behavior --- .../java/com/buaisociety/pacman/Main.java | 14 +- .../com/buaisociety/pacman/NeatConfig.java | 14 ++ .../entity/behavior/NeatPacmanBehavior.java | 170 ++++++++++++++++-- .../pacman/lwjgl3/Lwjgl3Launcher.java | 5 +- 4 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 core/src/main/java/com/buaisociety/pacman/NeatConfig.java diff --git a/core/src/main/java/com/buaisociety/pacman/Main.java b/core/src/main/java/com/buaisociety/pacman/Main.java index 29113e7..a7bfa39 100644 --- a/core/src/main/java/com/buaisociety/pacman/Main.java +++ b/core/src/main/java/com/buaisociety/pacman/Main.java @@ -75,7 +75,7 @@ public void create() { secondLoop = new GameLoop(1); int processors = Runtime.getRuntime().availableProcessors(); - threadPool = Executors.newFixedThreadPool(processors); + threadPool = Executors.newFixedThreadPool(Math.max(1, processors - 1)); System.out.println("Using " + processors + " threads"); // When all games have ended, reset @@ -120,12 +120,12 @@ public void create() { return impl; } else { Parameters neatParameters = new Parameters(); - neatParameters.setMutateWeightChance(0.75f); - neatParameters.setWeightCoefficient(1.0f); // speciate on weight more often - 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); + neatParameters.setMutateWeightChance(NeatConfig.mutateWeightChance); + neatParameters.setWeightCoefficient(NeatConfig.weightCoefficient); // speciate on weight more often + neatParameters.setTargetClientsPerSpecies(NeatConfig.targetClientsPerSpecies); // targeting ~12 clients per species + neatParameters.setStagnationLimit(NeatConfig.stagnationLimit); // lower stagnation limit + neatParameters.setUseBiasNode(NeatConfig.biasEnabled); // use bias node + return new NeatImpl(NeatConfig.neatInputNodes, NeatConfig.neatOutputNodes, totalGames, neatParameters); } } diff --git a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java new file mode 100644 index 0000000..748ffc6 --- /dev/null +++ b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java @@ -0,0 +1,14 @@ +package com.buaisociety.pacman; + +public class NeatConfig { + // NEAT parameters + public static float mutateWeightChance = 0.75f; + public static float weightCoefficient = 1.0f; + public static int targetClientsPerSpecies = 12; + public static int stagnationLimit = 10; // what does this do? + + public static boolean biasEnabled = true; + + public static int neatInputNodes = 6; + public static int neatOutputNodes = 4; +} 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..42943a1 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 @@ -3,6 +3,8 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.SpriteBatch; 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.entity.Direction; @@ -10,6 +12,13 @@ import com.buaisociety.pacman.entity.PacmanEntity; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; public class NeatPacmanBehavior implements Behavior { @@ -21,6 +30,13 @@ public class NeatPacmanBehavior implements Behavior { // specific pools of points instead of subtracting from all. private int scoreModifier = 0; + public ArrayList moveHistory = new ArrayList<>(); + + int lastScore = 0; + int updateSinceLastScore = 0; + + int[][] distances; + public NeatPacmanBehavior(@NotNull Client client) { this.client = client; } @@ -37,28 +53,70 @@ public Direction getDirection(@NotNull Entity entity) { if (pacman == null) { pacman = (PacmanEntity) entity; } - - // SPECIAL TRAINING CONDITIONS - // TODO: Make changes here to help with your training... - // END OF SPECIAL TRAINING CONDITIONS - + distances = computeDistances(); // We are going to use these directions a lot for different inputs. Get them all once for clarity and brevity Direction forward = pacman.getDirection(); Direction left = pacman.getDirection().left(); Direction right = pacman.getDirection().right(); Direction behind = pacman.getDirection().behind(); + + // SPECIAL TRAINING CONDITIONS + // TODO: Make changes here to help with your training... + int newScore = pacman.getMaze().getLevelManager().getScore(); + if (newScore > lastScore) { + lastScore = newScore; + updateSinceLastScore = 0; + } else { + updateSinceLastScore++; + } + + if (updateSinceLastScore > 60 * 60) { + pacman.kill(); + return Direction.UP; + + } + + // END OF SPECIAL TRAINING CONDITIONS + + // 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); + float[] rayCast = new float[4]; + for (int i = 0; i < 4; i++) { + Direction direction = switch (i) { + case 0 -> forward; + case 1 -> left; + case 2 -> right; + case 3 -> behind; + default -> throw new IllegalStateException("Unexpected value: " + i); + }; + Vector2i position = new Vector2i(pacman.getTilePosition().x(), pacman.getTilePosition().y()); + while (pacman.getMaze().getTile(position).getState() != TileState.WALL) { + position.add(direction.getDx(), direction.getDy()); + if (position.x() < 0 || position.x() >= pacman.getMaze().getDimensions().x() || position.y() < 0 || position.y() >= pacman.getMaze().getDimensions().y()) { + break; + } + rayCast[i] = (float) Math.sqrt(Math.pow(position.x() - pacman.getTilePosition().x(), 2) + Math.pow(position.y() - pacman.getTilePosition().y(), 2)); + } + } + + Vector2ic dimensions = pacman.getMaze().getDimensions(); + + Tile nearestPellet = getNearestPellet(); + Vector2d relativeCoordinates = translateRelative(nearestPellet.getPosition()); + float[] outputs = client.getCalculator().calculate(new float[]{ - canMoveForward ? 1f : 0f, - canMoveLeft ? 1f : 0f, - canMoveRight ? 1f : 0f, - canMoveBehind ? 1f : 0f, + rayCast[0], + rayCast[1], + rayCast[2], + rayCast[3], + (float) relativeCoordinates.x() / (float) dimensions.x(), + (float) relativeCoordinates.y() / (float) dimensions.y() }).join(); int index = 0; @@ -78,7 +136,20 @@ public Direction getDirection(@NotNull Entity entity) { default -> throw new IllegalStateException("Unexpected value: " + index); }; - client.setScore(pacman.getMaze().getLevelManager().getScore() + scoreModifier); + client.setScore(pacman.getMaze().getLevelManager().getScore() + scoreModifier); // need to improve score hurisitcs + // ideas + /* + * Give reward for moving in direction of nearest pellet + * Give reward for eating pellet + * Give reward for eating power pellet + * Give reward for eating fruit + * Give reward for eating ghost + * Give penalty for getting eaten by ghost + * Being alive is a penalty unless you run out of power pellets + * + * Reward engineering + */ + moveHistory.add(newDirection); return newDirection; } @@ -91,5 +162,84 @@ public void render(@NotNull SpriteBatch batch) { DebugDrawing.drawDirection(batch, pacman.getTilePosition().x() * Maze.TILE_SIZE, pacman.getTilePosition().y() * Maze.TILE_SIZE, pacman.getDirection(), Color.RED); } */ + if(pacman != null){ + Tile nearestPellet = getNearestPellet(); + DebugDrawing.outlineTile(batch, nearestPellet, Color.GREEN); + } + } + + public Vector2d translateRelative(Vector2ic coordinates) { + // new coordinates are relative to pacman + Vector2i pacmanCoordinates = pacman.getTilePosition(); + Vector2d newCoordinates = new Vector2d(coordinates.x() - pacmanCoordinates.x(), coordinates.y() - pacmanCoordinates.y()); + // also orient the coordinates to the direction of pacman + switch (pacman.getDirection()) { + case UP: + break; + case DOWN: + newCoordinates.set(-newCoordinates.x, -newCoordinates.y); + break; + case LEFT: + newCoordinates.set(-newCoordinates.y, newCoordinates.x); + break; + case RIGHT: + newCoordinates.set(newCoordinates.y, -newCoordinates.x); + break; + } + + return newCoordinates; + } + + public Tile getNearestPellet() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Tile nearestPellet = null; + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + Tile tile = pacman.getMaze().getTile(x, y); + if (tile.getState() == TileState.PELLET || tile.getState() == TileState.POWER_PELLET) { + if (nearestPellet == null) { + nearestPellet = tile; + } else { + if (distances[x][y] < distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()]) { + nearestPellet = tile; + } + } + } + } + } + return nearestPellet; } + + public int[][] computeDistances() { + // path search + Vector2ic dimensions = this.pacman.getMaze().getDimensions(); + Vector2ic pacmanPosition = this.pacman.getTilePosition(); + int[][] distance = new int[dimensions.x()][dimensions.y()]; + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + distance[x][y] = -1; + } + } + distance[pacmanPosition.x()][pacmanPosition.y()] = 0; + Queue queue = new LinkedList<>(); + queue.add(pacmanPosition); + while (!queue.isEmpty()) { + Vector2ic current = queue.poll(); + for (Direction direction : Direction.values()) { + int dx = direction.getDx(); + int dy = direction.getDy(); + Vector2i next = new Vector2i(current.x() + dx, current.y() + dy); + if (next.x() < 0 || next.x() >= dimensions.x() || next.y() < 0 || next.y() >= dimensions.y()) { + continue; + } + if (this.pacman.getMaze().getTile(next).getState() != TileState.WALL && distance[next.x()][next.y()] == -1) { + distance[next.x()][next.y()] = distance[current.x()][current.y()] + 1; + queue.add(next); + } + } + } + + return distance; + } + } diff --git a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java index 0e83fca..f013cf1 100644 --- a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java +++ b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java @@ -13,14 +13,13 @@ public static void main(String[] args) { } private static Lwjgl3Application createApplication() { - boolean isTraining = false; // set this as false to try out the tournament settings + boolean isTraining = true; // set this as false to try out the tournament settings if (isTraining) { Lwjgl3ApplicationConfiguration config = getDefaultConfiguration(); // disable vsync to run the game as fast as possible config.useVsync(false); // hard limit on fps to see the game running at a reasonable speed - config.setForegroundFPS(400); - return new Lwjgl3Application(new Main(), config); + return new Lwjgl3Application(new Main(), config); } else { Lwjgl3ApplicationConfiguration config = getDefaultConfiguration(); // enable vsync to limit the fps to the monitor refresh rate From 205e0de33196489232cc8b5f0fbb2ce73271dc33 Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Fri, 25 Oct 2024 23:51:30 -0400 Subject: [PATCH 02/17] Normalized raycasting --- .../pacman/entity/behavior/NeatPacmanBehavior.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 42943a1..09e797a 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 @@ -54,6 +54,7 @@ public Direction getDirection(@NotNull Entity entity) { pacman = (PacmanEntity) entity; } distances = computeDistances(); + Vector2ic dimensions = pacman.getMaze().getDimensions(); // We are going to use these directions a lot for different inputs. Get them all once for clarity and brevity Direction forward = pacman.getDirection(); Direction left = pacman.getDirection().left(); @@ -101,11 +102,10 @@ public Direction getDirection(@NotNull Entity entity) { if (position.x() < 0 || position.x() >= pacman.getMaze().getDimensions().x() || position.y() < 0 || position.y() >= pacman.getMaze().getDimensions().y()) { break; } - rayCast[i] = (float) Math.sqrt(Math.pow(position.x() - pacman.getTilePosition().x(), 2) + Math.pow(position.y() - pacman.getTilePosition().y(), 2)); + rayCast[i] = (float) Math.sqrt(Math.pow((0.0 + position.x() - pacman.getTilePosition().x())/dimensions.x(), 2) + Math.pow((position.y() - pacman.getTilePosition().y() + 0.0)/dimensions.y(), 2)); } } - Vector2ic dimensions = pacman.getMaze().getDimensions(); Tile nearestPellet = getNearestPellet(); Vector2d relativeCoordinates = translateRelative(nearestPellet.getPosition()); From 4a41d9e4fcc82f919c5a756971735f0e8582aefd Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Fri, 25 Oct 2024 23:57:09 -0400 Subject: [PATCH 03/17] Improved directionality --- .../entity/behavior/NeatPacmanBehavior.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) 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 09e797a..ab5a9a9 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 @@ -72,7 +72,7 @@ public Direction getDirection(@NotNull Entity entity) { updateSinceLastScore++; } - if (updateSinceLastScore > 60 * 60) { + if (updateSinceLastScore > 60 * 20) { pacman.kill(); return Direction.UP; @@ -174,17 +174,23 @@ public Vector2d translateRelative(Vector2ic coordinates) { Vector2d newCoordinates = new Vector2d(coordinates.x() - pacmanCoordinates.x(), coordinates.y() - pacmanCoordinates.y()); // also orient the coordinates to the direction of pacman switch (pacman.getDirection()) { - case UP: - break; - case DOWN: - newCoordinates.set(-newCoordinates.x, -newCoordinates.y); - break; - case LEFT: - newCoordinates.set(-newCoordinates.y, newCoordinates.x); - break; - case RIGHT: - newCoordinates.set(newCoordinates.y, -newCoordinates.x); - break; + case UP -> { + double temp = newCoordinates.x(); + newCoordinates.x = -newCoordinates.y(); + newCoordinates.y = temp; + } + case DOWN -> { + double temp = newCoordinates.x(); + newCoordinates.x = newCoordinates.y(); + newCoordinates.y = -temp; + } + case LEFT -> { + newCoordinates.x = -newCoordinates.x(); + newCoordinates.y = -newCoordinates.y(); + } + case RIGHT -> { + // do nothing + } } return newCoordinates; From 9936301e07f0d60e034230c54ba0ffdff0af6185 Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 00:07:02 -0400 Subject: [PATCH 04/17] Improved directionality --- .../entity/behavior/NeatPacmanBehavior.java | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) 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 ab5a9a9..27f4040 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 @@ -25,6 +25,11 @@ public class NeatPacmanBehavior implements Behavior { private final @NotNull Client client; private @Nullable PacmanEntity pacman; + private Direction forward = Direction.UP; + private Direction left = Direction.LEFT; + private Direction right = Direction.RIGHT; + private Direction behind = Direction.DOWN; + // 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. @@ -56,10 +61,10 @@ public Direction getDirection(@NotNull Entity entity) { distances = computeDistances(); Vector2ic dimensions = pacman.getMaze().getDimensions(); // We are going to use these directions a lot for different inputs. Get them all once for clarity and brevity - Direction forward = pacman.getDirection(); - Direction left = pacman.getDirection().left(); - Direction right = pacman.getDirection().right(); - Direction behind = pacman.getDirection().behind(); + forward = pacman.getDirection(); + left = pacman.getDirection().left(); + right = pacman.getDirection().right(); + behind = pacman.getDirection().behind(); // SPECIAL TRAINING CONDITIONS @@ -170,30 +175,18 @@ public void render(@NotNull SpriteBatch batch) { public Vector2d translateRelative(Vector2ic coordinates) { // new coordinates are relative to pacman + Vector2i pacmanCoordinates = pacman.getTilePosition(); - Vector2d newCoordinates = new Vector2d(coordinates.x() - pacmanCoordinates.x(), coordinates.y() - pacmanCoordinates.y()); + Vector2d relative = new Vector2d(coordinates.x() - pacmanCoordinates.x(), coordinates.y() - pacmanCoordinates.y()); // also orient the coordinates to the direction of pacman - switch (pacman.getDirection()) { - case UP -> { - double temp = newCoordinates.x(); - newCoordinates.x = -newCoordinates.y(); - newCoordinates.y = temp; - } - case DOWN -> { - double temp = newCoordinates.x(); - newCoordinates.x = newCoordinates.y(); - newCoordinates.y = -temp; - } - case LEFT -> { - newCoordinates.x = -newCoordinates.x(); - newCoordinates.y = -newCoordinates.y(); - } - case RIGHT -> { - // do nothing - } - } - - return newCoordinates; + /* + * ex + * pelletX = (float) (relative.x() * forward.getDx() + relative.x() * right.getDx()); + * pelletY = (float) (relative.y() * forward.getDy() + relative.y() * right.getDy());*/ + double newCoordinatesX = relative.x() * forward.getDx() + relative.x() * right.getDx(); + double newCoordinatesY = relative.y() * forward.getDy() + relative.y() * right.getDy(); + relative.set(newCoordinatesX, newCoordinatesY); + return relative; } public Tile getNearestPellet() { From cf081ad6dc4d5b9ec03a780c294fc2684fe1d64a Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 09:27:09 -0400 Subject: [PATCH 05/17] Added randomization --- .../pacman/entity/behavior/NeatPacmanBehavior.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 27f4040..01ea042 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 @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.Queue; +import java.util.Random; public class NeatPacmanBehavior implements Behavior { @@ -42,8 +43,11 @@ public class NeatPacmanBehavior implements Behavior { int[][] distances; + Random random; + public NeatPacmanBehavior(@NotNull Client client) { this.client = client; + random = new Random(); } /** @@ -58,6 +62,8 @@ public Direction getDirection(@NotNull Entity entity) { if (pacman == null) { pacman = (PacmanEntity) entity; } + + distances = computeDistances(); Vector2ic dimensions = pacman.getMaze().getDimensions(); // We are going to use these directions a lot for different inputs. Get them all once for clarity and brevity @@ -121,7 +127,9 @@ public Direction getDirection(@NotNull Entity entity) { rayCast[2], rayCast[3], (float) relativeCoordinates.x() / (float) dimensions.x(), - (float) relativeCoordinates.y() / (float) dimensions.y() + (float) relativeCoordinates.y() / (float) dimensions.y(), + random.nextFloat(), + }).join(); int index = 0; From 00f931d5cc8169a2a3e3be58417e63fd39f9f31f Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 13:58:54 -0400 Subject: [PATCH 06/17] Save --- .../entity/behavior/NeatPacmanBehavior.java | 265 ++++++++++++++++-- 1 file changed, 246 insertions(+), 19 deletions(-) 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 01ea042..5d49bc1 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,6 +2,7 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.buaisociety.pacman.entity.GhostEntity; import com.buaisociety.pacman.maze.Maze; import com.buaisociety.pacman.maze.Tile; import com.buaisociety.pacman.maze.TileState; @@ -16,10 +17,7 @@ import org.joml.Vector2i; import org.joml.Vector2ic; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.Queue; -import java.util.Random; +import java.util.*; public class NeatPacmanBehavior implements Behavior { @@ -31,10 +29,11 @@ public class NeatPacmanBehavior implements Behavior { private Direction right = Direction.RIGHT; private Direction behind = Direction.DOWN; + // 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; + private float scoreModifier = 0; public ArrayList moveHistory = new ArrayList<>(); @@ -79,11 +78,15 @@ public Direction getDirection(@NotNull Entity entity) { if (newScore > lastScore) { lastScore = newScore; updateSinceLastScore = 0; + if(newScore > 100_000){ + pacman.kill(); + return Direction.UP; + } } else { updateSinceLastScore++; } - if (updateSinceLastScore > 60 * 20) { + if (updateSinceLastScore > 60 * 10) { pacman.kill(); return Direction.UP; @@ -113,24 +116,115 @@ public Direction getDirection(@NotNull Entity entity) { if (position.x() < 0 || position.x() >= pacman.getMaze().getDimensions().x() || position.y() < 0 || position.y() >= pacman.getMaze().getDimensions().y()) { break; } - rayCast[i] = (float) Math.sqrt(Math.pow((0.0 + position.x() - pacman.getTilePosition().x())/dimensions.x(), 2) + Math.pow((position.y() - pacman.getTilePosition().y() + 0.0)/dimensions.y(), 2)); + rayCast[i] = (float) Math.sqrt(Math.pow((0.0 + position.x() - pacman.getTilePosition().x()) / dimensions.x(), 2) + Math.pow((position.y() - pacman.getTilePosition().y() + 0.0) / dimensions.y(), 2)); } } - Tile nearestPellet = getNearestPellet(); Vector2d relativeCoordinates = translateRelative(nearestPellet.getPosition()); - float[] outputs = client.getCalculator().calculate(new float[]{ + Direction suggestedDirection = getFirstStepToTile(nearestPellet); + if (suggestedDirection == null) { + suggestedDirection = pacman.getDirection(); + } + // transform suggested direction to be relative to pacman + Vector2d suggestedDirectionRelative = rotateRelative(new Vector2d(suggestedDirection.getDx(), suggestedDirection.getDy())); + + Tile nearestPowerPellet = nearestPowerPellet(); + if(nearestPowerPellet == null){ + nearestPowerPellet = pacman.getMaze().getTile(pacman.getTilePosition()); + } + Vector2d relativePowerPellet = translateRelative(nearestPowerPellet.getPosition()); + + Direction suggestedPowerDirection = getFirstStepToTile(nearestPowerPellet); + if (suggestedPowerDirection == null) { + suggestedPowerDirection = pacman.getDirection(); + } + Vector2d suggestedPowerDirectionRelative = rotateRelative(new Vector2d(suggestedPowerDirection.getDx(), suggestedPowerDirection.getDy())); + + + List entities = pacman.getMaze().getEntities(); + + List ghosts = new ArrayList<>(); + for(Entity mapEntity : entities){ + if(mapEntity instanceof PacmanEntity){ + continue; + } + if(mapEntity instanceof GhostEntity){ + ghosts.add((GhostEntity) mapEntity); + } + } + + int[] ghostDistances = new int[4]; + Vector2d[] ghostDirections = new Vector2d[4]; + for(int i = 0; i < 4; i++){ + ghostDistances[i] = -1; + ghostDirections[i] = null; + } + int ghostIndex = 0; + for(GhostEntity ghost : ghosts){ + int distance = distances[ghost.getTilePosition().x()][ghost.getTilePosition().y()]; + if(distance == -1){ + continue; + } + Vector2i vector2i = ghost.getTilePosition(); + Tile tile = pacman.getMaze().getTile(vector2i); + Direction direction = getFirstStepToTile(tile); + if(direction == null){ + continue; + } + ghostDistances[ghostIndex] = distance; + Vector2d relativeDirection = new Vector2d(direction.getDx(), direction.getDy()); + relativeDirection = rotateRelative(relativeDirection); + ghostDirections[ghostIndex] = relativeDirection; + ghostIndex++; + } + + + float[] inputs = new float[]{ rayCast[0], rayCast[1], rayCast[2], rayCast[3], (float) relativeCoordinates.x() / (float) dimensions.x(), (float) relativeCoordinates.y() / (float) dimensions.y(), - random.nextFloat(), - }).join(); + // suggested direction x, y + (float) suggestedDirectionRelative.x(), + (float) suggestedDirectionRelative.y(), + // distance to nearest pellet + distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()], + +// (float) relativePowerPellet.x() / (float) dimensions.x(), +// (float) relativePowerPellet.y() / (float) dimensions.y(), +// (float) suggestedPowerDirectionRelative.x(), +// (float) suggestedPowerDirectionRelative.y(), +// // distance to nearest power pellet +// distances[nearestPowerPellet.getPosition().x()][nearestPowerPellet.getPosition().y()], +// ghostDistances[0], +// ghostDistances[1], +// ghostDistances[2], +// ghostDistances[3], +// ghostDirections[0] == null ? 0 : (float) ghostDirections[0].x(), +// ghostDirections[0] == null ? 0 : (float) ghostDirections[0].y(), +// ghostDirections[1] == null ? 0 : (float) ghostDirections[1].x(), +// ghostDirections[1] == null ? 0 : (float) ghostDirections[1].y(), +// ghostDirections[2] == null ? 0 : (float) ghostDirections[2].x(), +// ghostDirections[2] == null ? 0 : (float) ghostDirections[2].y(), +// ghostDirections[3] == null ? 0 : (float) ghostDirections[3].x(), +// ghostDirections[3] == null ? 0 : (float) ghostDirections[3].y(), + + // pacman.getDirection().ordinal() / 4.0f, + // (float) pacman.getTilePosition().x() / (float) dimensions.x(), + // (float) pacman.getTilePosition().y() / (float) dimensions.y(), + + random.nextFloat() * 2 - 1, + }; + +// System.out.println(Arrays.toString(inputs)); + + + float[] outputs = client.getCalculator().calculate(inputs).join(); int index = 0; float max = outputs[0]; @@ -149,7 +243,6 @@ public Direction getDirection(@NotNull Entity entity) { default -> throw new IllegalStateException("Unexpected value: " + index); }; - client.setScore(pacman.getMaze().getLevelManager().getScore() + scoreModifier); // need to improve score hurisitcs // ideas /* * Give reward for moving in direction of nearest pellet @@ -162,10 +255,32 @@ public Direction getDirection(@NotNull Entity entity) { * * Reward engineering */ + // Modded Rewards + Vector2ic newPosition = new Vector2i(pacman.getTilePosition().x() + newDirection.getDx(), pacman.getTilePosition().y() + newDirection.getDy()); +// if(newPosition.distanceSquared(nearestPellet.getPosition()) < pacman.getTilePosition().distanceSquared(nearestPellet.getPosition())){ +// scoreModifier += 5; +// } + +// if(pacman.getMaze().getTile((Vector2i) newPosition).getState() == TileState.PELLET){ +// scoreModifier += 100; +// } +// +// if (!pacman.canMove(newDirection)) { +// scoreModifier -= 10; +// } + +// scoreModifier -= 0.1f; + + client.setScore( + pacman.getMaze().getLevelManager().getScore() + scoreModifier + ); // need to improve score hurisitcs + + moveHistory.add(newDirection); return newDirection; } + @Override public void render(@NotNull SpriteBatch batch) { // TODO: You can render debug information here @@ -175,28 +290,141 @@ public void render(@NotNull SpriteBatch batch) { DebugDrawing.drawDirection(batch, pacman.getTilePosition().x() * Maze.TILE_SIZE, pacman.getTilePosition().y() * Maze.TILE_SIZE, pacman.getDirection(), Color.RED); } */ - if(pacman != null){ + if (pacman != null) { Tile nearestPellet = getNearestPellet(); DebugDrawing.outlineTile(batch, nearestPellet, Color.GREEN); } } + /** + * Gets the first direction Pacman should move to reach the target tile via the shortest path. + * Uses the pre-computed distances array to work backwards from the target to find the optimal first step. + * + * @param targetTile the destination tile we want to reach + * @return the Direction to move in, or null if no path exists + */ + @Nullable + public Direction getFirstStepToTile(@NotNull Tile targetTile) { + Vector2ic targetPos = targetTile.getPosition(); + Vector2ic currentPos = pacman.getTilePosition(); + Vector2ic dimensions = pacman.getMaze().getDimensions(); + + // If we're already at the target, no need to move + if (currentPos.equals(targetPos)) { + return null; + } + + // If target is unreachable (distance == -1), return null + if (distances[targetPos.x()][targetPos.y()] == -1) { + return null; + } + + // Start from the target and work backwards to find the first step + Vector2i currentBacktrack = new Vector2i(targetPos); + int currentDistance = distances[targetPos.x()][targetPos.y()]; + + // Keep backtracking until we find a position adjacent to our current position + while (currentDistance > 1) { + boolean foundStep = false; + + // Check all adjacent tiles + for (Direction dir : Direction.values()) { + int nextX = currentBacktrack.x() + dir.getDx(); + int nextY = currentBacktrack.y() + dir.getDy(); + + // Skip if out of bounds + if (nextX < 0 || nextX >= dimensions.x() || + nextY < 0 || nextY >= dimensions.y()) { + continue; + } + + // If this adjacent tile is one step closer to Pacman + if (distances[nextX][nextY] == currentDistance - 1) { + currentBacktrack.set(nextX, nextY); + currentDistance--; + foundStep = true; + break; + } + } + + // If we couldn't find a valid step back, something's wrong with the path + if (!foundStep) { + return null; + } + } + + // Now currentBacktrack should be adjacent to Pacman's position + // Find which direction leads to it + for (Direction dir : Direction.values()) { + int stepX = currentPos.x() + dir.getDx(); + int stepY = currentPos.y() + dir.getDy(); + + if (stepX == currentBacktrack.x() && stepY == currentBacktrack.y()) { + return dir; + } + } + + // If we get here, something went wrong + return null; + } + + /** + * Translates the given coordinates to be relative to Pacman's position and orientation. + * This is useful for converting the target tile's position to a relative position. + * + * @param coordinates the coordinates to translate + * @return the translated coordinates + */ public Vector2d translateRelative(Vector2ic coordinates) { // new coordinates are relative to pacman Vector2i pacmanCoordinates = pacman.getTilePosition(); Vector2d relative = new Vector2d(coordinates.x() - pacmanCoordinates.x(), coordinates.y() - pacmanCoordinates.y()); // also orient the coordinates to the direction of pacman - /* - * ex - * pelletX = (float) (relative.x() * forward.getDx() + relative.x() * right.getDx()); - * pelletY = (float) (relative.y() * forward.getDy() + relative.y() * right.getDy());*/ double newCoordinatesX = relative.x() * forward.getDx() + relative.x() * right.getDx(); double newCoordinatesY = relative.y() * forward.getDy() + relative.y() * right.getDy(); relative.set(newCoordinatesX, newCoordinatesY); return relative; } + + public Vector2d rotateRelative(Vector2d coordinates){ + Vector2d relative = new Vector2d(coordinates.x(), coordinates.y()); + // also orient the coordinates to the direction of pacman + double newCoordinatesX = relative.x() * forward.getDx() + relative.x() * right.getDx(); + double newCoordinatesY = relative.y() * forward.getDy() + relative.y() * right.getDy(); + relative.set(newCoordinatesX, newCoordinatesY); + return relative; + } + public Vector2d translateRelative(Vector2d coordinates){ + Vector2i pacmanCoordinates = pacman.getTilePosition(); + Vector2d relative = new Vector2d(coordinates.x - pacmanCoordinates.x(), coordinates.y - pacmanCoordinates.y); + double newCoordinatesX = relative.x * forward.getDx() + relative.x * right.getDx(); + double newCoordinatesY = relative.y * forward.getDy() + relative.y * right.getDy(); + relative.set(newCoordinatesX, newCoordinatesY); + return relative; + } + + public Tile nearestPowerPellet() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Tile nearestPowerPellet = null; + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + Tile tile = pacman.getMaze().getTile(x, y); + if (tile.getState() == TileState.POWER_PELLET) { + if (nearestPowerPellet == null) { + nearestPowerPellet = tile; + } else { + if (distances[x][y] < distances[nearestPowerPellet.getPosition().x()][nearestPowerPellet.getPosition().y()]) { + nearestPowerPellet = tile; + } + } + } + } + } + return nearestPowerPellet; + } + public Tile getNearestPellet() { Vector2ic dimensions = pacman.getMaze().getDimensions(); Tile nearestPellet = null; @@ -239,7 +467,7 @@ public int[][] computeDistances() { if (next.x() < 0 || next.x() >= dimensions.x() || next.y() < 0 || next.y() >= dimensions.y()) { continue; } - if (this.pacman.getMaze().getTile(next).getState() != TileState.WALL && distance[next.x()][next.y()] == -1) { + if (this.pacman.getMaze().getTile(next).getState() != TileState.WALL && distance[next.x()][next.y()] == -1) { // also avoid ghosts distance[next.x()][next.y()] = distance[current.x()][current.y()] + 1; queue.add(next); } @@ -248,5 +476,4 @@ public int[][] computeDistances() { return distance; } - } From 491728989df4fad47da15b83a1aef02f71c6518e Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 17:09:30 -0400 Subject: [PATCH 07/17] Tournament ready code --- .gitignore | 2 +- assets/levels.json | 9 + .../java/com/buaisociety/pacman/Main.java | 13 +- .../com/buaisociety/pacman/NeatConfig.java | 9 +- .../pacman/SpecialTrainingConditions.java | 3 +- .../com/buaisociety/pacman/Tournament.java | 2 +- .../entity/behavior/NeatPacmanBehavior.java | 668 +++++++++++------- .../entity/behavior/TournamentBehavior.java | 586 ++++++++++++++- gradle.properties | 2 +- .../pacman/lwjgl3/Lwjgl3Launcher.java | 2 +- 10 files changed, 995 insertions(+), 301 deletions(-) diff --git a/.gitignore b/.gitignore index 7bb0aa0..d0c6412 100644 --- a/.gitignore +++ b/.gitignore @@ -164,4 +164,4 @@ Thumbs.db ## You should delete or comment out the next line if you have configuration in a different resource-config.json . **/resource-config.json -assets/saves/ +#assets/saves/ diff --git a/assets/levels.json b/assets/levels.json index 40f82bd..d954192 100644 --- a/assets/levels.json +++ b/assets/levels.json @@ -1,5 +1,14 @@ { "levels": [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", "9" ] } diff --git a/core/src/main/java/com/buaisociety/pacman/Main.java b/core/src/main/java/com/buaisociety/pacman/Main.java index a7bfa39..b5ab23d 100644 --- a/core/src/main/java/com/buaisociety/pacman/Main.java +++ b/core/src/main/java/com/buaisociety/pacman/Main.java @@ -51,13 +51,15 @@ public class Main extends ApplicationAdapter { private final @NotNull EventSystem events = new EventSystem(); private final @NotNull Vector2i visibleGames = new Vector2i(4, 2); private final @NotNull List managers = new ArrayList<>(); - private final int totalGames = 250; + private final int totalGames = 500; private GameLoop secondLoop; // 1 update per second private boolean paused; private boolean showNetworks; private int frames; private int fps; + private boolean USE_TOURNAMENT_SETTINGS = NeatConfig.USE_TOURNAMENT_SETTINGS; + // deep learning private Neat neat; private NeatPrinter neatPrinter; @@ -103,9 +105,9 @@ public void create() { public @NotNull Neat createNeat() { // Change this to true/false as needed, if you want to load from file - if (false) { + if (NeatConfig.loadFromFile) { // 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 + NeatConfig.folder + File.separator + NeatConfig.file); // load exactFile contents to string String json; try { @@ -180,6 +182,11 @@ public void reset() { for (int i = 0; i < totalGames; i++) { GameManager.Config config = new GameManager.Config(); config.id = i; + if(USE_TOURNAMENT_SETTINGS) { + config.handicap = 8; + config.levelsPreset = "tournament_levels.json"; + } + GameManager gameManager = new GameManager(events, config); gameManager.nextLevel(); gameManager.setExtraLives(0); diff --git a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java index 748ffc6..a3a23bc 100644 --- a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java +++ b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java @@ -9,6 +9,13 @@ public class NeatConfig { public static boolean biasEnabled = true; - public static int neatInputNodes = 6; + public static int neatInputNodes = 9; public static int neatOutputNodes = 4; + + + public static boolean loadFromFile = false; + public static String folder = "oct26-5"; + public static String file = "generation-8.json"; + + public static boolean USE_TOURNAMENT_SETTINGS = false; } diff --git a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java index d9d5684..cff26fb 100644 --- a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java +++ b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java @@ -21,7 +21,6 @@ * maze, or even change the behavior of the entities during training. */ public final class SpecialTrainingConditions { - // Prevent instantiation private SpecialTrainingConditions() { } @@ -30,7 +29,7 @@ private SpecialTrainingConditions() { return event -> { // Prevent ghosts from spawning during training if (event.getEntityType() == EntityType.GHOST) { - event.setCancelled(true); +// 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..59d6f9f 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-72" + File.separator + "best-calculator-35.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 5d49bc1..b141fd5 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 @@ -29,24 +29,27 @@ public class NeatPacmanBehavior implements Behavior { private Direction right = Direction.RIGHT; private Direction behind = Direction.DOWN; - // 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 float scoreModifier = 0; - public ArrayList moveHistory = new ArrayList<>(); + public final List moveHistory = new ArrayList<>(); + + private int lastScore = 0; + private int updatesSinceLastScore = 0; + + private int[][] distances; - int lastScore = 0; - int updateSinceLastScore = 0; + public int numGhosts = 0; - int[][] distances; + public int visionRange = 2; - Random random; + private final Random random; public NeatPacmanBehavior(@NotNull Client client) { this.client = client; - random = new Random(); + this.random = new Random(); } /** @@ -58,249 +61,348 @@ public NeatPacmanBehavior(@NotNull Client client) { @NotNull @Override public Direction getDirection(@NotNull Entity entity) { + initializePacman(entity); + updateDirections(); + + distances = computeDistances(); + + handleSpecialTrainingConditions(); + + float[] rayCastDistances = performRayCasting(); + + Tile nearestPellet = getNearestPellet(); + Vector2d relativePelletPos = translateRelative(nearestPellet.getPosition()); + + Direction suggestedPelletDirection = getSuggestedDirection(nearestPellet); + Vector2d suggestedPelletDirRelative = rotateRelative(new Vector2d( + suggestedPelletDirection.getDx(), + suggestedPelletDirection.getDy() + )); + + Tile nearestPowerPellet = getNearestPowerPellet(); + Vector2d relativePowerPelletPos = translateRelative(nearestPowerPellet.getPosition()); + + Direction suggestedPowerDirection = getSuggestedDirection(nearestPowerPellet); + Vector2d suggestedPowerDirRelative = rotateRelative(new Vector2d( + suggestedPowerDirection.getDx(), + suggestedPowerDirection.getDy() + )); + + GhostInfo ghostInfo = gatherGhostInformation(); + + float[] inputs = buildInputs(rayCastDistances, relativePelletPos, suggestedPelletDirRelative, + nearestPellet, relativePowerPelletPos, suggestedPowerDirRelative, ghostInfo); + + float[] outputs = client.getCalculator().calculate(inputs).join(); + + Direction newDirection = selectDirectionFromOutputs(outputs); + + updateScore(newDirection); + + moveHistory.add(newDirection); + return newDirection; + } + + @Override + public void render(@NotNull SpriteBatch batch) { + if (pacman != null) { + Tile nearestPellet = getNearestPellet(); + DebugDrawing.outlineTile(batch, nearestPellet, Color.GREEN); + // Additional debug rendering can be added here + } + } + + /** + * Initializes the pacman entity if it's not already set. + */ + private void initializePacman(@NotNull Entity entity) { if (pacman == null) { pacman = (PacmanEntity) entity; } + } - - distances = computeDistances(); - Vector2ic dimensions = pacman.getMaze().getDimensions(); - // We are going to use these directions a lot for different inputs. Get them all once for clarity and brevity + /** + * Updates the directional fields based on Pacman's current direction. + */ + private void updateDirections() { forward = pacman.getDirection(); left = pacman.getDirection().left(); right = pacman.getDirection().right(); behind = pacman.getDirection().behind(); + } - - // SPECIAL TRAINING CONDITIONS - // TODO: Make changes here to help with your training... - int newScore = pacman.getMaze().getLevelManager().getScore(); - if (newScore > lastScore) { - lastScore = newScore; - updateSinceLastScore = 0; - if(newScore > 100_000){ + /** + * Handles special training conditions such as score updates and kill conditions. + */ + private void handleSpecialTrainingConditions() { + int currentScore = pacman.getMaze().getLevelManager().getScore(); + if (currentScore > lastScore) { + lastScore = currentScore; + updatesSinceLastScore = 0; + if (currentScore > 100_000) { pacman.kill(); - return Direction.UP; + return; } } else { - updateSinceLastScore++; + updatesSinceLastScore++; + } + int maxUpdates = 60 * 10; // 10 seconds + if(lastScore > 5000){ + maxUpdates = 60 * 20; // 20 seconds } - if (updateSinceLastScore > 60 * 10) { + if(lastScore > 10000){ + maxUpdates = 60 * 30; // 30 seconds + } + if (numGhosts > 0) { + if(lastScore > 200){ + maxUpdates = 60 * 120; // 2 minutes + }else{ + maxUpdates = 60 * 3; // 3 seconds + } + } + if (updatesSinceLastScore > maxUpdates) { pacman.kill(); - return Direction.UP; - } + } - // END OF SPECIAL TRAINING CONDITIONS - - - // 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); - + /** + * Performs ray casting in all four directions to detect distances to walls. + * + * @return an array containing distances in forward, left, right, and behind directions + */ + private float[] performRayCasting() { float[] rayCast = new float[4]; + Vector2ic dimensions = pacman.getMaze().getDimensions(); + for (int i = 0; i < 4; i++) { - Direction direction = switch (i) { - case 0 -> forward; - case 1 -> left; - case 2 -> right; - case 3 -> behind; - default -> throw new IllegalStateException("Unexpected value: " + i); - }; + Direction direction = getDirectionByIndex(i); Vector2i position = new Vector2i(pacman.getTilePosition().x(), pacman.getTilePosition().y()); - while (pacman.getMaze().getTile(position).getState() != TileState.WALL) { + + while (isWithinBounds(position, dimensions) && + pacman.getMaze().getTile(position).getState() != TileState.WALL) { position.add(direction.getDx(), direction.getDy()); - if (position.x() < 0 || position.x() >= pacman.getMaze().getDimensions().x() || position.y() < 0 || position.y() >= pacman.getMaze().getDimensions().y()) { + if (!isWithinBounds(position, dimensions)) { break; } - rayCast[i] = (float) Math.sqrt(Math.pow((0.0 + position.x() - pacman.getTilePosition().x()) / dimensions.x(), 2) + Math.pow((position.y() - pacman.getTilePosition().y() + 0.0) / dimensions.y(), 2)); + rayCast[i] = (float) Math.sqrt( + Math.pow((position.x() - pacman.getTilePosition().x()) / (double) dimensions.x(), 2) + + Math.pow((position.y() - pacman.getTilePosition().y()) / (double) dimensions.y(), 2) + ); } } - Tile nearestPellet = getNearestPellet(); - Vector2d relativeCoordinates = translateRelative(nearestPellet.getPosition()); + return rayCast; + } - Direction suggestedDirection = getFirstStepToTile(nearestPellet); - if (suggestedDirection == null) { - suggestedDirection = pacman.getDirection(); - } - // transform suggested direction to be relative to pacman - Vector2d suggestedDirectionRelative = rotateRelative(new Vector2d(suggestedDirection.getDx(), suggestedDirection.getDy())); + /** + * Retrieves the corresponding Direction based on the index. + * + * @param index the index (0: forward, 1: left, 2: right, 3: behind) + * @return the corresponding Direction + */ + private Direction getDirectionByIndex(int index) { + return switch (index) { + case 0 -> forward; + case 1 -> left; + case 2 -> right; + case 3 -> behind; + default -> throw new IllegalStateException("Unexpected value: " + index); + }; + } - Tile nearestPowerPellet = nearestPowerPellet(); - if(nearestPowerPellet == null){ - nearestPowerPellet = pacman.getMaze().getTile(pacman.getTilePosition()); - } - Vector2d relativePowerPellet = translateRelative(nearestPowerPellet.getPosition()); + /** + * Checks if the given position is within the maze boundaries. + * + * @param position the position to check + * @param dimensions the dimensions of the maze + * @return true if within bounds, false otherwise + */ + private boolean isWithinBounds(Vector2i position, Vector2ic dimensions) { + return position.x() >= 0 && position.x() < dimensions.x() && + position.y() >= 0 && position.y() < dimensions.y(); + } + + /** + * Gathers information about the ghosts in the maze. + * + * @return a GhostInfo object containing distances and directions of ghosts + */ + private GhostInfo gatherGhostInformation() { + List ghosts = getGhostEntities(); + numGhosts = ghosts.size(); + float[] ghostDistances = new float[4]; + Vector2d[] ghostDirections = new Vector2d[4]; + Arrays.fill(ghostDistances, 1); - Direction suggestedPowerDirection = getFirstStepToTile(nearestPowerPellet); - if (suggestedPowerDirection == null) { - suggestedPowerDirection = pacman.getDirection(); + int ghostIndex = 0; + for (GhostEntity ghost : ghosts) { + if (ghostIndex >= 4) break; // Limit to 4 ghosts + float distance = distances[ghost.getTilePosition().x()][ghost.getTilePosition().y()]; + if (distance == -1) continue; + Tile ghostTile = pacman.getMaze().getTile(ghost.getTilePosition()); + Direction direction = getFirstStepToTile(ghostTile); + if (direction == null) continue; + + ghostDistances[ghostIndex] = distance / (pacman.getMaze().getDimensions().x() + pacman.getMaze().getDimensions().y()); + ghostDirections[ghostIndex] = rotateRelative(new Vector2d(direction.getDx(), direction.getDy())); + ghostIndex++; } - Vector2d suggestedPowerDirectionRelative = rotateRelative(new Vector2d(suggestedPowerDirection.getDx(), suggestedPowerDirection.getDy())); + return new GhostInfo(ghostDistances, ghostDirections); + } + /** + * Retrieves all ghost entities from the maze. + * + * @return a list of GhostEntity objects + */ + private List getGhostEntities() { List entities = pacman.getMaze().getEntities(); - List ghosts = new ArrayList<>(); - for(Entity mapEntity : entities){ - if(mapEntity instanceof PacmanEntity){ - continue; - } - if(mapEntity instanceof GhostEntity){ - ghosts.add((GhostEntity) mapEntity); + for (Entity entity : entities) { + if (entity instanceof GhostEntity ghost) { + ghosts.add(ghost); } } + return ghosts; + } - int[] ghostDistances = new int[4]; - Vector2d[] ghostDirections = new Vector2d[4]; - for(int i = 0; i < 4; i++){ - ghostDistances[i] = -1; - ghostDirections[i] = null; - } - int ghostIndex = 0; - for(GhostEntity ghost : ghosts){ - int distance = distances[ghost.getTilePosition().x()][ghost.getTilePosition().y()]; - if(distance == -1){ - continue; - } - Vector2i vector2i = ghost.getTilePosition(); - Tile tile = pacman.getMaze().getTile(vector2i); - Direction direction = getFirstStepToTile(tile); - if(direction == null){ - continue; - } - ghostDistances[ghostIndex] = distance; - Vector2d relativeDirection = new Vector2d(direction.getDx(), direction.getDy()); - relativeDirection = rotateRelative(relativeDirection); - ghostDirections[ghostIndex] = relativeDirection; - ghostIndex++; - } - + /** + * Builds the input array for the NEAT calculator. + * + * @param rayCastDistances distances from ray casting + * @param relativePelletPos relative position to the nearest pellet + * @param suggestedPelletDirRelative relative direction to the nearest pellet + * @param nearestPellet the nearest pellet tile + * @param relativePowerPelletPos relative position to the nearest power pellet + * @param suggestedPowerDirRelative relative direction to the nearest power pellet + * @param ghostInfo information about ghosts + * @return an array of input values + */ + private float[] buildInputs(float[] rayCastDistances, Vector2d relativePelletPos, + Vector2d suggestedPelletDirRelative, Tile nearestPellet, + Vector2d relativePowerPelletPos, Vector2d suggestedPowerDirRelative, + GhostInfo ghostInfo) { + Vector2ic dimensions = pacman.getMaze().getDimensions(); +// System.out.println("rayCastDistances: " + Arrays.toString(rayCastDistances)); +// if(numGhosts > 0) +// System.out.println(Arrays.toString(ghostInfo.ghostDirections) + " " + Arrays.toString(ghostInfo.ghostDistances)); + return new float[]{ +// // Ray cast distances +// rayCastDistances[0], +// rayCastDistances[1], +// rayCastDistances[2], +// rayCastDistances[3], +// +// // Relative pellet coordinates +// (float) relativePelletPos.x() / dimensions.x(), +// (float) relativePelletPos.y() / dimensions.y(), - float[] inputs = new float[]{ - rayCast[0], - rayCast[1], - rayCast[2], - rayCast[3], - (float) relativeCoordinates.x() / (float) dimensions.x(), - (float) relativeCoordinates.y() / (float) dimensions.y(), - - // suggested direction x, y - (float) suggestedDirectionRelative.x(), - (float) suggestedDirectionRelative.y(), - // distance to nearest pellet - distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()], - -// (float) relativePowerPellet.x() / (float) dimensions.x(), -// (float) relativePowerPellet.y() / (float) dimensions.y(), -// (float) suggestedPowerDirectionRelative.x(), -// (float) suggestedPowerDirectionRelative.y(), -// // distance to nearest power pellet -// distances[nearestPowerPellet.getPosition().x()][nearestPowerPellet.getPosition().y()], -// ghostDistances[0], -// ghostDistances[1], -// ghostDistances[2], -// ghostDistances[3], -// ghostDirections[0] == null ? 0 : (float) ghostDirections[0].x(), -// ghostDirections[0] == null ? 0 : (float) ghostDirections[0].y(), -// ghostDirections[1] == null ? 0 : (float) ghostDirections[1].x(), -// ghostDirections[1] == null ? 0 : (float) ghostDirections[1].y(), -// ghostDirections[2] == null ? 0 : (float) ghostDirections[2].x(), -// ghostDirections[2] == null ? 0 : (float) ghostDirections[2].y(), -// ghostDirections[3] == null ? 0 : (float) ghostDirections[3].x(), -// ghostDirections[3] == null ? 0 : (float) ghostDirections[3].y(), - - // pacman.getDirection().ordinal() / 4.0f, - // (float) pacman.getTilePosition().x() / (float) dimensions.x(), - // (float) pacman.getTilePosition().y() / (float) dimensions.y(), - - random.nextFloat() * 2 - 1, + // Suggested pellet direction (x, y) + (float) suggestedPelletDirRelative.x(), + (float) suggestedPelletDirRelative.y(), +// +// // Distance to nearest pellet +// distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()] / (dimensions.x() + dimensions.y() + 0f), +// +// // Relative power pellet coordinates + (float) relativePowerPelletPos.x() / dimensions.x(), + (float) relativePowerPelletPos.y() / dimensions.y(), +// +// // Suggested power pellet direction (x, y) +// (float) suggestedPowerDirRelative.x(), +// (float) suggestedPowerDirRelative.y(), +// +// // Distance to nearest power pellet +// distances[getNearestPowerPellet().getPosition().x()][getNearestPellet().getPosition().y()], +// +//// // Ghost distances + ghostInfo.ghostDistances[0], +// ghostInfo.ghostDistances[1], +// ghostInfo.ghostDistances[2], +// ghostInfo.ghostDistances[3], +//// +//// // Ghost directions (x, y) + ghostInfo.ghostDirections[0] != null ? (float) ghostInfo.ghostDirections[0].x() : 0, + ghostInfo.ghostDirections[0] != null ? (float) ghostInfo.ghostDirections[0].y() : 0, +// ghostInfo.ghostDirections[1] != null ? (float) ghostInfo.ghostDirections[1].x() : 0, +// ghostInfo.ghostDirections[1] != null ? (float) ghostInfo.ghostDirections[1].y() : 0, +// ghostInfo.ghostDirections[2] != null ? (float) ghostInfo.ghostDirections[2].x() : 0, +// ghostInfo.ghostDirections[2] != null ? (float) ghostInfo.ghostDirections[2].y() : 0, +// ghostInfo.ghostDirections[3] != null ? (float) ghostInfo.ghostDirections[3].x() : 0, +// ghostInfo.ghostDirections[3] != null ? (float) ghostInfo.ghostDirections[3].y() : 0, +// + pacman.getMaze().getFrightenedTimer() <= 3 ? 0 : (float) (pacman.getMaze().getFrightenedTimer() / 200f + 0.5), +// +// // Random input for variability + random.nextFloat(), }; -// System.out.println(Arrays.toString(inputs)); + } - float[] outputs = client.getCalculator().calculate(inputs).join(); + /** + * Selects the direction based on the outputs from the NEAT calculator. + * + * @param outputs the output array from the NEAT calculator + * @return the selected Direction + */ + private Direction selectDirectionFromOutputs(float[] outputs) { + int selectedIndex = 0; + float maxOutput = outputs[0]; - int index = 0; - float max = outputs[0]; for (int i = 1; i < outputs.length; i++) { - if (outputs[i] > max) { - max = outputs[i]; - index = i; + if (outputs[i] > maxOutput) { + maxOutput = outputs[i]; + selectedIndex = i; } } - Direction newDirection = switch (index) { - case 0 -> pacman.getDirection(); - case 1 -> pacman.getDirection().left(); - case 2 -> pacman.getDirection().right(); - case 3 -> pacman.getDirection().behind(); - default -> throw new IllegalStateException("Unexpected value: " + index); + return switch (selectedIndex) { + case 0 -> forward; + case 1 -> left; + case 2 -> right; + case 3 -> behind; + default -> throw new IllegalStateException("Unexpected output index: " + selectedIndex); }; + } - // ideas - /* - * Give reward for moving in direction of nearest pellet - * Give reward for eating pellet - * Give reward for eating power pellet - * Give reward for eating fruit - * Give reward for eating ghost - * Give penalty for getting eaten by ghost - * Being alive is a penalty unless you run out of power pellets - * - * Reward engineering - */ - // Modded Rewards - Vector2ic newPosition = new Vector2i(pacman.getTilePosition().x() + newDirection.getDx(), pacman.getTilePosition().y() + newDirection.getDy()); -// if(newPosition.distanceSquared(nearestPellet.getPosition()) < pacman.getTilePosition().distanceSquared(nearestPellet.getPosition())){ -// scoreModifier += 5; -// } - -// if(pacman.getMaze().getTile((Vector2i) newPosition).getState() == TileState.PELLET){ -// scoreModifier += 100; -// } -// -// if (!pacman.canMove(newDirection)) { -// scoreModifier -= 10; -// } + /** + * Updates the score based on the new direction. + * + * @param newDirection the direction chosen + */ + private void updateScore(Direction newDirection) { + Vector2i newPosition = new Vector2i( + pacman.getTilePosition().x() + newDirection.getDx(), + pacman.getTilePosition().y() + newDirection.getDy() + ); + + // Example score modifications (can be customized) + // if (isMovingTowardsPellet(newPosition)) { + // scoreModifier += 5; + // } + // if (pacman.getMaze().getTile(newPosition).getState() == TileState.PELLET) { + // scoreModifier += 100; + // } + // if (!pacman.canMove(newDirection)) { + // scoreModifier -= 10; + // } + + // Global score penalty to encourage progress +// scoreModifier -= 0.001f; -// scoreModifier -= 0.1f; client.setScore( pacman.getMaze().getLevelManager().getScore() + scoreModifier - ); // need to improve score hurisitcs - - - moveHistory.add(newDirection); - 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); - } - */ - if (pacman != null) { - Tile nearestPellet = getNearestPellet(); - DebugDrawing.outlineTile(batch, nearestPellet, Color.GREEN); - } + ); // TODO: Improve score heuristics } /** - * Gets the first direction Pacman should move to reach the target tile via the shortest path. - * Uses the pre-computed distances array to work backwards from the target to find the optimal first step. + * Retrieves the first step direction towards the target tile using the pre-computed distances. * - * @param targetTile the destination tile we want to reach + * @param targetTile the destination tile * @return the Direction to move in, or null if no path exists */ @Nullable @@ -320,160 +422,194 @@ public Direction getFirstStepToTile(@NotNull Tile targetTile) { } // Start from the target and work backwards to find the first step - Vector2i currentBacktrack = new Vector2i(targetPos); + Vector2i backtrackPos = new Vector2i(targetPos); int currentDistance = distances[targetPos.x()][targetPos.y()]; - // Keep backtracking until we find a position adjacent to our current position while (currentDistance > 1) { - boolean foundStep = false; + boolean stepFound = false; - // Check all adjacent tiles for (Direction dir : Direction.values()) { - int nextX = currentBacktrack.x() + dir.getDx(); - int nextY = currentBacktrack.y() + dir.getDy(); + int nextX = backtrackPos.x() + dir.getDx(); + int nextY = backtrackPos.y() + dir.getDy(); - // Skip if out of bounds - if (nextX < 0 || nextX >= dimensions.x() || - nextY < 0 || nextY >= dimensions.y()) { + if (!isWithinBounds(new Vector2i(nextX, nextY), dimensions)) { continue; } - // If this adjacent tile is one step closer to Pacman if (distances[nextX][nextY] == currentDistance - 1) { - currentBacktrack.set(nextX, nextY); + backtrackPos.set(nextX, nextY); currentDistance--; - foundStep = true; + stepFound = true; break; } } - // If we couldn't find a valid step back, something's wrong with the path - if (!foundStep) { - return null; + if (!stepFound) { + return null; // Path is broken } } - // Now currentBacktrack should be adjacent to Pacman's position - // Find which direction leads to it + // Determine the direction from Pacman's current position to the backtrack position for (Direction dir : Direction.values()) { int stepX = currentPos.x() + dir.getDx(); int stepY = currentPos.y() + dir.getDy(); - if (stepX == currentBacktrack.x() && stepY == currentBacktrack.y()) { + if (stepX == backtrackPos.x() && stepY == backtrackPos.y()) { return dir; } } - // If we get here, something went wrong - return null; + return null; // Should not reach here } /** * Translates the given coordinates to be relative to Pacman's position and orientation. - * This is useful for converting the target tile's position to a relative position. * * @param coordinates the coordinates to translate * @return the translated coordinates */ - public Vector2d translateRelative(Vector2ic coordinates) { - // new coordinates are relative to pacman - - Vector2i pacmanCoordinates = pacman.getTilePosition(); - Vector2d relative = new Vector2d(coordinates.x() - pacmanCoordinates.x(), coordinates.y() - pacmanCoordinates.y()); - // also orient the coordinates to the direction of pacman - double newCoordinatesX = relative.x() * forward.getDx() + relative.x() * right.getDx(); - double newCoordinatesY = relative.y() * forward.getDy() + relative.y() * right.getDy(); - relative.set(newCoordinatesX, newCoordinatesY); - return relative; + private Vector2d translateRelative(Vector2ic coordinates) { + Vector2i pacmanPos = pacman.getTilePosition(); + Vector2d relative = new Vector2d( + coordinates.x() - pacmanPos.x(), + coordinates.y() - pacmanPos.y() + ); + return rotateRelative(relative); } - - public Vector2d rotateRelative(Vector2d coordinates){ - Vector2d relative = new Vector2d(coordinates.x(), coordinates.y()); - // also orient the coordinates to the direction of pacman - double newCoordinatesX = relative.x() * forward.getDx() + relative.x() * right.getDx(); - double newCoordinatesY = relative.y() * forward.getDy() + relative.y() * right.getDy(); - relative.set(newCoordinatesX, newCoordinatesY); - return relative; - } - public Vector2d translateRelative(Vector2d coordinates){ - Vector2i pacmanCoordinates = pacman.getTilePosition(); - Vector2d relative = new Vector2d(coordinates.x - pacmanCoordinates.x(), coordinates.y - pacmanCoordinates.y); - double newCoordinatesX = relative.x * forward.getDx() + relative.x * right.getDx(); - double newCoordinatesY = relative.y * forward.getDy() + relative.y * right.getDy(); - relative.set(newCoordinatesX, newCoordinatesY); - return relative; + /** + * Rotates the given coordinates based on Pacman's current direction. + * + * @param coordinates the coordinates to rotate + * @return the rotated coordinates + */ + private Vector2d rotateRelative(Vector2d coordinates) { + // Assuming rotation based on forward and right directions + double rotatedX = coordinates.x() * forward.getDx() + coordinates.y() * right.getDx(); + double rotatedY = coordinates.x() * forward.getDy() + coordinates.y() * right.getDy(); + return new Vector2d(rotatedX, rotatedY); } - public Tile nearestPowerPellet() { + /** + * Finds the nearest power pellet in the maze. + * + * @return the nearest power pellet tile + */ + @NotNull + public Tile getNearestPowerPellet() { Vector2ic dimensions = pacman.getMaze().getDimensions(); Tile nearestPowerPellet = null; + for (int y = 0; y < dimensions.y(); y++) { for (int x = 0; x < dimensions.x(); x++) { Tile tile = pacman.getMaze().getTile(x, y); if (tile.getState() == TileState.POWER_PELLET) { - if (nearestPowerPellet == null) { + if (nearestPowerPellet == null || + distances[x][y] < distances[nearestPowerPellet.getPosition().x()][nearestPowerPellet.getPosition().y()]) { nearestPowerPellet = tile; - } else { - if (distances[x][y] < distances[nearestPowerPellet.getPosition().x()][nearestPowerPellet.getPosition().y()]) { - nearestPowerPellet = tile; - } } } } } + + // Fallback to -1, -1 if no power pellets are found + if (nearestPowerPellet == null) { + nearestPowerPellet = pacman.getMaze().getTile(0, 0); + } + return nearestPowerPellet; } + /** + * Finds the nearest pellet (regular or power) in the maze. + * + * @return the nearest pellet tile + */ + @NotNull public Tile getNearestPellet() { Vector2ic dimensions = pacman.getMaze().getDimensions(); Tile nearestPellet = null; + for (int y = 0; y < dimensions.y(); y++) { for (int x = 0; x < dimensions.x(); x++) { Tile tile = pacman.getMaze().getTile(x, y); if (tile.getState() == TileState.PELLET || tile.getState() == TileState.POWER_PELLET) { - if (nearestPellet == null) { + if (nearestPellet == null || + distances[x][y] < distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()]) { nearestPellet = tile; - } else { - if (distances[x][y] < distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()]) { - nearestPellet = tile; - } } } } } + return nearestPellet; } + + + /** + * Computes the distances from Pacman's current position to all reachable tiles using BFS. + * + * @return a 2D array of distances + */ public int[][] computeDistances() { - // path search - Vector2ic dimensions = this.pacman.getMaze().getDimensions(); - Vector2ic pacmanPosition = this.pacman.getTilePosition(); + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Vector2ic pacmanPos = pacman.getTilePosition(); int[][] distance = new int[dimensions.x()][dimensions.y()]; + for (int y = 0; y < dimensions.y(); y++) { for (int x = 0; x < dimensions.x(); x++) { distance[x][y] = -1; } } - distance[pacmanPosition.x()][pacmanPosition.y()] = 0; - Queue queue = new LinkedList<>(); - queue.add(pacmanPosition); + + distance[pacmanPos.x()][pacmanPos.y()] = 0; + Queue queue = new LinkedList<>(); + queue.add(new Vector2i(pacmanPos)); + while (!queue.isEmpty()) { - Vector2ic current = queue.poll(); - for (Direction direction : Direction.values()) { - int dx = direction.getDx(); - int dy = direction.getDy(); - Vector2i next = new Vector2i(current.x() + dx, current.y() + dy); - if (next.x() < 0 || next.x() >= dimensions.x() || next.y() < 0 || next.y() >= dimensions.y()) { + Vector2i current = queue.poll(); + for (Direction dir : Direction.values()) { + int nextX = current.x() + dir.getDx(); + int nextY = current.y() + dir.getDy(); + + if (!isWithinBounds(new Vector2i(nextX, nextY), dimensions)) { continue; } - if (this.pacman.getMaze().getTile(next).getState() != TileState.WALL && distance[next.x()][next.y()] == -1) { // also avoid ghosts - distance[next.x()][next.y()] = distance[current.x()][current.y()] + 1; - queue.add(next); + + if (pacman.getMaze().getTile(nextX, nextY).getState() != TileState.WALL && + distance[nextX][nextY] == -1) { + distance[nextX][nextY] = distance[current.x()][current.y()] + 1; + queue.add(new Vector2i(nextX, nextY)); } } } return distance; } + + /** + * Retrieves the suggested direction towards the target tile. + * + * @param targetTile the target tile + * @return the suggested Direction + */ + @Nullable + private Direction getSuggestedDirection(Tile targetTile) { + Direction direction = getFirstStepToTile(targetTile); + return direction != null ? direction : pacman.getDirection(); + } + + /** + * Helper class to store ghost distances and directions. + */ + private static class GhostInfo { + float[] ghostDistances; + Vector2d[] ghostDirections; + + GhostInfo(float[] ghostDistances, Vector2d[] ghostDirections) { + this.ghostDistances = ghostDistances; + this.ghostDirections = ghostDirections; + } + } } 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..edf2cf8 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,22 +1,59 @@ package com.buaisociety.pacman.entity.behavior; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.buaisociety.pacman.entity.Direction; import com.buaisociety.pacman.entity.Entity; +import com.buaisociety.pacman.entity.GhostEntity; import com.buaisociety.pacman.entity.PacmanEntity; +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.compute.Calculator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; +import org.joml.Vector2i; +import org.joml.Vector2ic; +import java.util.*; + +/** + * TournamentBehavior class migrated from NeatPacmanBehavior. + * Implements the Behavior interface and uses a neural network to decide Pacman's movement. + */ public class TournamentBehavior implements Behavior { private final Calculator calculator; private @Nullable PacmanEntity pacman; - private int previousScore = 0; - private int framesSinceScoreUpdate = 0; + private Direction forward = Direction.UP; + private Direction left = Direction.LEFT; + private Direction right = Direction.RIGHT; + private Direction behind = Direction.DOWN; + + // 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 float scoreModifier = 0; + + public final List moveHistory = new ArrayList<>(); + + private int lastScore = 0; + private int updatesSinceLastScore = 0; + + private int[][] distances; + + public int numGhosts = 0; - public TournamentBehavior(Calculator calculator) { + public int visionRange = 2; + + private final Random random; + + public TournamentBehavior(@NotNull Calculator calculator) { this.calculator = calculator; + this.random = new Random(); } /** @@ -34,43 +71,542 @@ public Direction getDirection(@NotNull Entity entity) { } int newScore = pacman.getMaze().getLevelManager().getScore(); - if (previousScore != newScore) { - previousScore = newScore; - framesSinceScoreUpdate = 0; + if (lastScore != newScore) { + lastScore = newScore; + updatesSinceLastScore = 0; } else { - framesSinceScoreUpdate++; + updatesSinceLastScore++; } - if (framesSinceScoreUpdate > 60 * 40) { + if (updatesSinceLastScore > 60 * 40) { // 40 seconds pacman.kill(); - framesSinceScoreUpdate = 0; + updatesSinceLastScore = 0; } // --- END OF DO NOT REMOVE --- - // TODO: Put all your code for info into the neural network here + // Initialize directions based on current direction + updateDirections(); - float[] inputs = new float[] { - // TODO: Add your inputs here - }; + // Compute distances using BFS + distances = computeDistances(); + + // Handle special training conditions (similar to original behavior) + handleSpecialTrainingConditions(); + + // Perform ray casting to detect walls + float[] rayCastDistances = performRayCasting(); + + // Get nearest pellets + Tile nearestPellet = getNearestPellet(); + Vector2d relativePelletPos = translateRelative(nearestPellet.getPosition()); + + Direction suggestedPelletDirection = getSuggestedDirection(nearestPellet); + Vector2d suggestedPelletDirRelative = rotateRelative(new Vector2d( + suggestedPelletDirection.getDx(), + suggestedPelletDirection.getDy() + )); + + // Get nearest power pellets + Tile nearestPowerPellet = getNearestPowerPellet(); + Vector2d relativePowerPelletPos = translateRelative(nearestPowerPellet.getPosition()); + + Direction suggestedPowerDirection = getSuggestedDirection(nearestPowerPellet); + Vector2d suggestedPowerDirRelative = rotateRelative(new Vector2d( + suggestedPowerDirection.getDx(), + suggestedPowerDirection.getDy() + )); + + // Gather ghost information + GhostInfo ghostInfo = gatherGhostInformation(); + + // Build inputs for the neural network + float[] inputs = buildInputs(rayCastDistances, relativePelletPos, suggestedPelletDirRelative, + nearestPellet, relativePowerPelletPos, suggestedPowerDirRelative, ghostInfo); + + // Calculate outputs from the neural network float[] outputs = calculator.calculate(inputs).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 - int index = 0; - float max = outputs[0]; - for (int i = 1; i < outputs.length; i++) { - if (outputs[i] > max) { - max = outputs[i]; - index = i; + // Select the direction based on outputs + Direction newDirection = selectDirectionFromOutputs(outputs); + + // Update the score based on the chosen direction + updateScore(newDirection); + + // Add the new direction to move history + moveHistory.add(newDirection); + + return newDirection; + } + + /** + * Updates the directional fields based on Pacman's current direction. + */ + private void updateDirections() { + forward = pacman.getDirection(); + left = pacman.getDirection().left(); + right = pacman.getDirection().right(); + behind = pacman.getDirection().behind(); + } + + /** + * Handles special training conditions such as score updates and kill conditions. + */ + private void handleSpecialTrainingConditions() { + int currentScore = pacman.getMaze().getLevelManager().getScore(); + if (currentScore > lastScore) { + lastScore = currentScore; + updatesSinceLastScore = 0; + if (currentScore > 100_000) { + pacman.kill(); + return; + } + } else { + updatesSinceLastScore++; + } + int maxUpdates = 60 * 10; // 10 seconds + if (lastScore > 5000) { + maxUpdates = 60 * 20; // 20 seconds + } + + if (lastScore > 10000) { + maxUpdates = 60 * 30; // 30 seconds + } + if (numGhosts > 0) { + if (lastScore > 200) { + maxUpdates = 60 * 120; // 2 minutes + } else { + maxUpdates = 60 * 3; // 3 seconds + } + } + if (updatesSinceLastScore > maxUpdates) { + pacman.kill(); + } + } + + /** + * Performs ray casting in all four directions to detect distances to walls. + * + * @return an array containing distances in forward, left, right, and behind directions + */ + private float[] performRayCasting() { + float[] rayCast = new float[4]; + Vector2ic dimensions = pacman.getMaze().getDimensions(); + + for (int i = 0; i < 4; i++) { + Direction direction = getDirectionByIndex(i); + Vector2i position = new Vector2i(pacman.getTilePosition().x(), pacman.getTilePosition().y()); + + while (isWithinBounds(position, dimensions) && + pacman.getMaze().getTile(position).getState() != TileState.WALL) { + position.add(direction.getDx(), direction.getDy()); + if (!isWithinBounds(position, dimensions)) { + break; + } + rayCast[i] = (float) Math.sqrt( + Math.pow((position.x() - pacman.getTilePosition().x()) / (double) dimensions.x(), 2) + + Math.pow((position.y() - pacman.getTilePosition().y()) / (double) dimensions.y(), 2) + ); } } + return rayCast; + } + + /** + * Retrieves the corresponding Direction based on the index. + * + * @param index the index (0: forward, 1: left, 2: right, 3: behind) + * @return the corresponding Direction + */ + private Direction getDirectionByIndex(int index) { return switch (index) { - case 0 -> pacman.getDirection(); - case 1 -> pacman.getDirection().left(); - case 2 -> pacman.getDirection().right(); - case 3 -> pacman.getDirection().behind(); + case 0 -> forward; + case 1 -> left; + case 2 -> right; + case 3 -> behind; default -> throw new IllegalStateException("Unexpected value: " + index); }; } + + /** + * Checks if the given position is within the maze boundaries. + * + * @param position the position to check + * @param dimensions the dimensions of the maze + * @return true if within bounds, false otherwise + */ + private boolean isWithinBounds(Vector2i position, Vector2ic dimensions) { + return position.x() >= 0 && position.x() < dimensions.x() && + position.y() >= 0 && position.y() < dimensions.y(); + } + + /** + * Gathers information about the ghosts in the maze. + * + * @return a GhostInfo object containing distances and directions of ghosts + */ + private GhostInfo gatherGhostInformation() { + List ghosts = getGhostEntities(); + numGhosts = ghosts.size(); + float[] ghostDistances = new float[4]; + Vector2d[] ghostDirections = new Vector2d[4]; + Arrays.fill(ghostDistances, 1); + + int ghostIndex = 0; + for (GhostEntity ghost : ghosts) { + if (ghostIndex >= 4) break; // Limit to 4 ghosts + float distance = distances[ghost.getTilePosition().x()][ghost.getTilePosition().y()]; + if (distance == -1) continue; + Tile ghostTile = pacman.getMaze().getTile(ghost.getTilePosition()); + Direction direction = getFirstStepToTile(ghostTile); + if (direction == null) continue; + + ghostDistances[ghostIndex] = distance / (pacman.getMaze().getDimensions().x() + pacman.getMaze().getDimensions().y()); + ghostDirections[ghostIndex] = rotateRelative(new Vector2d(direction.getDx(), direction.getDy())); + ghostIndex++; + } + + return new GhostInfo(ghostDistances, ghostDirections); + } + + /** + * Retrieves all ghost entities from the maze. + * + * @return a list of GhostEntity objects + */ + private List getGhostEntities() { + List entities = pacman.getMaze().getEntities(); + List ghosts = new ArrayList<>(); + for (Entity entity : entities) { + if (entity instanceof GhostEntity ghost) { + ghosts.add(ghost); + } + } + return ghosts; + } + + /** + * Builds the input array for the neural network. + * + * @param rayCastDistances distances from ray casting + * @param relativePelletPos relative position to the nearest pellet + * @param suggestedPelletDirRelative relative direction to the nearest pellet + * @param nearestPellet the nearest pellet tile + * @param relativePowerPelletPos relative position to the nearest power pellet + * @param suggestedPowerDirRelative relative direction to the nearest power pellet + * @param ghostInfo information about ghosts + * @return an array of input values + */ + private float[] buildInputs(float[] rayCastDistances, Vector2d relativePelletPos, + Vector2d suggestedPelletDirRelative, Tile nearestPellet, + Vector2d relativePowerPelletPos, Vector2d suggestedPowerDirRelative, + GhostInfo ghostInfo) { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + return new float[]{ + // Suggested pellet direction (x, y) + (float) suggestedPelletDirRelative.x(), + (float) suggestedPelletDirRelative.y(), + + // Relative power pellet coordinates +// (float) relativePowerPelletPos.x() / dimensions.x(), +// (float) relativePowerPelletPos.y() / dimensions.y(), + + // Suggested power pellet direction (x, y) + (float) suggestedPowerDirRelative.x(), + (float) suggestedPowerDirRelative.y(), + + // Ghost distances + ghostInfo.ghostDistances[0], +// ghostInfo.ghostDistances[1], +// ghostInfo.ghostDistances[2], +// ghostInfo.ghostDistances[3], + + // Ghost directions (x, y) for each ghost + ghostInfo.ghostDirections[0] != null ? (float) ghostInfo.ghostDirections[0].x() : 0, + ghostInfo.ghostDirections[0] != null ? (float) ghostInfo.ghostDirections[0].y() : 0, +// ghostInfo.ghostDirections[1] != null ? (float) ghostInfo.ghostDirections[1].x() : 0, +// ghostInfo.ghostDirections[1] != null ? (float) ghostInfo.ghostDirections[1].y() : 0, +// ghostInfo.ghostDirections[2] != null ? (float) ghostInfo.ghostDirections[2].x() : 0, +// ghostInfo.ghostDirections[2] != null ? (float) ghostInfo.ghostDirections[2].y() : 0, +// ghostInfo.ghostDirections[3] != null ? (float) ghostInfo.ghostDirections[3].x() : 0, +// ghostInfo.ghostDirections[3] != null ? (float) ghostInfo.ghostDirections[3].y() : 0, + + // Frightened timer status + pacman.getMaze().getFrightenedTimer() <= 3 ? 0 : (float) (pacman.getMaze().getFrightenedTimer() / 200f + 0.5f), + + // Random input for variability + random.nextFloat(), + }; + } + + /** + * Selects the direction based on the outputs from the neural network. + * + * @param outputs the output array from the neural network + * @return the selected Direction + */ + private Direction selectDirectionFromOutputs(float[] outputs) { + int selectedIndex = 0; + float maxOutput = outputs[0]; + + for (int i = 1; i < outputs.length; i++) { + if (outputs[i] > maxOutput) { + maxOutput = outputs[i]; + selectedIndex = i; + } + } + + return switch (selectedIndex) { + case 0 -> forward; + case 1 -> left; + case 2 -> right; + case 3 -> behind; + default -> throw new IllegalStateException("Unexpected output index: " + selectedIndex); + }; + } + + /** + * Updates the score based on the new direction. + * + * @param newDirection the direction chosen + */ + private void updateScore(Direction newDirection) { + Vector2i newPosition = new Vector2i( + pacman.getTilePosition().x() + newDirection.getDx(), + pacman.getTilePosition().y() + newDirection.getDy() + ); + + // Example score modifications (can be customized) + // if (isMovingTowardsPellet(newPosition)) { + // scoreModifier += 5; + // } + // if (pacman.getMaze().getTile(newPosition).getState() == TileState.PELLET) { + // scoreModifier += 100; + // } + // if (!pacman.canMove(newDirection)) { + // scoreModifier -= 10; + // } + + // Global score penalty to encourage progress + // scoreModifier -= 0.001f; + + // Update the score in the calculator + } + + /** + * Retrieves the first step direction towards the target tile using the pre-computed distances. + * + * @param targetTile the destination tile + * @return the Direction to move in, or null if no path exists + */ + @Nullable + public Direction getFirstStepToTile(@NotNull Tile targetTile) { + Vector2ic targetPos = targetTile.getPosition(); + Vector2ic currentPos = pacman.getTilePosition(); + Vector2ic dimensions = pacman.getMaze().getDimensions(); + + // If we're already at the target, no need to move + if (currentPos.equals(targetPos)) { + return null; + } + + // If target is unreachable (distance == -1), return null + if (distances[targetPos.x()][targetPos.y()] == -1) { + return null; + } + + // Start from the target and work backwards to find the first step + Vector2i backtrackPos = new Vector2i(targetPos); + int currentDistance = distances[targetPos.x()][targetPos.y()]; + + while (currentDistance > 1) { + boolean stepFound = false; + + for (Direction dir : Direction.values()) { + int nextX = backtrackPos.x() + dir.getDx(); + int nextY = backtrackPos.y() + dir.getDy(); + + if (!isWithinBounds(new Vector2i(nextX, nextY), dimensions)) { + continue; + } + + if (distances[nextX][nextY] == currentDistance - 1) { + backtrackPos.set(nextX, nextY); + currentDistance--; + stepFound = true; + break; + } + } + + if (!stepFound) { + return null; // Path is broken + } + } + + // Determine the direction from Pacman's current position to the backtrack position + for (Direction dir : Direction.values()) { + int stepX = currentPos.x() + dir.getDx(); + int stepY = currentPos.y() + dir.getDy(); + + if (stepX == backtrackPos.x() && stepY == backtrackPos.y()) { + return dir; + } + } + + return null; // Should not reach here + } + + /** + * Translates the given coordinates to be relative to Pacman's position and orientation. + * + * @param coordinates the coordinates to translate + * @return the translated coordinates + */ + private Vector2d translateRelative(Vector2ic coordinates) { + Vector2i pacmanPos = pacman.getTilePosition(); + Vector2d relative = new Vector2d( + coordinates.x() - pacmanPos.x(), + coordinates.y() - pacmanPos.y() + ); + return rotateRelative(relative); + } + + /** + * Rotates the given coordinates based on Pacman's current direction. + * + * @param coordinates the coordinates to rotate + * @return the rotated coordinates + */ + private Vector2d rotateRelative(Vector2d coordinates) { + // Assuming rotation based on forward and right directions + double rotatedX = coordinates.x() * forward.getDx() + coordinates.y() * right.getDx(); + double rotatedY = coordinates.x() * forward.getDy() + coordinates.y() * right.getDy(); + return new Vector2d(rotatedX, rotatedY); + } + + /** + * Finds the nearest power pellet in the maze. + * + * @return the nearest power pellet tile + */ + @NotNull + public Tile getNearestPowerPellet() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Tile nearestPowerPellet = null; + + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + Tile tile = pacman.getMaze().getTile(x, y); + if (tile.getState() == TileState.POWER_PELLET) { + if (nearestPowerPellet == null || + distances[x][y] < distances[nearestPowerPellet.getPosition().x()][nearestPowerPellet.getPosition().y()]) { + nearestPowerPellet = tile; + } + } + } + } + + // Fallback to (0,0) if no power pellets are found + if (nearestPowerPellet == null) { + nearestPowerPellet = pacman.getMaze().getTile(0, 0); + } + + return nearestPowerPellet; + } + + /** + * Finds the nearest pellet (regular or power) in the maze. + * + * @return the nearest pellet tile + */ + @NotNull + public Tile getNearestPellet() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Tile nearestPellet = null; + + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + Tile tile = pacman.getMaze().getTile(x, y); + if (tile.getState() == TileState.PELLET || tile.getState() == TileState.POWER_PELLET) { + if (nearestPellet == null || + distances[x][y] < distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()]) { + nearestPellet = tile; + } + } + } + } + + + if(nearestPellet == null) { + nearestPellet = pacman.getMaze().getTile(0, 0); + } + + return nearestPellet; + } + + /** + * Computes the distances from Pacman's current position to all reachable tiles using BFS. + * + * @return a 2D array of distances + */ + public int[][] computeDistances() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Vector2ic pacmanPos = pacman.getTilePosition(); + int[][] distance = new int[dimensions.x()][dimensions.y()]; + + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + distance[x][y] = -1; + } + } + + distance[pacmanPos.x()][pacmanPos.y()] = 0; + Queue queue = new LinkedList<>(); + queue.add(new Vector2i(pacmanPos)); + + while (!queue.isEmpty()) { + Vector2i current = queue.poll(); + for (Direction dir : Direction.values()) { + int nextX = current.x() + dir.getDx(); + int nextY = current.y() + dir.getDy(); + + if (!isWithinBounds(new Vector2i(nextX, nextY), dimensions)) { + continue; + } + + if (pacman.getMaze().getTile(nextX, nextY).getState() != TileState.WALL && + distance[nextX][nextY] == -1) { + distance[nextX][nextY] = distance[current.x()][current.y()] + 1; + queue.add(new Vector2i(nextX, nextY)); + } + } + } + + return distance; + } + + /** + * Retrieves the suggested direction towards the target tile. + * + * @param targetTile the target tile + * @return the suggested Direction + */ + @Nullable + private Direction getSuggestedDirection(Tile targetTile) { + Direction direction = getFirstStepToTile(targetTile); + return direction != null ? direction : pacman.getDirection(); + } + + /** + * Helper class to store ghost distances and directions. + */ + private static class GhostInfo { + float[] ghostDistances; + Vector2d[] ghostDirections; + + GhostInfo(float[] ghostDistances, Vector2d[] ghostDirections) { + this.ghostDistances = ghostDistances; + this.ghostDirections = ghostDirections; + } + } } diff --git a/gradle.properties b/gradle.properties index ad1758b..9cf3fe2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.daemon=false -org.gradle.jvmargs=-Xms512M -Xmx1G -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8 +org.gradle.jvmargs=-Xms512M -Xmx2G -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8 org.gradle.configureondemand=false graalHelperVersion=2.0.1 enableGraalNative=false diff --git a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java index f013cf1..72942c6 100644 --- a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java +++ b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java @@ -13,7 +13,7 @@ public static void main(String[] args) { } private static Lwjgl3Application createApplication() { - boolean isTraining = true; // set this as false to try out the tournament settings + boolean isTraining = false; // set this as false to try out the tournament settings if (isTraining) { Lwjgl3ApplicationConfiguration config = getDefaultConfiguration(); // disable vsync to run the game as fast as possible From 6a50b276255688a45fe154819ceaf173dafda244 Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 17:10:07 -0400 Subject: [PATCH 08/17] Added calculator --- assets/saves/oct26-72/best-calculator-36.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 assets/saves/oct26-72/best-calculator-36.json diff --git a/assets/saves/oct26-72/best-calculator-36.json b/assets/saves/oct26-72/best-calculator-36.json new file mode 100644 index 0000000..a4c3c89 --- /dev/null +++ b/assets/saves/oct26-72/best-calculator-36.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":[]}],"hidden":[{"id":50,"x":0.29999998,"incoming":[{"fromId":4,"weight":0.3260812,"enabled":true},{"fromId":0,"weight":-0.13214411,"enabled":true},{"fromId":5,"weight":-0.23394921,"enabled":true}]},{"id":65,"x":0.29999998,"incoming":[{"fromId":1,"weight":0.4682414,"enabled":true},{"fromId":0,"weight":-0.47455698,"enabled":true}]},{"id":95,"x":0.29999998,"incoming":[{"fromId":3,"weight":0.8860415,"enabled":true},{"fromId":0,"weight":0.5815747,"enabled":true}]},{"id":15,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.104795486,"enabled":true},{"fromId":0,"weight":-0.98747563,"enabled":true},{"fromId":2,"weight":1.0192616,"enabled":true},{"fromId":50,"weight":0.20612434,"enabled":true},{"fromId":6,"weight":-0.91859585,"enabled":true},{"fromId":8,"weight":-1.532623,"enabled":true}]},{"id":18,"x":0.49999997,"incoming":[{"fromId":6,"weight":-0.31562048,"enabled":true},{"fromId":0,"weight":0.054992825,"enabled":true},{"fromId":2,"weight":0.061889596,"enabled":true}]},{"id":19,"x":0.49999997,"incoming":[{"fromId":6,"weight":-0.5483341,"enabled":true},{"fromId":0,"weight":-0.43364102,"enabled":true},{"fromId":1,"weight":0.09399427,"enabled":true},{"fromId":8,"weight":-0.56737363,"enabled":true},{"fromId":50,"weight":0.81796086,"enabled":true}]},{"id":21,"x":0.49999997,"incoming":[{"fromId":5,"weight":0.15442422,"enabled":true},{"fromId":0,"weight":0.11729443,"enabled":true},{"fromId":7,"weight":0.29076958,"enabled":true},{"fromId":9,"weight":0.869735,"enabled":true},{"fromId":2,"weight":-0.18688652,"enabled":true},{"fromId":1,"weight":-0.069211215,"enabled":true},{"fromId":50,"weight":-0.095420495,"enabled":true}]},{"id":22,"x":0.49999997,"incoming":[{"fromId":1,"weight":-0.34996057,"enabled":true},{"fromId":0,"weight":1.5778112,"enabled":true}]},{"id":23,"x":0.49999997,"incoming":[{"fromId":3,"weight":-0.097104095,"enabled":true},{"fromId":0,"weight":0.2671735,"enabled":true}]},{"id":24,"x":0.49999997,"incoming":[{"fromId":0,"weight":0.35363954,"enabled":true}]},{"id":25,"x":0.49999997,"incoming":[{"fromId":4,"weight":1.7080326,"enabled":true},{"fromId":0,"weight":-0.22062598,"enabled":true}]},{"id":26,"x":0.49999997,"incoming":[{"fromId":5,"weight":1.9095197,"enabled":true},{"fromId":0,"weight":-0.2787925,"enabled":true}]},{"id":27,"x":0.49999997,"incoming":[{"fromId":8,"weight":-0.3263183,"enabled":true},{"fromId":0,"weight":-0.41584808,"enabled":true}]},{"id":33,"x":0.49999997,"incoming":[{"fromId":7,"weight":0.9111565,"enabled":true},{"fromId":0,"weight":0.764455,"enabled":true},{"fromId":2,"weight":-0.17805265,"enabled":true},{"fromId":9,"weight":0.63011473,"enabled":true},{"fromId":65,"weight":-0.77902955,"enabled":true}]},{"id":34,"x":0.49999997,"incoming":[{"fromId":5,"weight":-0.5812924,"enabled":true},{"fromId":0,"weight":0.119829625,"enabled":true},{"fromId":3,"weight":0.94418555,"enabled":false},{"fromId":95,"weight":-0.17384759,"enabled":true}]},{"id":35,"x":0.49999997,"incoming":[{"fromId":9,"weight":0.50162625,"enabled":true},{"fromId":0,"weight":0.015230969,"enabled":true},{"fromId":2,"weight":0.6678108,"enabled":true},{"fromId":6,"weight":1.6147876,"enabled":true},{"fromId":1,"weight":0.84308577,"enabled":true}]},{"id":37,"x":0.49999997,"incoming":[{"fromId":9,"weight":0.5279875,"enabled":true},{"fromId":0,"weight":-0.57465184,"enabled":true}]},{"id":38,"x":0.49999997,"incoming":[{"fromId":9,"weight":1.2731506,"enabled":true},{"fromId":0,"weight":0.5932157,"enabled":true}]},{"id":41,"x":0.49999997,"incoming":[{"fromId":3,"weight":-0.85861194,"enabled":true},{"fromId":0,"weight":0.29206973,"enabled":true}]},{"id":44,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.93435645,"enabled":true},{"fromId":0,"weight":-0.34713316,"enabled":true},{"fromId":9,"weight":0.36525097,"enabled":true}]},{"id":48,"x":0.49999997,"incoming":[{"fromId":1,"weight":0.209748,"enabled":true},{"fromId":0,"weight":0.06655161,"enabled":true},{"fromId":6,"weight":0.29044563,"enabled":true},{"fromId":65,"weight":-0.07227522,"enabled":true}]},{"id":53,"x":0.49999997,"incoming":[{"fromId":8,"weight":0.86038285,"enabled":true},{"fromId":0,"weight":0.30322415,"enabled":true}]},{"id":54,"x":0.49999997,"incoming":[{"fromId":1,"weight":0.8321307,"enabled":true},{"fromId":0,"weight":0.19514272,"enabled":true}]},{"id":45,"x":0.7,"incoming":[{"fromId":33,"weight":1.1273955,"enabled":true},{"fromId":0,"weight":-0.93755716,"enabled":true}]},{"id":49,"x":0.7,"incoming":[{"fromId":15,"weight":-0.13298008,"enabled":true},{"fromId":0,"weight":-0.28581312,"enabled":true},{"fromId":48,"weight":-0.24489906,"enabled":true},{"fromId":7,"weight":-0.49334025,"enabled":true},{"fromId":35,"weight":-0.29637682,"enabled":true}]},{"id":85,"x":0.7,"incoming":[{"fromId":41,"weight":0.028685674,"enabled":true},{"fromId":0,"weight":-1.0208642,"enabled":true}]}],"outputs":[{"id":10,"x":0.9,"incoming":[{"fromId":0,"weight":0.42874485,"enabled":true},{"fromId":1,"weight":1.5450724,"enabled":true},{"fromId":2,"weight":0.38866156,"enabled":true},{"fromId":3,"weight":-0.048597246,"enabled":true},{"fromId":4,"weight":-0.11140473,"enabled":true},{"fromId":5,"weight":-0.059036836,"enabled":true},{"fromId":6,"weight":1.029555,"enabled":true},{"fromId":7,"weight":0.41845065,"enabled":true},{"fromId":8,"weight":0.15941091,"enabled":true},{"fromId":9,"weight":-0.5737207,"enabled":true},{"fromId":19,"weight":-0.5511075,"enabled":true},{"fromId":23,"weight":0.5776908,"enabled":true},{"fromId":25,"weight":-0.37440747,"enabled":true},{"fromId":26,"weight":-0.34981215,"enabled":true},{"fromId":27,"weight":0.50026274,"enabled":true},{"fromId":37,"weight":-0.45316577,"enabled":true},{"fromId":48,"weight":-0.11845264,"enabled":true}]},{"id":11,"x":0.9,"incoming":[{"fromId":0,"weight":0.316352,"enabled":true},{"fromId":1,"weight":-1.1575654,"enabled":true},{"fromId":2,"weight":-0.64221,"enabled":true},{"fromId":3,"weight":0.09709452,"enabled":true},{"fromId":4,"weight":-0.099510014,"enabled":true},{"fromId":5,"weight":-0.5341325,"enabled":true},{"fromId":6,"weight":0.43441856,"enabled":true},{"fromId":7,"weight":-0.07322796,"enabled":true},{"fromId":8,"weight":-0.03106527,"enabled":true},{"fromId":9,"weight":0.358767,"enabled":true},{"fromId":18,"weight":-0.7623632,"enabled":true},{"fromId":22,"weight":-0.07749091,"enabled":true},{"fromId":38,"weight":0.98119074,"enabled":true},{"fromId":48,"weight":-0.65435505,"enabled":true},{"fromId":50,"weight":-0.25067723,"enabled":true}]},{"id":12,"x":0.9,"incoming":[{"fromId":0,"weight":0.21142665,"enabled":true},{"fromId":1,"weight":-0.61067617,"enabled":true},{"fromId":2,"weight":1.0964764,"enabled":true},{"fromId":3,"weight":0.34340036,"enabled":true},{"fromId":4,"weight":0.005664274,"enabled":true},{"fromId":5,"weight":-0.37394798,"enabled":true},{"fromId":6,"weight":0.26421446,"enabled":true},{"fromId":7,"weight":0.4770956,"enabled":true},{"fromId":8,"weight":0.29335403,"enabled":true},{"fromId":9,"weight":1.217599,"enabled":true},{"fromId":15,"weight":0.49869615,"enabled":true},{"fromId":24,"weight":0.4653223,"enabled":true},{"fromId":34,"weight":-0.073580146,"enabled":true},{"fromId":18,"weight":-0.7540115,"enabled":true},{"fromId":33,"weight":0.5142838,"enabled":true},{"fromId":49,"weight":0.3121156,"enabled":true},{"fromId":53,"weight":0.3028291,"enabled":true},{"fromId":50,"weight":-0.16403285,"enabled":true},{"fromId":21,"weight":-1.3699591,"enabled":true}]},{"id":13,"x":0.9,"incoming":[{"fromId":0,"weight":-0.002495844,"enabled":true},{"fromId":1,"weight":-0.2662651,"enabled":true},{"fromId":2,"weight":-0.10949284,"enabled":true},{"fromId":3,"weight":0.035460964,"enabled":true},{"fromId":4,"weight":-0.2954322,"enabled":true},{"fromId":5,"weight":-0.5018914,"enabled":true},{"fromId":6,"weight":0.42833543,"enabled":true},{"fromId":7,"weight":-0.2399191,"enabled":true},{"fromId":8,"weight":0.028984532,"enabled":true},{"fromId":9,"weight":0.402998,"enabled":true},{"fromId":21,"weight":-0.385299,"enabled":true},{"fromId":33,"weight":-0.25534916,"enabled":true},{"fromId":35,"weight":5.688518E-4,"enabled":true},{"fromId":41,"weight":-0.20653962,"enabled":true},{"fromId":44,"weight":0.27038646,"enabled":true},{"fromId":45,"weight":-0.16030513,"enabled":true},{"fromId":54,"weight":-0.87239826,"enabled":true},{"fromId":38,"weight":0.6673388,"enabled":true},{"fromId":85,"weight":-0.8848945,"enabled":true}]}]} \ No newline at end of file From b7cff286d1c53941ee6f9d14adfc74be2e339bdb Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 17:23:12 -0400 Subject: [PATCH 09/17] Tournament ready code --- core/src/main/java/com/buaisociety/pacman/Tournament.java | 6 +++--- .../pacman/entity/behavior/TournamentBehavior.java | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/buaisociety/pacman/Tournament.java b/core/src/main/java/com/buaisociety/pacman/Tournament.java index 59d6f9f..8d301dc 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 + "oct26-72" + File.separator + "best-calculator-35.json"); + File file = new File("saves" + File.separator + "oct26-72" + File.separator + "best-calculator-36.json"); if (!file.exists()) { System.err.println("Could not find the file: " + file.getAbsolutePath()); return null; @@ -98,8 +98,8 @@ public void onEvent(@NotNull EntityPreSpawnEvent event) { gameManager = new GameManager(events, config); gameManager.nextLevel(); - gameLoop = new GameLoop(60); - secondLoop = new GameLoop(1); + gameLoop = new GameLoop(6000); + secondLoop = new GameLoop(1000); // Maximize window Graphics.DisplayMode displayMode = Gdx.graphics.getDisplayMode(); 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 edf2cf8..0b6bf37 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 @@ -51,9 +51,12 @@ public class TournamentBehavior implements Behavior { private final Random random; + int movesMade; + public TournamentBehavior(@NotNull Calculator calculator) { this.calculator = calculator; this.random = new Random(); + } /** @@ -157,7 +160,7 @@ private void handleSpecialTrainingConditions() { lastScore = currentScore; updatesSinceLastScore = 0; if (currentScore > 100_000) { - pacman.kill(); +// pacman.kill(); return; } } else { @@ -179,7 +182,7 @@ private void handleSpecialTrainingConditions() { } } if (updatesSinceLastScore > maxUpdates) { - pacman.kill(); +// pacman.kill(); } } From 6e12b24a90f9411216bef32174afe26dde74a0e2 Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 18:17:11 -0400 Subject: [PATCH 10/17] Tournament ready code --- .../com/buaisociety/pacman/NeatConfig.java | 8 ++-- .../pacman/SpecialTrainingConditions.java | 2 +- .../com/buaisociety/pacman/Tournament.java | 2 +- .../entity/behavior/NeatPacmanBehavior.java | 4 +- .../entity/behavior/TournamentBehavior.java | 48 +++++++------------ 5 files changed, 24 insertions(+), 40 deletions(-) diff --git a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java index a3a23bc..e32506c 100644 --- a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java +++ b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java @@ -9,13 +9,13 @@ public class NeatConfig { public static boolean biasEnabled = true; - public static int neatInputNodes = 9; + public static int neatInputNodes = 8; public static int neatOutputNodes = 4; - public static boolean loadFromFile = false; - public static String folder = "oct26-5"; - public static String file = "generation-8.json"; + public static boolean loadFromFile = true; + public static String folder = "oct26-76"; + public static String file = "generation-54.json"; public static boolean USE_TOURNAMENT_SETTINGS = false; } diff --git a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java index cff26fb..a119c57 100644 --- a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java +++ b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java @@ -50,7 +50,7 @@ private SpecialTrainingConditions() { continue; if (ThreadLocalRandom.current().nextDouble() < pelletDensity) { - maze.getTile(x, y).setState(TileState.SPACE); +// maze.getTile(x, y).setState(TileState.SPACE); } } } diff --git a/core/src/main/java/com/buaisociety/pacman/Tournament.java b/core/src/main/java/com/buaisociety/pacman/Tournament.java index 8d301dc..9abbe00 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 + "oct26-72" + File.separator + "best-calculator-36.json"); + File file = new File("saves" + File.separator + "oct26-80" + File.separator + "best-calculator-63.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 b141fd5..6ea65f8 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 @@ -156,7 +156,7 @@ private void handleSpecialTrainingConditions() { } if (numGhosts > 0) { if(lastScore > 200){ - maxUpdates = 60 * 120; // 2 minutes + maxUpdates = 60 * 30; // 2 minutes }else{ maxUpdates = 60 * 3; // 3 seconds } @@ -336,7 +336,7 @@ private float[] buildInputs(float[] rayCastDistances, Vector2d relativePelletPos pacman.getMaze().getFrightenedTimer() <= 3 ? 0 : (float) (pacman.getMaze().getFrightenedTimer() / 200f + 0.5), // // // Random input for variability - random.nextFloat(), +// random.nextFloat(), }; } 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 0b6bf37..3027b7f 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 @@ -53,6 +53,8 @@ public class TournamentBehavior implements Behavior { int movesMade; + ArrayList positions = new ArrayList(); + public TournamentBehavior(@NotNull Calculator calculator) { this.calculator = calculator; this.random = new Random(); @@ -70,8 +72,8 @@ public TournamentBehavior(@NotNull Calculator calculator) { public Direction getDirection(@NotNull Entity entity) { // --- DO NOT REMOVE --- if (pacman == null) { - pacman = (PacmanEntity) entity; } + pacman = (PacmanEntity) entity; int newScore = pacman.getMaze().getLevelManager().getScore(); if (lastScore != newScore) { @@ -132,11 +134,17 @@ public Direction getDirection(@NotNull Entity entity) { // Select the direction based on outputs Direction newDirection = selectDirectionFromOutputs(outputs); - // Update the score based on the chosen direction - updateScore(newDirection); - // Add the new direction to move history moveHistory.add(newDirection); + positions.add(new Vector2d(pacman.getTilePosition().x(), pacman.getTilePosition().y())); + if (positions.size() > 100 && updatesSinceLastScore > 60) { + positions.remove(0); + if(positions.get(0).equals(positions.get(99))) { + Random random = new Random(); + newDirection = Direction.values()[random.nextInt(4)]; + } + } + return newDirection; } @@ -338,7 +346,7 @@ private float[] buildInputs(float[] rayCastDistances, Vector2d relativePelletPos pacman.getMaze().getFrightenedTimer() <= 3 ? 0 : (float) (pacman.getMaze().getFrightenedTimer() / 200f + 0.5f), // Random input for variability - random.nextFloat(), +// random.nextFloat(), }; } @@ -368,33 +376,7 @@ private Direction selectDirectionFromOutputs(float[] outputs) { }; } - /** - * Updates the score based on the new direction. - * - * @param newDirection the direction chosen - */ - private void updateScore(Direction newDirection) { - Vector2i newPosition = new Vector2i( - pacman.getTilePosition().x() + newDirection.getDx(), - pacman.getTilePosition().y() + newDirection.getDy() - ); - // Example score modifications (can be customized) - // if (isMovingTowardsPellet(newPosition)) { - // scoreModifier += 5; - // } - // if (pacman.getMaze().getTile(newPosition).getState() == TileState.PELLET) { - // scoreModifier += 100; - // } - // if (!pacman.canMove(newDirection)) { - // scoreModifier -= 10; - // } - - // Global score penalty to encourage progress - // scoreModifier -= 0.001f; - - // Update the score in the calculator - } /** * Retrieves the first step direction towards the target tile using the pre-computed distances. @@ -514,6 +496,8 @@ public Tile getNearestPowerPellet() { nearestPowerPellet = pacman.getMaze().getTile(0, 0); } + System.out.println(nearestPowerPellet.getPosition()); + return nearestPowerPellet; } @@ -543,7 +527,7 @@ public Tile getNearestPellet() { if(nearestPellet == null) { nearestPellet = pacman.getMaze().getTile(0, 0); } - + System.out.println(nearestPellet.getPosition()); return nearestPellet; } From 393528ff173b425270c35ab86ea144cd89c1fd5e Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 18:17:47 -0400 Subject: [PATCH 11/17] calculator --- assets/saves/oct26-80/best-calculator-63.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 assets/saves/oct26-80/best-calculator-63.json diff --git a/assets/saves/oct26-80/best-calculator-63.json b/assets/saves/oct26-80/best-calculator-63.json new file mode 100644 index 0000000..9a84641 --- /dev/null +++ b/assets/saves/oct26-80/best-calculator-63.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":[]}],"hidden":[{"id":50,"x":0.29999998,"incoming":[{"fromId":5,"weight":-0.73318624,"enabled":true},{"fromId":0,"weight":-0.09799453,"enabled":true}]},{"id":86,"x":0.29999998,"incoming":[{"fromId":5,"weight":0.9698178,"enabled":true},{"fromId":0,"weight":0.82551205,"enabled":true}]},{"id":14,"x":0.49999997,"incoming":[{"fromId":4,"weight":-0.25745088,"enabled":true},{"fromId":0,"weight":0.2020576,"enabled":true}]},{"id":17,"x":0.49999997,"incoming":[{"fromId":7,"weight":0.8697504,"enabled":true},{"fromId":0,"weight":-0.309209,"enabled":true},{"fromId":2,"weight":-1.008094,"enabled":true},{"fromId":1,"weight":0.45947468,"enabled":true}]},{"id":18,"x":0.49999997,"incoming":[{"fromId":6,"weight":0.04953271,"enabled":true},{"fromId":0,"weight":-0.55681276,"enabled":true}]},{"id":21,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.04523669,"enabled":true},{"fromId":0,"weight":0.21809094,"enabled":true},{"fromId":3,"weight":0.08068417,"enabled":true},{"fromId":50,"weight":0.6940344,"enabled":true}]},{"id":24,"x":0.49999997,"incoming":[{"fromId":0,"weight":0.276672,"enabled":true},{"fromId":3,"weight":-0.35569942,"enabled":true},{"fromId":4,"weight":-0.56869346,"enabled":true}]},{"id":25,"x":0.49999997,"incoming":[{"fromId":8,"weight":-0.56451136,"enabled":true},{"fromId":0,"weight":-0.02433632,"enabled":true},{"fromId":3,"weight":0.11776473,"enabled":true}]},{"id":26,"x":0.49999997,"incoming":[{"fromId":2,"weight":0.1757319,"enabled":true},{"fromId":0,"weight":0.4079069,"enabled":true}]},{"id":27,"x":0.49999997,"incoming":[{"fromId":0,"weight":0.55360836,"enabled":true},{"fromId":6,"weight":-0.015474588,"enabled":true}]},{"id":29,"x":0.49999997,"incoming":[{"fromId":3,"weight":0.83256924,"enabled":true},{"fromId":0,"weight":-0.12941436,"enabled":true},{"fromId":50,"weight":0.32883006,"enabled":true}]},{"id":32,"x":0.49999997,"incoming":[{"fromId":1,"weight":-0.8327364,"enabled":true},{"fromId":0,"weight":0.37621683,"enabled":true},{"fromId":2,"weight":-0.4280192,"enabled":true}]},{"id":33,"x":0.49999997,"incoming":[{"fromId":2,"weight":0.20178892,"enabled":true},{"fromId":0,"weight":0.03770241,"enabled":true},{"fromId":50,"weight":0.91409963,"enabled":true}]},{"id":35,"x":0.49999997,"incoming":[{"fromId":5,"weight":-0.5423191,"enabled":true},{"fromId":0,"weight":0.39796975,"enabled":true},{"fromId":86,"weight":-0.3052985,"enabled":true}]},{"id":38,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.28430638,"enabled":true},{"fromId":0,"weight":0.43830466,"enabled":true},{"fromId":5,"weight":0.4438919,"enabled":true}]},{"id":40,"x":0.49999997,"incoming":[{"fromId":1,"weight":-0.26690724,"enabled":true},{"fromId":0,"weight":-0.4035197,"enabled":true}]},{"id":43,"x":0.49999997,"incoming":[{"fromId":7,"weight":0.0696076,"enabled":true},{"fromId":0,"weight":0.67335284,"enabled":true},{"fromId":3,"weight":0.15251754,"enabled":true}]},{"id":46,"x":0.49999997,"incoming":[{"fromId":5,"weight":0.3982815,"enabled":true},{"fromId":0,"weight":0.051497206,"enabled":true},{"fromId":50,"weight":0.7691549,"enabled":true},{"fromId":6,"weight":0.13945112,"enabled":true},{"fromId":4,"weight":0.4318214,"enabled":true}]},{"id":56,"x":0.7,"incoming":[{"fromId":29,"weight":0.2432541,"enabled":true},{"fromId":0,"weight":0.32452047,"enabled":true}]},{"id":87,"x":0.7,"incoming":[{"fromId":27,"weight":1.0535529,"enabled":true},{"fromId":0,"weight":0.23740153,"enabled":true},{"fromId":8,"weight":0.27270532,"enabled":true},{"fromId":33,"weight":0.07614358,"enabled":true},{"fromId":17,"weight":0.5065347,"enabled":true}]}],"outputs":[{"id":9,"x":0.9,"incoming":[{"fromId":0,"weight":0.9560259,"enabled":true},{"fromId":1,"weight":2.7942674,"enabled":true},{"fromId":2,"weight":-0.012877494,"enabled":true},{"fromId":3,"weight":0.41387114,"enabled":true},{"fromId":4,"weight":-0.03746769,"enabled":true},{"fromId":5,"weight":-0.08882332,"enabled":true},{"fromId":6,"weight":-1.0062743,"enabled":true},{"fromId":7,"weight":-0.29130673,"enabled":true},{"fromId":8,"weight":-0.15766335,"enabled":true},{"fromId":18,"weight":-0.5196569,"enabled":true},{"fromId":33,"weight":-0.18276952,"enabled":true},{"fromId":38,"weight":-0.20709828,"enabled":true}]},{"id":10,"x":0.9,"incoming":[{"fromId":0,"weight":0.42232144,"enabled":true},{"fromId":1,"weight":-0.59807914,"enabled":true},{"fromId":2,"weight":-0.5933764,"enabled":true},{"fromId":3,"weight":0.41245896,"enabled":true},{"fromId":4,"weight":0.23198506,"enabled":true},{"fromId":5,"weight":-0.49270597,"enabled":true},{"fromId":6,"weight":0.10625306,"enabled":true},{"fromId":7,"weight":-0.27555874,"enabled":true},{"fromId":8,"weight":-0.6163728,"enabled":true},{"fromId":14,"weight":0.78394365,"enabled":true},{"fromId":17,"weight":-0.15328407,"enabled":true},{"fromId":24,"weight":0.22443217,"enabled":true},{"fromId":32,"weight":1.2688023,"enabled":true},{"fromId":25,"weight":0.20290792,"enabled":true},{"fromId":46,"weight":-0.5708654,"enabled":true}]},{"id":11,"x":0.9,"incoming":[{"fromId":0,"weight":0.2933759,"enabled":true},{"fromId":1,"weight":-0.264471,"enabled":true},{"fromId":2,"weight":1.3373718,"enabled":true},{"fromId":3,"weight":-0.3901256,"enabled":true},{"fromId":4,"weight":0.53775966,"enabled":true},{"fromId":5,"weight":1.0048475,"enabled":true},{"fromId":6,"weight":-0.68406343,"enabled":true},{"fromId":7,"weight":0.5846671,"enabled":true},{"fromId":8,"weight":0.16000403,"enabled":true},{"fromId":21,"weight":-0.02291429,"enabled":true},{"fromId":26,"weight":0.122866854,"enabled":true},{"fromId":29,"weight":0.31331462,"enabled":true},{"fromId":40,"weight":0.045506343,"enabled":true},{"fromId":27,"weight":-0.25766852,"enabled":true},{"fromId":46,"weight":0.95972085,"enabled":true}]},{"id":12,"x":0.9,"incoming":[{"fromId":0,"weight":-0.47128052,"enabled":true},{"fromId":1,"weight":-0.2914877,"enabled":true},{"fromId":2,"weight":0.588234,"enabled":true},{"fromId":3,"weight":-0.84448004,"enabled":true},{"fromId":4,"weight":0.23557794,"enabled":true},{"fromId":5,"weight":-0.28685936,"enabled":true},{"fromId":6,"weight":0.0016622841,"enabled":true},{"fromId":7,"weight":-0.70946217,"enabled":true},{"fromId":8,"weight":-0.11265129,"enabled":true},{"fromId":25,"weight":0.75856704,"enabled":true},{"fromId":27,"weight":-0.3125484,"enabled":true},{"fromId":35,"weight":0.01801601,"enabled":true},{"fromId":43,"weight":0.2998522,"enabled":true},{"fromId":29,"weight":0.5195759,"enabled":true},{"fromId":56,"weight":0.20899533,"enabled":true},{"fromId":87,"weight":0.12560014,"enabled":true}]}]} \ No newline at end of file From 11fb6567912c81caaafee68c7b0e1069f57cb8cf Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 18:29:23 -0400 Subject: [PATCH 12/17] Tournament ready code --- core/src/main/java/com/buaisociety/pacman/NeatConfig.java | 4 ++-- .../pacman/entity/behavior/TournamentBehavior.java | 2 +- .../java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java index e32506c..f137893 100644 --- a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java +++ b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java @@ -14,8 +14,8 @@ public class NeatConfig { public static boolean loadFromFile = true; - public static String folder = "oct26-76"; - public static String file = "generation-54.json"; + public static String folder = "oct26-80"; + public static String file = "generation-64.json"; public static boolean USE_TOURNAMENT_SETTINGS = false; } 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 3027b7f..31703be 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 @@ -141,7 +141,7 @@ public Direction getDirection(@NotNull Entity entity) { positions.remove(0); if(positions.get(0).equals(positions.get(99))) { Random random = new Random(); - newDirection = Direction.values()[random.nextInt(4)]; +// newDirection = Direction.values()[random.nextInt(4)]; } } diff --git a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java index 72942c6..11aecaf 100644 --- a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java +++ b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java @@ -18,6 +18,8 @@ private static Lwjgl3Application createApplication() { Lwjgl3ApplicationConfiguration config = getDefaultConfiguration(); // disable vsync to run the game as fast as possible config.useVsync(false); + config.setForegroundFPS(200); + // hard limit on fps to see the game running at a reasonable speed return new Lwjgl3Application(new Main(), config); } else { From 82e0ce16a4090f0b7be867102e595656c5225093 Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 18:39:01 -0400 Subject: [PATCH 13/17] Tournament ready code --- core/src/main/java/com/buaisociety/pacman/NeatConfig.java | 2 +- core/src/main/java/com/buaisociety/pacman/Tournament.java | 2 +- .../main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java index f137893..4e906c3 100644 --- a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java +++ b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java @@ -15,7 +15,7 @@ public class NeatConfig { public static boolean loadFromFile = true; public static String folder = "oct26-80"; - public static String file = "generation-64.json"; + public static String file = "generation-70.json"; public static boolean USE_TOURNAMENT_SETTINGS = false; } diff --git a/core/src/main/java/com/buaisociety/pacman/Tournament.java b/core/src/main/java/com/buaisociety/pacman/Tournament.java index 9abbe00..db3bc44 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 + "oct26-80" + File.separator + "best-calculator-63.json"); + File file = new File("saves" + File.separator + "oct26-83" + File.separator + "best-calculator-87.json"); if (!file.exists()) { System.err.println("Could not find the file: " + file.getAbsolutePath()); return null; diff --git a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java index 11aecaf..6ffdc38 100644 --- a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java +++ b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java @@ -18,7 +18,7 @@ private static Lwjgl3Application createApplication() { Lwjgl3ApplicationConfiguration config = getDefaultConfiguration(); // disable vsync to run the game as fast as possible config.useVsync(false); - config.setForegroundFPS(200); + config.setForegroundFPS(2000); // hard limit on fps to see the game running at a reasonable speed return new Lwjgl3Application(new Main(), config); From 1f256fc38b45bf438b304aa8f9f3b0e34012e349 Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 18:39:25 -0400 Subject: [PATCH 14/17] calculator --- assets/saves/oct26-83/best-calculator-87.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 assets/saves/oct26-83/best-calculator-87.json diff --git a/assets/saves/oct26-83/best-calculator-87.json b/assets/saves/oct26-83/best-calculator-87.json new file mode 100644 index 0000000..df495dc --- /dev/null +++ b/assets/saves/oct26-83/best-calculator-87.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":[]}],"hidden":[{"id":103,"x":0.29999998,"incoming":[{"fromId":4,"weight":-0.75605005,"enabled":true},{"fromId":0,"weight":-0.395471,"enabled":true}]},{"id":147,"x":0.29999998,"incoming":[{"fromId":4,"weight":-1.1067865,"enabled":true},{"fromId":0,"weight":0.5648355,"enabled":true},{"fromId":8,"weight":0.20269266,"enabled":true},{"fromId":5,"weight":-2.3135078,"enabled":true}]},{"id":149,"x":0.29999998,"incoming":[{"fromId":2,"weight":0.45141992,"enabled":true},{"fromId":0,"weight":0.39749306,"enabled":true},{"fromId":1,"weight":0.6346309,"enabled":true},{"fromId":4,"weight":-0.6604395,"enabled":true}]},{"id":160,"x":0.29999998,"incoming":[{"fromId":4,"weight":-1.7306871,"enabled":true},{"fromId":0,"weight":0.5938391,"enabled":true}]},{"id":16,"x":0.49999997,"incoming":[{"fromId":6,"weight":-0.14614072,"enabled":true},{"fromId":0,"weight":0.2316266,"enabled":true}]},{"id":18,"x":0.49999997,"incoming":[{"fromId":6,"weight":0.46015835,"enabled":true},{"fromId":0,"weight":-0.38740024,"enabled":true},{"fromId":2,"weight":-0.14090341,"enabled":false},{"fromId":5,"weight":-0.59524506,"enabled":true},{"fromId":3,"weight":-0.10561895,"enabled":true},{"fromId":4,"weight":0.3116178,"enabled":true},{"fromId":7,"weight":0.060221955,"enabled":true},{"fromId":1,"weight":0.9951101,"enabled":true},{"fromId":8,"weight":-0.28201458,"enabled":true},{"fromId":149,"weight":1.4118636,"enabled":true},{"fromId":147,"weight":-0.20409444,"enabled":true}]},{"id":23,"x":0.49999997,"incoming":[{"fromId":8,"weight":-0.013752326,"enabled":true},{"fromId":0,"weight":-0.47912228,"enabled":true},{"fromId":6,"weight":-0.4030968,"enabled":true},{"fromId":4,"weight":-1.0460085,"enabled":true},{"fromId":3,"weight":0.5285127,"enabled":true},{"fromId":147,"weight":0.32999167,"enabled":true}]},{"id":25,"x":0.49999997,"incoming":[{"fromId":8,"weight":1.4021529,"enabled":true},{"fromId":0,"weight":-1.6461971,"enabled":true}]},{"id":26,"x":0.49999997,"incoming":[{"fromId":2,"weight":1.013501,"enabled":true},{"fromId":0,"weight":-1.2715883,"enabled":true}]},{"id":28,"x":0.49999997,"incoming":[{"fromId":1,"weight":1.5577924,"enabled":true},{"fromId":0,"weight":-0.2747485,"enabled":true}]},{"id":30,"x":0.49999997,"incoming":[{"fromId":2,"weight":-0.12490612,"enabled":true},{"fromId":0,"weight":-2.2526577,"enabled":true}]},{"id":31,"x":0.49999997,"incoming":[{"fromId":7,"weight":-0.7602389,"enabled":true},{"fromId":0,"weight":-0.8037754,"enabled":true}]},{"id":34,"x":0.49999997,"incoming":[{"fromId":0,"weight":1.1757369,"enabled":true},{"fromId":8,"weight":-0.36619347,"enabled":true},{"fromId":5,"weight":-0.8266476,"enabled":true},{"fromId":147,"weight":-0.38919455,"enabled":true}]},{"id":35,"x":0.49999997,"incoming":[{"fromId":5,"weight":0.40289527,"enabled":true},{"fromId":0,"weight":-0.6015227,"enabled":true},{"fromId":4,"weight":0.75680274,"enabled":true},{"fromId":8,"weight":-3.0960274,"enabled":true},{"fromId":7,"weight":1.3233759,"enabled":true},{"fromId":103,"weight":-0.5590126,"enabled":true},{"fromId":6,"weight":-1.6878694,"enabled":true}]},{"id":38,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.022669524,"enabled":true},{"fromId":0,"weight":0.48031467,"enabled":true},{"fromId":7,"weight":-1.3196592,"enabled":true},{"fromId":1,"weight":0.23968288,"enabled":true}]},{"id":40,"x":0.49999997,"incoming":[{"fromId":1,"weight":-0.25357947,"enabled":true},{"fromId":0,"weight":-0.70107985,"enabled":true},{"fromId":7,"weight":0.67628485,"enabled":true},{"fromId":4,"weight":-1.2484431,"enabled":true},{"fromId":8,"weight":0.5711151,"enabled":true},{"fromId":147,"weight":-0.08900313,"enabled":true},{"fromId":160,"weight":-1.1897418,"enabled":true}]},{"id":46,"x":0.49999997,"incoming":[{"fromId":5,"weight":-1.5862174,"enabled":true},{"fromId":0,"weight":-0.608724,"enabled":true},{"fromId":6,"weight":-0.7941735,"enabled":true},{"fromId":2,"weight":-0.56962997,"enabled":true},{"fromId":8,"weight":-0.99704194,"enabled":true}]},{"id":52,"x":0.49999997,"incoming":[{"fromId":3,"weight":0.85444355,"enabled":true},{"fromId":0,"weight":0.15006885,"enabled":true},{"fromId":8,"weight":-1.6256567,"enabled":true}]},{"id":114,"x":0.7,"incoming":[{"fromId":18,"weight":0.8579911,"enabled":true},{"fromId":0,"weight":0.12111913,"enabled":true},{"fromId":8,"weight":0.5964794,"enabled":true},{"fromId":28,"weight":1.7859622,"enabled":true}]}],"outputs":[{"id":9,"x":0.9,"incoming":[{"fromId":0,"weight":0.031564653,"enabled":true},{"fromId":1,"weight":2.9249825,"enabled":true},{"fromId":2,"weight":0.064846426,"enabled":true},{"fromId":3,"weight":-0.27084315,"enabled":true},{"fromId":4,"weight":-0.68686,"enabled":true},{"fromId":5,"weight":-0.46270803,"enabled":true},{"fromId":6,"weight":-0.90852225,"enabled":true},{"fromId":7,"weight":-1.3909911,"enabled":true},{"fromId":8,"weight":-0.81490093,"enabled":true},{"fromId":18,"weight":-0.04064625,"enabled":true},{"fromId":34,"weight":-1.0095365,"enabled":true},{"fromId":38,"weight":-0.32835463,"enabled":true},{"fromId":23,"weight":0.5576244,"enabled":true},{"fromId":147,"weight":-0.57810694,"enabled":true},{"fromId":149,"weight":0.24368237,"enabled":true}]},{"id":10,"x":0.9,"incoming":[{"fromId":0,"weight":-0.6603104,"enabled":true},{"fromId":1,"weight":-0.74902034,"enabled":true},{"fromId":2,"weight":-2.0361514,"enabled":true},{"fromId":3,"weight":-0.8444284,"enabled":false},{"fromId":4,"weight":0.6633264,"enabled":true},{"fromId":5,"weight":0.119130395,"enabled":true},{"fromId":6,"weight":-1.1926677,"enabled":true},{"fromId":7,"weight":0.1384106,"enabled":true},{"fromId":8,"weight":0.18462567,"enabled":true},{"fromId":16,"weight":1.0407348,"enabled":true},{"fromId":52,"weight":-0.53094435,"enabled":true},{"fromId":18,"weight":-0.16371676,"enabled":true},{"fromId":46,"weight":0.4681051,"enabled":true},{"fromId":35,"weight":-0.41891068,"enabled":true},{"fromId":114,"weight":-0.37253943,"enabled":true}]},{"id":11,"x":0.9,"incoming":[{"fromId":0,"weight":-1.191597,"enabled":true},{"fromId":1,"weight":-0.7668504,"enabled":true},{"fromId":2,"weight":2.0141513,"enabled":true},{"fromId":3,"weight":-0.61426795,"enabled":true},{"fromId":4,"weight":-0.05082631,"enabled":true},{"fromId":5,"weight":0.39534637,"enabled":true},{"fromId":6,"weight":0.54902935,"enabled":true},{"fromId":7,"weight":-0.51054525,"enabled":false},{"fromId":8,"weight":-1.3694528,"enabled":true},{"fromId":23,"weight":-0.23818377,"enabled":true},{"fromId":26,"weight":0.84556675,"enabled":true},{"fromId":31,"weight":0.7206137,"enabled":true},{"fromId":40,"weight":-0.23447919,"enabled":true},{"fromId":46,"weight":-0.33683693,"enabled":true},{"fromId":16,"weight":0.25415665,"enabled":true}]},{"id":12,"x":0.9,"incoming":[{"fromId":0,"weight":-2.46104,"enabled":true},{"fromId":1,"weight":0.09756243,"enabled":true},{"fromId":2,"weight":-0.9753169,"enabled":true},{"fromId":3,"weight":-0.5540126,"enabled":true},{"fromId":4,"weight":0.97165066,"enabled":true},{"fromId":5,"weight":-0.8292797,"enabled":true},{"fromId":6,"weight":0.19022086,"enabled":true},{"fromId":7,"weight":-0.38471147,"enabled":true},{"fromId":8,"weight":-0.13585834,"enabled":true},{"fromId":25,"weight":-0.39404047,"enabled":true},{"fromId":28,"weight":-2.1526086,"enabled":true},{"fromId":30,"weight":-1.2365108,"enabled":true},{"fromId":35,"weight":0.6078776,"enabled":true},{"fromId":46,"weight":-0.68654394,"enabled":true},{"fromId":40,"weight":-0.60150987,"enabled":true},{"fromId":18,"weight":1.7129339,"enabled":true}]}]} \ No newline at end of file From 07aee0f4f205e69281ccdcd88a041c75add7c803 Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 18:42:23 -0400 Subject: [PATCH 15/17] Tournament ready code --- .../buaisociety/pacman/entity/behavior/TournamentBehavior.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 31703be..3027b7f 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 @@ -141,7 +141,7 @@ public Direction getDirection(@NotNull Entity entity) { positions.remove(0); if(positions.get(0).equals(positions.get(99))) { Random random = new Random(); -// newDirection = Direction.values()[random.nextInt(4)]; + newDirection = Direction.values()[random.nextInt(4)]; } } From 601dfdf4700d8e82d796001aa0cc54514af8383d Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Sat, 26 Oct 2024 19:15:12 -0400 Subject: [PATCH 16/17] Tournament ready code --- core/src/main/java/com/buaisociety/pacman/Tournament.java | 2 +- .../pacman/entity/behavior/TournamentBehavior.java | 4 ++-- .../java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/buaisociety/pacman/Tournament.java b/core/src/main/java/com/buaisociety/pacman/Tournament.java index db3bc44..9abbe00 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 + "oct26-83" + File.separator + "best-calculator-87.json"); + File file = new File("saves" + File.separator + "oct26-80" + File.separator + "best-calculator-63.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/TournamentBehavior.java b/core/src/main/java/com/buaisociety/pacman/entity/behavior/TournamentBehavior.java index 3027b7f..4a43b58 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 @@ -137,9 +137,9 @@ public Direction getDirection(@NotNull Entity entity) { // Add the new direction to move history moveHistory.add(newDirection); positions.add(new Vector2d(pacman.getTilePosition().x(), pacman.getTilePosition().y())); - if (positions.size() > 100 && updatesSinceLastScore > 60) { + if (positions.size() > 40 && updatesSinceLastScore > 30) { positions.remove(0); - if(positions.get(0).equals(positions.get(99))) { + if(positions.get(0).equals(positions.get(39))) { Random random = new Random(); newDirection = Direction.values()[random.nextInt(4)]; } diff --git a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java index 6ffdc38..d3336ea 100644 --- a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java +++ b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java @@ -25,9 +25,9 @@ private static Lwjgl3Application createApplication() { } else { Lwjgl3ApplicationConfiguration config = getDefaultConfiguration(); // enable vsync to limit the fps to the monitor refresh rate - config.useVsync(true); + config.useVsync(false); // pacman runs at 60 updates per second - config.setForegroundFPS(60); + config.setForegroundFPS(240); return new Lwjgl3Application(new Tournament(), config); } } From 8dae18fe271b848c7023ac76b7a029e9eb327d79 Mon Sep 17 00:00:00 2001 From: Aakash Kumar Date: Thu, 5 Dec 2024 12:09:39 -0500 Subject: [PATCH 17/17] Refactor game configuration and level data for tournament readiness --- assets/levels.json | 11 +- .../java/com/buaisociety/pacman/Main.java | 2 +- .../com/buaisociety/pacman/NeatConfig.java | 10 +- .../pacman/SpecialTrainingConditions.java | 2 +- .../entity/behavior/NeatPacmanBehavior.java | 408 +++++++++++------- .../entity/behavior/TournamentBehavior.java | 1 + gradle.properties | 4 +- lwjgl3/build.gradle | 3 + 8 files changed, 258 insertions(+), 183 deletions(-) diff --git a/assets/levels.json b/assets/levels.json index d954192..f2c9759 100644 --- a/assets/levels.json +++ b/assets/levels.json @@ -1,14 +1,5 @@ { "levels": [ - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9" + "0" ] } diff --git a/core/src/main/java/com/buaisociety/pacman/Main.java b/core/src/main/java/com/buaisociety/pacman/Main.java index b5ab23d..d705307 100644 --- a/core/src/main/java/com/buaisociety/pacman/Main.java +++ b/core/src/main/java/com/buaisociety/pacman/Main.java @@ -51,7 +51,7 @@ public class Main extends ApplicationAdapter { private final @NotNull EventSystem events = new EventSystem(); private final @NotNull Vector2i visibleGames = new Vector2i(4, 2); private final @NotNull List managers = new ArrayList<>(); - private final int totalGames = 500; + private final int totalGames = NeatConfig.populationSize; private GameLoop secondLoop; // 1 update per second private boolean paused; private boolean showNetworks; diff --git a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java index 4e906c3..6dbbf52 100644 --- a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java +++ b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java @@ -2,18 +2,20 @@ public class NeatConfig { // NEAT parameters + public static int populationSize = 300; public static float mutateWeightChance = 0.75f; - public static float weightCoefficient = 1.0f; + public static float weightCoefficient = 0.3f; public static int targetClientsPerSpecies = 12; - public static int stagnationLimit = 10; // what does this do? + public static int stagnationLimit = 10; public static boolean biasEnabled = true; public static int neatInputNodes = 8; - public static int neatOutputNodes = 4; +// public static int neatInputNodes = 22; + public static int neatOutputNodes = 6; - public static boolean loadFromFile = true; + public static boolean loadFromFile = false; public static String folder = "oct26-80"; public static String file = "generation-70.json"; diff --git a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java index a119c57..cff26fb 100644 --- a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java +++ b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java @@ -50,7 +50,7 @@ private SpecialTrainingConditions() { continue; if (ThreadLocalRandom.current().nextDouble() < pelletDensity) { -// maze.getTile(x, y).setState(TileState.SPACE); + maze.getTile(x, y).setState(TileState.SPACE); } } } 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 6ea65f8..4173ecf 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,6 +2,7 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.buaisociety.pacman.NeatConfig; import com.buaisociety.pacman.entity.GhostEntity; import com.buaisociety.pacman.maze.Maze; import com.buaisociety.pacman.maze.Tile; @@ -11,6 +12,7 @@ import com.buaisociety.pacman.entity.Direction; import com.buaisociety.pacman.entity.Entity; import com.buaisociety.pacman.entity.PacmanEntity; +import com.cjcrafter.neat.Neat; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.Vector2d; @@ -20,6 +22,7 @@ import java.util.*; public class NeatPacmanBehavior implements Behavior { + public static float epsilon = 1.0f; private final @NotNull Client client; private @Nullable PacmanEntity pacman; @@ -43,9 +46,15 @@ public class NeatPacmanBehavior implements Behavior { public int numGhosts = 0; - public int visionRange = 2; + private float[] lastOutputs = new float[NeatConfig.neatOutputNodes]; private final Random random; + List inputs = new ArrayList<>(); + + List highlightedTiles = new ArrayList<>(); + List highlightedPellets = new ArrayList<>(); + + public static boolean useRelative = true; public NeatPacmanBehavior(@NotNull Client client) { this.client = client; @@ -64,38 +73,19 @@ public Direction getDirection(@NotNull Entity entity) { initializePacman(entity); updateDirections(); + highlightedTiles.clear(); + highlightedPellets.clear(); distances = computeDistances(); handleSpecialTrainingConditions(); - float[] rayCastDistances = performRayCasting(); - Tile nearestPellet = getNearestPellet(); - Vector2d relativePelletPos = translateRelative(nearestPellet.getPosition()); - - Direction suggestedPelletDirection = getSuggestedDirection(nearestPellet); - Vector2d suggestedPelletDirRelative = rotateRelative(new Vector2d( - suggestedPelletDirection.getDx(), - suggestedPelletDirection.getDy() - )); - - Tile nearestPowerPellet = getNearestPowerPellet(); - Vector2d relativePowerPelletPos = translateRelative(nearestPowerPellet.getPosition()); - - Direction suggestedPowerDirection = getSuggestedDirection(nearestPowerPellet); - Vector2d suggestedPowerDirRelative = rotateRelative(new Vector2d( - suggestedPowerDirection.getDx(), - suggestedPowerDirection.getDy() - )); - - GhostInfo ghostInfo = gatherGhostInformation(); - - float[] inputs = buildInputs(rayCastDistances, relativePelletPos, suggestedPelletDirRelative, - nearestPellet, relativePowerPelletPos, suggestedPowerDirRelative, ghostInfo); + float[] inputs = buildInputs(); float[] outputs = client.getCalculator().calculate(inputs).join(); + lastOutputs = outputs; - Direction newDirection = selectDirectionFromOutputs(outputs); + Direction newDirection = selectDirectionFromOutputs(new float[]{outputs[0], outputs[1], outputs[2], outputs[3]}); updateScore(newDirection); @@ -110,6 +100,14 @@ public void render(@NotNull SpriteBatch batch) { DebugDrawing.outlineTile(batch, nearestPellet, Color.GREEN); // Additional debug rendering can be added here } + + for(Tile tile : highlightedTiles){ + DebugDrawing.outlineTile(batch, tile, Color.RED); + } + + for (Tile tile : highlightedPellets) { + DebugDrawing.outlineTile(batch, tile, Color.BLUE); + } } /** @@ -125,6 +123,7 @@ private void initializePacman(@NotNull Entity entity) { * Updates the directional fields based on Pacman's current direction. */ private void updateDirections() { + assert pacman != null; forward = pacman.getDirection(); left = pacman.getDirection().left(); right = pacman.getDirection().right(); @@ -146,71 +145,18 @@ private void handleSpecialTrainingConditions() { } else { updatesSinceLastScore++; } + int maxUpdates = 60 * 10; // 10 seconds - if(lastScore > 5000){ - maxUpdates = 60 * 20; // 20 seconds - } - if(lastScore > 10000){ - maxUpdates = 60 * 30; // 30 seconds - } - if (numGhosts > 0) { - if(lastScore > 200){ - maxUpdates = 60 * 30; // 2 minutes - }else{ - maxUpdates = 60 * 3; // 3 seconds - } + if(numGhosts > 0){ + maxUpdates = 60 * 60; // 1 minute } + if (updatesSinceLastScore > maxUpdates) { pacman.kill(); } } - /** - * Performs ray casting in all four directions to detect distances to walls. - * - * @return an array containing distances in forward, left, right, and behind directions - */ - private float[] performRayCasting() { - float[] rayCast = new float[4]; - Vector2ic dimensions = pacman.getMaze().getDimensions(); - - for (int i = 0; i < 4; i++) { - Direction direction = getDirectionByIndex(i); - Vector2i position = new Vector2i(pacman.getTilePosition().x(), pacman.getTilePosition().y()); - - while (isWithinBounds(position, dimensions) && - pacman.getMaze().getTile(position).getState() != TileState.WALL) { - position.add(direction.getDx(), direction.getDy()); - if (!isWithinBounds(position, dimensions)) { - break; - } - rayCast[i] = (float) Math.sqrt( - Math.pow((position.x() - pacman.getTilePosition().x()) / (double) dimensions.x(), 2) + - Math.pow((position.y() - pacman.getTilePosition().y()) / (double) dimensions.y(), 2) - ); - } - } - - return rayCast; - } - - /** - * Retrieves the corresponding Direction based on the index. - * - * @param index the index (0: forward, 1: left, 2: right, 3: behind) - * @return the corresponding Direction - */ - private Direction getDirectionByIndex(int index) { - return switch (index) { - case 0 -> forward; - case 1 -> left; - case 2 -> right; - case 3 -> behind; - default -> throw new IllegalStateException("Unexpected value: " + index); - }; - } - /** * Checks if the given position is within the maze boundaries. * @@ -271,74 +217,194 @@ private List getGhostEntities() { /** * Builds the input array for the NEAT calculator. * - * @param rayCastDistances distances from ray casting - * @param relativePelletPos relative position to the nearest pellet - * @param suggestedPelletDirRelative relative direction to the nearest pellet - * @param nearestPellet the nearest pellet tile - * @param relativePowerPelletPos relative position to the nearest power pellet - * @param suggestedPowerDirRelative relative direction to the nearest power pellet - * @param ghostInfo information about ghosts * @return an array of input values */ - private float[] buildInputs(float[] rayCastDistances, Vector2d relativePelletPos, - Vector2d suggestedPelletDirRelative, Tile nearestPellet, - Vector2d relativePowerPelletPos, Vector2d suggestedPowerDirRelative, - GhostInfo ghostInfo) { + private float[] buildInputs() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); +// addMazeInfo(); + addRayCasts(); + // Disabled for debugging + // addHistory(); +// addSuggestedPellet(); + // addGhostInfo(); + // addSuggestedPowerPellet(); + + /** Disabled for inefficiency + addVision(ghostInfo); too many inputs + */ + // Convert List to float[] + float[] inputArray = new float[inputs.size()]; + for (int i = 0; i < inputs.size(); i++) { + inputArray[i] = inputs.get(i); + } + inputs.clear(); + return inputArray; + } + + private void addMazeInfo() { Vector2ic dimensions = pacman.getMaze().getDimensions(); -// System.out.println("rayCastDistances: " + Arrays.toString(rayCastDistances)); -// if(numGhosts > 0) -// System.out.println(Arrays.toString(ghostInfo.ghostDirections) + " " + Arrays.toString(ghostInfo.ghostDistances)); - return new float[]{ -// // Ray cast distances -// rayCastDistances[0], -// rayCastDistances[1], -// rayCastDistances[2], -// rayCastDistances[3], -// -// // Relative pellet coordinates -// (float) relativePelletPos.x() / dimensions.x(), -// (float) relativePelletPos.y() / dimensions.y(), - - // Suggested pellet direction (x, y) - (float) suggestedPelletDirRelative.x(), - (float) suggestedPelletDirRelative.y(), -// -// // Distance to nearest pellet -// distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()] / (dimensions.x() + dimensions.y() + 0f), -// -// // Relative power pellet coordinates - (float) relativePowerPelletPos.x() / dimensions.x(), - (float) relativePowerPelletPos.y() / dimensions.y(), -// -// // Suggested power pellet direction (x, y) -// (float) suggestedPowerDirRelative.x(), -// (float) suggestedPowerDirRelative.y(), -// -// // Distance to nearest power pellet -// distances[getNearestPowerPellet().getPosition().x()][getNearestPellet().getPosition().y()], -// -//// // Ghost distances - ghostInfo.ghostDistances[0], -// ghostInfo.ghostDistances[1], -// ghostInfo.ghostDistances[2], -// ghostInfo.ghostDistances[3], -//// -//// // Ghost directions (x, y) - ghostInfo.ghostDirections[0] != null ? (float) ghostInfo.ghostDirections[0].x() : 0, - ghostInfo.ghostDirections[0] != null ? (float) ghostInfo.ghostDirections[0].y() : 0, -// ghostInfo.ghostDirections[1] != null ? (float) ghostInfo.ghostDirections[1].x() : 0, -// ghostInfo.ghostDirections[1] != null ? (float) ghostInfo.ghostDirections[1].y() : 0, -// ghostInfo.ghostDirections[2] != null ? (float) ghostInfo.ghostDirections[2].x() : 0, -// ghostInfo.ghostDirections[2] != null ? (float) ghostInfo.ghostDirections[2].y() : 0, -// ghostInfo.ghostDirections[3] != null ? (float) ghostInfo.ghostDirections[3].x() : 0, -// ghostInfo.ghostDirections[3] != null ? (float) ghostInfo.ghostDirections[3].y() : 0, -// - pacman.getMaze().getFrightenedTimer() <= 3 ? 0 : (float) (pacman.getMaze().getFrightenedTimer() / 200f + 0.5), -// -// // Random input for variability -// random.nextFloat(), + float pelletPercentage = pacman.getMaze().getPelletsRemaining() / (float) (dimensions.x() * dimensions.y()); + inputs.add(pelletPercentage); + + } + + private void addGhostInfo(){ + GhostInfo ghostInfo = gatherGhostInformation(); + float nearestDistance = 1000.0f; + Vector2d nearestDirection = null; + + for (int i = 0; i < ghostInfo.ghostDistances.length; i++) { + if (ghostInfo.ghostDirections[i] != null && ghostInfo.ghostDistances[i] < nearestDistance) { + nearestDistance = ghostInfo.ghostDistances[i]; + nearestDirection = ghostInfo.ghostDirections[i]; + } + } + + inputs.add(1.0f / (nearestDistance + epsilon)); + inputs.add(nearestDirection != null ? (float) nearestDirection.x() : 0f); + inputs.add(nearestDirection != null ? (float) nearestDirection.y() : 0f); + // timer + float timeLeft = pacman.getMaze().getFrightenedTimer() <= 3 ? 0 : (float) (pacman.getMaze().getFrightenedTimer() / 200f + 0.5f); + inputs.add(timeLeft); + } + + private void addRayCasts() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + float maxDistance = dimensions.x() + dimensions.y(); // Maximum possible distance + + Vector2i forwardVec = pacman.getDirection().asVector(); + Vector2i rightVec = pacman.getDirection().right().asVector(); + if(!useRelative){ + forwardVec = new Vector2i(0, 1); + rightVec = new Vector2i(1, 0); + } + + // Define the 8 directions for raycasting + Vector2i[] directions = new Vector2i[]{ + new Vector2i(forwardVec.x, forwardVec.y), + new Vector2i(forwardVec.x + rightVec.x, forwardVec.y + rightVec.y), + new Vector2i(rightVec.x, rightVec.y), + new Vector2i(-forwardVec.x + rightVec.x, -forwardVec.y + rightVec.y), +// new Vector2i(-forwardVec.x, -forwardVec.y), +// new Vector2i(-forwardVec.x - rightVec.x, -forwardVec.y - rightVec.y), +// new Vector2i(-rightVec.x, -rightVec.y), +// new Vector2i(forwardVec.x - rightVec.x, forwardVec.y - rightVec.y) }; + float[] rayCastWalls = new float[directions.length]; + float[] rayCastPellets = new float[directions.length]; + + for (int i = 0; i < directions.length; i++) { + Vector2i direction = directions[i]; + Vector2i position = new Vector2i(pacman.getTilePosition()); + + // Ray cast for walls + int wallDistance = 0; + while (isWithinBounds(position, dimensions) && + pacman.getMaze().getTile(position).getState() != TileState.WALL) { + position.add(direction); + wallDistance++; + } + + if (isWithinBounds(position, dimensions)) { + highlightedTiles.add(pacman.getMaze().getTile(position)); + // Normalize distance to [0,1] range - closer walls give higher values + rayCastWalls[i] = 1.0f - (wallDistance / maxDistance); + } + + // Reset position for pellet raycast + position.set(pacman.getTilePosition()); + int pelletDistance = 0; + + // Ray cast for pellets + while (isWithinBounds(position, dimensions) && + pacman.getMaze().getTile(position).getState() != TileState.PELLET && + pacman.getMaze().getTile(position).getState() != TileState.POWER_PELLET) { + position.add(direction); + pelletDistance++; + if (!isWithinBounds(position, dimensions)) { + pelletDistance = (int)maxDistance; // If no pellet found, set to max distance + break; + } + } + + if (isWithinBounds(position, dimensions)) { + highlightedPellets.add(pacman.getMaze().getTile(position)); + // Normalize distance to [0,1] range - closer pellets give higher values + rayCastPellets[i] = 1.0f - (pelletDistance / maxDistance); + } + } + + // Add normalized distances to inputs + for (float wallDist : rayCastWalls) { + inputs.add(wallDist); + } + for (float pelletDist : rayCastPellets) { + inputs.add(pelletDist); + } + } + + private void addHistory(){ + for (int i = 4; i < lastOutputs.length; i++) { + inputs.add(lastOutputs[i]); + } + } + + private void addVision(GhostInfo ghostInfo){ + int visionRange = 4; // n by n grid around pacman + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Vector2i pacmanPos = pacman.getTilePosition(); + + for(int y = -visionRange; y <= visionRange; y++){ + for(int x = -visionRange; x <= visionRange; x++){ + Vector2i pos = new Vector2i(pacmanPos.x() + x, pacmanPos.y() + y); + if(isWithinBounds(pos, dimensions)){ + Tile tile = pacman.getMaze().getTile(pos); + float tileState = 0; + if(tile.getState() == TileState.WALL){ + tileState = 1; + } else if(tile.getState() == TileState.PELLET){ + tileState = 2; + } else if(tile.getState() == TileState.POWER_PELLET){ + tileState = 3; + } + else{ + tileState = 0; + } + inputs.add(tileState); + }else{ + inputs.add(-1f); + } + } + } + } + + private void addSuggestedPowerPellet(){ + Tile nearestPowerPellet = getNearestPowerPellet(); + Vector2d relativePowerPelletPos = translateRelative(nearestPowerPellet.getPosition()); + + Direction suggestedPowerDirection = getSuggestedDirection(nearestPowerPellet); + Vector2d suggestedPowerDirRelative = rotateRelative(new Vector2d( + suggestedPowerDirection.getDx(), + suggestedPowerDirection.getDy() + )); + + inputs.add((float) suggestedPowerDirRelative.x()); + inputs.add((float) suggestedPowerDirRelative.y()); + + } + private void addSuggestedPellet() { + Tile nearestPellet = getNearestPellet(); + Vector2d relativePelletPos = translateRelative(nearestPellet.getPosition()); + + Direction suggestedPelletDirection = getSuggestedDirection(nearestPellet); + Vector2d suggestedPelletDirRelative = rotateRelative(new Vector2d( + suggestedPelletDirection.getDx(), + suggestedPelletDirection.getDy() + )); + + inputs.add((float) suggestedPelletDirRelative.x()); + inputs.add((float) suggestedPelletDirRelative.y()); } @@ -358,14 +424,24 @@ private Direction selectDirectionFromOutputs(float[] outputs) { selectedIndex = i; } } + if (useRelative) { + return switch (selectedIndex) { + case 0 -> forward; + case 1 -> left; + case 2 -> right; + case 3 -> behind; + default -> throw new IllegalStateException("Unexpected output index: " + selectedIndex); + }; + } else{ + return switch (selectedIndex) { + case 0 -> Direction.UP; + case 1 -> Direction.LEFT; + case 2 -> Direction.RIGHT; + case 3 -> Direction.DOWN; + default -> throw new IllegalStateException("Unexpected output index: " + selectedIndex); + }; + } - return switch (selectedIndex) { - case 0 -> forward; - case 1 -> left; - case 2 -> right; - case 3 -> behind; - default -> throw new IllegalStateException("Unexpected output index: " + selectedIndex); - }; } /** @@ -379,19 +455,21 @@ private void updateScore(Direction newDirection) { pacman.getTilePosition().y() + newDirection.getDy() ); - // Example score modifications (can be customized) - // if (isMovingTowardsPellet(newPosition)) { - // scoreModifier += 5; - // } - // if (pacman.getMaze().getTile(newPosition).getState() == TileState.PELLET) { - // scoreModifier += 100; - // } - // if (!pacman.canMove(newDirection)) { - // scoreModifier -= 10; - // } - // Global score penalty to encourage progress -// scoreModifier -= 0.001f; + Tile newTile = pacman.getMaze().getTile(newPosition.x(), newPosition.y()); +// if (newTile.getState() == TileState.PELLET) { +// scoreModifier += 10; // Reward for collecting a pellet +// } else if (newTile.getState() == TileState.POWER_PELLET) { +// scoreModifier += 50; // Higher reward for collecting a power pellet +// } + + // Optional: Penalize for invalid moves +// if (!pacman.canMove(newDirection)) { +// scoreModifier -= 5; +// } + + // Optional: Small penalty to encourage faster completion +// scoreModifier += 0.01f; client.setScore( @@ -458,7 +536,7 @@ public Direction getFirstStepToTile(@NotNull Tile targetTile) { return dir; } } - + System.out.println("No direction found"); return null; // Should not reach here } 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 4a43b58..b70f6e5 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 @@ -73,6 +73,7 @@ public Direction getDirection(@NotNull Entity entity) { // --- DO NOT REMOVE --- if (pacman == null) { } + assert false; pacman = (PacmanEntity) entity; int newScore = pacman.getMaze().getLevelManager().getScore(); diff --git a/gradle.properties b/gradle.properties index 9cf3fe2..599e4de 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ -org.gradle.daemon=false -org.gradle.jvmargs=-Xms512M -Xmx2G -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8 +org.gradle.daemon=true +org.gradle.jvmargs=-Xms4G -Xmx8G -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8 org.gradle.configureondemand=false graalHelperVersion=2.0.1 enableGraalNative=false diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index e7676a9..59fdaa9 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -17,6 +17,8 @@ plugins { import io.github.fourlastor.construo.Target + + sourceSets.main.resources.srcDirs += [ rootProject.file('assets').path ] mainClassName = 'com.buaisociety.pacman.lwjgl3.Lwjgl3Launcher' application.setMainClass(mainClassName) @@ -110,6 +112,7 @@ tasks.register('dist') { dependsOn 'jar' } + distributions { main { contents {