X-Git-Url: https://troll.desast.re/troll.git/blobdiff_plain/1b8132031651654d02bd60fd5dc3a6fd1b724236..HEAD:/src/test/java/TrollTest.java?ds=sidebyside diff --git a/src/test/java/TrollTest.java b/src/test/java/TrollTest.java index e8dff3f..9112b28 100644 --- a/src/test/java/TrollTest.java +++ b/src/test/java/TrollTest.java @@ -1,7 +1,10 @@ import java.util.Properties; +import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; import java.io.PrintStream; import static org.junit.Assert.assertEquals; @@ -11,15 +14,36 @@ import org.junit.Test; import com.codingame.gameengine.runner.MultiplayerGameRunner; import com.codingame.gameengine.runner.dto.*; -public class TrollTest { - static GameResult runGame(String left, String right) { +public class TrollTest implements Cloneable { + + // ==================== Game Runner encapsulation + + private int league = 1; // @#$%^&* this parameter is *global* despite API! + private Long seed; + private Integer roadLength; + private Integer initialStones; + + private TrollTest branch() { + try { return (TrollTest) clone(); } + catch (CloneNotSupportedException e) { throw new InternalError(e); } + } + private TrollTest setLeague(int l) { league = l; return this; } + private TrollTest setSeed(long s) { seed = s; return this; } + private TrollTest setRoadLength(int l) { roadLength = l; return this; } + private TrollTest setInitialStones(int s) { initialStones = s; return this; } + + private GameResult runGame(String left, String right) { MultiplayerGameRunner gameRunner = new MultiplayerGameRunner(); + gameRunner.setLeagueLevel(league); // mandatory + if (seed != null) gameRunner.setSeed(seed); else gameRunner.setSeed(0l); + Properties gameParameters = new Properties(); - gameParameters.setProperty("roadLength", "6"); - gameParameters.setProperty("initialStones", "15"); gameRunner.setGameParameters(gameParameters); - gameRunner.setSeed(0l); - gameRunner.setLeagueLevel(1); + if (roadLength != null) + gameParameters.setProperty("roadLength", roadLength.toString()); + if (initialStones != null) + gameParameters.setProperty("initialStones", + initialStones.toString()); gameRunner.addAgent(left); gameRunner.addAgent(right); @@ -27,6 +51,8 @@ public class TrollTest { return gameRunner.simulate(); } + // ==================== Single-round game result testing + @Test public void drawGame() { assertIsDraw(runGame(agentOne, agentOne)); @@ -44,22 +70,133 @@ public class TrollTest { assertWinLose(agentTwo, agentOne); // win by fastforward after loser exhaustion - assertWinLose(agentOne, "yes 15"); + assertWinLose(agentOne, agentAllIn); // win despite fastforward after winner exhaustion // (harder to construct :-D ) - assertWinLose(agent(1,2,2,2,8), - agent(3,1,1,1,8)); + assertWinLose(agent(1,2,2,2,8), agent(3,1,1,1,8)); + } + + // ==================== Cheating games (league 1 perk) + + @Test + public void cheatingGames() { + // win by cheating (works in league 1, which is the default) + assertWinLose(agentCheat, agentTwo); + + // league 2 randomizes: we should be able to get a win and a loss + branch().setLeague(2).setSeed(0) + .setRoadLength(6).setInitialStones(15) + .assertWinLose(agentCheat, agentTwo); + branch().setLeague(2).setSeed(1) + .setRoadLength(6).setInitialStones(15) + .assertWinLose(agentTwo, agentCheat); + + // league 3 forbids + Random r = new Random(); + for (int i = 0; i < 16; i++) { + branch().setLeague(3).setSeed(r.nextLong()) + .setRoadLength(6).setInitialStones(15) + .assertWinLose(agentTwo, agentCheat); + } + } + + // ==================== Map generation testing + + static private class GameMap { + int roadLength; + int initialStones; + @Override public boolean equals(Object gm) { + if (gm instanceof GameMap) { + return roadLength == ((GameMap) gm).roadLength + && initialStones == ((GameMap) gm).initialStones; + } + else return false; + } + @Override public int hashCode() { + return roadLength << 16 | initialStones; + } } + @Test + public void maps() { + TrollTest test = branch(); + + // league 1, expect a single map + for (long s = 0; s < 16; s++) { + test.setSeed(s); + GameMap map = test.measureMap(); + assertEquals("Level 1 roadLength", 6, map.roadLength); + assertEquals("Level 1 initialStones", 15, map.initialStones); + } + + // league 2, expect one of four maps + test.setLeague(2); + HashSet maps = new HashSet(); + Random r = new Random(); + for (int i = 0; i < 16; i++) { + test.setSeed(r.nextLong()); + GameMap map = test.measureMap(); + maps.add(map); + } + assertEquals("Level 2 has four maps", 4, maps.size()); + + // league 3, maps simply have constraints + test.setLeague(3); + for (long s = 0; s < 16; s++) { + test.setSeed(s); + GameMap map = test.measureMap(); + assertTrue("Level 3 road length is at least 6", + map.roadLength >= 6); + assertTrue("Level 3 road length is at most 14", + map.roadLength <= 14); + assertTrue("Level 3 road length is even", + map.roadLength % 2 == 0); + assertTrue("Level 3 stones is at least 15", + map.initialStones >= 15); + assertTrue("Level 3 stones is at most 50", + map.initialStones <= 50); + } + } + + /* + * There's currently no way to extract the parameters (and verify + * them!) from a game. So for now we measure it from other + * traces. + */ + private GameMap measureMap() { + GameMap result = new GameMap(); + + // roadLength is twice the number of moves a troll takes + // before the end of the game in a + // position-independent-strategy game. + GameResult game = runGame(agentOne, agentTwo); + result.roadLength = 2 * game.summaries.stream() + .filter(s -> s.contains("walks")) + .collect(Collectors.counting()).intValue(); + + // initialStones is the number of times a troll stands still + // in a one-stone-throw draw. + game = runGame(agentOne, agentOne); + result.initialStones = game.summaries.stream() + .filter(s -> s.contains("still")) + .collect(Collectors.counting()).intValue(); + + return result; + } + + // ==================== Common test agents + // great thanks to @dbdr for the intense moral support leading to - // the following: - static String agentOne = "yes 1"; - static String agentTwo = "yes 2"; - static String agentCrash = "false"; - static String agentGarbage = "yes this_is_assuredly_not_an_int"; + // the following acceptance of failure: + static private final String agentOne = "yes 1"; + static private final String agentTwo = "yes 2"; + static private final String agentAllIn = "yes 15"; + static private final String agentCrash = "false"; // SLOW, try and avoid + static private final String agentGarbage = "yes assuredly_not_an_int"; + static private final String agentCheat = agent(-100,25,25,25,25); - static String agent(int... tosses) { + static private String agent(int... tosses) { String cmd = "echo -e "; for (int i = 0; i < tosses.length; i++) { if (i > 0) cmd += "\\n"; @@ -68,6 +205,8 @@ public class TrollTest { return cmd; } + // ==================== Debug routines + static void dumpGameResult(PrintStream p, GameResult gameResult) { for (AgentDto agent : gameResult.agents) dumpAgent(p, agent); dumpMap(p, "errors", gameResult.errors); @@ -80,7 +219,7 @@ public class TrollTest { // dumpList(p, "views", gameResult.views); } - static void dumpList(PrintStream p, String tag, List list) { + static private void dumpList(PrintStream p, String tag, List list) { ListIterator i = list.listIterator(); while (i.hasNext()) { if (tag != null) p.print(tag + " "); @@ -89,14 +228,14 @@ public class TrollTest { } } - static void dumpMap(PrintStream p, String tag, Map map) { + static private void dumpMap(PrintStream p, String tag, Map map) { for (Map.Entry kv : map.entrySet()) { p.print(tag + " for " + kv.getKey() + ": "); dumpGeneric(p, kv.getValue()); } } - static void dumpGeneric(PrintStream p, E v) { + static private void dumpGeneric(PrintStream p, E v) { if (v instanceof List) { dumpList(p, null, (List) v); } @@ -105,7 +244,7 @@ public class TrollTest { } } - static void dumpString(PrintStream p, String tag, String msg) { + static private void dumpString(PrintStream p, String tag, String msg) { p.println(tag + ": " + msg); } @@ -113,25 +252,35 @@ public class TrollTest { p.println("[agent] " + agent.agentId + ": " + agent.avatar + " " + agent.index + " " + agent.name); } - static void assertWinLose(String winner, String loser) { + // ==================== Test assertions + + private void assertWinLose(String winner, String loser) { assertLeftWin(runGame(winner, loser)); assertRightWin(runGame(loser, winner)); } static void assertLeftWin(GameResult gameResult) { + assertLeftWin(gameResult, false); + } + static void assertLeftWin(GameResult gameResult, boolean strict) { int[] scores = assertTwoScores(gameResult); if (scores == null) return; int s1 = scores[0], s2 = scores[1]; assertTrue("Left player has higher score than right player", s1 > s2); + if (strict) assertTrue("Right player isn't disqualified", s2 >= 0); } static void assertRightWin(GameResult gameResult) { + assertRightWin(gameResult, false); + } + static void assertRightWin(GameResult gameResult, boolean strict) { int[] scores = assertTwoScores(gameResult); if (scores == null) return; int s1 = scores[0], s2 = scores[1]; assertTrue("Right player has higher score than right player", s2 > s1); + if (strict) assertTrue("Left player isn't disqualified", s1 >= 0); } static void assertIsDraw(GameResult gameResult) {