From 4dd5a19bb798308105ee566a051b4854b4ea1b4f Mon Sep 17 00:00:00 2001 From: hoangvu05 Date: Sat, 26 Oct 2024 18:23:52 -0400 Subject: [PATCH 1/2] done --- .../java/com/buaisociety/pacman/Clusters.java | 54 ++++ .../java/com/buaisociety/pacman/Main.java | 11 +- .../java/com/buaisociety/pacman/Searcher.java | 136 +++++++++ .../com/buaisociety/pacman/Tournament.java | 2 +- .../entity/behavior/NeatPacmanBehavior.java | 243 ++++++++++++++- .../entity/behavior/TournamentBehavior.java | 282 +++++++++++++++++- 6 files changed, 709 insertions(+), 19 deletions(-) create mode 100644 core/src/main/java/com/buaisociety/pacman/Clusters.java create mode 100644 core/src/main/java/com/buaisociety/pacman/Searcher.java diff --git a/core/src/main/java/com/buaisociety/pacman/Clusters.java b/core/src/main/java/com/buaisociety/pacman/Clusters.java new file mode 100644 index 0000000..1625976 --- /dev/null +++ b/core/src/main/java/com/buaisociety/pacman/Clusters.java @@ -0,0 +1,54 @@ +package com.buaisociety.pacman; + +import org.joml.Vector2i; + +import java.util.ArrayList; +import java.util.List; + +public class Clusters { + + /** + * Identifies clusters of points (pellets) within a given distance threshold. + * + * @param points List of points (Vector2i) representing pellet positions. + * @param distanceThreshold The maximum distance to consider points as part of the same cluster. + * @return A list of clusters, where each cluster is a list of Vector2i points. + */ + public List> identifyClusters(List points, double distanceThreshold) { + List> clusters = new ArrayList<>(); + boolean[] visited = new boolean[points.size()]; + + for (int i = 0; i < points.size(); i++) { + if (!visited[i]) { + List cluster = new ArrayList<>(); + findCluster(points, i, visited, cluster, distanceThreshold); + clusters.add(cluster); + } + } + return clusters; + } + + /** + * Recursively finds all points in the same cluster as the given point. + * + * @param points List of points (Vector2i) to check. + * @param index The index of the point to check. + * @param visited Array to track visited points. + * @param cluster List to accumulate points in the current cluster. + * @param distanceThreshold The maximum distance to consider points as part of the same cluster. + */ + private void findCluster(List points, int index, boolean[] visited, List cluster, double distanceThreshold) { + visited[index] = true; + cluster.add(points.get(index)); + + Vector2i currentPoint = points.get(index); + for (int i = 0; i < points.size(); i++) { + if (!visited[i]) { + Vector2i neighborPoint = points.get(i); + if (currentPoint.distance(neighborPoint) <= distanceThreshold) { + findCluster(points, i, visited, cluster, distanceThreshold); + } + } + } + } +} diff --git a/core/src/main/java/com/buaisociety/pacman/Main.java b/core/src/main/java/com/buaisociety/pacman/Main.java index 29113e7..2de6f81 100644 --- a/core/src/main/java/com/buaisociety/pacman/Main.java +++ b/core/src/main/java/com/buaisociety/pacman/Main.java @@ -103,9 +103,9 @@ public void create() { public @NotNull Neat createNeat() { // Change this to true/false as needed, if you want to load from file - if (false) { - // TODO: Change this to the exact file you want to load - File exactFile = new File("saves" + File.separator + "oct26-4" + File.separator + "generation-51.json"); + if (true) { + // TODO: Change this to the exact file you wan q t to load + File exactFile = new File("saves" + File.separator + "oct26-24" + File.separator + "generation-91.json"); // load exactFile contents to string String json; try { @@ -115,7 +115,7 @@ public void create() { } NeatImpl impl = NeatImpl.fromJson(json); // modify this as needed - //impl.updateNodeCounts(8, 4); // Add 4 new inputs + impl.updateNodeCounts(12, 4); // Add 4 new inputs //impl.updateClients(200); // have 200 pacman games at once return impl; } else { @@ -125,7 +125,8 @@ public void create() { neatParameters.setTargetClientsPerSpecies(12); // targeting ~12 clients per species neatParameters.setStagnationLimit(10); // lower stagnation limit neatParameters.setUseBiasNode(true); // use bias node - return new NeatImpl(4, 4, totalGames, neatParameters); + // CHANGE THE PARAMETER TO # OF INPUTS + return new NeatImpl(12,4, totalGames, neatParameters); } } diff --git a/core/src/main/java/com/buaisociety/pacman/Searcher.java b/core/src/main/java/com/buaisociety/pacman/Searcher.java new file mode 100644 index 0000000..ea6ef03 --- /dev/null +++ b/core/src/main/java/com/buaisociety/pacman/Searcher.java @@ -0,0 +1,136 @@ +package com.buaisociety.pacman; + +import com.buaisociety.pacman.entity.Direction; +import com.buaisociety.pacman.maze.Maze; +import com.buaisociety.pacman.maze.Tile; +import com.buaisociety.pacman.maze.TileState; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2d; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.function.Predicate; +import java.util.Map; + +public class Searcher { + + /** + * Stores the result of the BFS search, including the found tile and the distance from the start tile. + */ + public static class SearchResult { + private final Tile tile; + private final int distance; + + public SearchResult(Tile tile, int distance) { + this.tile = tile; + this.distance = distance; + } + + public Tile getTile() { + return tile; + } + + public int getDistance() { + return distance; + } + } + + /** + * Performs a BFS search in each of the four directions to find tiles matching the predicate. + * + * @param startTile The starting tile for the BFS. + * @param predicate The predicate to test each tile. + * @return A Map containing the first matching tile and distance for each direction. + */ + public static Map findTileInAllDirections(@NotNull Tile startTile, @NotNull Predicate predicate) { + Map results = new EnumMap<>(Direction.class); + + for (Direction direction : Direction.values()) { + SearchResult result = findTileWithBFS(startTile, predicate, direction); + if (result != null) { + results.put(direction, result); + } + } + + return results; + } + + /** + * Performs a BFS to find the closest tile in the specified direction that matches the predicate. + * + * @param startTile The starting tile for the BFS. + * @param predicate The predicate to test each tile. + * @param direction The initial direction for the search. + * @return The SearchResult containing the tile and distance, or null if no matching tile is found. + */ + private static SearchResult findTileWithBFS(@NotNull Tile startTile, @NotNull Predicate predicate, @NotNull Direction direction) { + Queue queue = new ArrayDeque<>(); + Set visited = new HashSet<>(); + Queue distances = new ArrayDeque<>(); + + Vector2i startPosition = new Vector2i(startTile.getPosition()).add(getDirectionOffset(direction)); + queue.add(startPosition); + visited.add(startPosition); + distances.add(1); + + while (!queue.isEmpty()) { + Vector2ic currentPos = queue.poll(); + int currentDistance = distances.poll(); + Tile currentTile = startTile.getMaze().getTile(new Vector2i(currentPos)); + + if (predicate.test(currentTile)) { + return new SearchResult(currentTile, currentDistance); + } + + // Enqueue neighboring tiles only in the initial search direction + for (Vector2i neighbor : getNeighborsInDirection(new Vector2i(currentPos), direction)) { + Tile neighborTile = startTile.getMaze().getTile(neighbor); + if (neighborTile.getState() == TileState.WALL) continue; + + if (!visited.contains(neighbor)) { + visited.add(neighbor); + queue.add(neighbor); + distances.add(currentDistance + 1); + } + } + } + + return null; // No tile matching the predicate was found + } + + /** + * Returns the offset vector based on the initial direction. + * + * @param direction The direction for the offset. + * @return The vector offset for moving in that direction. + */ + private static Vector2i getDirectionOffset(Direction direction) { + return switch (direction) { + case UP -> new Vector2i(0, 1); + case DOWN -> new Vector2i(0, -1); + case LEFT -> new Vector2i(-1, 0); + case RIGHT -> new Vector2i(1, 0); + }; + } + + /** + * Returns neighboring tiles based on the given initial direction. + * + * @param position The tile position for which to get neighbors. + * @param direction The primary direction for the search. + * @return A list of neighboring tile positions limited to the specified direction. + */ + private static List getNeighborsInDirection(Vector2i position, Direction direction) { + List neighbors = new ArrayList<>(); + Vector2i offset = getDirectionOffset(direction); + neighbors.add(new Vector2i(position.x + offset.x, position.y + offset.y)); + return neighbors; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/buaisociety/pacman/Tournament.java b/core/src/main/java/com/buaisociety/pacman/Tournament.java index 697fa74..6f79e19 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-24" + File.separator + "best-calculator-91.json"); if (!file.exists()) { System.err.println("Could not find the file: " + file.getAbsolutePath()); return null; diff --git a/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java b/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java index d3fd62f..b038682 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,16 +12,40 @@ import com.buaisociety.pacman.entity.PacmanEntity; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; +import org.joml.Vector2f; +import org.joml.Vector2i; +import org.joml.Vector2ic; +import java.util.ArrayList; +import com.buaisociety.pacman.Searcher; +import com.buaisociety.pacman.Clusters; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.Random; +import java.util.function.Predicate; +import java.util.Map; public class NeatPacmanBehavior implements Behavior { private final @NotNull Client client; private @Nullable PacmanEntity pacman; - + private int pelletCount = 0; // Track the number of pellets collected // 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 int lastDirection = 0; + + private int lastScore = 0; + private int updatesSinceLastScore = 0; + + public NeatPacmanBehavior(@NotNull Client client) { this.client = client; @@ -37,9 +63,20 @@ public Direction getDirection(@NotNull Entity entity) { if (pacman == null) { pacman = (PacmanEntity) entity; } - + //Yen Added // SPECIAL TRAINING CONDITIONS // TODO: Make changes here to help with your training... + int newScore = pacman.getMaze().getLevelManager().getScore(); + if (newScore > lastScore) { + lastScore = newScore; + pelletCount++; + updatesSinceLastScore = 0; + } + + if (updatesSinceLastScore++ > 60 * 10) { + pacman.kill(); + return Direction.UP; + } // END OF SPECIAL TRAINING CONDITIONS // We are going to use these directions a lot for different inputs. Get them all once for clarity and brevity @@ -48,21 +85,171 @@ public Direction getDirection(@NotNull Entity entity) { Direction right = pacman.getDirection().right(); Direction behind = pacman.getDirection().behind(); + + //pacman.getTilePosition().x() + //Tile tile=pacman.getMaze().getTile(pacman.getTilePosition()); + //pacman.getMaze().getEntity() + //entityType + //TileState.WALL + + // 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); + // Calculate nearest pellets +// if (lastDirection==0 && canMoveForward==true) return forward; +// if (lastDirection==1 && canMoveLeft==true) return left; +// if (lastDirection==2 && canMoveRight==true) return right; +// if (lastDirection==3 && canMoveBehind==true) return behind; +// +// //if (pelletCount==0) { +// Random random = new Random(); +// int rand=random.nextInt(4); +// if (rand==0) { +// lastDirection=0; +// return forward; +// } +// if (rand==1) { +// lastDirection=1; +// return left; +// } +// if (rand==2) { +// lastDirection=2; +// return right; +// } +// if (rand==3) { +// lastDirection=3; +// return behind; +// } + //} +// if (pelletCount < 30) { +// Map nearestPellets = Searcher.findTileInAllDirections(pacman.getMaze().getTile(pacman.getTilePosition()), +// tile -> tile.getState() == TileState.PELLET); +// +// if (nearestPellets.isEmpty()) { +// if (canMoveForward) return forward; +// if (canMoveLeft) return left; +// if (canMoveRight) return right; +// if (canMoveBehind) return behind; +// } +// +// } + Tile currentTile = pacman.getMaze().getTile(pacman.getTilePosition()); + Map nearestPellets = Searcher.findTileInAllDirections(currentTile, tile -> tile.getState() == TileState.PELLET); + + // Calculate nearest power pellets + Map nearestPowerPellets = Searcher.findTileInAllDirections(currentTile, tile -> tile.getState() == TileState.POWER_PELLET); + + + // Clusters +// +// // Get all pellet positions in the maze +// List pelletPositions = new ArrayList<>(); +// Vector2ic dimensions = pacman.getMaze().getDimensions(); +// 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) { +// pelletPositions.add(new Vector2i(x, y)); +// } +// } +// } +// // Identify clusters of pellets +// +// Clusters clusters = new Clusters(); +// List> identifiedClusters = clusters.identifyClusters(pelletPositions, 4.0); // 4.0 is a distance threshold +// +// // Initialize variables for cluster features +// List largestCluster = null; +// List nearestCluster = null; +// double nearestDistance = Double.MAX_VALUE; +// int largestSize = 0; +// +// Vector2i pacmanPosition = pacman.getTilePosition(); +// +// // Analyze clusters to find the largest and nearest +// for (List cluster : identifiedClusters) { +// double distanceToCluster = pacmanPosition.distance(cluster.get(0)); // Using the first pellet in the cluster as reference +// +// // Check for the largest cluster +// if (cluster.size() > largestSize) { +// largestSize = cluster.size(); +// largestCluster = cluster; +// } +// +// // Check for the nearest cluster +// if (distanceToCluster < nearestDistance) { +// nearestDistance = distanceToCluster; +// nearestCluster = cluster; +// } +// } +// +// // Extract features for the neural network +// double nearestClusterSize = nearestCluster != null ? nearestCluster.size() : 0; +// double nearestClusterDistance = nearestCluster != null ? nearestDistance : Double.MAX_VALUE; +// double largestClusterSize = largestCluster != null ? largestCluster.size() : 0; +// double largestClusterDistance = largestCluster != null ? pacmanPosition.distance(largestCluster.get(0)) : Double.MAX_VALUE; + + // Calculate max distance for pellets + int maxDistance = -1; + for (Searcher.SearchResult result : nearestPellets.values()) { + if (result != null) { + maxDistance = Math.max(maxDistance, result.getDistance()); + } + } + + // Calculate max distance for power pellets + int maxDistPowerPellets = -1; + for (Searcher.SearchResult result : nearestPowerPellets.values()) { + if (result != null) { + maxDistPowerPellets = Math.max(maxDistPowerPellets, result.getDistance()); + } + } + + + + // Normalize distances for nearest pellets + double nearestPelletForward = nearestPellets.get(forward) != null ? 1 - (double) nearestPellets.get(forward).getDistance() / maxDistance : 0; + double nearestPelletLeft = nearestPellets.get(left) != null ? 1 - (double) nearestPellets.get(left).getDistance() / maxDistance : 0; + double nearestPelletRight = nearestPellets.get(right) != null ? 1 - (double) nearestPellets.get(right).getDistance() / maxDistance : 0; + double nearestPelletBehind = nearestPellets.get(behind) != null ? 1 - (double) nearestPellets.get(behind).getDistance() / maxDistance : 0; + + // Normalize distances for nearest power pellets + double nearestPowerPelletForward = nearestPowerPellets.get(forward) != null ? 1 - (double) nearestPowerPellets.get(forward).getDistance() / maxDistPowerPellets : 0; + double nearestPowerPelletLeft = nearestPowerPellets.get(left) != null ? 1 - (double) nearestPowerPellets.get(left).getDistance() / maxDistPowerPellets : 0; + double nearestPowerPelletRight = nearestPowerPellets.get(right) != null ? 1 - (double) nearestPowerPellets.get(right).getDistance() / maxDistPowerPellets : 0; + double nearestPowerPelletBehind = nearestPowerPellets.get(behind) != null ? 1 - (double) nearestPowerPellets.get(behind).getDistance() / maxDistPowerPellets : 0; + + // Pass the normalized distances to the neural network float[] outputs = client.getCalculator().calculate(new float[]{ canMoveForward ? 1f : 0f, canMoveLeft ? 1f : 0f, canMoveRight ? 1f : 0f, canMoveBehind ? 1f : 0f, + // MOVING + (float)nearestPelletForward, + (float)nearestPelletLeft, + (float)nearestPelletRight, + (float)nearestPelletBehind, + // Include normalized power pellet distances as inputs + (float)nearestPowerPelletForward, + (float)nearestPowerPelletLeft, + (float)nearestPowerPelletRight, + (float)nearestPowerPelletBehind, + // Cluster features +// (float)nearestClusterSize, +// (float)nearestClusterDistance, +// (float)largestClusterSize, +// (float)largestClusterDistance }).join(); + + int index = 0; - float max = outputs[0]; + double max = outputs[0]; for (int i = 1; i < outputs.length; i++) { if (outputs[i] > max) { max = outputs[i]; @@ -78,6 +265,41 @@ public Direction getDirection(@NotNull Entity entity) { default -> throw new IllegalStateException("Unexpected value: " + index); }; + + // Phase 3: After threshold, prioritize clusters of pellets + /*if (lastScore >= CLUSTER_PRIORITY_SCORE_THRESHOLD) { + List pelletPositions = new ArrayList<>(); + Vector2ic dimensions = pacman.getMaze().getDimensions(); + 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) { + pelletPositions.add(new Vector2i(x, y)); + } + } + } + + Clusters clusters_0 = new Clusters(); + List> clusters = clusters_0.identifyClusters(pelletPositions, 4.0); + + List largestCluster = clusters.stream() + .max((c1, c2) -> Integer.compare(c1.size(), c2.size())) + .orElse(null); + + if (largestCluster != null && !largestCluster.isEmpty()) { + Vector2i target = largestCluster.get(0); // Use the first pellet as a target in the cluster + Direction bestClusterDirection = getDirectionToTarget(currentTile, target); + if (bestClusterDirection != null) { + return bestClusterDirection; + } + } + } + + // Fall back to secondary direction if no cluster direction is determined + return secondaryDirection;*/ + + + client.setScore(pacman.getMaze().getLevelManager().getScore() + scoreModifier); return newDirection; } @@ -92,4 +314,19 @@ public void render(@NotNull SpriteBatch batch) { } */ } + + public void procedure (){ + + } + + /*private Direction getDirectionToTarget(Tile startTile, Vector2i targetPosition) { + Direction[] directions = Direction.values(); + for (Direction direction : directions) { + Tile nextTile = pacman.getMaze().getTile(startTile.getPosition().add(direction.vector())); + if (nextTile != null && nextTile.getPosition().equals(targetPosition)) { + return direction; + } + } + 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 22cab50..3517d50 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,20 +1,56 @@ package com.buaisociety.pacman.entity.behavior; + +import com.cjcrafter.neat.compute.Calculator; +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; import com.buaisociety.pacman.entity.Entity; import com.buaisociety.pacman.entity.PacmanEntity; -import com.cjcrafter.neat.compute.Calculator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; +import org.joml.Vector2f; +import org.joml.Vector2i; +import org.joml.Vector2ic; +import java.util.ArrayList; +import com.buaisociety.pacman.Searcher; +import com.buaisociety.pacman.Clusters; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.Random; +import java.util.function.Predicate; +import java.util.Map; + public class TournamentBehavior implements Behavior { private final Calculator calculator; - private @Nullable PacmanEntity pacman; private int previousScore = 0; private int framesSinceScoreUpdate = 0; + private @Nullable PacmanEntity pacman; + private int pelletCount = 0; // Track the number of pellets collected + // 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 int lastDirection = 0; + + + public TournamentBehavior(Calculator calculator) { this.calculator = calculator; } @@ -48,16 +84,177 @@ public Direction getDirection(@NotNull Entity entity) { // --- END OF DO NOT REMOVE --- // TODO: Put all your code for info into the neural network here +// 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(); + + + //pacman.getTilePosition().x() + //Tile tile=pacman.getMaze().getTile(pacman.getTilePosition()); + //pacman.getMaze().getEntity() + //entityType + //TileState.WALL + + + // 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); + + // Calculate nearest pellets +// if (lastDirection==0 && canMoveForward==true) return forward; +// if (lastDirection==1 && canMoveLeft==true) return left; +// if (lastDirection==2 && canMoveRight==true) return right; +// if (lastDirection==3 && canMoveBehind==true) return behind; +// +// //if (pelletCount==0) { +// Random random = new Random(); +// int rand=random.nextInt(4); +// if (rand==0) { +// lastDirection=0; +// return forward; +// } +// if (rand==1) { +// lastDirection=1; +// return left; +// } +// if (rand==2) { +// lastDirection=2; +// return right; +// } +// if (rand==3) { +// lastDirection=3; +// return behind; +// } + //} +// if (pelletCount < 30) { +// Map nearestPellets = Searcher.findTileInAllDirections(pacman.getMaze().getTile(pacman.getTilePosition()), +// tile -> tile.getState() == TileState.PELLET); +// +// if (nearestPellets.isEmpty()) { +// if (canMoveForward) return forward; +// if (canMoveLeft) return left; +// if (canMoveRight) return right; +// if (canMoveBehind) return behind; +// } +// +// } + Tile currentTile = pacman.getMaze().getTile(pacman.getTilePosition()); + Map nearestPellets = Searcher.findTileInAllDirections(currentTile, tile -> tile.getState() == TileState.PELLET); + + // Calculate nearest power pellets + Map nearestPowerPellets = Searcher.findTileInAllDirections(currentTile, tile -> tile.getState() == TileState.POWER_PELLET); + + + // Clusters +// +// // Get all pellet positions in the maze +// List pelletPositions = new ArrayList<>(); +// Vector2ic dimensions = pacman.getMaze().getDimensions(); +// 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) { +// pelletPositions.add(new Vector2i(x, y)); +// } +// } +// } +// // Identify clusters of pellets +// +// Clusters clusters = new Clusters(); +// List> identifiedClusters = clusters.identifyClusters(pelletPositions, 4.0); // 4.0 is a distance threshold +// +// // Initialize variables for cluster features +// List largestCluster = null; +// List nearestCluster = null; +// double nearestDistance = Double.MAX_VALUE; +// int largestSize = 0; +// +// Vector2i pacmanPosition = pacman.getTilePosition(); +// +// // Analyze clusters to find the largest and nearest +// for (List cluster : identifiedClusters) { +// double distanceToCluster = pacmanPosition.distance(cluster.get(0)); // Using the first pellet in the cluster as reference +// +// // Check for the largest cluster +// if (cluster.size() > largestSize) { +// largestSize = cluster.size(); +// largestCluster = cluster; +// } +// +// // Check for the nearest cluster +// if (distanceToCluster < nearestDistance) { +// nearestDistance = distanceToCluster; +// nearestCluster = cluster; +// } +// } +// +// // Extract features for the neural network +// double nearestClusterSize = nearestCluster != null ? nearestCluster.size() : 0; +// double nearestClusterDistance = nearestCluster != null ? nearestDistance : Double.MAX_VALUE; +// double largestClusterSize = largestCluster != null ? largestCluster.size() : 0; +// double largestClusterDistance = largestCluster != null ? pacmanPosition.distance(largestCluster.get(0)) : Double.MAX_VALUE; + + // Calculate max distance for pellets + int maxDistance = -1; + for (Searcher.SearchResult result : nearestPellets.values()) { + if (result != null) { + maxDistance = Math.max(maxDistance, result.getDistance()); + } + } + + // Calculate max distance for power pellets + int maxDistPowerPellets = -1; + for (Searcher.SearchResult result : nearestPowerPellets.values()) { + if (result != null) { + maxDistPowerPellets = Math.max(maxDistPowerPellets, result.getDistance()); + } + } + + + + // Normalize distances for nearest pellets + double nearestPelletForward = nearestPellets.get(forward) != null ? 1 - (double) nearestPellets.get(forward).getDistance() / maxDistance : 0; + double nearestPelletLeft = nearestPellets.get(left) != null ? 1 - (double) nearestPellets.get(left).getDistance() / maxDistance : 0; + double nearestPelletRight = nearestPellets.get(right) != null ? 1 - (double) nearestPellets.get(right).getDistance() / maxDistance : 0; + double nearestPelletBehind = nearestPellets.get(behind) != null ? 1 - (double) nearestPellets.get(behind).getDistance() / maxDistance : 0; + + // Normalize distances for nearest power pellets + double nearestPowerPelletForward = nearestPowerPellets.get(forward) != null ? 1 - (double) nearestPowerPellets.get(forward).getDistance() / maxDistPowerPellets : 0; + double nearestPowerPelletLeft = nearestPowerPellets.get(left) != null ? 1 - (double) nearestPowerPellets.get(left).getDistance() / maxDistPowerPellets : 0; + double nearestPowerPelletRight = nearestPowerPellets.get(right) != null ? 1 - (double) nearestPowerPellets.get(right).getDistance() / maxDistPowerPellets : 0; + double nearestPowerPelletBehind = nearestPowerPellets.get(behind) != null ? 1 - (double) nearestPowerPellets.get(behind).getDistance() / maxDistPowerPellets : 0; + + // Pass the normalized distances to the neural network + float[] outputs = calculator.calculate(new float[]{ + canMoveForward ? 1f : 0f, + canMoveLeft ? 1f : 0f, + canMoveRight ? 1f : 0f, + canMoveBehind ? 1f : 0f, + // MOVING + (float)nearestPelletForward, + (float)nearestPelletLeft, + (float)nearestPelletRight, + (float)nearestPelletBehind, + // Include normalized power pellet distances as inputs + (float)nearestPowerPelletForward, + (float)nearestPowerPelletLeft, + (float)nearestPowerPelletRight, + (float)nearestPowerPelletBehind, + // Cluster features +// (float)nearestClusterSize, +// (float)nearestClusterDistance, +// (float)largestClusterSize, +// (float)largestClusterDistance + }).join(); + - float[] inputs = new float[] { - // TODO: Add your inputs here - }; - 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]; + double max = outputs[0]; for (int i = 1; i < outputs.length; i++) { if (outputs[i] > max) { max = outputs[i]; @@ -65,12 +262,77 @@ public Direction getDirection(@NotNull Entity entity) { } } - return switch (index) { + 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); }; + + + // Phase 3: After threshold, prioritize clusters of pellets + /*if (lastScore >= CLUSTER_PRIORITY_SCORE_THRESHOLD) { + List pelletPositions = new ArrayList<>(); + Vector2ic dimensions = pacman.getMaze().getDimensions(); + 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) { + pelletPositions.add(new Vector2i(x, y)); + } + } + } + + Clusters clusters_0 = new Clusters(); + List> clusters = clusters_0.identifyClusters(pelletPositions, 4.0); + + List largestCluster = clusters.stream() + .max((c1, c2) -> Integer.compare(c1.size(), c2.size())) + .orElse(null); + + if (largestCluster != null && !largestCluster.isEmpty()) { + Vector2i target = largestCluster.get(0); // Use the first pellet as a target in the cluster + Direction bestClusterDirection = getDirectionToTarget(currentTile, target); + if (bestClusterDirection != null) { + return bestClusterDirection; + } + } + } + + // Fall back to secondary direction if no cluster direction is determined + return secondaryDirection;*/ + + + + //client.setScore(pacman.getMaze().getLevelManager().getScore() + scoreModifier); + 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); + } + */ + } + + public void procedure (){ + + } + + /*private Direction getDirectionToTarget(Tile startTile, Vector2i targetPosition) { + Direction[] directions = Direction.values(); + for (Direction direction : directions) { + Tile nextTile = pacman.getMaze().getTile(startTile.getPosition().add(direction.vector())); + if (nextTile != null && nextTile.getPosition().equals(targetPosition)) { + return direction; + } + } + return null; + }*/ } + From 11a9a6d57e74e8ee134c52db9972ca3a1ae744f2 Mon Sep 17 00:00:00 2001 From: hoangvu05 Date: Sat, 26 Oct 2024 18:24:53 -0400 Subject: [PATCH 2/2] asd --- assets/saves/oct26-24/best-calculator-91.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 assets/saves/oct26-24/best-calculator-91.json diff --git a/assets/saves/oct26-24/best-calculator-91.json b/assets/saves/oct26-24/best-calculator-91.json new file mode 100644 index 0000000..4b6688e --- /dev/null +++ b/assets/saves/oct26-24/best-calculator-91.json @@ -0,0 +1 @@ +{"isAddBias":true,"inputs":[{"id":0,"x":0.1,"incoming":[]},{"id":1,"x":0.1,"incoming":[]},{"id":2,"x":0.1,"incoming":[]},{"id":3,"x":0.1,"incoming":[]},{"id":4,"x":0.1,"incoming":[]},{"id":5,"x":0.1,"incoming":[]},{"id":6,"x":0.1,"incoming":[]},{"id":7,"x":0.1,"incoming":[]},{"id":8,"x":0.1,"incoming":[]},{"id":9,"x":0.1,"incoming":[]},{"id":10,"x":0.1,"incoming":[]},{"id":11,"x":0.1,"incoming":[]},{"id":12,"x":0.1,"incoming":[]}],"hidden":[{"id":138,"x":0.19999999,"incoming":[{"fromId":10,"weight":0.5807351,"enabled":true},{"fromId":0,"weight":0.22659247,"enabled":true}]},{"id":47,"x":0.29999998,"incoming":[{"fromId":10,"weight":0.13833706,"enabled":false},{"fromId":0,"weight":0.8638843,"enabled":true},{"fromId":11,"weight":0.114609145,"enabled":true},{"fromId":138,"weight":-0.39168766,"enabled":true},{"fromId":7,"weight":-0.88716316,"enabled":true}]},{"id":96,"x":0.29999998,"incoming":[{"fromId":8,"weight":1.0027258,"enabled":true},{"fromId":0,"weight":1.0218897,"enabled":true}]},{"id":107,"x":0.29999998,"incoming":[{"fromId":12,"weight":1.6018159,"enabled":true},{"fromId":0,"weight":0.8021799,"enabled":true}]},{"id":118,"x":0.29999998,"incoming":[{"fromId":9,"weight":1.422273,"enabled":true},{"fromId":0,"weight":1.4237465,"enabled":true}]},{"id":119,"x":0.29999998,"incoming":[{"fromId":6,"weight":1.0108635,"enabled":true},{"fromId":0,"weight":0.7187625,"enabled":true}]},{"id":120,"x":0.29999998,"incoming":[{"fromId":12,"weight":0.9531394,"enabled":true},{"fromId":0,"weight":-2.1661384,"enabled":true}]},{"id":125,"x":0.29999998,"incoming":[{"fromId":1,"weight":1.394831,"enabled":true},{"fromId":0,"weight":-0.003965676,"enabled":true}]},{"id":156,"x":0.29999998,"incoming":[{"fromId":3,"weight":0.2965174,"enabled":true},{"fromId":0,"weight":0.0014105588,"enabled":true}]},{"id":174,"x":0.29999998,"incoming":[{"fromId":8,"weight":0.5149684,"enabled":true},{"fromId":0,"weight":1.7600298,"enabled":true}]},{"id":142,"x":0.39999998,"incoming":[{"fromId":47,"weight":-0.85746104,"enabled":true},{"fromId":0,"weight":-0.8789022,"enabled":true}]},{"id":169,"x":0.39999998,"incoming":[{"fromId":156,"weight":-0.12980099,"enabled":true},{"fromId":0,"weight":0.25130045,"enabled":true}]},{"id":17,"x":0.49999997,"incoming":[{"fromId":4,"weight":-0.66960955,"enabled":true},{"fromId":0,"weight":-1.5877311,"enabled":true}]},{"id":18,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.7819293,"enabled":true},{"fromId":0,"weight":-0.7592402,"enabled":true}]},{"id":22,"x":0.49999997,"incoming":[{"fromId":8,"weight":-0.46638352,"enabled":true},{"fromId":0,"weight":0.60404813,"enabled":true},{"fromId":7,"weight":-0.09308106,"enabled":true},{"fromId":9,"weight":0.31939274,"enabled":true},{"fromId":2,"weight":1.1114357,"enabled":true},{"fromId":1,"weight":0.7371818,"enabled":true},{"fromId":5,"weight":0.17170045,"enabled":true},{"fromId":96,"weight":0.25535092,"enabled":true},{"fromId":118,"weight":-1.3291246,"enabled":true},{"fromId":11,"weight":-0.5761671,"enabled":true}]},{"id":23,"x":0.49999997,"incoming":[{"fromId":11,"weight":0.23857765,"enabled":true},{"fromId":0,"weight":0.6205106,"enabled":true}]},{"id":24,"x":0.49999997,"incoming":[{"fromId":0,"weight":-0.41310495,"enabled":true}]},{"id":25,"x":0.49999997,"incoming":[{"fromId":12,"weight":-0.023898602,"enabled":true},{"fromId":0,"weight":-1.0644224,"enabled":true},{"fromId":6,"weight":-0.46032798,"enabled":true},{"fromId":11,"weight":1.5198973,"enabled":true},{"fromId":120,"weight":0.34432274,"enabled":true}]},{"id":26,"x":0.49999997,"incoming":[{"fromId":2,"weight":0.72625834,"enabled":true},{"fromId":0,"weight":-1.2335293,"enabled":true},{"fromId":12,"weight":1.2802169,"enabled":true},{"fromId":47,"weight":0.7016369,"enabled":true},{"fromId":3,"weight":-0.9299006,"enabled":true}]},{"id":28,"x":0.49999997,"incoming":[{"fromId":8,"weight":-0.45007634,"enabled":true},{"fromId":0,"weight":0.7933529,"enabled":true},{"fromId":9,"weight":0.6294149,"enabled":true},{"fromId":5,"weight":-0.30390713,"enabled":true},{"fromId":12,"weight":-0.32314524,"enabled":true},{"fromId":7,"weight":0.08348696,"enabled":true},{"fromId":10,"weight":0.6788537,"enabled":true},{"fromId":6,"weight":-0.21117067,"enabled":true},{"fromId":3,"weight":-0.16420892,"enabled":true},{"fromId":107,"weight":-0.734861,"enabled":true}]},{"id":29,"x":0.49999997,"incoming":[{"fromId":0,"weight":-0.33807686,"enabled":true},{"fromId":2,"weight":-0.6972983,"enabled":true},{"fromId":11,"weight":0.58172214,"enabled":true}]},{"id":32,"x":0.49999997,"incoming":[{"fromId":5,"weight":-1.2645385,"enabled":true},{"fromId":0,"weight":-1.6730626,"enabled":true}]},{"id":33,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.12285073,"enabled":true},{"fromId":0,"weight":0.33647,"enabled":true},{"fromId":3,"weight":1.0953741,"enabled":true},{"fromId":1,"weight":-0.99417984,"enabled":true},{"fromId":8,"weight":-0.26607046,"enabled":true}]},{"id":34,"x":0.49999997,"incoming":[{"fromId":6,"weight":-0.45717594,"enabled":true},{"fromId":0,"weight":0.75535524,"enabled":true},{"fromId":5,"weight":-0.7277607,"enabled":true},{"fromId":9,"weight":-1.1032555,"enabled":true}]},{"id":35,"x":0.49999997,"incoming":[{"fromId":6,"weight":0.4234132,"enabled":true},{"fromId":0,"weight":0.0830487,"enabled":true},{"fromId":9,"weight":-0.07648743,"enabled":true},{"fromId":11,"weight":-0.36659092,"enabled":true},{"fromId":8,"weight":0.12926109,"enabled":true},{"fromId":119,"weight":0.93270504,"enabled":true},{"fromId":156,"weight":1.1376671,"enabled":true}]},{"id":36,"x":0.49999997,"incoming":[{"fromId":3,"weight":-0.88278866,"enabled":true},{"fromId":0,"weight":-0.34027028,"enabled":true},{"fromId":11,"weight":1.4066954,"enabled":true},{"fromId":1,"weight":0.8055922,"enabled":true},{"fromId":47,"weight":1.527518,"enabled":true}]},{"id":37,"x":0.49999997,"incoming":[{"fromId":10,"weight":-0.5801935,"enabled":true},{"fromId":0,"weight":0.02177757,"enabled":true},{"fromId":9,"weight":-1.332419,"enabled":true},{"fromId":47,"weight":-0.102652535,"enabled":true},{"fromId":1,"weight":-0.34144613,"enabled":true},{"fromId":11,"weight":-0.72796696,"enabled":true},{"fromId":12,"weight":-1.4006401,"enabled":true},{"fromId":125,"weight":0.21409732,"enabled":true}]},{"id":38,"x":0.49999997,"incoming":[{"fromId":3,"weight":1.2533152,"enabled":true},{"fromId":0,"weight":-0.45313838,"enabled":true}]},{"id":39,"x":0.49999997,"incoming":[{"fromId":0,"weight":-1.5184984,"enabled":true}]},{"id":41,"x":0.49999997,"incoming":[{"fromId":1,"weight":0.33920702,"enabled":true},{"fromId":0,"weight":-0.23522192,"enabled":true},{"fromId":4,"weight":0.22130756,"enabled":true},{"fromId":11,"weight":-1.2067285,"enabled":true},{"fromId":9,"weight":-0.40337855,"enabled":true},{"fromId":3,"weight":0.26519522,"enabled":true},{"fromId":156,"weight":0.4456704,"enabled":true},{"fromId":169,"weight":0.96954924,"enabled":true}]},{"id":42,"x":0.49999997,"incoming":[{"fromId":9,"weight":0.25949907,"enabled":true},{"fromId":0,"weight":0.98366356,"enabled":true},{"fromId":5,"weight":0.1588257,"enabled":true},{"fromId":11,"weight":1.5999814,"enabled":true}]},{"id":43,"x":0.49999997,"incoming":[{"fromId":2,"weight":0.7223487,"enabled":true},{"fromId":0,"weight":-0.07819393,"enabled":true},{"fromId":4,"weight":-0.8469484,"enabled":true}]},{"id":44,"x":0.49999997,"incoming":[{"fromId":11,"weight":-1.4815595,"enabled":true},{"fromId":0,"weight":1.1481223,"enabled":true}]},{"id":45,"x":0.49999997,"incoming":[{"fromId":8,"weight":-0.5083741,"enabled":true},{"fromId":0,"weight":0.78522456,"enabled":true}]},{"id":46,"x":0.49999997,"incoming":[{"fromId":9,"weight":0.43718413,"enabled":true},{"fromId":0,"weight":-0.038320426,"enabled":true},{"fromId":1,"weight":0.105907455,"enabled":true},{"fromId":2,"weight":-0.58617306,"enabled":true},{"fromId":5,"weight":0.047705293,"enabled":true},{"fromId":10,"weight":-0.28660116,"enabled":true},{"fromId":47,"weight":0.6828208,"enabled":true},{"fromId":6,"weight":-0.112256505,"enabled":true},{"fromId":11,"weight":-0.35031062,"enabled":true},{"fromId":142,"weight":0.15660088,"enabled":true}]},{"id":49,"x":0.49999997,"incoming":[{"fromId":5,"weight":1.0409725,"enabled":true},{"fromId":0,"weight":0.06528221,"enabled":true}]},{"id":51,"x":0.49999997,"incoming":[{"fromId":12,"weight":1.213491,"enabled":true},{"fromId":0,"weight":-0.78576064,"enabled":true},{"fromId":8,"weight":-1.1854901,"enabled":true},{"fromId":9,"weight":-2.7525127,"enabled":true},{"fromId":174,"weight":-2.7437499,"enabled":true}]},{"id":52,"x":0.49999997,"incoming":[{"fromId":5,"weight":0.5771716,"enabled":true},{"fromId":0,"weight":-0.81940556,"enabled":true}]},{"id":57,"x":0.49999997,"incoming":[{"fromId":2,"weight":0.11596434,"enabled":true},{"fromId":0,"weight":-0.7709476,"enabled":true}]},{"id":64,"x":0.49999997,"incoming":[{"fromId":9,"weight":0.36887053,"enabled":true},{"fromId":0,"weight":-0.8471908,"enabled":true},{"fromId":10,"weight":0.73166144,"enabled":true},{"fromId":11,"weight":-0.7821404,"enabled":true},{"fromId":1,"weight":1.5330994,"enabled":true}]},{"id":67,"x":0.49999997,"incoming":[{"fromId":7,"weight":0.5706729,"enabled":true},{"fromId":0,"weight":-0.048603125,"enabled":true},{"fromId":6,"weight":0.5699382,"enabled":true}]},{"id":82,"x":0.49999997,"incoming":[{"fromId":12,"weight":0.54029274,"enabled":true},{"fromId":0,"weight":0.18460958,"enabled":true},{"fromId":7,"weight":-1.2513688,"enabled":true}]},{"id":94,"x":0.49999997,"incoming":[{"fromId":10,"weight":-0.3028361,"enabled":true},{"fromId":0,"weight":0.44351012,"enabled":true}]},{"id":61,"x":0.7,"incoming":[{"fromId":22,"weight":-0.4289503,"enabled":true},{"fromId":0,"weight":-0.49135128,"enabled":true},{"fromId":29,"weight":-0.22232087,"enabled":true},{"fromId":36,"weight":1.2372675,"enabled":true},{"fromId":11,"weight":-0.2707971,"enabled":true},{"fromId":1,"weight":0.41976425,"enabled":true}]},{"id":80,"x":0.7,"incoming":[{"fromId":28,"weight":0.76458806,"enabled":true},{"fromId":0,"weight":1.3328228,"enabled":true}]},{"id":137,"x":0.7,"incoming":[{"fromId":36,"weight":0.18074742,"enabled":true},{"fromId":0,"weight":0.47848338,"enabled":true},{"fromId":47,"weight":-0.3503705,"enabled":true}]}],"outputs":[{"id":13,"x":0.9,"incoming":[{"fromId":0,"weight":0.8489344,"enabled":true},{"fromId":1,"weight":1.7435569,"enabled":true},{"fromId":2,"weight":0.09558889,"enabled":true},{"fromId":3,"weight":-0.3189107,"enabled":true},{"fromId":4,"weight":0.90253204,"enabled":true},{"fromId":5,"weight":0.40512154,"enabled":true},{"fromId":6,"weight":0.022068426,"enabled":true},{"fromId":7,"weight":0.5527247,"enabled":true},{"fromId":8,"weight":-0.24080929,"enabled":true},{"fromId":9,"weight":-0.63626856,"enabled":true},{"fromId":10,"weight":-0.11889595,"enabled":true},{"fromId":11,"weight":0.103898585,"enabled":true},{"fromId":12,"weight":-1.9707518,"enabled":true},{"fromId":24,"weight":1.0570796,"enabled":true},{"fromId":34,"weight":-0.6206633,"enabled":true},{"fromId":49,"weight":0.6805499,"enabled":true},{"fromId":37,"weight":0.008294433,"enabled":true},{"fromId":51,"weight":0.30816755,"enabled":true},{"fromId":82,"weight":0.6758362,"enabled":true},{"fromId":26,"weight":1.1180416,"enabled":true},{"fromId":67,"weight":-0.6929537,"enabled":true}]},{"id":14,"x":0.9,"incoming":[{"fromId":0,"weight":0.5834821,"enabled":true},{"fromId":1,"weight":-1.3229873,"enabled":true},{"fromId":2,"weight":1.2751708,"enabled":true},{"fromId":3,"weight":-1.4780912,"enabled":true},{"fromId":4,"weight":0.4440413,"enabled":true},{"fromId":5,"weight":-2.103299,"enabled":true},{"fromId":6,"weight":0.69142044,"enabled":true},{"fromId":7,"weight":0.21513775,"enabled":true},{"fromId":8,"weight":-0.16004284,"enabled":true},{"fromId":9,"weight":0.4217276,"enabled":true},{"fromId":10,"weight":0.54439163,"enabled":true},{"fromId":11,"weight":-0.15912816,"enabled":true},{"fromId":12,"weight":-0.27535063,"enabled":true},{"fromId":18,"weight":-0.21414359,"enabled":true},{"fromId":22,"weight":-0.12528804,"enabled":true},{"fromId":25,"weight":0.5284929,"enabled":true},{"fromId":32,"weight":-0.49929798,"enabled":true},{"fromId":36,"weight":0.14917468,"enabled":true},{"fromId":28,"weight":1.240942,"enabled":true},{"fromId":57,"weight":0.16037054,"enabled":true},{"fromId":61,"weight":-0.32510173,"enabled":true},{"fromId":64,"weight":0.47228408,"enabled":true},{"fromId":17,"weight":0.10126019,"enabled":true},{"fromId":37,"weight":-0.27108335,"enabled":true},{"fromId":137,"weight":0.42850506,"enabled":true}]},{"id":15,"x":0.9,"incoming":[{"fromId":0,"weight":-0.095100716,"enabled":true},{"fromId":1,"weight":0.49145037,"enabled":true},{"fromId":2,"weight":-0.117602795,"enabled":true},{"fromId":3,"weight":0.7718548,"enabled":true},{"fromId":4,"weight":0.6772822,"enabled":true},{"fromId":5,"weight":0.23492894,"enabled":true},{"fromId":6,"weight":1.3584967,"enabled":true},{"fromId":7,"weight":0.38494068,"enabled":true},{"fromId":8,"weight":0.6689533,"enabled":true},{"fromId":9,"weight":-0.82131505,"enabled":true},{"fromId":10,"weight":-1.2322628,"enabled":true},{"fromId":11,"weight":-0.2102966,"enabled":true},{"fromId":12,"weight":-0.038619325,"enabled":true},{"fromId":17,"weight":0.5491561,"enabled":true},{"fromId":23,"weight":0.22385696,"enabled":true},{"fromId":26,"weight":0.4961376,"enabled":true},{"fromId":28,"weight":-0.4993202,"enabled":true},{"fromId":29,"weight":-0.90252453,"enabled":true},{"fromId":35,"weight":0.259121,"enabled":true},{"fromId":37,"weight":0.34430844,"enabled":true},{"fromId":38,"weight":1.3568952,"enabled":true},{"fromId":42,"weight":-0.03744307,"enabled":true},{"fromId":51,"weight":0.6172887,"enabled":true},{"fromId":52,"weight":-0.830686,"enabled":true},{"fromId":80,"weight":-0.059826232,"enabled":true},{"fromId":47,"weight":0.022520773,"enabled":true}]},{"id":16,"x":0.9,"incoming":[{"fromId":0,"weight":0.05752715,"enabled":true},{"fromId":1,"weight":0.17750078,"enabled":true},{"fromId":2,"weight":-0.16059414,"enabled":true},{"fromId":3,"weight":-0.69479257,"enabled":true},{"fromId":4,"weight":1.2423437,"enabled":true},{"fromId":5,"weight":-0.7319915,"enabled":true},{"fromId":6,"weight":1.0490878,"enabled":true},{"fromId":7,"weight":-0.5577472,"enabled":true},{"fromId":8,"weight":0.6106347,"enabled":true},{"fromId":9,"weight":-0.4465298,"enabled":true},{"fromId":10,"weight":1.3088456,"enabled":true},{"fromId":11,"weight":0.7235506,"enabled":true},{"fromId":12,"weight":0.13476378,"enabled":true},{"fromId":33,"weight":-0.86628056,"enabled":true},{"fromId":39,"weight":2.253516,"enabled":true},{"fromId":41,"weight":0.5718906,"enabled":true},{"fromId":43,"weight":-0.8556352,"enabled":true},{"fromId":44,"weight":-0.78872555,"enabled":true},{"fromId":45,"weight":0.31282192,"enabled":true},{"fromId":46,"weight":-0.52201295,"enabled":true},{"fromId":67,"weight":-0.10036896,"enabled":true},{"fromId":22,"weight":-1.0645776,"enabled":true},{"fromId":64,"weight":-0.3860336,"enabled":true},{"fromId":94,"weight":0.4999118,"enabled":true},{"fromId":35,"weight":0.22714612,"enabled":true},{"fromId":49,"weight":-1.4732869,"enabled":true}]}]} \ No newline at end of file