Improve style, implement 'next' for corr games. TODO: rematch logic
[vchess.git] / client / src / components / BaseGame.vue
index 488567b..ac8b323 100644 (file)
@@ -8,21 +8,6 @@ div#baseGame
     .card.text-center
       label.modal-close(for="modalEog")
       h3.section {{ endgameMessage }}
-  input#modalAdjust.modal(type="checkbox")
-  div#adjuster(
-    role="dialog"
-    data-checkbox="modalAdjust"
-  )
-    .card.text-center
-      label.modal-close(for="modalAdjust")
-      label(for="boardSize") {{ st.tr["Board size"] }}
-      input#boardSize.slider(
-        type="range"
-        min="0"
-        max="100"
-        value="50"
-        @input="adjustBoard()"
-      )
   #gameContainer
     #boardContainer
       Board(
@@ -38,36 +23,29 @@ div#baseGame
       )
       #turnIndicator(v-if="showTurn") {{ turn }}
       #controls
-        button(@click="gotoBegin()") <<
-        button(@click="undo()") <
-        button(v-if="canFlip" @click="flip()") &#8645;
-        button(@click="play()") >
-        button(@click="gotoEnd()") >>
-      #belowControls
-        #downloadDiv(v-if="allowDownloadPGN")
-          a#download(href="#")
-          button(@click="download()") {{ st.tr["Download"] }} PGN
-        button(onClick="window.doClick('modalAdjust')") &#10530;
-        button(
-          v-if="canAnalyze"
-          @click="analyzePosition()"
-        )
-          | {{ st.tr["Analyse"] }}
-        // NOTE: variants pages already have a "Rules" link on top
-        button(
-          v-if="!$route.path.match('/variants/')"
-          @click="showRules()"
-        )
-          | {{ st.tr["Rules"] }}
+        button(@click="gotoBegin()")
+          img.inline(src="/images/icons/fast-forward_rev.svg")
+        button(@click="undo()")
+          img.inline(src="/images/icons/play_rev.svg")
+        button(v-if="canFlip" @click="flip()")
+          img.inline(src="/images/icons/flip.svg")
+        button(@click="play()")
+          img.inline(src="/images/icons/play.svg")
+        button(@click="gotoEnd()")
+          img.inline(src="/images/icons/fast-forward.svg")
     #movesList
       MoveList(
-        v-if="showMoves != 'none'"
         :show="showMoves"
+        :canAnalyze="canAnalyze"
+        :canDownload="allowDownloadPGN"
         :score="game.score"
         :message="game.scoreMsg"
         :firstNum="firstMoveNumber"
         :moves="moves"
         :cursor="cursor"
+        @download="download"
+        @showrules="showRules"
+        @analyze="analyzePosition"
         @goto-move="gotoMove"
       )
     .clearer
@@ -99,12 +77,13 @@ export default {
       orientation: "w",
       score: "*", //'*' means 'unfinished'
       moves: [],
-      // TODO: later, use subCursor to navigate intra-multimoves?
       cursor: -1, //index of the move just played
       lastMove: null,
       firstMoveNumber: 0, //for printing
       incheck: [], //for Board
-      inMultimove: false
+      inMultimove: false,
+      inPlay: false,
+      stackToPlay: []
     };
   },
   watch: {
@@ -158,55 +137,14 @@ export default {
       baseGameDiv.addEventListener("keydown", this.handleKeys);
       baseGameDiv.addEventListener("wheel", this.handleScroll);
     }
-    [
-      document.getElementById("eogDiv"),
-      document.getElementById("adjuster")
-    ].forEach(elt => elt.addEventListener("click", processModalClick));
-    // Take full width on small screens:
-    let boardSize = parseInt(localStorage.getItem("boardSize"));
-    if (!boardSize) {
-      boardSize =
-        window.innerWidth >= 768
-          ? 0.75 * Math.min(window.innerWidth, window.innerHeight)
-          : window.innerWidth;
-    }
-    const movesWidth = window.innerWidth >= 768 ? 280 : 0;
-    document.getElementById("boardContainer").style.width = boardSize + "px";
-    let gameContainer = document.getElementById("gameContainer");
-    gameContainer.style.width = boardSize + movesWidth + "px";
-    document.getElementById("boardSize").value =
-      (boardSize * 100) / (window.innerWidth - movesWidth);
-    // timeout to avoid calling too many time the adjust method
-    let timeoutLaunched = false;
-    window.addEventListener("resize", () => {
-      if (!timeoutLaunched) {
-        timeoutLaunched = true;
-        setTimeout(() => {
-          this.adjustBoard();
-          timeoutLaunched = false;
-        }, 500);
-      }
-    });
+    document.getElementById("eogDiv").addEventListener(
+      "click",
+      processModalClick);
   },
   methods: {
     focusBg: function() {
       document.getElementById("baseGame").focus();
     },
-    adjustBoard: function() {
-      const boardContainer = document.getElementById("boardContainer");
-      if (!boardContainer) return; //no board on page
-      const k = document.getElementById("boardSize").value;
-      const movesWidth = window.innerWidth >= 768 ? 280 : 0;
-      const minBoardWidth = 240; //TODO: these 240 and 280 are arbitrary...
-      // Value of 0 is board min size; 100 is window.width [- movesWidth]
-      const boardSize =
-        minBoardWidth +
-        (k * (window.innerWidth - (movesWidth + minBoardWidth))) / 100;
-      localStorage.setItem("boardSize", boardSize);
-      boardContainer.style.width = boardSize + "px";
-      document.getElementById("gameContainer").style.width =
-        boardSize + movesWidth + "px";
-    },
     handleKeys: function(e) {
       if ([32, 37, 38, 39, 40].includes(e.keyCode)) e.preventDefault();
       switch (e.keyCode) {
@@ -255,9 +193,10 @@ export default {
         });
       });
       if (firstMoveColor == "b") {
-        // 'end' is required for Board component to check lastMove for e.p.
+        // 'start' & 'end' is required for Board component
         this.moves.unshift({
           notation: "...",
+          start: { x: -1, y: -1 },
           end: { x: -1, y: -1 }
         });
       }
