Rollback last (bad) improving attempt
[vchess.git] / client / src / views / Game.vue
index 4afac12..1071ece 100644 (file)
@@ -7,7 +7,7 @@ main
   )
     .card
       label.modal-close(for="modalRules")
-      a#variantNameInGame(:href="'/#/variants/'+game.vname") {{ game.vname }}
+      a#variantNameInGame(:href="'/#/variants/'+game.vname") {{ game.vdisp }}
       div(v-html="rulesContent")
   input#modalScore.modal(type="checkbox")
   div#scoreDiv(
@@ -78,7 +78,9 @@ main
   .row
     #aboveBoard.col-sm-12
       span.variant-cadence(v-if="game.type!='import'") {{ game.cadence }}
-      span.variant-name {{ game.vname }}
+      span.variant-name
+        | {{ game.vname }}
+        | {{ !!vr ? vr.constructor.AbbreviateOptions(game.options) : '' }}
       span#nextGame(
         v-if="nextIds.length > 0"
         @click="showNextGame()"
@@ -196,6 +198,7 @@ import { getDiagram, replaceByDiag } from "@/utils/printDiagram";
 import { processModalClick } from "@/utils/modalClick";
 import { playMove, getFilteredMove } from "@/utils/playUndo";
 import { ArrayFun } from "@/utils/array";
+import afterRawLoad from "@/utils/afterRawLoad";
 import params from "@/parameters";
 export default {
   name: "my-game",
@@ -213,6 +216,7 @@ export default {
       game: {}, //passed to BaseGame
       focus: !document.hidden, //will not always work... TODO
       // virtualClocks will be initialized from true game.clocks
+      // TODO: clock update triggers re-rendering. Should be out of Vue
       virtualClocks: [],
       vr: null, //"variant rules" object initialized from FEN
       rulesContent: "",
@@ -329,6 +333,29 @@ export default {
         )
       );
     },
+    requestLastate: function(sid) {
+      // TODO: maybe also find opponent SID ?
+      //const oppSid =
+      //  this.game.players.find(p => p.sid != this.st.user.sid).sid;
+      this.send("asklastate", { target: sid });
+      let counter = 1;
+      this.askLastate = setInterval(
+        () => {
+          // Ask at most 3 times:
+          // if no reply after that there should be a network issue.
+          if (
+            counter < 3 &&
+            !this.gotLastate &&
+            !!this.people[sid]
+          ) {
+            this.send("asklastate", { target: sid });
+            counter++;
+          }
+          else clearInterval(this.askLastate);
+        },
+        1500
+      );
+    },
     atCreation: function() {
       document.addEventListener('visibilitychange', this.visibilityChange);
       window.addEventListener('focus', this.onFocus);
@@ -347,7 +374,7 @@ export default {
           id: my.id,
           name: my.name,
           tmpIds: {
-            tmpId: { focus: true }
+            [tmpId]: { focus: true }
           }
         }
       );
@@ -391,6 +418,8 @@ export default {
               "message", this.socketMessageListener);
             this.conn = new WebSocket(this.connexionString);
             this.conn.addEventListener("message", this.socketMessageListener);
+            const oppSid = this.getOppsid();
+            if (!!oppSid) this.requestLastate(oppSid); //in case of
           }
         },
         1000
