1 import java.util.Properties;
2 import java.util.HashSet;
4 import java.util.ListIterator;
6 import java.util.Random;
7 import java.util.stream.Collectors;
8 import java.io.PrintStream;
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.*;
12 import org.junit.Test;
14 import com.codingame.gameengine.runner.MultiplayerGameRunner;
15 import com.codingame.gameengine.runner.dto.*;
17 public class TrollTest implements Cloneable {
19 // ==================== Game Runner encapsulation
21 private int league = 1; // @#$%^&* this parameter is *global* despite API!
23 private Integer roadLength;
24 private Integer initialStones;
26 private TrollTest branch() {
27 try { return (TrollTest) clone(); }
28 catch (CloneNotSupportedException e) { throw new InternalError(e); }
30 private TrollTest setLeague(int l) { league = l; return this; }
31 private TrollTest setSeed(long s) { seed = s; return this; }
32 private TrollTest setRoadLength(int l) { roadLength = l; return this; }
33 private TrollTest setInitialStones(int s) { initialStones = s; return this; }
35 private GameResult runGame(String left, String right) {
36 MultiplayerGameRunner gameRunner = new MultiplayerGameRunner();
37 gameRunner.setLeagueLevel(league); // mandatory
38 if (seed != null) gameRunner.setSeed(seed); else gameRunner.setSeed(0l);
40 Properties gameParameters = new Properties();
41 gameRunner.setGameParameters(gameParameters);
42 if (roadLength != null)
43 gameParameters.setProperty("roadLength", roadLength.toString());
44 if (initialStones != null)
45 gameParameters.setProperty("initialStones",
46 initialStones.toString());
48 gameRunner.addAgent(left);
49 gameRunner.addAgent(right);
51 return gameRunner.simulate();
54 // ==================== Single-round game result testing
57 public void drawGame() {
58 assertIsDraw(runGame(agentOne, agentOne));
62 public void defeatGames() {
63 assertIsDefeat(runGame(agentCrash, agentCrash));
64 assertIsDefeat(runGame(agentGarbage, agentGarbage));
68 public void simpleGames() {
69 // wins by direct reach, no fastforward
70 assertWinLose(agentTwo, agentOne);
72 // win by fastforward after loser exhaustion
73 assertWinLose(agentOne, agentAllIn);
75 // win despite fastforward after winner exhaustion
76 // (harder to construct :-D )
77 assertWinLose(agent(1,2,2,2,8), agent(3,1,1,1,8));
80 // ==================== Cheating games (league 1 perk)
83 public void cheatingGames() {
84 // win by cheating (works in league 1, which is the default)
85 assertWinLose(agentCheat, agentTwo);
87 // league 2 randomizes: we should be able to get a win and a loss
88 branch().setLeague(2).setSeed(0)
89 .setRoadLength(6).setInitialStones(15)
90 .assertWinLose(agentCheat, agentTwo);
91 branch().setLeague(2).setSeed(1)
92 .setRoadLength(6).setInitialStones(15)
93 .assertWinLose(agentTwo, agentCheat);
96 Random r = new Random();
97 for (int i = 0; i < 16; i++) {
98 branch().setLeague(3).setSeed(r.nextLong())
99 .setRoadLength(6).setInitialStones(15)
100 .assertWinLose(agentTwo, agentCheat);
104 // ==================== Map generation testing
106 static private class GameMap {
109 @Override public boolean equals(Object gm) {
110 if (gm instanceof GameMap) {
111 return roadLength == ((GameMap) gm).roadLength
112 && initialStones == ((GameMap) gm).initialStones;
116 @Override public int hashCode() {
117 return roadLength << 16 | initialStones;
123 TrollTest test = branch();
125 // league 1, expect a single map
126 for (long s = 0; s < 16; s++) {
128 GameMap map = test.measureMap();
129 assertEquals("Level 1 roadLength", 6, map.roadLength);
130 assertEquals("Level 1 initialStones", 15, map.initialStones);
133 // league 2, expect one of four maps
135 HashSet<GameMap> maps = new HashSet<GameMap>();
136 Random r = new Random();
137 for (int i = 0; i < 16; i++) {
138 test.setSeed(r.nextLong());
139 GameMap map = test.measureMap();
142 assertEquals("Level 2 has four maps", 4, maps.size());
144 // league 3, maps simply have constraints
146 for (long s = 0; s < 16; s++) {
148 GameMap map = test.measureMap();
149 assertTrue("Level 3 road length is at least 6",
150 map.roadLength >= 6);
151 assertTrue("Level 3 road length is at most 14",
152 map.roadLength <= 14);
153 assertTrue("Level 3 road length is even",
154 map.roadLength % 2 == 0);
155 assertTrue("Level 3 stones is at least 15",
156 map.initialStones >= 15);
157 assertTrue("Level 3 stones is at most 50",
158 map.initialStones <= 50);
163 * There's currently no way to extract the parameters (and verify
164 * them!) from a game. So for now we measure it from other
167 private GameMap measureMap() {
168 GameMap result = new GameMap();
170 // roadLength is twice the number of moves a troll takes
171 // before the end of the game in a
172 // position-independent-strategy game.
173 GameResult game = runGame(agentOne, agentTwo);
174 result.roadLength = 2 * game.summaries.stream()
175 .filter(s -> s.contains("walks"))
176 .collect(Collectors.counting()).intValue();
178 // initialStones is the number of times a troll stands still
179 // in a one-stone-throw draw.
180 game = runGame(agentOne, agentOne);
181 result.initialStones = game.summaries.stream()
182 .filter(s -> s.contains("still"))
183 .collect(Collectors.counting()).intValue();
188 // ==================== Common test agents
190 // great thanks to @dbdr for the intense moral support leading to
191 // the following acceptance of failure:
192 static private final String agentOne = "yes 1";
193 static private final String agentTwo = "yes 2";
194 static private final String agentAllIn = "yes 15";
195 static private final String agentCrash = "false"; // SLOW, try and avoid
196 static private final String agentGarbage = "yes assuredly_not_an_int";
197 static private final String agentCheat = agent(-100,25,25,25,25);
199 static private String agent(int... tosses) {
200 String cmd = "echo -e ";
201 for (int i = 0; i < tosses.length; i++) {
202 if (i > 0) cmd += "\\n";
208 // ==================== Debug routines
210 static void dumpGameResult(PrintStream p, GameResult gameResult) {
211 for (AgentDto agent : gameResult.agents) dumpAgent(p, agent);
212 dumpMap(p, "errors", gameResult.errors);
213 dumpString(p, "failCause", gameResult.failCause);
214 dumpMap(p, "ids", gameResult.ids);
215 dumpString(p, "metadata", gameResult.metadata);
216 dumpMap(p, "outputs", gameResult.outputs);
217 dumpList(p, "summaries", gameResult.summaries);
218 dumpList(p, "tooltips", gameResult.tooltips);
219 // dumpList(p, "views", gameResult.views);
222 static private <V> void dumpList(PrintStream p, String tag, List<V> list) {
223 ListIterator i = list.listIterator();
224 while (i.hasNext()) {
225 if (tag != null) p.print(tag + " ");
226 p.print(i.nextIndex() + ": ");
227 dumpGeneric(p, i.next());
231 static private <K,V> void dumpMap(PrintStream p, String tag, Map<K,V> map) {
232 for (Map.Entry kv : map.entrySet()) {
233 p.print(tag + " for " + kv.getKey() + ": ");
234 dumpGeneric(p, kv.getValue());
238 static private <E,V> void dumpGeneric(PrintStream p, E v) {
239 if (v instanceof List) {
240 dumpList(p, null, (List<?>) v);
247 static private void dumpString(PrintStream p, String tag, String msg) {
248 p.println(tag + ": " + msg);
251 static void dumpAgent(PrintStream p, AgentDto agent) {
252 p.println("[agent] " + agent.agentId + ": " + agent.avatar + " " + agent.index + " " + agent.name);
255 // ==================== Test assertions
257 private void assertWinLose(String winner, String loser) {
258 assertLeftWin(runGame(winner, loser));
259 assertRightWin(runGame(loser, winner));
262 static void assertLeftWin(GameResult gameResult) {
263 assertLeftWin(gameResult, false);
265 static void assertLeftWin(GameResult gameResult, boolean strict) {
266 int[] scores = assertTwoScores(gameResult);
267 if (scores == null) return;
269 int s1 = scores[0], s2 = scores[1];
270 assertTrue("Left player has higher score than right player", s1 > s2);
271 if (strict) assertTrue("Right player isn't disqualified", s2 >= 0);
274 static void assertRightWin(GameResult gameResult) {
275 assertRightWin(gameResult, false);
277 static void assertRightWin(GameResult gameResult, boolean strict) {
278 int[] scores = assertTwoScores(gameResult);
279 if (scores == null) return;
281 int s1 = scores[0], s2 = scores[1];
282 assertTrue("Right player has higher score than right player", s2 > s1);
283 if (strict) assertTrue("Left player isn't disqualified", s1 >= 0);
286 static void assertIsDraw(GameResult gameResult) {
287 int[] scores = assertTwoScores(gameResult);
288 if (scores == null) return;
290 int s1 = scores[0], s2 = scores[1];
291 assertEquals("Player scores are equal", s1, s2);
292 assertTrue("Player scores are non-negative", s1 >= 0);
295 static void assertIsDefeat(GameResult gameResult) {
296 int[] scores = assertTwoScores(gameResult);
297 if (scores == null) return;
299 int s1 = scores[0], s2 = scores[1];
300 assertTrue("First player score is negative", s1 < 0);
301 assertTrue("Second player score is negative", s2 < 0);
304 static int[] assertTwoScores(GameResult gameResult) {
305 Map<Integer,Integer> scores = gameResult.scores;
306 assertEquals("Two scores are reported", scores.size(), 2);
307 assertTrue("Player 0 has a score", scores.containsKey(0));
308 assertTrue("Player 1 has a score", scores.containsKey(1));
309 return new int[] { scores.get(0), scores.get(1) };