Early game termination
authorJBM <jbm@codingame.com>
Sun, 7 Jun 2020 16:00:08 +0000 (18:00 +0200)
committerJBM <jbm@codingame.com>
Sun, 7 Jun 2020 16:05:49 +0000 (18:05 +0200)
Game termination is now a frame of its own; castle destruction
is pushed to a new "endgame" frame.

PLAN.org
config/statement_en.html
src/main/java/com/codingame/game/Model.java
src/main/java/com/codingame/game/Referee.java
src/main/java/com/codingame/game/View.java
src/test/java/Main.java

index b8d16aa..5b3be7f 100644 (file)
--- a/PLAN.org
+++ b/PLAN.org
@@ -49,7 +49,7 @@
 ** DONE html for salting the seed
 ** TODO Leagues (need multiround)
 ** TODO Multiround (need early termination)
-** TODO Early termination (need time rationalization)
+** DONE Early termination (need time rationalization)
 ** DONE Time rationalization (need code reorg)
 ** TODO Code cleanup
 
@@ -59,6 +59,8 @@ That one's probably never going to be DONE ^^'
 
 *** split out what can be
 
+*** factor stone throwing ref/view interface
+
 * BUGS
 ** viewer goes blank <2020-06-03 mer. 22:58>
 (22:19:12) Astrobytes: JBM, if you're around, the TVC viewer goes blank after a couple of games. Consistently. In Chrome. Other games not doing the same.
index 35242fa..05a95cf 100644 (file)
      <p>
        This draft's last change is:
        <strong>
-         demo references still existing sprites.
+         early game termination.
        </strong>
      </p>
    </div>
