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;
import com.codingame.gameengine.runner.MultiplayerGameRunner;
import com.codingame.gameengine.runner.dto.*;
-public class TrollTest {
- @Test
- public void drawGame() {
+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(agentOne);
- gameRunner.addAgent(agentOne);
+ gameRunner.addAgent(left);
+ gameRunner.addAgent(right);
- assertIsDraw(gameRunner.simulate());
+ return gameRunner.simulate();
}
+ // ==================== Single-round game result testing
+
@Test
- public void crashGame() {
- MultiplayerGameRunner gameRunner = new MultiplayerGameRunner();
- Properties gameParameters = new Properties();
- gameParameters.setProperty("roadLength", "6");
- gameParameters.setProperty("initialStones", "15");
- gameRunner.setGameParameters(gameParameters);
- gameRunner.setSeed(0l);
- gameRunner.setLeagueLevel(1);
+ public void drawGame() {
+ assertIsDraw(runGame(agentOne, agentOne));
+ }
+
+ @Test
+ public void defeatGames() {
+ assertIsDefeat(runGame(agentCrash, agentCrash));
+ assertIsDefeat(runGame(agentGarbage, agentGarbage));
+ }
- gameRunner.addAgent(agentCrash);
- gameRunner.addAgent(agentCrash);
+ @Test
+ public void simpleGames() {
+ // wins by direct reach, no fastforward
+ assertWinLose(agentTwo, agentOne);
+
+ // win by fastforward after loser exhaustion
+ 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));
+ }
+
+ // ==================== Cheating games (league 1 perk)
- assertIsDefeat(gameRunner.simulate());
+ @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<GameMap> maps = new HashSet<GameMap>();
+ 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";
+ // 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 private String agent(int... tosses) {
+ String cmd = "echo -e ";
+ for (int i = 0; i < tosses.length; i++) {
+ if (i > 0) cmd += "\\n";
+ cmd += tosses[i];
+ }
+ return cmd;
+ }
+
+ // ==================== Debug routines
static void dumpGameResult(PrintStream p, GameResult gameResult) {
for (AgentDto agent : gameResult.agents) dumpAgent(p, agent);
// dumpList(p, "views", gameResult.views);
}
- static <V> void dumpList(PrintStream p, String tag, List<V> list) {
- V[] a = (V[]) list.toArray();
- for (int i = 0; i < a.length; i++) {
+ static private <V> void dumpList(PrintStream p, String tag, List<V> list) {
+ ListIterator i = list.listIterator();
+ while (i.hasNext()) {
if (tag != null) p.print(tag + " ");
- p.print(i + ": ");
- dumpGeneric(p, a[i]);
+ p.print(i.nextIndex() + ": ");
+ dumpGeneric(p, i.next());
}
}
- static <K,V> void dumpMap(PrintStream p, String tag, Map<K,V> map) {
+ static private <K,V> void dumpMap(PrintStream p, String tag, Map<K,V> map) {
for (Map.Entry kv : map.entrySet()) {
p.print(tag + " for " + kv.getKey() + ": ");
dumpGeneric(p, kv.getValue());
}
}
- static <V> void dumpGeneric(PrintStream p, V v) {
+ static private <E,V> void dumpGeneric(PrintStream p, E v) {
if (v instanceof List) {
- dumpList(p, null, (List) v);
+ dumpList(p, null, (List<?>) v);
}
else {
p.println(v);
}
}
- static void dumpString(PrintStream p, String tag, String msg) {
+ static private void dumpString(PrintStream p, String tag, String msg) {
p.println(tag + ": " + msg);
}
p.println("[agent] " + agent.agentId + ": " + agent.avatar + " " + agent.index + " " + agent.name);
}
+ // ==================== 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) {
int[] scores = assertTwoScores(gameResult);
if (scores == null) return;