diff --git a/.gitignore b/.gitignore index c6bf628c..5f8ca151 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ settings.json +BattleResults/ ### IntelliJ IDEA ### .idea/modules.xml diff --git a/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/CSVWriter.java b/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/CSVWriter.java new file mode 100644 index 00000000..913a7498 --- /dev/null +++ b/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/CSVWriter.java @@ -0,0 +1,79 @@ +package dk.sdu.mmmi.modulemon.HeadlessBattleView; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class CSVWriter { + private char separator = ','; + private boolean writeExcelHeader = false; + private String[] columnTitles; + + private List rows; + + public CSVWriter(){ + rows = new ArrayList<>(); + } + + public boolean save(String fileToWriteTo) throws IOException { + try (var writer = new BufferedWriter(new FileWriter(fileToWriteTo))) { + if(writeExcelHeader){ + writer.append("sep=").append(separator); + writer.newLine(); + } + + if(columnTitles != null){ + writer.append(String.join(String.valueOf(separator), columnTitles)); + writer.newLine(); + } + + for(var row : rows){ + boolean first = true; // Used to make sure we don't have trailing separators + for(var field : row){ + if(!first){ + writer.append(separator); + } + first = false; + + writer.append(field.toString()); + } + writer.newLine(); + } + + writer.flush(); + } + + return true; + } + + public void addRow(Object... row){ + rows.add(row); + } + + public char getSeparator() { + return separator; + } + + public void setSeparator(char separator) { + this.separator = separator; + } + + public String[] getColumnTitles() { + return columnTitles; + } + + public void setColumnTitles(String... columnTitles) { + this.columnTitles = columnTitles; + } + + public boolean isWriteExcelHeader() { + return writeExcelHeader; + } + + public void setWriteExcelHeader(boolean writeExcelHeader) { + this.writeExcelHeader = writeExcelHeader; + } +} diff --git a/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/EmptyOutHandler.java b/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/EmptyOutHandler.java new file mode 100644 index 00000000..f8ba8fa0 --- /dev/null +++ b/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/EmptyOutHandler.java @@ -0,0 +1,54 @@ +package dk.sdu.mmmi.modulemon.HeadlessBattleView; + +//Basically a fast out handler that captures all logging and does nothing. Speeding up due to excessive logging. +public class EmptyOutHandler { + private static final java.io.PrintStream defaultStream = System.out; + public static void setEmptyHandler(){ + System.out.println("Disabling System.out.println()"); + System.setOut(getHandler()); + } + + public static void setDefaultHandler(){ + System.setOut(defaultStream); + System.out.println("Enabled System.out.println()"); + } + + public static java.io.PrintStream getHandler(){ + // Stolen from https://stackoverflow.com/a/34839209 + return new java.io.PrintStream(new java.io.OutputStream() { + @Override public void write(int b) {} + }) { + @Override public void flush() {} + @Override public void close() {} + @Override public void write(int b) {} + @Override public void write(byte[] b) {} + @Override public void write(byte[] buf, int off, int len) {} + @Override public void print(boolean b) {} + @Override public void print(char c) {} + @Override public void print(int i) {} + @Override public void print(long l) {} + @Override public void print(float f) {} + @Override public void print(double d) {} + @Override public void print(char[] s) {} + @Override public void print(String s) {} + @Override public void print(Object obj) {} + @Override public void println() {} + @Override public void println(boolean x) {} + @Override public void println(char x) {} + @Override public void println(int x) {} + @Override public void println(long x) {} + @Override public void println(float x) {} + @Override public void println(double x) {} + @Override public void println(char[] x) {} + @Override public void println(String x) {} + @Override public void println(Object x) {} + @Override public java.io.PrintStream printf(String format, Object... args) { return this; } + @Override public java.io.PrintStream printf(java.util.Locale l, String format, Object... args) { return this; } + @Override public java.io.PrintStream format(String format, Object... args) { return this; } + @Override public java.io.PrintStream format(java.util.Locale l, String format, Object... args) { return this; } + @Override public java.io.PrintStream append(CharSequence csq) { return this; } + @Override public java.io.PrintStream append(CharSequence csq, int start, int end) { return this; } + @Override public java.io.PrintStream append(char c) { return this; } + }; + } +} diff --git a/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/HeadlessBattleView.java b/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/HeadlessBattleView.java index 480e0241..54d6dbe3 100644 --- a/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/HeadlessBattleView.java +++ b/src/main/java/dk/sdu/mmmi/modulemon/HeadlessBattleView/HeadlessBattleView.java @@ -6,6 +6,7 @@ import dk.sdu.mmmi.modulemon.Battle.BattleParticipant; import dk.sdu.mmmi.modulemon.BattleScene.BattleResult; import dk.sdu.mmmi.modulemon.BattleSimulation.BattleSimulation; +import dk.sdu.mmmi.modulemon.CommonBattle.IBattleParticipant; import dk.sdu.mmmi.modulemon.CommonBattleClient.IBattleResult; import dk.sdu.mmmi.modulemon.CommonBattleSimulation.BattleEvents.AICrashedEvent; import dk.sdu.mmmi.modulemon.CommonBattleSimulation.BattleEvents.ChangeMonsterBattleEvent; @@ -24,6 +25,11 @@ import dk.sdu.mmmi.modulemon.common.services.IGameViewService; import dk.sdu.mmmi.modulemon.CommonBattleSimulation.BattleEvents.MoveBattleEvent; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.sql.Array; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -62,6 +68,8 @@ public class HeadlessBattleView implements IGameViewService { private int[] battleAmounts = {1, 10, 100, 250, 500, 750, 1000}; private int battleAmountIndex = 1; + private CSVWriter csvWriter; + @Override public void init(IGameViewManager gameViewManager) { selectSound = AssetLoader.getInstance().getSoundAsset("/sounds/select.ogg", this.getClass()); @@ -69,12 +77,30 @@ public void init(IGameViewManager gameViewManager) { yaySound = AssetLoader.getInstance().getSoundAsset("/sounds/YAY.ogg", this.getClass()); battleWaitMusic = AssetLoader.getInstance().getMusicAsset("/music/headless.ogg", this.getClass()); battleWaitMusic.setLooping(true); - battleWaitMusic.setVolume(getSoundVolume()); + battleWaitMusic.setVolume(getMusicVolume()); menuMusic = AssetLoader.getInstance().getMusicAsset("/music/headlessMenu.ogg", this.getClass()); menuMusic.setLooping(true); - menuMusic.setVolume(getSoundVolume()); + menuMusic.setVolume(getMusicVolume()); menuMusic.play(); + csvWriter = new CSVWriter(); + csvWriter.setWriteExcelHeader(true); // Makes it so the file is easy to open in Excel. + + var coloumnTitles = new ArrayList() {{ + add("Battle"); + add("WinnerTeam"); + add("TurnsToWin"); + }}; + int monstersPerTeam = 3; + for(String team : new String[]{"WinnerTeam", "LoserTeam"}) { + for (int i = 0; i < monstersPerTeam; i++) { + coloumnTitles.add(String.format("%s_Monster%d_Name", team, i+1)); + coloumnTitles.add(String.format("%s_Monster%d_HP", team, i+1)); + } + } + + csvWriter.setColumnTitles(coloumnTitles.toArray(new String[0])); + scene = new HeadlessBattleScene(settings); battlingScene = new HeadlessBattlingScene(); } @@ -96,6 +122,7 @@ public void update(GameData gameData, IGameViewManager gameViewManager) { if (battleResultFuture.isDone()) { try { var battleResult = battleResultFuture.get(); + boolean teamAWon = true; if (battleResult.getWinner() == battleResult.getPlayer()) { teamAWins++; if(battleResult.getStarter() == battleResult.getWinner()){ @@ -103,6 +130,7 @@ public void update(GameData gameData, IGameViewManager gameViewManager) { } winTurnsA += battleResult.getTurns(); } else { + teamAWon = false; teamBWins++; if(battleResult.getStarter() == battleResult.getWinner()){ teamBStartWins++; @@ -112,9 +140,24 @@ public void update(GameData gameData, IGameViewManager gameViewManager) { completedBattles++; currentBattles--; battleResultsToRemove.add(battleResultFuture); + + var row = new ArrayList(); + row.add(completedBattles); + row.add(teamAWon ? "A" : "B"); + row.add(battleResult.getTurns()); + + var participantWinner = battleResult.getWinner(); + var participantLoser = battleResult.getWinner().equals(battleResult.getPlayer()) ? battleResult.getEnemy() : battleResult.getPlayer(); + for(var team : new IBattleParticipant[]{participantWinner, participantLoser}) { + for (var monster : team.getMonsterTeam()) { + row.add(monster.getName()); + row.add(monster.getHitPoints()); + } + } + + csvWriter.addRow(row.toArray()); } catch (InterruptedException | ExecutionException e) { System.out.println("Battle was cancelled!"); - ; } } } @@ -129,10 +172,26 @@ public void update(GameData gameData, IGameViewManager gameViewManager) { if (completedBattles >= battleAmount) { yaySound.play(getSoundVolume()); battling = false; + EmptyOutHandler.setDefaultHandler(); doneBattling = true; currentBattles = 0; // Stop any remaining battles stopAllBattles(); + + // Save CSV file + try { + final String outputDir = "BattleResults"; + var dir = new File(outputDir); + if(!dir.exists()) { + dir.mkdirs(); + } + var filename = Path.of(outputDir, String.format("battle_simulation_results_%d.csv", System.currentTimeMillis() / 1000L)); + csvWriter.save(filename.toAbsolutePath().toString()); + } catch (IOException e) { + System.out.println("Tried to save CSV file, but failed! Oh no! Anyway.."); + e.printStackTrace(); + } + } else if (currentBattles < concurrentBattles && currentBattles + completedBattles < battleAmount) { var amountBattlesToStart = concurrentBattles - currentBattles; for (int i = 0; i < amountBattlesToStart; i++) { @@ -290,6 +349,7 @@ public void handleInput(GameData gameData, IGameViewManager gameViewManager) { battleWaitMusic.setVolume(getSoundVolume()); battleWaitMusic.play(); battling = true; + EmptyOutHandler.setEmptyHandler(); } else if (gameData.getKeys().isPressed(GameKeys.ACTION)) { chooseSound.play(getSoundVolume()); editingMode = true; @@ -373,6 +433,10 @@ private float getSoundVolume() { return ((int) settings.getSetting(settingsRegistry.getSoundVolumeSetting()) / 100f); } + private float getMusicVolume() { + return ((int) settings.getSetting(settingsRegistry.getMusicVolumeSetting()) / 100f); + } + public void setSettings(IGameSettings settings) { this.settings = settings; } diff --git a/src/main/resources/music/headless.ogg b/src/main/resources/music/headless.ogg index 65946053..de658238 100644 Binary files a/src/main/resources/music/headless.ogg and b/src/main/resources/music/headless.ogg differ