index 00701d1..6eb0546 100644 (file)
@@ -66,7 +66,9 @@ class Model {
         }
 
         public void adjustScore(int trollPosition) {
-            gp.setScore(Math.abs(castlePosition - trollPosition));
+            if (gp.isActive()) {
+                gp.setScore(Math.abs(castlePosition - trollPosition));
+            }
         }
 
         public int getTrollDistance() {
@@ -149,24 +151,30 @@ class Model {
         p1.setStones(initialStones);
     }
 
-    private int winner;
-    boolean haveWinner() {
-        if (trollPosition == 0) {
+    void moveTroll(int delta) {
+        trollPosition += delta;
+        if (trollPosition <= 0) {
+            trollPosition = 0;
             winner = 1;
-            return true;
         }
-        else if (trollPosition == roadLength) {
+        if (trollPosition >= roadLength) {
+            trollPosition = roadLength;
             winner = 0;
-            return true;
-        }
-        else {
-            return false;
         }
+
+        p0.adjustScore(trollPosition);
+        p1.adjustScore(trollPosition);
     }
+
+    private Integer winner;
+    boolean haveWinner() {
+        return winner != null;
+    }
+
     int getWinner() { return winner; }
     int getLoser() { return 1 - winner; }
 
     boolean exhausted() {
-        return p0.getStones() <= 0 && p1.getStones() <= 0;
+        return p0.getStones() <= 0 || p1.getStones() <= 0;
     }
 }
index 186ca88..dfa3324 100644 (file)
@@ -24,6 +24,8 @@ public class Referee extends AbstractReferee {
     @Inject private View view;
     @Inject private Model model;
 
+    boolean disqual = false;
+
     @Override
     public void init() {
         model.init(gameManager.getSeed());
@@ -53,6 +55,11 @@ public class Referee extends AbstractReferee {
 
         view.startTurn();
 
+        // Did I mention I hate Java? It didn't *have* to be this ugly!
+        if (disqual) { endGame(); return; }
+        if (model.exhausted()) { finishStones(); return ;}
+        if (model.haveWinner()) { endGame(); return; }
+
         for (Player player : gameManager.getActivePlayers()) {
             player.sendGameTurn();
         }
@@ -63,7 +70,6 @@ public class Referee extends AbstractReferee {
          * is ill-formed it could help them debug.  Or shame them, at
          * least.
          */
-        boolean disqual = false;
         for (Player player : gameManager.getActivePlayers()) {
             player.receiveGameTurn();
             switch (player.type) {
@@ -116,8 +122,6 @@ public class Referee extends AbstractReferee {
          * exhaustion).
          */
         int delta = 0;
-        boolean victory = false;
-        boolean exhausted = false;
         for (Player player : gameManager.getActivePlayers()) {
             player.view.throwStones(player.stoneThrow);
             delta += player.model.getMultiplier() * player.stoneThrow;
@@ -150,34 +154,46 @@ public class Referee extends AbstractReferee {
         else if (cheat1) delta =  1;
 
         if (delta > 0) {
-            model.trollPosition++;
+            model.moveTroll(+1);
             view.moveTroll(View.Dir.RIGHT);
         }
         else if (delta < 0) {
-            model.trollPosition--;
+            model.moveTroll(-1);
             view.moveTroll(View.Dir.LEFT);
         }
         else {
             view.moveTroll(View.Dir.STILL);
             // XXX animate
         }
+    }
 
+    // XXX very similar to main turn pendant
+    private void finishStones() {
+        boolean noStones = true;
+        int delta = 0;
         for (Player player : gameManager.getActivePlayers()) {
-            player.model.adjustScore(model.trollPosition);
+            player.stoneThrow = player.model.getStones();
+            player.model.setStones(0);
+            delta += player.stoneThrow * player.model.getMultiplier();
+            player.view.throwStones(player.stoneThrow);
+            if (player.stoneThrow != 0) {
+                noStones = false;
+                player.view.animateStones(player.stoneThrow);
+                player.view.updateStoneCounter();
+            }
         }
+        if (noStones) { endGame(); return; }
+        model.moveTroll(delta);
+        view.moveTroll();
+    }
+
+    private void endGame() {
+        gameManager.endGame();
 
         if (model.haveWinner()) {
             int loser = model.getLoser();
             gameManager.getPlayer(loser).view.defeat();
-            victory = true;
         }
-        else if (model.exhausted()) exhausted = true;
-
-        if (disqual || victory || exhausted) endGame();
-    }
-
-    private void endGame() {
-        gameManager.endGame();
 
         Player p0 = gameManager.getPlayer(0);
         Player p1 = gameManager.getPlayer(1);
index 52cfa64..40aae3d 100644 (file)
@@ -26,25 +26,22 @@ class View {
      *   - first half: stone throw
      *   - second half: troll move
      * The troll message is anchored around the troll move.
-     *
-     * The castle destruction is currently ad hoc simultaneously with
-     * the troll move, but this ought to change if I get some
-     * lengthened frame system ready.
-     *
-     * The endgame message is completely ad hoc, and really ought to
-     * improve.
      */
-    private final double AVATAR_ANIMATION_START = 0.5;
     private final double STONE_THROW_START = 0.0;
     private final double STONE_THROW_PEAK = 0.25;
     private final double STONE_THROW_END = 0.5;
-    private final double CASTLE_DESTRUCTION_START = 0.5;
-    private final double CASTLE_DESTRUCTION_END = 1.0;
     private final double TROLL_MOVE_START = 0.5;
     private final double TROLL_MOVE_END = 1.0;
     private final double TROLL_MESSAGE_START = 0.5;
     private final double TROLL_MESSAGE_END = 1.0;
-    private final double ENDGAME_MESSAGE_START = 0.25;
+
+    /*
+     * Castle destruction and endgame message pertain to an endgame
+     * frame only.
+     */
+    private final double AVATAR_ANIMATION_START = 0.5;
+    private final double CASTLE_DESTRUCTION_START = 0.0;
+    private final double CASTLE_DESTRUCTION_END = 0.5;
 
     class Player {
         Model.Player model;
@@ -158,6 +155,7 @@ class View {
 
         void victory() {
             gameManager.addToGameSummary(GameManager.formatSuccessMessage(nicknameToken + " wins."));
+            View.this.endgameFrame();
             markWinner();
         }
 
@@ -221,6 +219,7 @@ class View {
             else {
                 stoneCounter.setText(stones + " stones");
             }
+            graphicEntityModule.commitEntityState(STONE_THROW_PEAK, stoneCounter);
         }
 
         void animateStones(int stones) {
@@ -303,13 +302,19 @@ class View {
         animateTurnCounter();
     }
 
+    void endgameFrame() {
+        gameManager.setFrameDuration(2000);
+    }
+
     void doubleDefeat() {
         gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
+        endgameFrame();
         animateLoss(1920/2, 680, 150, "L0SERZ!");
     }
 
     void draw() {
         gameManager.addToGameSummary("Draw.");
+        endgameFrame();
         animateLoss(1920/2, 680, 200, "DRAW");
     }
 
@@ -513,7 +518,7 @@ class View {
         pantsModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
     }
 
-    private void moveTroll() {
+    void moveTroll() {
         graphicEntityModule.commitEntityState(TROLL_MOVE_START, troll, trollPositionGauge);
         int x0 = p0.castle.getX(), x1 = p1.castle.getX();
         int y0 = p0.castle.getY(), y1 = p1.castle.getY();
@@ -641,7 +646,7 @@ class View {
             .setFillColor(0xff7f7f)
             .setFontWeight(Text.FontWeight.BOLD)
             .setTextAlign(TextBasedEntity.TextAlign.CENTER);
-        graphicEntityModule.commitEntityState(ENDGAME_MESSAGE_START, msg);
+        graphicEntityModule.commitEntityState(0.0, msg);
         Curve curve = Curve.ELASTIC;
         msg.setX(x, Curve.EASE_OUT)
             .setY(y, Curve.ELASTIC)
index cbb15e8..1488dee 100644 (file)
@@ -12,7 +12,7 @@ public class Main {
         gameRunner.setGameParameters(props);
 
         gameRunner.addAgent(Player1.class);
-        gameRunner.addAgent(PlayerCheatSmart.class);
+        gameRunner.addAgent(PlayerRand.class);
         
         // gameRunner.addAgent("python3 /home/user/player.py");