Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/saves/oct26-25/best-calculator-22.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"isAddBias":true,"inputs":[{"id":0,"x":0.1,"incoming":[]},{"id":1,"x":0.1,"incoming":[]},{"id":2,"x":0.1,"incoming":[]},{"id":3,"x":0.1,"incoming":[]},{"id":4,"x":0.1,"incoming":[]},{"id":5,"x":0.1,"incoming":[]},{"id":6,"x":0.1,"incoming":[]},{"id":7,"x":0.1,"incoming":[]},{"id":8,"x":0.1,"incoming":[]},{"id":9,"x":0.1,"incoming":[]},{"id":10,"x":0.1,"incoming":[]},{"id":11,"x":0.1,"incoming":[]},{"id":12,"x":0.1,"incoming":[]}],"hidden":[{"id":36,"x":0.49999997,"incoming":[{"fromId":11,"weight":-0.62742525,"enabled":true},{"fromId":0,"weight":-0.46833527,"enabled":true},{"fromId":12,"weight":-2.0767417,"enabled":true}]},{"id":37,"x":0.49999997,"incoming":[{"fromId":3,"weight":-0.63522226,"enabled":true},{"fromId":0,"weight":-0.26396576,"enabled":true},{"fromId":9,"weight":1.2500377,"enabled":true},{"fromId":7,"weight":-0.39467263,"enabled":true},{"fromId":11,"weight":-0.10830047,"enabled":true},{"fromId":8,"weight":-1.3098693,"enabled":true}]},{"id":50,"x":0.49999997,"incoming":[{"fromId":12,"weight":2.3026857,"enabled":true},{"fromId":0,"weight":-1.6496298,"enabled":true},{"fromId":4,"weight":0.79640305,"enabled":true},{"fromId":10,"weight":0.011615746,"enabled":true}]}],"outputs":[{"id":13,"x":0.9,"incoming":[{"fromId":0,"weight":-0.6293719,"enabled":true},{"fromId":1,"weight":0.91749305,"enabled":true},{"fromId":2,"weight":0.8695058,"enabled":true},{"fromId":3,"weight":-0.05514489,"enabled":true},{"fromId":4,"weight":0.81233287,"enabled":true},{"fromId":5,"weight":1.4128559,"enabled":true},{"fromId":6,"weight":1.4513752,"enabled":true},{"fromId":7,"weight":0.028820913,"enabled":true},{"fromId":8,"weight":-0.37260377,"enabled":true},{"fromId":9,"weight":0.23969495,"enabled":true},{"fromId":10,"weight":0.5924212,"enabled":true},{"fromId":11,"weight":-1.0123627,"enabled":true},{"fromId":12,"weight":-0.30324888,"enabled":true}]},{"id":14,"x":0.9,"incoming":[{"fromId":0,"weight":0.61271966,"enabled":true},{"fromId":1,"weight":-0.43170908,"enabled":true},{"fromId":2,"weight":0.63351446,"enabled":true},{"fromId":3,"weight":0.39771095,"enabled":true},{"fromId":4,"weight":-0.23856583,"enabled":true},{"fromId":5,"weight":0.9336664,"enabled":true},{"fromId":6,"weight":0.09452582,"enabled":true},{"fromId":7,"weight":0.06078539,"enabled":true},{"fromId":8,"weight":-0.18390569,"enabled":true},{"fromId":9,"weight":-0.4636197,"enabled":true},{"fromId":10,"weight":-0.6440598,"enabled":true},{"fromId":11,"weight":-1.955276,"enabled":true},{"fromId":12,"weight":0.13735065,"enabled":true},{"fromId":37,"weight":0.23361547,"enabled":true}]},{"id":15,"x":0.9,"incoming":[{"fromId":0,"weight":-1.7581626,"enabled":true},{"fromId":1,"weight":-0.41642317,"enabled":true},{"fromId":2,"weight":0.08641729,"enabled":true},{"fromId":3,"weight":0.24208727,"enabled":true},{"fromId":4,"weight":2.0819035,"enabled":true},{"fromId":5,"weight":0.76017636,"enabled":true},{"fromId":6,"weight":-0.20650533,"enabled":true},{"fromId":7,"weight":1.0411346,"enabled":true},{"fromId":8,"weight":-0.63636374,"enabled":true},{"fromId":9,"weight":1.2254355,"enabled":true},{"fromId":10,"weight":0.29987216,"enabled":true},{"fromId":11,"weight":-0.19834638,"enabled":false},{"fromId":12,"weight":0.44649446,"enabled":true},{"fromId":36,"weight":-1.0254033,"enabled":true},{"fromId":37,"weight":-0.13361458,"enabled":true}]},{"id":16,"x":0.9,"incoming":[{"fromId":0,"weight":1.1678987,"enabled":true},{"fromId":1,"weight":-0.4645596,"enabled":true},{"fromId":2,"weight":-0.2257801,"enabled":true},{"fromId":3,"weight":0.4425527,"enabled":true},{"fromId":4,"weight":-0.4424284,"enabled":true},{"fromId":5,"weight":0.3860943,"enabled":true},{"fromId":6,"weight":-0.22475135,"enabled":true},{"fromId":7,"weight":0.52331454,"enabled":true},{"fromId":8,"weight":1.3660479,"enabled":true},{"fromId":9,"weight":0.019432902,"enabled":true},{"fromId":10,"weight":-0.7152641,"enabled":true},{"fromId":11,"weight":0.22664385,"enabled":true},{"fromId":12,"weight":0.22737403,"enabled":true},{"fromId":50,"weight":-0.3173502,"enabled":true}]}]}
102 changes: 54 additions & 48 deletions core/src/main/java/com/buaisociety/pacman/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public class Main extends ApplicationAdapter {
private OrthographicCamera camera;

private final @NotNull EventSystem events = new EventSystem();
private final @NotNull Vector2i visibleGames = new Vector2i(4, 2);
private final @NotNull Vector2i visibleGames = new Vector2i(4, 4);
private final @NotNull List<PacmanNeatClient> managers = new ArrayList<>();
private final int totalGames = 250;
private GameLoop secondLoop; // 1 update per second
Expand All @@ -69,13 +69,14 @@ public void create() {
camera = new OrthographicCamera();
batch = new SpriteBatch();
camera.setToOrtho(false, 8 * 28 * visibleGames.x, 8 * 36 * visibleGames.y);
Gdx.graphics.setWindowedMode(8 * 28 * 4, 8 * 36 * 4);
neat = createNeat();
neatPrinter = new NeatPrinter(neat);
neatSaver = new NeatSaver(neat, getSaveFolder());
secondLoop = new GameLoop(1);

int processors = Runtime.getRuntime().availableProcessors();
threadPool = Executors.newFixedThreadPool(processors);
threadPool = Executors.newFixedThreadPool(processors * 2);
System.out.println("Using " + processors + " threads");

// When all games have ended, reset
Expand Down Expand Up @@ -105,7 +106,7 @@ public void create() {
// Change this to true/false as needed, if you want to load from file
if (false) {
// TODO: Change this to the exact file you want to load
File exactFile = new File("saves" + File.separator + "oct26-4" + File.separator + "generation-51.json");
File exactFile = new File("saves" + File.separator + "oct25-12" + File.separator + "generation-102.json");
// load exactFile contents to string
String json;
try {
Expand All @@ -115,7 +116,7 @@ public void create() {
}
NeatImpl impl = NeatImpl.fromJson(json);
// modify this as needed
//impl.updateNodeCounts(8, 4); // Add 4 new inputs
//impl.updateNodeCounts(6, 4); // Add 4 new inputs
//impl.updateClients(200); // have 200 pacman games at once
return impl;
} else {
Expand All @@ -125,7 +126,7 @@ public void create() {
neatParameters.setTargetClientsPerSpecies(12); // targeting ~12 clients per species
neatParameters.setStagnationLimit(10); // lower stagnation limit
neatParameters.setUseBiasNode(true); // use bias node
return new NeatImpl(4, 4, totalGames, neatParameters);
return new NeatImpl(12, 4, totalGames, neatParameters);
}
}

Expand Down Expand Up @@ -191,37 +192,41 @@ public void reset() {
}

@Override
public void render() {
public void render() {
paused ^= Gdx.input.isKeyJustPressed(Input.Keys.SPACE);
showNetworks ^= Gdx.input.isKeyJustPressed(Input.Keys.TAB);

paused ^= Gdx.input.isKeyJustPressed(Input.Keys.SPACE);
showNetworks ^= Gdx.input.isKeyJustPressed(Input.Keys.TAB);
frames++;
fps++;

frames++;
fps++;

if (secondLoop.update()) {
System.out.println("FPS: " + fps + ", Frames: " + frames);
fps = 0;
}
if (secondLoop.update()) {
System.out.println("FPS: " + fps + ", Frames: " + frames);
fps = 0;
}

// If all games are complete, reset
if (managers.stream().map(PacmanNeatClient::getGameCompleteFuture).allMatch(CompletableFuture::isDone)) {
reset();
System.out.println(neatPrinter.render());
neatSaver.save();
neat.evolve();
}
// If all games are complete, reset
if (managers.stream().map(PacmanNeatClient::getGameCompleteFuture).allMatch(CompletableFuture::isDone)) {
reset();
System.out.println(neatPrinter.render());
neatSaver.save();
neat.evolve();
}

// Update games
// Update games in batches (e.g., 100 games at a time)
int batchSize = 100; // Customize this to control batch size
for (int i = 0; i < totalGames; i += batchSize) {
List<Future<?>> futures = new ArrayList<>();
List<PacmanNeatClient> updatedManagers = new ArrayList<>();
for (PacmanNeatClient manager : managers) {

// Process a batch of games in parallel
for (int j = i; j < Math.min(i + batchSize, totalGames); j++) {
PacmanNeatClient manager = managers.get(j);
manager.setRenderNetwork(showNetworks);
if (manager.getGameCompleteFuture().isDone())
continue;

if (!paused) {
// Submit the update task and add to updatedManagers
// Submit the update task for the current batch
Future<?> future = threadPool.submit(() -> {
manager.getGameManager().update();
});
Expand All @@ -230,7 +235,7 @@ public void render() {
}
}

// Wait for all games to be updated
// Wait for all games in the batch to be updated
for (Future<?> future : futures) {
try {
future.get();
Expand All @@ -243,31 +248,32 @@ public void render() {
for (PacmanNeatClient manager : updatedManagers) {
manager.getGameManager().postUpdate();
}
}

// Render everything
ScreenUtils.clear(0, 0, 0, 1);
batch.begin();

// Get a copy of the managers list and sort by score so the best are rendered first
List<PacmanNeatClient> sortedManagers = new ArrayList<>(managers);
sortedManagers.sort(Comparator.comparingInt(manager -> -manager.getGameManager().getScore()));

int renderCount = 0;
for (PacmanNeatClient manager : managers) {
if (manager.getGameCompleteFuture().isDone())
continue;
if (renderCount >= visibleGames.x * visibleGames.y)
break;
// Render everything
ScreenUtils.clear(0, 0, 0, 1);
batch.begin();

// Render the managers (you can sort by score as in the original code)
int renderCount = 0;
for (PacmanNeatClient manager : managers) {
if (manager.getGameCompleteFuture().isDone())
continue;
if (renderCount >= visibleGames.x * visibleGames.y)
break;

// Render the game objects (e.g., Pacman, ghosts)
int gameX = renderCount % visibleGames.x;
int gameY = renderCount / visibleGames.x;
renderCount++;

batch.setProjectionMatrix(camera.combined.cpy().translate(gameX * 8 * 28, gameY * 8 * 36, 0));
manager.render(batch);
}

int gameX = renderCount % visibleGames.x;
int gameY = renderCount / visibleGames.x;
renderCount++;
batch.end();
}

batch.setProjectionMatrix(camera.combined.cpy().translate(gameX * 8 * 28, gameY * 8 * 36, 0));
manager.render(batch);
}
batch.end();
}

@Override
public void dispose() {
Expand Down
136 changes: 136 additions & 0 deletions core/src/main/java/com/buaisociety/pacman/Searcher.java
Original file line number Diff line number Diff line change
@@ -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<Direction, SearchResult> findTileInAllDirections(@NotNull Tile startTile, @NotNull Predicate<Tile> predicate) {
Map<Direction, SearchResult> 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<Tile> predicate, @NotNull Direction direction) {
Queue<Vector2ic> queue = new ArrayDeque<>();
Set<Vector2ic> visited = new HashSet<>();
Queue<Integer> 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<Vector2i> getNeighborsInDirection(Vector2i position, Direction direction) {
List<Vector2i> neighbors = new ArrayList<>();
Vector2i offset = getDirectionOffset(direction);
neighbors.add(new Vector2i(position.x + offset.x, position.y + offset.y));
return neighbors;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ private SpecialTrainingConditions() {

public static @NotNull EventListener<EntityPreSpawnEvent> onEntityPreSpawn() {
return event -> {
// Prevent ghosts from spawning during training
// // Prevent ghosts from spawning during training
if (event.getEntityType() == EntityType.GHOST) {
event.setCancelled(true);
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/com/buaisociety/pacman/Tournament.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class Tournament extends ApplicationAdapter {
*/
public Behavior setupBehavior() {
// TODO: Choose your best client here
File file = new File("saves" + File.separator + "oct20-4" + File.separator + "best-calculator-127.json");
File file = new File("saves" + File.separator + "oct26-25" + File.separator + "best-calculator-22.json");
if (!file.exists()) {
System.err.println("Could not find the file: " + file.getAbsolutePath());
return null;
Expand Down
Loading