@@ -279,11 +218,13 @@ export default {
         this.lastMove = null;
     },
     analyzePosition: function() {
-      const newUrl =
+      let newUrl =
         "/analyse/" +
         this.game.vname +
         "/?fen=" +
         this.vr.getFen().replace(/ /g, "_");
+      if (this.game.mycolor)
+        newUrl += "&side=" + this.game.mycolor;
       // Open in same tab in live games (against cheating)
       if (this.game.type == "live") this.$router.push(newUrl);
       else window.open("#" + newUrl);
@@ -324,6 +265,7 @@ export default {
     // Animate an elementary move
     animateMove: function(move, callback) {
       let startSquare = document.getElementById(getSquareId(move.start));
+      if (!startSquare) return; //shouldn't happen but...
       let endSquare = document.getElementById(getSquareId(move.end));
       let rectStart = startSquare.getBoundingClientRect();
       let rectEnd = endSquare.getBoundingClientRect();
@@ -334,6 +276,9 @@ export default {
       let movingPiece = document.querySelector(
         "#" + getSquareId(move.start) + " > img.piece"
       );
+      // For some unknown reasons Opera get "movingPiece == null" error
+      // TOOO: is it calling 'animate()' twice ? One extra time ?
+      if (!movingPiece) return;
       // HACK for animation (with positive translate, image slides "under background")
       // Possible improvement: just alter squares on the piece's way...
       const squares = document.getElementsByClassName("board");
@@ -352,9 +297,25 @@ export default {
         callback();
       }, 250);
     },
+    // For Analyse mode:
+    emitFenIfAnalyze: function() {
+      if (this.game.mode == "analyze") {
+        this.$emit(
+          "fenchange",
+          this.lastMove ? this.lastMove.fen : this.game.fenStart
+        );
+      }
+    },
     // "light": if gotoMove() or gotoEnd()
-    // data: some custom data (addTime) to be re-emitted
-    play: function(move, received, light, data) {
+    play: function(move, received, light, noemit) {
+      if (!!noemit) {
+        if (this.inPlay) {
+          // Received moves in observed games can arrive too fast:
+          this.stackToPlay.unshift(move);
+          return;
+        }
+        this.inPlay = true;
+      }
       const navigate = !move;
       const playSubmove = (smove) => {
         if (!navigate) smove.notation = this.vr.getNotation(smove);
@@ -365,7 +326,7 @@ export default {
         if (!navigate) {
           if (!this.inMultimove) {
             if (this.cursor < this.moves.length - 1)
-              this.moves = this.moves.slice(0, Math.max(this.cursor, 0));
+              this.moves = this.moves.slice(0, this.cursor + 1);
             this.moves.push(smove);
             this.inMultimove = true; //potentially
             this.cursor++;
@@ -380,7 +341,7 @@ export default {
         }
       };
       const playMove = () => {
-        const animate = V.ShowMoves == "all" && received;
+        const animate = V.ShowMoves == "all" && (received || navigate);
         if (!Array.isArray(move)) move = [move];
         let moveIdx = 0;
         let self = this;
@@ -402,23 +363,34 @@ export default {
         })();
       };
       const afterMove = (smove, initurn) => {
-        if (this.st.settings.sound == 2)
-          new Audio("/sounds/move.mp3").play().catch(() => {});
         if (this.vr.turn != initurn) {
           // Turn has changed: move is complete
+          if (!smove.fen) {
+            // NOTE: only FEN of last sub-move is required (thus setting it here)
+            smove.fen = this.vr.getFen();
+            this.emitFenIfAnalyze();
+          }
           this.inMultimove = false;
           const score = this.vr.getCurrentScore();
           if (score != "*") {
             const message = getScoreMessage(score);
             if (!navigate && this.game.mode != "analyze")
               this.$emit("gameover", score, message);
-            // Just show score on screen (allow undo)
-            else this.showEndgameMsg(score + " . " + message);
+            else if (this.game.mode == "analyze")
+              // Just show score on screen (allow undo)
+              this.showEndgameMsg(score + " . " + this.st.tr[message]);
           }
           if (!navigate && this.game.mode != "analyze") {
             const L = this.moves.length;
-            // Post-processing (e.g. computer play)
-            this.$emit("newmove", this.moves[L-1], data);
+            if (!noemit)
+              // Post-processing (e.g. computer play)
+              this.$emit("newmove", this.moves[L-1]);
+            else {
+              this.inPlay = false;
+              if (this.stackToPlay.length > 0)
+                // Move(s) arrived in-between
+                this.play(this.stackToPlay.pop(), received, light, noemit);
+            }
           }
         }
       };
@@ -432,7 +404,10 @@ export default {
           if (!Array.isArray(move)) move = [move];
           for (let i=0; i < move.length; i++) this.vr.play(move[i]);
         }
-        else playMove();
+        else {
+          playMove();
+          this.emitFenIfAnalyze();
+        }
         this.cursor++;
         return;
       }
@@ -451,7 +426,6 @@ export default {
       playMove();
     },
     cancelCurrentMultimove: function() {
-      // Cancel current multi-move
       const L = this.moves.length;
       let move = this.moves[L-1];
       if (!Array.isArray(move)) move = [move];
@@ -480,9 +454,8 @@ export default {
         if (light) this.cursor--;
         else {
           this.positionCursorTo(this.cursor - 1);
-          if (this.st.settings.sound == 2)
-            new Audio("/sounds/undo.mp3").play().catch(() => {});
           this.incheck = this.vr.getCheckSquares(this.vr.turn);
+          this.emitFenIfAnalyze();
         }
       }
     },
@@ -501,6 +474,7 @@ export default {
       // NOTE: next line also re-assign cursor, but it's very light
       this.positionCursorTo(index);
       this.incheck = this.vr.getCheckSquares(this.vr.turn);
+      this.emitFenIfAnalyze();
     },
     gotoBegin: function() {
       if (this.inMultimove) this.cancelCurrentMultimove();
@@ -513,10 +487,12 @@ export default {
         this.lastMove = null;
       }
       this.incheck = [];
+      this.emitFenIfAnalyze();
     },
     gotoEnd: function() {
       if (this.cursor == this.moves.length - 1) return;
       this.gotoMove(this.moves.length - 1);
+      this.emitFenIfAnalyze();
     },
     flip: function() {
       this.orientation = V.GetOppCol(this.orientation);
@@ -529,9 +505,6 @@ export default {
 [type="checkbox"]#modalEog+div .card
   min-height: 45px
 
-[type="checkbox"]#modalAdjust+div .card
-  padding: 5px
-
 #baseGame
   width: 100%
   &:focus
@@ -547,27 +520,24 @@ export default {
 #controls
   margin: 0 auto
   text-align: center
+  display: flex
   button
     display: inline-block
     width: 20%
     margin: 0
+    padding-top: 5px
+    padding-bottom: 5px
+
+img.inline
+  height: 24px
+  padding-top: 5px
+  @media screen and (max-width: 767px)
+    height: 18px
 
 #turnIndicator
   text-align: center
   font-weight: bold
 
-#belowControls
-  border-top: 1px solid #2f4f4f
-  text-align: center
-  margin: 0 auto
-  & > #downloadDiv
-    margin: 0
-    & > button
-      margin: 0
-  & > button
-    border-left: 1px solid #2f4f4f
-    margin: 0
-
 #boardContainer
   float: left
 // TODO: later, maybe, allow movesList of variable width