@@ -406,8 +435,15 @@ export default {
           this.conn.onopen = () => callback();
       };
       this.fetchGame((game) => {
-        if (!!game)
+        if (!!game) {
+          if (!game.options) {
+            // Patch for retro-compatibility (TODO: remove it)
+            game.options = { randomness: game.randomness };
+            delete game["randomness"];
+          }
+          else game.options = JSON.parse(game.options);
           this.loadVariantThenGame(game, () => socketInit(this.roomInit));
+        }
         else
           // Live game stored remotely: need socket to retrieve it
           // NOTE: the callback "roomInit" will be lost, so it's not provided.
@@ -512,7 +548,8 @@ export default {
             "DELETE",
             { data: { gid: this.game.id } }
           );
-        } else {
+        }
+        else {
           // Live game
           GameStorage.update(this.gameRef, { delchat: true });
         }
@@ -521,7 +558,7 @@ export default {
     },
     getGameType: function(game) {
       if (!!game.id.toString().match(/^i/)) return "import";
-      return game.cadence.indexOf("d") >= 0 ? "corr" : "live";
+      return (game.cadence.indexOf("d") >= 0 ? "corr" : "live");
     },
     // Notify something after a new move (to opponent and me on MyGames page)
     notifyMyGames: function(thing, data) {
@@ -576,7 +613,8 @@ export default {
             // For self multi-connects tests:
             this.newConnect[data.from[0]] = true;
             this.send("askidentity", { target: data.from[0] });
-          } else {
+          }
+          else {
             this.people[data.from[0]].tmpIds[data.from[1]] = { focus: true };
             this.$forceUpdate(); //TODO: shouldn't be required
           }
@@ -659,25 +697,7 @@ export default {
             this.game.type == "live" &&
             this.game.players.some(p => p.sid == user.sid)
           ) {
-            this.send("asklastate", { target: user.sid });
-            let counter = 1;
-            this.askLastate = setInterval(
-              () => {
-                // Ask at most 3 times:
-                // if no reply after that there should be a network issue.
-                if (
-                  counter < 3 &&
-                  !this.gotLastate &&
-                  !!this.people[user.sid]
-                ) {
-                  this.send("asklastate", { target: user.sid });
-                  counter++;
-                } else {
-                  clearInterval(this.askLastate);
-                }
-              },
-              1500
-            );
+            this.requestLastate(user.sid);
           }
           break;
         }
@@ -704,7 +724,7 @@ export default {
           const gameToSend = Object.keys(this.game)
             .filter(k =>
               [
-                "id","fen","players","vid","cadence","fenStart","vname",
+                "id","fen","players","vid","cadence","fenStart","options",
                 "moves","clocks","score","drawOffer","rematchOffer"
               ].includes(k))
             .reduce(
@@ -750,9 +770,9 @@ export default {
         case "newmove": {
 
 // DEBUG:
-console.log("Receive move");
-console.log(data.data);
-//moveslist not updated when receiving a move? (see in baseGame)
+//console.log("Receive move");
+//console.log(data.data);
+//moveslist not updated when receiving a move? (see in BaseGame)
 
           const movePlus = data.data;
           const movesCount = this.game.moves.length;
@@ -872,13 +892,15 @@ console.log(data.data);
             gameInfo.players.some(p => p.sid == this.st.user.sid)
           ) {
             this.addAndGotoLiveGame(gameInfo);
-          } else if (
+          }
+          else if (
             gameType == "corr" &&
             this.st.user.id > 0 &&
             gameInfo.players.some(p => p.id == this.st.user.id)
           ) {
             this.$router.push("/game/" + gameInfo.id);
-          } else {
+          }
+          else {
             this.rematchId = gameInfo.id;
             document.getElementById("modalRules").checked = false;
             document.getElementById("modalScore").checked = false;
@@ -970,7 +992,8 @@ console.log(data.data);
           // Just got last move from him
           this.$refs["basegame"].play(data.lastMove, "received");
           this.processMove(data.lastMove);
-        } else {
+        }
+        else {
           if (!!this.clockUpdate) clearInterval(this.clockUpdate);
           this.re_setClocks();
         }
@@ -992,7 +1015,8 @@ console.log(data.data);
             : "Three repetitions";
         this.send("draw", { data: message });
         this.gameOver("1/2", message);
-      } else if (this.drawOffer == "") {
+      }
+      else if (this.drawOffer == "") {
         // No effect if drawOffer == "sent"
         if (this.game.mycolor != this.vr.turn) {
           alert(this.st.tr["Draw offer only in your turn"]);
@@ -1006,7 +1030,8 @@ console.log(data.data);
             this.gameRef,
             { drawOffer: this.game.mycolor }
           );
-        } else this.updateCorrGame({ drawOffer: this.game.mycolor });
+        }
+        else this.updateCorrGame({ drawOffer: this.game.mycolor });
       }
     },
     addAndGotoLiveGame: function(gameInfo, callback) {
@@ -1016,7 +1041,6 @@ console.log(data.data);
         {
           // (other) Game infos: constant
           fenStart: gameInfo.fen,
-          vname: this.game.vname,
           created: Date.now(),
           // Game state (including FEN): will be updated
           moves: [],
@@ -1041,39 +1065,51 @@ console.log(data.data);
         // Start a new game!
         let gameInfo = {
           id: getRandString(), //ignored if corr
-          fen: V.GenRandInitFen(this.game.randomness),
+          fen: V.GenRandInitFen(this.game.options),
+          options: JSON.stringify(this.game.options),
           players: [this.game.players[1], this.game.players[0]],
           vid: this.game.vid,
           cadence: this.game.cadence
         };
         const notifyNewGame = () => {
-          const oppsid = this.getOppsid(); //may be null
-          this.send("rnewgame", { data: gameInfo, oppsid: oppsid });
+          this.send("rnewgame", { data: gameInfo });
           // To main Hall if corr game:
           if (this.game.type == "corr")
             this.send("newgame", { data: gameInfo, page: "/" });
           // Also to MyGames page:
           this.notifyMyGames("newgame", gameInfo);
         };
-        if (this.game.type == "live")
+        if (this.game.type == "live") {
+          GameStorage.update(
+            this.gameRef,
+            { rematchOffer: "" }
+          );
+          // Increment game stats counter in DB
+          ajax(
+            "/gamestat",
+            "POST",
+            { data: { vid: gameInfo.vid } }
+          );
           this.addAndGotoLiveGame(gameInfo, notifyNewGame);
+        }
         else {
           // corr game
+          this.updateCorrGame({ rematchOffer: 'n' });
           ajax(
             "/games",
             "POST",
             {
-              // cid is useful to delete the challenge:
               data: { gameInfo: gameInfo },
               success: (response) => {
-                gameInfo.id = response.gameId;
+                gameInfo.id = response.id;
                 notifyNewGame();
-                this.$router.push("/game/" + response.gameId);
+                this.$router.push("/game/" + response.id);
               }
             }
           );
         }
-      } else if (this.rematchOffer == "") {
+      }
+      else if (this.rematchOffer == "") {
         this.rematchOffer = "sent";
         this.send("rematchoffer", { data: true });
         if (this.game.type == "live") {
@@ -1081,8 +1117,10 @@ console.log(data.data);
             this.gameRef,
             { rematchOffer: this.game.mycolor }
           );
-        } else this.updateCorrGame({ rematchOffer: this.game.mycolor });
-      } else if (this.rematchOffer == "sent") {
+        }
+        else this.updateCorrGame({ rematchOffer: this.game.mycolor });
+      }
+      else if (this.rematchOffer == "sent") {
         // Toggle rematch offer (on --> off)
         this.rematchOffer = "";
         this.send("rematchoffer", { data: false });
@@ -1091,7 +1129,8 @@ console.log(data.data);
             this.gameRef,
             { rematchOffer: '' }
           );
