Debug the CG SDK. Errr… I mean leaguify maps.
authorJBM <jbm@codingame.com>
Sat, 13 Jun 2020 22:31:25 +0000 (00:31 +0200)
committerJBM <jbm@codingame.com>
Sat, 13 Jun 2020 22:33:27 +0000 (00:33 +0200)
I'm not too happy with having wasted so much time on this.

PLAN.org
src/main/java/com/codingame/game/LeagueManager.java
src/main/java/com/codingame/game/Model.java
src/test/java/TrollTest.java

index 25d67cd..bfda8b2 100644 (file)
--- a/PLAN.org
+++ b/PLAN.org
@@ -51,7 +51,7 @@
 ** TODO Multiround (need early termination)
 ** TODO Leaguification
 - [X] Cheating
-- [ ] Maps
+- [X] Maps
 - [ ] Trolls
 - [ ] 
 ** DONE Early termination (need time rationalization)
@@ -73,6 +73,11 @@ That one's probably never going to be DONE ^^'
 *** TODO more generic "referee crash"
 *** TODO parameters feedback
 *** TODO cheating cases
+** TODO add a background to the favicon
+
+That transparency is nefarious on my browser's default tab background
+color.
+
 * BUGS
 ** GodMode crash
 Reported by @Illedan
index 94a7f5f..fe2187c 100644 (file)
@@ -12,15 +12,30 @@ class LeagueManager {
         TOLERATED,
         FORBIDDEN
     }
-
     CheatLevel cheatLevel;
 
+    enum MapLevel {
+        SINGLE,
+        DISCRETE,
+        CONTINUOUS
+    }
+    MapLevel mapLevel;
+
     @Inject
     LeagueManager(MultiplayerGameManager<AbstractMultiplayerPlayer> gameManager)
     {
         int level = gameManager.getLeagueLevel();
+        if (level < 1 || level > 3) {
+            throw new InternalError("This game does not implement level " + level);
+        }
+
         cheatLevel = level <= 1 ? CheatLevel.ALLOWED
                    : level <= 2 ? CheatLevel.TOLERATED
                    : CheatLevel.FORBIDDEN;
+
+        mapLevel = level <= 1 ? MapLevel.SINGLE
+                 : level <= 2 ? MapLevel.DISCRETE
+                 : MapLevel.CONTINUOUS;
+
     }    
 }
index 217388a..96140f5 100644 (file)
@@ -9,6 +9,7 @@ import com.google.inject.Inject;
 
 class Model {
     @Inject private MultiplayerGameManager<com.codingame.game.Player> gameManager;
+    @Inject private LeagueManager league;
     long seed;
     Random random;
     int roadLength;
@@ -89,22 +90,36 @@ class Model {
     void init() {
         seed = gameManager.getSeed();
         random = new Random(seed);
-        switch (random.nextInt(4)) {
-        case 0:
+
+        switch(league.mapLevel) {
+        case SINGLE:
             roadLength = 6;
             initialStones = 15;
             break;
-        case 1:
-            roadLength = 6;
-            initialStones = 30;
-            break;
-        case 2:
-            roadLength = 14;
-            initialStones = 30;
+        case DISCRETE:
+            int i = random.nextInt(4);
+            switch (i) {
+            case 0:
+                roadLength = 6;
+                initialStones = 15;
+                break;
+            case 1:
+                roadLength = 6;
+                initialStones = 30;
+                break;
+            case 2:
+                roadLength = 14;
+                initialStones = 30;
+                break;
+            case 3:
+                roadLength = 14;
+                initialStones = 50;
+                break;
+            }
             break;
-        case 3:
-            roadLength = 14;
-            initialStones = 50;
+        case CONTINUOUS:
+            roadLength = 2 * (3 + random.nextInt(7-3+1));
+            initialStones = 15 + random.nextInt(50-15+1);
             break;
         }
 
index 7b998b9..2018014 100644 (file)
@@ -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;
@@ -12,23 +15,29 @@ import com.codingame.gameengine.runner.MultiplayerGameRunner;
 import com.codingame.gameengine.runner.dto.*;
 
 public class TrollTest implements Cloneable {
-    Integer leagueLevel;
+    int leagueLevel = 1; // @#$%^&* league parameter is *global* despite API!
     Long seed;
+    Integer roadLength;
+    Integer initialStones;
     TrollTest branch() {
         try { return (TrollTest) clone(); }
         catch (CloneNotSupportedException e) { throw new InternalError(e); }
     }
     TrollTest setLeague(int league) { leagueLevel = league; return this; }
     TrollTest setSeed(long seed) { this.seed = seed; return this; }
+    TrollTest setRoadLength(int l) { roadLength = l; return this; }
+    TrollTest setInitialStones(int s) { initialStones = s; return this; }
 
     GameResult runGame(String left, String right) {
         MultiplayerGameRunner gameRunner = new MultiplayerGameRunner();
+        gameRunner.setLeagueLevel(leagueLevel);
+        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);
-        if (seed != null) gameRunner.setSeed(seed); else gameRunner.setSeed(0l);
-        if (leagueLevel != null) gameRunner.setLeagueLevel(leagueLevel);
+        if (roadLength != null)
+            gameParameters.setProperty("roadLength", roadLength.toString());
+        if (initialStones != null)
+            gameParameters.setProperty("initialStones", initialStones.toString());
 
         gameRunner.addAgent(left);
         gameRunner.addAgent(right);
@@ -53,7 +62,7 @@ public class TrollTest implements Cloneable {
         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 )
@@ -66,14 +75,101 @@ public class TrollTest implements Cloneable {
         assertWinLose(agentCheat, agentTwo);
 
         // league 2 randomizes: we should be able to get a win and a loss
-        branch().setLeague(2).setSeed(0).assertWinLose(agentCheat, agentTwo);
-        branch().setLeague(2).setSeed(1).assertWinLose(agentTwo, agentCheat);
+        branch().setLeague(2).setSeed(0)
+            .setRoadLength(6).setInitialStones(15)
+            .assertWinLose(agentCheat, agentTwo);
+        branch().setLeague(2).setSeed(1)
+            .setRoadLength(6).setInitialStones(15)
+            .assertWinLose(agentTwo, agentCheat);
+    }
+
+    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;
     }
 
     // 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 agentAllIn = "yes 15";
     static String agentCrash = "false";
     static String agentGarbage = "yes this_is_assuredly_not_an_int";
     static String agentCheat = agent(-100,25,25,25,25);