commit 8e1d03c5af6cadc81bc998b83ad6166098fecf68
parent a03e2ab9c5ec6a232687628223b95ff51da01db8
Author: A Koens <[email protected]>
Date: Tue, 24 Jan 2023 21:57:49 +0100
Changes DataSaver and multithreading.
Enhanced the DataSaver class by including automatic directory creation and some code restructuring. Also changed the multithreading to a thread pool executor. Lastly removed excess function from headless.
Diffstat:
7 files changed, 259 insertions(+), 210 deletions(-)
diff --git a/src/main/java/nl/isygameclient/DataSaver.java b/src/main/java/nl/isygameclient/DataSaver.java
@@ -1,97 +0,0 @@
-package nl.isygameclient;
-
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Random;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-class DataSaver {
- private String path;
- private File file;
-
- public DataSaver(String path) {
- this.path = path;
- this.createFile();
- }
-
- public DataSaver() {
- String name = this.randomString();
- this.path = "./data/temp/" + name + ".csv";
- this.createFile();
- }
-
- public void createFile() {
- System.out.println(this.path);
- this.file = new File(this.path);
- try {
- this.file.createNewFile();
- } catch (IOException ex) {
- System.out.print("Could not create file.");
- }
-
- this.saveData("Is game draw? | Game winners? | Moves made?");
- }
-
- public String randomString() {
- String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- StringBuilder sb = new StringBuilder();
- Random random = new Random();
- int length = 7;
-
- for (int i = 0; i < length; i++) {
- int index = random.nextInt(alphabet.length());
- char randomChar = alphabet.charAt(index);
- sb.append(randomChar);
- }
- return sb.toString();
- }
-
- public void saveData(String data) {
- try {
- BufferedWriter output = new BufferedWriter(new FileWriter(this.path, true));
- output.write(data);
- output.newLine();
- output.close();
- } catch (IOException ex) {
- System.out.print("Invalid Path");
- }
- }
-
- public void mergeFiles(String path) {
- Set<String> files = this.getTempFiles(path);
-
- assert files != null;
- for (String file : files) {
- System.out.println("./data/temp/" + file);
- try {
- BufferedReader bf = new BufferedReader(new FileReader("./data/temp/" + file));
- bf.readLine();
- String line = bf.readLine();
-
- while (line != null) {
- this.saveData(line);
- line = bf.readLine();
- }
- bf.close();
- } catch (IOException ignore) {
- System.out.println(ignore);
- }
- }
- }
-
- private Set<String> getTempFiles(String dir) {
- try (Stream<Path> stream = Files.list(Paths.get(dir))) {
- return stream
- .filter(file -> !Files.isDirectory(file))
- .map(Path::getFileName)
- .map(Path::toString)
- .collect(Collectors.toSet());
- } catch (IOException ignored) {
- return null;
- }
- }
-}
diff --git a/src/main/java/nl/isygameclient/Headless.java b/src/main/java/nl/isygameclient/Headless.java
@@ -7,6 +7,7 @@ import nl.isygameclient.models.Game;
import nl.isygameclient.models.Player;
import nl.isygameclient.models.PlayerManager;
import nl.isygameclient.models.games.othello.Othello;
+import nl.isygameclient.util.DataSaver;
import nl.isygameclient.util.Vector2D;
import java.io.IOException;
@@ -14,70 +15,60 @@ import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
+import java.util.*;
+import java.util.concurrent.Executors;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
-public class Headless implements Runnable {
- private Thread t;
- public final int numOfGames;
+public class Headless {
private static final String JSON_FILENAME = "heuristics.json";
- private DataSaver dataSaver;
+ private static final int THREAD_POOL_SIZE = 10;
+ private static final int ROUNDS_PER_GAME = 100;
- public static void main(String[] args) {
- new Headless().run();
+ private static long calculateAverage(List<Long> values) {
+ long sum = values.stream().mapToLong(Long::longValue).sum();
+ return sum / values.size();
}
- public Headless() {
- this(1);
- }
+ private static Map<String, Game> createGames(Map<String, int[][]> heuristics) {
+ Map<String, Game> games = new HashMap<>();
+ for (Map.Entry<String, int[][]> heuristic1 : heuristics.entrySet()) {
+ for (Map.Entry<String, int[][]> heuristic2 : heuristics.entrySet()) {
- public Headless(int numOfGames) {
- this.numOfGames = numOfGames;
- }
+ ArrayList<Player> players = new ArrayList<>();
+ players.add(new Ai(heuristic1.getKey(), "black", heuristic1.getValue()));
+ players.add(new Ai(heuristic2.getKey(), "white", heuristic2.getValue()));
- private void simulate(String name, Game game) {
- var playerManager = game.getPlayerManager();
- Map<Player, ArrayList<Long>> playersTimePerMoves = new HashMap<>();
- Map<Player, LinkedList<Vector2D<Integer, Integer>>> movesMade = new HashMap<>();
-
- System.out.println("Starting game: " + name);
- while (!game.isGameOver()) {
- var currentPlayer = playerManager.getCurrentPlayer();
- if (game.getValidMoves(currentPlayer).isEmpty()) {
- playerManager.nextPlayer();
- continue;
+ var playerManager = new PlayerManager(0, players);
+ Othello othello = new Othello(playerManager);
+ games.put("Ai1="+heuristic1.getKey() + " against " + "Ai2=" + heuristic2.getKey(), othello);
}
-
-
- final long startGameTime = System.currentTimeMillis();
- var move = currentPlayer.onPlayerTurn();
- game.move(currentPlayer, move);
- final long endTime = System.currentTimeMillis();
-
- movesMade.computeIfAbsent(currentPlayer, k -> new LinkedList<>()).add(move);
- playersTimePerMoves.computeIfAbsent(currentPlayer, k -> new ArrayList<>()).add((endTime - startGameTime));
}
+ return games;
+ }
- System.out.println("Is game draw: " + game.isDraw());
- System.out.println("Game winners: " + game.getWinners().toString());
- System.out.println("Moves made: " + movesMade);
- this.dataSaver.saveData(game.isDraw() + " | " + game.getWinners().toString() + " | " + movesMade);
-
- long totalGameTime = 0;
- for (Map.Entry<Player, ArrayList<Long>> playerTimePerMoves : playersTimePerMoves.entrySet()) {
- var player = playerTimePerMoves.getKey();
- var moveTimes = playerTimePerMoves.getValue();
- System.out.println(player + "'s Time per moves: " + moveTimes);
- var optional = moveTimes.stream().reduce(Long::sum);
- if (optional.isPresent()) {
- totalGameTime += optional.get();
- }
- }
- System.out.println("Total game time in milliseconds: " + totalGameTime);
- System.out.println();
+ private static String buildDataString(
+ String gameName, int gameTurns, boolean isDraw, Player winner, long totalGameTime,
+ Map<Player, ArrayList<Long>> playersTimePerMoves,
+ Map<Player, ArrayList<Vector2D<Integer, Integer>>> playersMovesMade) {
+ StringBuilder builder = new StringBuilder();
+
+ String joined = String.join(",", gameName, String.valueOf(gameTurns), String.valueOf(isDraw),
+ winner.getPlayerName(), winner.getPlayingAs(), String.valueOf(totalGameTime)) + ",";
+ builder.append(joined);
+
+ playersTimePerMoves.forEach((k, v) -> {
+ var average = calculateAverage(v);
+ builder.append(average).append(",");
+ });
+ playersMovesMade.forEach((k, v) -> {
+ String listString = v.stream().map(Object::toString)
+ .collect(Collectors.joining("; "));
+ builder.append(listString).append(",");
+ });
+ return builder.toString();
}
private static Map<String, int[][]> loadHeuristics() {
@@ -95,44 +86,73 @@ public class Headless implements Runnable {
return null;
}
- @Override
- public void run() {
+ public static void main(String[] args) {
var heuristics = loadHeuristics();
- this.dataSaver = new DataSaver();
-
if (heuristics == null) {
System.err.println("No heuristics in file.");
return;
}
- Map<String, Game> games = new HashMap<>();
+ var executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
+ var games = createGames(heuristics);
+ var dataSaver = new DataSaver();
+ dataSaver.saveData("Name, Game Turns, Draw, Winner Name, Winner Playing As, Total Game Time, Average Time Per Move Ai1, Average Time Per Move Ai2, Moves Ai1, Moves Ai2");
+ for (Map.Entry<String, Game> game : games.entrySet()) {
+ Runnable simulation = new Simulation(game.getKey(), game.getValue(), ROUNDS_PER_GAME, new ReentrantLock(), dataSaver);
+ executor.execute(simulation);
+ }
+ executor.shutdown();
- for (int i = 0; i < this.numOfGames; i++) {
- // Create Matches
- for (Map.Entry<String, int[][]> heuristic1 : heuristics.entrySet()) {
- for (Map.Entry<String, int[][]> heuristic2 : heuristics.entrySet()) {
+ while (!executor.isTerminated()) {
+ }
- ArrayList<Player> players = new ArrayList<>();
- players.add(new Ai("game: " + i + "ai1{ " + heuristic1.getKey() + "}", "black", heuristic1.getValue()));
- players.add(new Ai("game: " + i + "ai2{ " + heuristic2.getKey() + "}", "white", heuristic2.getValue()));
+ System.out.println("All simulations completed successfully.");
+ }
- var playerManager = new PlayerManager(0, players);
- Othello othello = new Othello(playerManager);
- games.put("game: " + i + " ai1: " + heuristic1.getKey() + "; against " + "ai2: " + heuristic2.getKey(), othello);
+ private record Simulation(String gameName, Game game, int rounds, Lock lock, DataSaver dataSaver) implements Runnable {
+ @Override
+ public void run() {
+ System.out.println("Starting simulation: " + gameName);
+
+ var playerManager = game.getPlayerManager();
+ for (int i = 0; i < rounds; i++) {
+
+ Map<Player, ArrayList<Long>> playersTimePerMoves = new HashMap<>();
+ Map<Player, ArrayList<Vector2D<Integer, Integer>>> PlayersMovesMade = new HashMap<>();
+
+ long totalGameTime = 0;
+ int gameTurns = 0;
+ while (!game.isGameOver()) {
+ gameTurns++;
+ var currentPlayer = playerManager.getCurrentPlayer();
+ if (game.getValidMoves(currentPlayer).isEmpty()) {
+ playerManager.nextPlayer();
+ continue;
+ }
+
+ final long startTime = System.currentTimeMillis();
+ var move = currentPlayer.onPlayerTurn();
+ game.move(currentPlayer, move);
+ final long endTime = System.currentTimeMillis();
+ final long deltaTime = endTime - startTime;
+ totalGameTime += deltaTime;
+
+ PlayersMovesMade.computeIfAbsent(currentPlayer, k -> new ArrayList<>()).add(move);
+ playersTimePerMoves.computeIfAbsent(currentPlayer, k -> new ArrayList<>()).add(deltaTime);
}
- }
- }
- for (Map.Entry<String, Game> game : games.entrySet()) {
- // add threading.
- simulate(game.getKey(), game.getValue());
- }
- }
+ var winner = game.getWinners().get(0);
+ var data = buildDataString(gameName, gameTurns, game.isDraw(), winner, totalGameTime, playersTimePerMoves, PlayersMovesMade);
- public void start() {
- if (t == null) {
- t = new Thread(this);
- t.start();
+ lock.lock();
+ try {
+ dataSaver.saveData(data);
+ } finally {
+ lock.unlock();
+ }
+ game.restart();
+ }
+ System.out.println(gameName + " has finished");
}
}
}
diff --git a/src/main/java/nl/isygameclient/MergeFiles.java b/src/main/java/nl/isygameclient/MergeFiles.java
@@ -0,0 +1,10 @@
+package nl.isygameclient;
+
+import nl.isygameclient.util.DataSaver;
+
+public class MergeFiles {
+ public static void main(String[] args) {
+ DataSaver dataSaver = new DataSaver("./data/", "results.csv");
+ dataSaver.mergeFiles(DataSaver.TEMP_PATH);
+ }
+}
diff --git a/src/main/java/nl/isygameclient/Multithreading.java b/src/main/java/nl/isygameclient/Multithreading.java
@@ -1,21 +0,0 @@
-package nl.isygameclient;
-
-import java.util.ArrayList;
-
-public class Multithreading {
- public static void main(String args[]) {
- ArrayList<Headless> threaths = new ArrayList<>();
- int threads = 3;
- for (int i = 0; i < threads; i++) {
- threaths.add(i, new Headless(1));
- threaths.get(i).start();
- }
- }
-}
-
-class MergeFiles {
- public static void main(String args[]) {
- DataSaver dataSaver = new DataSaver("./data/results.csv");
- dataSaver.mergeFiles("./data/temp");
- }
-}
-\ No newline at end of file
diff --git a/src/main/java/nl/isygameclient/models/games/othello/Othello.java b/src/main/java/nl/isygameclient/models/games/othello/Othello.java
@@ -12,25 +12,23 @@ import java.util.stream.Collectors;
public class Othello extends Game {
public Othello(PlayerManager playerManager) {
- super(playerManager, new HistoryBoard<>(8, 8) {
- });
+ super(playerManager, new HistoryBoard<>(8, 8) {});
initializeBoard();
}
@Override
public void restart() {
playerManager.restart();
- board = new HistoryBoard<>(8, 8) {
- };
+ board = new HistoryBoard<>(8, 8) {};
initializeBoard();
}
private void initializeBoard() {
var players = playerManager.getPlayers();
- board.set(players.get(0), new Vector2D<>(3, 3));
- board.set(players.get(0), new Vector2D<>(4, 4));
- board.set(players.get(1), new Vector2D<>(3, 4));
- board.set(players.get(1), new Vector2D<>(4, 3));
+ board.set(players.get(1), new Vector2D<>(3, 3));
+ board.set(players.get(0), new Vector2D<>(3, 4));
+ board.set(players.get(0), new Vector2D<>(4, 3));
+ board.set(players.get(1), new Vector2D<>(4, 4));
}
diff --git a/src/main/java/nl/isygameclient/util/DataSaver.java b/src/main/java/nl/isygameclient/util/DataSaver.java
@@ -0,0 +1,143 @@
+package nl.isygameclient.util;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class DataSaver {
+
+ public static final String TEMP_PATH = "./data/temp/";
+ private final String path;
+ private final String fileName;
+ private final String filePath;
+
+ public DataSaver() {
+ this(TEMP_PATH, randomString(7) + ".csv" );
+ }
+
+ public DataSaver(String path, String fileName) {
+ this.path = path;
+ this.fileName = fileName;
+ filePath = path + fileName;
+ this.createDirectories();
+ }
+
+ /**
+ * Using the path provided in the constructor this function created the necessary directories
+ * in case they are not present.
+ */
+ public void createDirectories() {
+ File file = new File(path);
+ if (file.exists()) {
+ return;
+ }
+ if (file.isFile()) {
+ System.err.println("Path provided is not a directory: \n" + path);
+ System.exit(-1);
+ }
+
+ boolean result = file.mkdirs();
+ if (result) {
+ System.out.println("Created directories for path:\n " + path);
+ } else {
+ System.err.println("Failed to create directories for path:\n" + path);
+ }
+
+ }
+
+ /**
+ * Given a String containing data. This function will store the data inside a csv file
+ * of which the filepath has been provided in the constructor.
+ * @param data to be stored inside a csv file.
+ */
+ public void saveData(String data) {
+ try {
+ BufferedWriter output = new BufferedWriter(new FileWriter(filePath, true));
+ output.write(data);
+ output.newLine();
+ output.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Given a directory containing csv files. This function will merge the contents into a singular csv file
+ * of which the filepath has been provided in the constructor.
+ * @param directoryPath to the directory containing csv files that need merging.
+ */
+ public void mergeFiles(String directoryPath) {
+ Set<String> files = getTempFiles(directoryPath);
+
+ assert files != null;
+ for (String file : files) {
+ System.out.println(directoryPath + file);
+ try {
+ BufferedReader bf = new BufferedReader(new FileReader(filePath));
+ bf.readLine();
+ String line = bf.readLine();
+
+ while (line != null) {
+ saveData(line);
+ line = bf.readLine();
+ }
+ bf.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Given a path to a directory containing files.
+ * @param directoryPath containing files.
+ * @return A Set containing the file paths of all the files in the given directory.
+ */
+ private static Set<String> getTempFiles(String directoryPath) {
+ try (Stream<Path> stream = Files.list(Paths.get(directoryPath))) {
+ return stream
+ .filter(file -> !Files.isDirectory(file))
+ .map(Path::getFileName)
+ .map(Path::toString)
+ .collect(Collectors.toSet());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Given a length this function returns a random string of said length consisting of capital letters, and numbers.
+ * @param length of the random string to be returned.
+ * @return String consisting of random capital letters A-Z, and numbers 0-9;
+ */
+ public static String randomString(int length) {
+ String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ StringBuilder sb = new StringBuilder();
+ Random random = new Random();
+
+ for (int i = 0; i < length; i++) {
+ int index = random.nextInt(alphabet.length());
+ char randomChar = alphabet.charAt(index);
+ sb.append(randomChar);
+ }
+ return sb.toString();
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public String getFilePath() {
+ return filePath;
+ }
+}
diff --git a/src/main/java/nl/isygameclient/util/Vector2D.java b/src/main/java/nl/isygameclient/util/Vector2D.java
@@ -28,9 +28,6 @@ public class Vector2D<X, Y> {
@Override
public String toString() {
- return "Vector2D{" +
- "x=" + x +
- ", y=" + y +
- '}';
+ return "(x" + x + " y" + y + ")";
}
}