-        } else this.updateCorrGame({ rematchOffer: 'n' });
+        }
+        else this.updateCorrGame({ rematchOffer: 'n' });
       }
     },
     abortGame: function() {
@@ -1160,11 +1199,10 @@ console.log(data.data);
               { clocks: game.clocks }
             );
           }
-        } else {
-          if (!!game.initime)
-            // It's my turn: clocks not updated yet
-            game.clocks[myIdx] -= (Date.now() - game.initime) / 1000;
         }
+        else if (!!game.initime)
+          // It's my turn: clocks not updated yet
+          game.clocks[myIdx] -= (Date.now() - game.initime) / 1000;
       }
       else
         // gtype == "import"
@@ -1260,31 +1298,45 @@ console.log(data.data);
       if (!!callback) callback();
     },
     loadVariantThenGame: async function(game, callback) {
-      await import("@/variants/" + game.vname + ".js")
-      .then((vModule) => {
-        window.V = vModule[game.vname + "Rules"];
-        this.loadGame(game, callback);
-      });
-      // (AJAX) Request to get rules content (plain text, HTML)
-      this.rulesContent =
-        require(
-          "raw-loader!@/translations/rules/" +
-          game.vname + "/" +
-          this.st.lang + ".pug"
-        )
-        // Next two lines fix a weird issue after last update (2019-11)
-        .replace(/\\n/g, " ")
-        .replace(/\\"/g, '"')
-        .replace('module.exports = "', "")
-        .replace(/"$/, "")
-        .replace(/(fen:)([^:]*):/g, replaceByDiag);
+      const afterSetVname = async () => {
+        await import("@/variants/" + game.vname + ".js")
+        .then((vModule) => {
+          window.V = vModule[game.vname + "Rules"];
+          this.loadGame(game, callback);
+        });
+        this.rulesContent =
+          afterRawLoad(
+            require(
+              "raw-loader!@/translations/rules/" +
+              game.vname + "/" + this.st.lang + ".pug"
+            ).default
+          ).replace(/(fen:)([^:]*):/g, replaceByDiag);
+      };
+      let variant = undefined;
+      const trySetVname = setInterval(
+        () => {
+          // this.st.variants might be uninitialized (variant == null)
+          variant = this.st.variants.find(v => {
+            return v.id == game.vid || v.name == game.vname
+          });
+          if (!!variant) {
+            clearInterval(trySetVname);
+            game.vname = variant.name;
+            game.vdisp = variant.display;
+            afterSetVname();
+          }
+        }, 500
+      );
     },
     // 3 cases for loading a game:
     //  - from indexedDB (running or completed live game I play)
     //  - from server (one correspondance game I play[ed] or not)
     //  - from remote peer (one live game I don't play, finished or not)
     fetchGame: function(callback) {
-      if (Number.isInteger(this.gameRef) || !isNaN(parseInt(this.gameRef))) {
+      if (
+        Number.isInteger(this.gameRef) ||
+        !isNaN(parseInt(this.gameRef, 10))
+      ) {
         // corr games identifiers are integers
         ajax(
           "/games",
@@ -1330,7 +1382,8 @@ console.log(data.data);
                 currentTurn == "w" ? "0-1" : "1-0",
                 "Time"
               );
-          } else {
+          }
+          else {
             this.$set(
               this.virtualClocks,
               colorIdx,
@@ -1386,14 +1439,20 @@ console.log(data.data);
           // In corr games, just reset clock to mainTime:
           this.game.clocks[colorIdx] = extractTime(this.game.cadence).mainTime;
         }
-        // If repetition detected, consider that a draw offer was received:
-        const fenObj = this.vr.getFenForRepeat();
-        this.repeat[fenObj] =
-          !!this.repeat[fenObj]
-            ? this.repeat[fenObj] + 1
-            : 1;
-        if (this.repeat[fenObj] >= 3) this.drawOffer = "threerep";
-        else if (this.drawOffer == "threerep") this.drawOffer = "";
+        if (!V.IgnoreRepetition) {
+          // If repetition detected, consider that a draw offer was received:
+          const fenObj = this.vr.getFenForRepeat();
+          this.repeat[fenObj] =
+            !!this.repeat[fenObj]
+              ? this.repeat[fenObj] + 1
+              : 1;
+          if (this.repeat[fenObj] >= 3) {
+            if (this.vr.loseOnRepetition())
+              this.gameOver(moveCol == "w" ? "0-1" : "1-0", "Repetition");
+            else this.drawOffer = "threerep";
+          }
+          else if (this.drawOffer == "threerep") this.drawOffer = "";
+        }
         if (!!this.game.mycolor && !data.receiveMyMove) {
           // NOTE: 'var' to see that variable outside this block
           var filtered_move = getFilteredMove(move);
@@ -1516,7 +1575,6 @@ console.log(data.data);
       };
       if (
         this.game.type == "corr" &&
-        V.CorrConfirm &&
         moveCol == this.game.mycolor &&
         !data.receiveMyMove
       ) {
@@ -1532,6 +1590,10 @@ console.log(data.data);
             if (data.score == "*") this.re_setClocks();
           }
         };
+        if (!V.CorrConfirm) {
+          afterSetScore();
+          return;
+        }
         let el = document.querySelector("#buttonsConfirm > .acceptBtn");
         // We may play several moves in a row: in case of, remove listener:
         let elClone = el.cloneNode(true);
@@ -1556,7 +1618,9 @@ console.log(data.data);
         if (["all","byrow"].includes(V.ShowMoves)) {
           this.curDiag = getDiagram({
             position: position,
-            orientation: V.CanFlip ? this.game.mycolor : "w"
+            orientation: V.CanFlip ? this.game.mycolor : "w",
+            color: this.game.mycolor,
+            score: "*"
           });
           document.querySelector("#confirmDiv > .card").style.width =
             boardDiv.offsetWidth + "px";
@@ -1586,7 +1650,7 @@ console.log(data.data);
     // In corr games, callback to change page only after score is set:
     gameOver: function(score, scoreMsg, callback) {
       this.game.score = score;
-      if (!scoreMsg) scoreMsg = getScoreMessage(score);
+      if (!scoreMsg) scoreMsg = getScoreMessage(score, V.ReverseColors);
       this.game.scoreMsg = scoreMsg;
       document.getElementById("modalRules").checked = false;
       // Display result in a un-missable way: