Finish Pacosako + add GameStat table to know how many live games are played
[vchess.git] / client / src / views / Game.vue
index 1753df0..ef3819b 100644 (file)
@@ -46,7 +46,7 @@ main
         span {{ st.tr["Participant(s):"] }} 
         span(
           v-for="p in Object.values(people)"
-          v-if="participateInChat(p)"
+          v-if="!!p.name"
         )
           | {{ p.name }} 
         span.anonymous(v-if="someAnonymousPresent()") + @nonymous
@@ -120,8 +120,11 @@ main
         img(src="/images/icons/rematch.svg")
       #playersInfo
         div(v-if="isLargeScreen()")
-          span.name(:class="{connected: isConnected(0)}")
-            | {{ game.players[0].name || "@nonymous" }}
+          UserBio.user-bio(
+            :class="{connected: isConnected(0)}"
+            :uid="game.players[0].id"
+            :uname="game.players[0].name"
+          )
           span.time(
             v-if="game.score=='*'"
             :class="{yourturn: !!vr && vr.turn == 'w'}"
@@ -131,8 +134,11 @@ main
             span.time-right(v-if="!!virtualClocks[0][1]")
               | {{ virtualClocks[0][1] }}
           span.split-names -
-          span.name(:class="{connected: isConnected(1)}")
-            | {{ game.players[1].name || "@nonymous" }}
+          UserBio.user-bio(
+            :class="{connected: isConnected(1)}"
+            :uid="game.players[1].id"
+            :uname="game.players[1].name"
+          )
           span.time(
             v-if="game.score=='*'"
             :class="{yourturn: !!vr && vr.turn == 'b'}"
@@ -142,11 +148,17 @@ main
             span.time-right(v-if="!!virtualClocks[1][1]")
               | {{ virtualClocks[1][1] }}
         div(v-else)
-          span.name(:class="{connected: isConnected(0)}")
-            | {{ game.players[0].name || "@nonymous" }}
+          UserBio.user-bio(
+            :class="{connected: isConnected(0)}"
+            :uid="game.players[0].id"
+            :uname="game.players[0].name"
+          )
           span.split-names -
-          span.name(:class="{connected: isConnected(1)}")
-            | {{ game.players[1].name || "@nonymous" }}
+          UserBio.user-bio(
+            :class="{connected: isConnected(1)}"
+            :uid="game.players[1].id"
+            :uname="game.players[1].name"
+          )
           div(v-if="game.score=='*'")
             span.time(:class="{yourturn: !!vr && vr.turn == 'w'}")
               span.time-left {{ virtualClocks[0][0] }}
@@ -168,6 +180,7 @@ main
 
 <script>
 import BaseGame from "@/components/BaseGame.vue";
+import UserBio from "@/components/UserBio.vue";
 import Chat from "@/components/Chat.vue";
 import { store } from "@/store";
 import { GameStorage } from "@/utils/gameStorage";
@@ -183,12 +196,14 @@ 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",
   components: {
     BaseGame,
-    Chat
+    Chat,
+    UserBio
   },
   data: function() {
     return {
@@ -199,6 +214,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: "",
@@ -212,9 +228,6 @@ export default {
       curDiag: "", //for corr moves confirmation
       conn: null,
       roomInitialized: false,
-      // If newmove has wrong index: ask fullgame again:
-      askGameTime: 0,
-      gameIsLoading: false,
       // If asklastate got no reply, ask again:
       gotLastate: false,
       gotMoveIdx: -1, //last move index received
@@ -245,7 +258,8 @@ export default {
           // In case of incomplete information variant:
           boardDiv.style.visibility = "hidden";
         this.atCreation();
-      } else
+      }
+      else
         // Same game ID
         this.nextIds = JSON.parse(this.$route.query["next"] || "[]");
     }
@@ -310,9 +324,6 @@ export default {
         )
       );
     },
-    participateInChat: function(p) {
-      return Object.keys(p.tmpIds).some(x => p.tmpIds[x].focus) && !!p.name;
-    },
     someAnonymousPresent: function() {
       return (
         Object.values(this.people).some(p =>
@@ -357,8 +368,6 @@ export default {
       this.rematchOffer = "";
       this.lastate = undefined;
       this.roomInitialized = false;
-      this.askGameTime = 0;
-      this.gameIsLoading = false;
       this.gotLastate = false;
       this.gotMoveIdx = -1;
       this.opponentGotMove = false;
@@ -505,7 +514,8 @@ export default {
             "DELETE",
             { data: { gid: this.game.id } }
           );
-        } else {
+        }
+        else {
           // Live game
           GameStorage.update(this.gameRef, { delchat: true });
         }
@@ -514,7 +524,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) {
@@ -536,27 +546,6 @@ export default {
       this.$router.push(
         "/game/" + nextGid + "/?next=" + JSON.stringify(this.nextIds));
     },
-    askGameAgain: function() {
-      this.gameIsLoading = true;
-      const currentUrl = document.location.href;
-      const doAskGame = () => {
-        if (document.location.href != currentUrl) return; //page change
-        this.fetchGame((game) => {
-          if (!!game)
-            // This is my game: just reload.
-            this.loadGame(game);
-          else
-            // Just ask fullgame again (once!), this is much simpler.
-            // If this fails, the user could just reload page :/
-            this.send("askfullgame");
-        });
-      };
-      // Delay of at least 2s between two game requests
-      const now = Date.now();
-      const delay = Math.max(2000 - (now - this.askGameTime), 0);
-      this.askGameTime = now;
-      setTimeout(doAskGame, delay);
-    },
     socketMessageListener: function(msg) {
       if (!this.conn) return;
       const data = JSON.parse(msg.data);
@@ -590,7 +579,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
           }
@@ -686,9 +676,8 @@ export default {
                 ) {
                   this.send("asklastate", { target: user.sid });
                   counter++;
-                } else {
-                  clearInterval(this.askLastate);
                 }
+                else clearInterval(this.askLastate);
               },
               1500
             );
@@ -753,6 +742,8 @@ export default {
           // Got opponent infos about last move
           this.gotLastate = true;
           this.lastate = data.data;
+          if (this.lastate.movesCount - 1 > this.gotMoveIdx)
+            this.gotMoveIdx = this.lastate.movesCount - 1;
           if (this.game.rendered)
             // Game is rendered (Board component)
             this.processLastate();
@@ -760,78 +751,77 @@ export default {
           break;
         }
         case "newmove": {
+
+// DEBUG:
+//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;
-          if (movePlus.index > movesCount) {
-            // This can only happen if I'm an observer and missed a move.
-            if (this.gotMoveIdx < movePlus.index)
-              this.gotMoveIdx = movePlus.index;
-            if (!this.gameIsLoading) this.askGameAgain();
+          if (
+            movePlus.index < movesCount ||
+            this.gotMoveIdx >= movePlus.index
+          ) {
+            // Opponent re-send but we already have the move:
+            // (maybe he didn't receive our pingback...)
+            this.send("gotmove", {data: movePlus.index, target: data.from});
           }
           else {
-            if (
-              movePlus.index < movesCount ||
-              this.gotMoveIdx >= movePlus.index
-            ) {
-              // Opponent re-send but we already have the move:
-              // (maybe he didn't receive our pingback...)
-              this.send("gotmove", {data: movePlus.index, target: data.from});
-            } else {
-              this.gotMoveIdx = movePlus.index;
-              const receiveMyMove = (movePlus.color == this.game.mycolor);
-              const moveColIdx = ["w", "b"].indexOf(movePlus.color);
-              if (!receiveMyMove && !!this.game.mycolor) {
-                // Notify opponent that I got the move:
-                this.send(
-                  "gotmove",
-                  { data: movePlus.index, target: data.from }
+            this.gotMoveIdx = movePlus.index;
+            const receiveMyMove = (movePlus.color == this.game.mycolor);
+            const moveColIdx = ["w", "b"].indexOf(movePlus.color);
+            if (!receiveMyMove && !!this.game.mycolor) {
+              // Notify opponent that I got the move:
+              this.send(
+                "gotmove",
+                { data: movePlus.index, target: data.from }
+              );
+              // And myself if I'm elsewhere:
+              if (!this.focus) {
+                notify(
+                  "New move",
+                  {
+                    body:
+                      (this.game.players[moveColIdx].name || "@nonymous") +
+                      " just played."
+                  }
                 );
-                // And myself if I'm elsewhere:
-                if (!this.focus) {
-                  notify(
-                    "New move",
-                    {
-                      body:
-                        (this.game.players[moveColIdx].name || "@nonymous") +
-                        " just played."
-                    }
-                  );
-                }
               }
-              if (movePlus.cancelDrawOffer) {
-                // Opponent refuses draw
-                this.drawOffer = "";
-                // NOTE for corr games: drawOffer reset by player in turn
-                if (
-                  this.game.type == "live" &&
-                  !!this.game.mycolor &&
-                  !receiveMyMove
-                ) {
-                  GameStorage.update(this.gameRef, { drawOffer: "" });
-                }
+            }
+            if (movePlus.cancelDrawOffer) {
+              // Opponent refuses draw
+              this.drawOffer = "";
+              // NOTE for corr games: drawOffer reset by player in turn
+              if (
+                this.game.type == "live" &&
+                !!this.game.mycolor &&
+                !receiveMyMove
+              ) {
+                GameStorage.update(this.gameRef, { drawOffer: "" });
               }
-              this.$refs["basegame"].play(movePlus.move, "received");
-              // Freeze time while the move is being play
-              // (TODO: a callback would be cleaner here)
-              clearInterval(this.clockUpdate);
-              this.clockUpdate = null;
-              const freezeDuration = ["all", "highlight"].includes(V.ShowMoves)
-                // 250 = length of animation, 500 = delay between sub-moves
-                ? 250 + 750 *
-                  (Array.isArray(movePlus.move) ? movePlus.move.length - 1 : 0)
-                // Incomplete information: no move animation
-                : 0;
-              setTimeout(
-                () => {
-                  this.game.clocks[moveColIdx] = movePlus.clock;
-                  this.processMove(
-                    movePlus.move,
-                    { receiveMyMove: receiveMyMove }
-                  );
-                },
-                freezeDuration
-              );
             }
+            this.$refs["basegame"].play(movePlus.move, "received");
+            // Freeze time while the move is being play
+            // (TODO: a callback would be cleaner here)
+            clearInterval(this.clockUpdate);
+            this.clockUpdate = null;
+            const freezeDuration = ["all", "highlight"].includes(V.ShowMoves)
+              // 250 = length of animation, 500 = delay between sub-moves
+              ? 250 + 750 *
+                (Array.isArray(movePlus.move) ? movePlus.move.length - 1 : 0)
+              // Incomplete information: no move animation
+              : 0;
+            setTimeout(
+              () => {
+                this.game.clocks[moveColIdx] = movePlus.clock;
+                this.processMove(
+                  movePlus.move,
+                  { receiveMyMove: receiveMyMove }
+                );
+              },
+              freezeDuration
+            );
           }
           break;
         }
@@ -885,13 +875,15 @@ export default {
             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;
@@ -983,7 +975,8 @@ export default {
           // 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();
         }
@@ -1005,7 +998,8 @@ export default {
             : "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"]);
@@ -1019,7 +1013,8 @@ export default {
             this.gameRef,
             { drawOffer: this.game.mycolor }
           );
-        } else this.updateCorrGame({ drawOffer: this.game.mycolor });
+        }
+        else this.updateCorrGame({ drawOffer: this.game.mycolor });
       }
     },
     addAndGotoLiveGame: function(gameInfo, callback) {
@@ -1034,6 +1029,7 @@ export default {
           // Game state (including FEN): will be updated
           moves: [],
           clocks: [-1, -1], //-1 = unstarted
+          chats: [],
           score: "*"
         }
       );
@@ -1054,7 +1050,8 @@ export default {
         let gameInfo = {
           id: getRandString(), //ignored if corr
           fen: V.GenRandInitFen(this.game.randomness),
-          players: this.game.players.reverse(),
+          randomness: this.game.randomness,
+          players: [this.game.players[1], this.game.players[0]],
           vid: this.game.vid,
           cadence: this.game.cadence
         };
@@ -1075,7 +1072,6 @@ export default {
             "/games",
             "POST",
             {
-              // cid is useful to delete the challenge:
               data: { gameInfo: gameInfo },
               success: (response) => {
                 gameInfo.id = response.gameId;
@@ -1085,7 +1081,8 @@ export default {
             }
           );
         }
-      } else if (this.rematchOffer == "") {
+      }
+      else if (this.rematchOffer == "") {
         this.rematchOffer = "sent";
         this.send("rematchoffer", { data: true });
         if (this.game.type == "live") {
@@ -1093,8 +1090,10 @@ export default {
             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 });
@@ -1103,7 +1102,8 @@ export default {
             this.gameRef,
             { rematchOffer: '' }
           );
-        } else this.updateCorrGame({ rematchOffer: 'n' });
+        }
+        else this.updateCorrGame({ rematchOffer: 'n' });
       }
     },
     abortGame: function() {
@@ -1172,11 +1172,10 @@ export default {
               { 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"
@@ -1253,30 +1252,22 @@ export default {
         game
       );
       this.$refs["basegame"].re_setVariables(this.game);
-      if (!this.gameIsLoading) {
-        // Initial loading:
-        this.gotMoveIdx = game.moves.length - 1;
-        // If we arrive here after 'nextGame' action, the board might be hidden
-        let boardDiv = document.querySelector(".game");
-        if (!!boardDiv && boardDiv.style.visibility == "hidden")
-          boardDiv.style.visibility = "visible";
-      }
+      // Initial loading:
+      this.gotMoveIdx = game.moves.length - 1;
+      // If we arrive here after 'nextGame' action, the board might be hidden
+      let boardDiv = document.querySelector(".game");
+      if (!!boardDiv && boardDiv.style.visibility == "hidden")
+        boardDiv.style.visibility = "visible";
       this.re_setClocks();
       this.$nextTick(() => {
         this.game.rendered = true;
         // Did lastate arrive before game was rendered?
-        if (this.lastate) this.processLastate();
+        if (!!this.lastate) this.processLastate();
       });
       if (this.lastateAsked) {
         this.lastateAsked = false;
         this.sendLastate(game.oppsid);
       }
-      if (this.gameIsLoading) {
-        this.gameIsLoading = false;
-        if (this.gotMoveIdx >= game.moves.length)
-          // Some moves arrived meanwhile...
-          this.askGameAgain();
-      }
       if (!!callback) callback();
     },
     loadVariantThenGame: async function(game, callback) {
@@ -1285,26 +1276,23 @@ export default {
         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);
+        afterRawLoad(
+          require(
+            "raw-loader!@/translations/rules/" +
+            game.vname + "/" + this.st.lang + ".pug"
+          ).default
+        ).replace(/(fen:)([^:]*):/g, replaceByDiag);
     },
     // 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",
@@ -1350,7 +1338,8 @@ export default {
                 currentTurn == "w" ? "0-1" : "1-0",
                 "Time"
               );
-          } else {
+          }
+          else {
             this.$set(
               this.virtualClocks,
               colorIdx,
@@ -1412,7 +1401,11 @@ export default {
           !!this.repeat[fenObj]
             ? this.repeat[fenObj] + 1
             : 1;
-        if (this.repeat[fenObj] >= 3) this.drawOffer = "threerep";
+        if (this.repeat[fenObj] >= 3) {
+          if (V.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
@@ -1504,7 +1497,7 @@ export default {
           this.opponentGotMove = false;
           this.send("newmove", {data: sendMove});
           // If the opponent doesn't reply gotmove soon enough, re-send move:
-          // Do this at most 2 times, because mpore would mean network issues,
+          // Do this at most 2 times, because more would mean network issues,
           // opponent would then be expected to disconnect/reconnect.
           let counter = 1;
           const currentUrl = document.location.href;
@@ -1551,6 +1544,10 @@ export default {
             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);
@@ -1579,7 +1576,8 @@ export default {
           });
           document.querySelector("#confirmDiv > .card").style.width =
             boardDiv.offsetWidth + "px";
-        } else {
+        }
+        else {
           // Incomplete information: just ask confirmation
           // Hide the board, because otherwise it could reveal infos
           boardDiv.style.visibility = "hidden";
@@ -1708,6 +1706,13 @@ button
 #aboveBoard
   text-align: center
 
+.user-bio
+  display: inline
+  font-size: 1.5rem
+  @media screen and (max-width: 767px)
+    font-size: 1.2rem
+  padding: 0 3px
+
 .variant-cadence
   padding-right: 10px
 
@@ -1727,12 +1732,6 @@ span.separator
   padding: 0
   width: 10px
 
-span.name
-  font-size: 1.5rem
-  @media screen and (max-width: 767px)
-    font-size: 1.2rem
-  padding: 0 3px
-
 span.time
   font-size: 2rem
   @media screen and (max-width: 767px)