201801441e745902539ad205c20cad487482a44e
[troll.git] / src / test / java / TrollTest.java
1 import java.util.Properties;
2 import java.util.HashSet;
3 import java.util.List;
4 import java.util.ListIterator;
5 import java.util.Map;
6 import java.util.Random;
7 import java.util.stream.Collectors;
8 import java.io.PrintStream;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.*;
12 import org.junit.Test;
13
14 import com.codingame.gameengine.runner.MultiplayerGameRunner;
15 import com.codingame.gameengine.runner.dto.*;
16
17 public class TrollTest implements Cloneable {
18     int leagueLevel = 1; // @#$%^&* league parameter is *global* despite API!
19     Long seed;
20     Integer roadLength;
21     Integer initialStones;
22     TrollTest branch() {
23         try { return (TrollTest) clone(); }
24         catch (CloneNotSupportedException e) { throw new InternalError(e); }
25     }
26     TrollTest setLeague(int league) { leagueLevel = league; return this; }
27     TrollTest setSeed(long seed) { this.seed = seed; return this; }
28     TrollTest setRoadLength(int l) { roadLength = l; return this; }
29     TrollTest setInitialStones(int s) { initialStones = s; return this; }
30
31     GameResult runGame(String left, String right) {
32         MultiplayerGameRunner gameRunner = new MultiplayerGameRunner();
33         gameRunner.setLeagueLevel(leagueLevel);
34         if (seed != null) gameRunner.setSeed(seed); else gameRunner.setSeed(0l);
35         Properties gameParameters = new Properties();
36         gameRunner.setGameParameters(gameParameters);
37         if (roadLength != null)
38             gameParameters.setProperty("roadLength", roadLength.toString());
39         if (initialStones != null)
40             gameParameters.setProperty("initialStones", initialStones.toString());
41
42         gameRunner.addAgent(left);
43         gameRunner.addAgent(right);
44
45         return gameRunner.simulate();
46     }
47
48     @Test
49     public void drawGame() {
50         assertIsDraw(runGame(agentOne, agentOne));
51     }
52
53     @Test
54     public void defeatGames() {
55         assertIsDefeat(runGame(agentCrash, agentCrash));
56         assertIsDefeat(runGame(agentGarbage, agentGarbage));
57     }
58
59     @Test
60     public void simpleGames() {
61         // wins by direct reach, no fastforward
62         assertWinLose(agentTwo, agentOne);
63
64         // win by fastforward after loser exhaustion
65         assertWinLose(agentOne, agentAllIn);
66
67         // win despite fastforward after winner exhaustion
68         // (harder to construct :-D )
69         assertWinLose(agent(1,2,2,2,8), agent(3,1,1,1,8));
70     }
71
72     @Test
73     public void cheatingGames() {
74         // win by cheating (works in league 1, which is the default)
75         assertWinLose(agentCheat, agentTwo);
76
77         // league 2 randomizes: we should be able to get a win and a loss
78         branch().setLeague(2).setSeed(0)
79             .setRoadLength(6).setInitialStones(15)
80             .assertWinLose(agentCheat, agentTwo);
81         branch().setLeague(2).setSeed(1)
82             .setRoadLength(6).setInitialStones(15)
83             .assertWinLose(agentTwo, agentCheat);
84     }
85
86     static private class GameMap {
87         int roadLength;
88         int initialStones;
89         @Override public boolean equals(Object gm) {
90             if (gm instanceof GameMap) {
91                 return roadLength == ((GameMap) gm).roadLength
92                     && initialStones == ((GameMap) gm).initialStones;
93             }
94             else return false;
95         }
96         @Override public int hashCode() {
97             return roadLength << 16 | initialStones;
98         }
99     }
100
101     @Test
102     public void maps() {
103         TrollTest test = branch();
104
105         // league 1, expect a single map
106         for (long s = 0; s < 16; s++) {
107             test.setSeed(s);
108             GameMap map = test.measureMap();
109             assertEquals("Level 1 roadLength", 6, map.roadLength);
110             assertEquals("Level 1 initialStones", 15, map.initialStones);
111         }
112
113         // league 2, expect one of four maps
114         test.setLeague(2);
115         HashSet<GameMap> maps = new HashSet<GameMap>();
116         Random r = new Random();
117         for (int i = 0; i < 16; i++) {
118             test.setSeed(r.nextLong());
119             GameMap map = test.measureMap();
120             maps.add(map);
121         }
122         assertEquals("Level 2 has four maps", 4, maps.size());
123
124         // league 3, maps simply have constraints
125         test.setLeague(3);
126         for (long s = 0; s < 16; s++) {
127             test.setSeed(s);
128             GameMap map = test.measureMap();
129             assertTrue("Level 3 road length is at least 6",
130                        map.roadLength >= 6);
131             assertTrue("Level 3 road length is at most 14",
132                        map.roadLength <= 14);
133             assertTrue("Level 3 road length is even",
134                        map.roadLength % 2 == 0);
135             assertTrue("Level 3 stones is at least 15",
136                        map.initialStones >= 15);
137             assertTrue("Level 3 stones is at most 50",
138                        map.initialStones <= 50);
139         }
140     }
141
142     /*
143      * There's currently no way to extract the parameters (and verify
144      * them!) from a game.  So for now we measure it from other
145      * traces.
146      */
147     private GameMap measureMap() {
148         GameMap result = new GameMap();
149
150         // roadLength is twice the number of moves a troll takes
151         // before the end of the game in a
152         // position-independent-strategy game.
153         GameResult game = runGame(agentOne, agentTwo);
154         result.roadLength = 2 * game.summaries.stream()
155             .filter(s -> s.contains("walks"))
156             .collect(Collectors.counting()).intValue();
157
158         // initialStones is the number of times a troll stands still
159         // in a one-stone-throw draw.
160         game = runGame(agentOne, agentOne);
161         result.initialStones = game.summaries.stream()
162             .filter(s -> s.contains("still"))
163             .collect(Collectors.counting()).intValue();
164
165         return result;
166     }
167
168     // great thanks to @dbdr for the intense moral support leading to
169     // the following:
170     static String agentOne = "yes 1";
171     static String agentTwo = "yes 2";
172     static String agentAllIn = "yes 15";
173     static String agentCrash = "false";
174     static String agentGarbage = "yes this_is_assuredly_not_an_int";
175     static String agentCheat = agent(-100,25,25,25,25);
176
177     static String agent(int... tosses) {
178         String cmd = "echo -e ";
179         for (int i = 0; i < tosses.length; i++) {
180             if (i > 0) cmd += "\\n";
181             cmd += tosses[i];
182         }
183         return cmd;
184     }
185
186     static void dumpGameResult(PrintStream p, GameResult gameResult) {
187         for (AgentDto agent : gameResult.agents) dumpAgent(p, agent);
188         dumpMap(p, "errors", gameResult.errors);
189         dumpString(p, "failCause", gameResult.failCause);
190         dumpMap(p, "ids", gameResult.ids);
191         dumpString(p, "metadata", gameResult.metadata);
192         dumpMap(p, "outputs", gameResult.outputs);
193         dumpList(p, "summaries", gameResult.summaries);
194         dumpList(p, "tooltips", gameResult.tooltips);
195         // dumpList(p, "views", gameResult.views);
196     }
197
198     static <V> void dumpList(PrintStream p, String tag, List<V> list) {
199         ListIterator i = list.listIterator();
200         while (i.hasNext()) {
201             if (tag != null) p.print(tag + " ");
202             p.print(i.nextIndex() + ": ");
203             dumpGeneric(p, i.next());
204         }
205     }
206
207     static <K,V> void dumpMap(PrintStream p, String tag, Map<K,V> map) {
208         for (Map.Entry kv : map.entrySet()) {
209             p.print(tag + " for " + kv.getKey() + ": ");
210             dumpGeneric(p, kv.getValue());
211         }
212     }
213
214     static <E,V> void dumpGeneric(PrintStream p, E v) {
215         if (v instanceof List) {
216             dumpList(p, null, (List<?>) v);
217         }
218         else {
219             p.println(v);
220         }
221     }
222
223     static void dumpString(PrintStream p, String tag, String msg) {
224         p.println(tag + ": " + msg);
225     }
226
227     static void dumpAgent(PrintStream p, AgentDto agent) {
228         p.println("[agent] " + agent.agentId + ": " + agent.avatar + " " + agent.index + " " + agent.name);
229     }
230
231     void assertWinLose(String winner, String loser) {
232         assertLeftWin(runGame(winner, loser));
233         assertRightWin(runGame(loser, winner));
234     }
235
236     static void assertLeftWin(GameResult gameResult) {
237         assertLeftWin(gameResult, false);
238     }
239     static void assertLeftWin(GameResult gameResult, boolean strict) {
240         int[] scores = assertTwoScores(gameResult);
241         if (scores == null) return;
242
243         int s1 = scores[0], s2 = scores[1];
244         assertTrue("Left player has higher score than right player", s1 > s2);
245         if (strict) assertTrue("Right player isn't disqualified", s2 >= 0);
246     }
247
248     static void assertRightWin(GameResult gameResult) {
249         assertRightWin(gameResult, false);
250     }
251     static void assertRightWin(GameResult gameResult, boolean strict) {
252         int[] scores = assertTwoScores(gameResult);
253         if (scores == null) return;
254
255         int s1 = scores[0], s2 = scores[1];
256         assertTrue("Right player has higher score than right player", s2 > s1);
257         if (strict) assertTrue("Left player isn't disqualified", s1 >= 0);
258     }
259
260     static void assertIsDraw(GameResult gameResult) {
261         int[] scores = assertTwoScores(gameResult);
262         if (scores == null) return;
263
264         int s1 = scores[0], s2 = scores[1];
265         assertEquals("Player scores are equal", s1, s2);
266         assertTrue("Player scores are non-negative", s1 >= 0);
267     }
268
269     static void assertIsDefeat(GameResult gameResult) {
270         int[] scores = assertTwoScores(gameResult);
271         if (scores == null) return;
272
273         int s1 = scores[0], s2 = scores[1];
274         assertTrue("First player score is negative", s1 < 0);
275         assertTrue("Second player score is negative", s2 < 0);
276     }
277
278     static int[] assertTwoScores(GameResult gameResult) {
279         Map<Integer,Integer> scores = gameResult.scores;
280         assertEquals("Two scores are reported", scores.size(), 2);
281         assertTrue("Player 0 has a score", scores.containsKey(0));
282         assertTrue("Player 1 has a score", scores.containsKey(1));
283         return new int[] { scores.get(0), scores.get(1) };
284     }
285 }