From 505d4ac3e3dc4a8f143faed4e6d61f7b9c8e2c33 Mon Sep 17 00:00:00 2001 From: JBM Date: Sun, 14 Jun 2020 00:31:25 +0200 Subject: [PATCH 1/1] =?utf8?q?Debug=20the=20CG=20SDK.=20=20Errr=E2=80=A6?= =?utf8?q?=20I=20mean=20leaguify=20maps.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit I'm not too happy with having wasted so much time on this. --- PLAN.org | 7 +- .../com/codingame/game/LeagueManager.java | 17 ++- src/main/java/com/codingame/game/Model.java | 39 ++++-- src/test/java/TrollTest.java | 112 ++++++++++++++++-- 4 files changed, 153 insertions(+), 22 deletions(-) diff --git a/PLAN.org b/PLAN.org index 25d67cd..bfda8b2 100644 --- 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 diff --git a/src/main/java/com/codingame/game/LeagueManager.java b/src/main/java/com/codingame/game/LeagueManager.java index 94a7f5f..fe2187c 100644 --- a/src/main/java/com/codingame/game/LeagueManager.java +++ b/src/main/java/com/codingame/game/LeagueManager.java @@ -12,15 +12,30 @@ class LeagueManager { TOLERATED, FORBIDDEN } - CheatLevel cheatLevel; + enum MapLevel { + SINGLE, + DISCRETE, + CONTINUOUS + } + MapLevel mapLevel; + @Inject LeagueManager(MultiplayerGameManager 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; + } } diff --git a/src/main/java/com/codingame/game/Model.java b/src/main/java/com/codingame/game/Model.java index 217388a..96140f5 100644 --- a/src/main/java/com/codingame/game/Model.java +++ b/src/main/java/com/codingame/game/Model.java @@ -9,6 +9,7 @@ import com.google.inject.Inject; class Model { @Inject private MultiplayerGameManager 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; } diff --git a/src/test/java/TrollTest.java b/src/test/java/TrollTest.java index 7b998b9..2018014 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; @@ -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 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; } // 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); -- 2.30.2