Show check(mate) indicators in moves list. No longer require the odd ?rid=... in...
authorBenjamin Auder <benjamin.auder@somewhere>
Thu, 19 Mar 2020 02:29:40 +0000 (03:29 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Thu, 19 Mar 2020 02:29:40 +0000 (03:29 +0100)
client/src/components/BaseGame.vue
client/src/utils/gameStorage.js
client/src/views/Game.vue
client/src/views/Hall.vue
server/sockets.js

index c4ffe5c..3446a03 100644 (file)
@@ -178,12 +178,15 @@ export default {
       const parsedFen = V.ParseFen(game.fenStart);
       const firstMoveColor = parsedFen.turn;
       this.firstMoveNumber = Math.floor(parsedFen.movesCount / 2);
+      let L = this.moves.length;
       this.moves.forEach(move => {
         // Strategy working also for multi-moves:
         if (!Array.isArray(move)) move = [move];
-        move.forEach(m => {
+        move.forEach((m,idx) => {
           m.notation = this.vr.getNotation(m);
           this.vr.play(m);
+          if (idx < L - 1 && this.vr.getCheckSquares(this.vr.turn).length > 0)
+            m.notation += "+";
         });
       });
       if (firstMoveColor == "b") {
@@ -194,9 +197,15 @@ export default {
           end: { x: -1, y: -1 },
           fen: game.fenStart
         });
+        L++;
       }
       this.positionCursorTo(this.moves.length - 1);
       this.incheck = this.vr.getCheckSquares(this.vr.turn);
+      const score = this.vr.getCurrentScore();
+      if (["1-0","0-1"].includes(score))
+        this.moves[L - 1].notation += "#";
+      else if (this.vr.getCheckSquares(this.vr.turn).length > 0)
+        this.moves[L - 1].notation += "+";
     },
     positionCursorTo: function(index) {
       this.cursor = index;
@@ -349,6 +358,12 @@ export default {
       };
       const computeScore = () => {
         const score = this.vr.getCurrentScore();
+        if (!navigate) {
+          if (["1-0","0-1"].includes(score))
+            this.lastMove.notation += "#";
+          else if (this.vr.getCheckSquares(this.vr.turn).length > 0)
+            this.lastMove.notation += "+";
+        }
         if (score != "*" && this.game.mode == "analyze") {
           const message = getScoreMessage(score);
           // Just show score on screen (allow undo)
@@ -366,15 +381,15 @@ export default {
           this.incheck = this.vr.getCheckSquares(this.vr.turn);
           this.emitFenIfAnalyze();
           this.inMultimove = false;
-          if (!noemit) var score = computeScore();
+          this.score = computeScore();
           if (this.game.mode != "analyze") {
-            const L = this.moves.length;
-            if (!noemit)
+            if (!noemit) {
               // Post-processing (e.g. computer play).
+              const L = this.moves.length;
               // NOTE: always emit the score, even in unfinished,
               // to tell Game::processMove() that it's not a received move.
-              this.$emit("newmove", this.moves[L-1], { score: score });
-            else {
+              this.$emit("newmove", this.moves[L-1], { score: this.score });
+            else {
               this.inPlay = false;
               if (this.stackToPlay.length > 0)
                 // Move(s) arrived in-between
@@ -394,7 +409,7 @@ export default {
         if (!light) {
           this.lastMove = move[move.length-1];
           this.incheck = this.vr.getCheckSquares(this.vr.turn);
-          computeScore();
+          this.score = computeScore();
           this.emitFenIfAnalyze();
         }
         this.cursor++;
index ba18949..1298667 100644 (file)
@@ -111,8 +111,8 @@ export const GameStorage = {
     dbOperation((err,db) => {
       let objectStore = db.transaction("games").objectStore("games");
       objectStore.get(gameId).onsuccess = function(event) {
-        if (event.target.result)
-          callback(event.target.result);
+        // event.target.result is null if game not found
+        callback(event.target.result);
       };
     });
   },
index 5785abd..68c8c94 100644 (file)
@@ -153,11 +153,8 @@ export default {
   data: function() {
     return {
       st: store.state,
-      gameRef: {
-        // rid = remote (socket) ID
-        id: "",
-        rid: ""
-      },
+      // gameRef can point to a corr game, local game or remote live game
+      gameRef: "",
       nextIds: [],
       game: {}, //passed to BaseGame
       // virtualClocks will be initialized from true game.clocks
@@ -205,10 +202,8 @@ export default {
         this.atCreation();
       } else {
         // Same game ID
-        this.gameRef.id = to.params["id"];
-        this.gameRef.rid = to.query["rid"];
         this.nextIds = JSON.parse(this.$route.query["next"] || "[]");
-        this.fetchGame();
+        this.loadGame(this.game);
       }
     }
   },
@@ -244,11 +239,8 @@ export default {
     },
     atCreation: function() {
       // 0] (Re)Set variables
-      this.gameRef.id = this.$route.params["id"];
-      // rid = remote ID to find an observed live game,
-      // next = next corr games IDs to navigate faster
-      // (Both might be undefined)
-      this.gameRef.rid = this.$route.query["rid"];
+      this.gameRef = this.$route.params["id"];
+      // next = next corr games IDs to navigate faster (if applicable)
       this.nextIds = JSON.parse(this.$route.query["next"] || "[]");
       // Always add myself to players' list
       const my = this.st.user;
@@ -307,18 +299,18 @@ export default {
           callback();
         else
           // Socket not ready yet (initial loading)
-          // NOTE: it's important to call callback without arguments,
-          // otherwise first arg is Websocket object and fetchGame fails.
+          // NOTE: first arg is Websocket object, unused here:
           this.conn.onopen = () => callback();
       };
-      if (!this.gameRef.rid)
-        // Game stored locally or on server
-        this.fetchGame(null, () => socketInit(this.roomInit));
-      else
-        // Game stored remotely: need socket to retrieve it
-        // NOTE: the callback "roomInit" will be lost, so we don't provide it.
-        // --> It will be given when receiving "fullgame" socket event.
-        socketInit(this.fetchGame);
+      this.fetchGame((game) => {
+        if (!!game)
+          this.loadVariantThenGame(game, () => socketInit(this.roomInit));
+        else
+          // Live game stored remotely: need socket to retrieve it
+          // NOTE: the callback "roomInit" will be lost, so we don't provide it.
+          // --> It will be given when receiving "fullgame" socket event.
+          socketInit(() => { this.send("askfullgame"); });
+      });
     },
     cleanBeforeDestroy: function() {
       if (!!this.askLastate)
@@ -432,13 +424,15 @@ export default {
       const currentUrl = document.location.href;
       const doAskGame = () => {
         if (document.location.href != currentUrl) return; //page change
-        if (!this.gameRef.rid)
-          // This is my game: just reload.
-          this.fetchGame();
-        else
-          // Just ask fullgame again (once!), this is much simpler.
-          // If this fails, the user could just reload page :/
-          this.send("askfullgame", { target: this.gameRef.rid });
+        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();
@@ -563,8 +557,7 @@ export default {
               players: this.game.players,
               vid: this.game.vid,
               cadence: this.game.cadence,
-              score: this.game.score,
-              rid: this.st.user.sid //useful in Hall if I'm an observer
+              score: this.game.score
             };
             this.send("game", { data: myGame, target: data.from });
           }
@@ -587,7 +580,7 @@ export default {
           break;
         case "fullgame":
           // Callback "roomInit" to poll clients only after game is loaded
-          this.fetchGame(data.data, this.roomInit);
+          this.loadVariantThenGame(data.data, this.roomInit);
           break;
         case "asklastate":
           // Sending informative last state if I played a move or score != "*"
@@ -639,7 +632,7 @@ export default {
                   !!this.game.mycolor &&
                   !receiveMyMove
                 ) {
-                  GameStorage.update(this.gameRef.id, { drawOffer: "" });
+                  GameStorage.update(this.gameRef, { drawOffer: "" });
                 }
               }
               this.$refs["basegame"].play(movePlus.move, "received", null, true);
@@ -676,10 +669,22 @@ export default {
         case "drawoffer":
           // NOTE: observers don't know who offered draw
           this.drawOffer = "received";
+          if (this.game.type == "live") {
+            GameStorage.update(
+              this.gameRef,
+              { drawOffer: V.GetOppCol(this.game.mycolor) }
+            );
+          }
           break;
         case "rematchoffer":
           // NOTE: observers don't know who offered rematch
           this.rematchOffer = data.data ? "received" : "";
+          if (this.game.type == "live") {
+            GameStorage.update(
+              this.gameRef,
+              { rematchOffer: V.GetOppCol(this.game.mycolor) }
+            );
+          }
           break;
         case "newgame": {
           // A game started, redirect if I'm playing in
@@ -696,17 +701,7 @@ export default {
           ) {
             this.$router.push("/game/" + gameInfo.id);
           } else {
-            let urlRid = "";
-            if (gameInfo.cadence.indexOf('d') === -1) {
-              urlRid = "/?rid=";
-              // Select sid of any of the online players:
-              let onlineSid = [];
-              gameInfo.players.forEach(p => {
-                if (!!this.people[p.sid]) onlineSid.push(p.sid);
-              });
-              urlRid += onlineSid[Math.floor(Math.random() * onlineSid.length)];
-            }
-            this.rematchId = gameInfo.id + urlRid;
+            this.rematchId = gameInfo.id;
             document.getElementById("modalInfo").checked = true;
           }
           break;
@@ -729,7 +724,7 @@ export default {
         "PUT",
         {
           data: {
-            gid: this.gameRef.id,
+            gid: this.gameRef,
             newObj: obj
           },
           success: () => {
@@ -804,7 +799,7 @@ export default {
         this.send("drawoffer");
         if (this.game.type == "live") {
           GameStorage.update(
-            this.gameRef.id,
+            this.gameRef,
             { drawOffer: this.game.mycolor }
           );
         } else this.updateCorrGame({ drawOffer: this.game.mycolor });
@@ -879,7 +874,7 @@ export default {
         this.send("rematchoffer", { data: true });
         if (this.game.type == "live") {
           GameStorage.update(
-            this.gameRef.id,
+            this.gameRef,
             { rematchOffer: this.game.mycolor }
           );
         } else this.updateCorrGame({ rematchOffer: this.game.mycolor });
@@ -889,7 +884,7 @@ export default {
         this.send("rematchoffer", { data: false });
         if (this.game.type == "live") {
           GameStorage.update(
-            this.gameRef.id,
+            this.gameRef,
             { rematchOffer: '' }
           );
         } else this.updateCorrGame({ rematchOffer: 'n' });
@@ -908,10 +903,6 @@ export default {
       const side = (this.game.mycolor == "w" ? "White" : "Black");
       this.gameOver(score, side + " surrender");
     },
-    // 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)
     loadGame: function(game, callback) {
       this.vr = new V(game.fen);
       const gtype = this.getGameType(game);
@@ -1052,45 +1043,36 @@ export default {
       }
       if (!!callback) callback();
     },
-    fetchGame: function(game, callback) {
-      const afterRetrieval = async (game) => {
-        await import("@/variants/" + game.vname + ".js")
-        .then((vModule) => {
-          window.V = vModule[game.vname + "Rules"];
-          this.loadGame(game, callback);
-        });
-      };
-      if (!!game) {
-        afterRetrieval(game);
-        return;
-      }
-      if (this.gameRef.rid)
-        // Remote live game: forgetting about callback func... (TODO: design)
-        this.send("askfullgame", { target: this.gameRef.rid });
-      else {
-        // Local or corr game on server.
-        // NOTE: afterRetrieval() is never called if game not found
-        const gid = this.gameRef.id;
-        if (Number.isInteger(gid) || !isNaN(parseInt(gid))) {
-          // corr games identifiers are integers
-          ajax(
-            "/games",
-            "GET",
-            {
-              data: { gid: gid },
-              success: (res) => {
-                res.game.moves.forEach(m => {
-                  m.squares = JSON.parse(m.squares);
-                });
-                afterRetrieval(res.game);
-              }
+    loadVariantThenGame: async function(game, callback) {
+      await import("@/variants/" + game.vname + ".js")
+      .then((vModule) => {
+        window.V = vModule[game.vname + "Rules"];
+        this.loadGame(game, callback);
+      });
+    },
+    // 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))) {
+        // corr games identifiers are integers
+        ajax(
+          "/games",
+          "GET",
+          {
+            data: { gid: this.gameRef },
+            success: (res) => {
+              res.game.moves.forEach(m => {
+                m.squares = JSON.parse(m.squares);
+              });
+              callback(res.game);
             }
-          );
-        }
-        else
-          // Local game
-          GameStorage.get(this.gameRef.id, afterRetrieval);
-      }
+          }
+        );
+      } else
+        // Local game (or live remote)
+        GameStorage.get(this.gameRef, callback);
     },
     re_setClocks: function() {
       if (this.game.moves.length < 2 || this.game.score != "*") {
@@ -1155,11 +1137,11 @@ export default {
         playMove(move, this.vr);
         // The move is played: stop clock
         clearInterval(this.clockUpdate);
-        if (!data.score) {
-          // Received move, score has not been computed in BaseGame (!!noemit)
-          const score = this.vr.getCurrentScore();
-          if (score != "*") this.gameOver(score);
-        }
+        if (!data.score)
+          // Received move, score is computed in BaseGame, but maybe not yet.
+          // ==> Compute it here, although this is redundant (TODO)
+          data.score = this.vr.getCurrentScore();
+        if (data.score != "*") this.gameOver(data.score);
         this.game.moves.push(move);
         this.game.fen = this.vr.getFen();
         if (this.game.type == "live") {
@@ -1194,7 +1176,7 @@ export default {
           this.notifyMyGames(
             "turn",
             {
-              gid: this.gameRef.id,
+              gid: this.gameRef,
               turn: this.vr.turn
             }
           );
@@ -1232,7 +1214,7 @@ export default {
           }
           else {
             const updateStorage = () => {
-              GameStorage.update(this.gameRef.id, {
+              GameStorage.update(this.gameRef, {
                 fen: this.game.fen,
                 move: filtered_move,
                 moveIdx: origMovescount,
@@ -1370,7 +1352,7 @@ export default {
           scoreMsg: scoreMsg
         };
         if (this.game.type == "live") {
-          GameStorage.update(this.gameRef.id, scoreObj);
+          GameStorage.update(this.gameRef, scoreObj);
           if (!!callback) callback();
         }
         else this.updateCorrGame(scoreObj, callback);
@@ -1380,7 +1362,7 @@ export default {
         this.notifyMyGames(
           "score",
           {
-            gid: this.gameRef.id,
+            gid: this.gameRef,
             score: score
           }
         );
index 0840cd9..d11da8b 100644 (file)
@@ -558,10 +558,7 @@ export default {
     showGame: function(g) {
       // NOTE: we are an observer, since only games I don't play are shown here
       // ==> Moves sent by connected remote player(s) if live game
-      let url = "/game/" + g.id;
-      if (g.type == "live")
-        url += "?rid=" + g.rids[Math.floor(Math.random() * g.rids.length)];
-      this.$router.push(url);
+      this.$router.push("/game/" + g.id);
     },
     resetSocialColor: function() {
       // TODO: this is called twice, once on opening an once on closing
@@ -612,15 +609,17 @@ export default {
         case "connect":
         case "gconnect": {
           const page = data.page || "/";
-          // Only ask game / challenges if first connexion:
-          if (!this.people[data.from]) {
-            this.people[data.from] = { pages: [{ path: page, focus: true }] };
-            if (data.code == "connect")
+          if (data.code == "connect") {
+            // Ask challenges only on first connexion:
+            if (!this.people[data.from])
               this.send("askchallenges", { target: data.from });
-            // Ask game only if live:
-            else if (!page.match(/\/[0-9]+$/))
-              this.send("askgame", { target: data.from, page: page });
-          } else {
+          }
+          // Ask game only if live:
+          else if (!page.match(/\/[0-9]+$/))
+            this.send("askgame", { target: data.from, page: page });
+          if (!this.people[data.from])
+            this.people[data.from] = { pages: [{ path: page, focus: true }] };
+          else {
             // Append page if not already in list
             if (!(this.people[data.from].pages.find(p => p.path == page)))
               this.people[data.from].pages.push({ path: page, focus: true });
@@ -638,6 +637,10 @@ export default {
           // the first reload won't have time to connect but will trigger a "close" event anyway.
           // ==> Next check is required.
           if (!this.people[data.from]) return;
+          const page = data.page || "/";
+          ArrayFun.remove(this.people[data.from].pages, p => p.path == page);
+          if (this.people[data.from].pages.length == 0)
+            this.$delete(this.people, data.from);
           // Disconnect means no more tmpIds:
           if (data.code == "disconnect") {
             // Remove the live challenges sent by this player:
@@ -648,22 +651,16 @@ export default {
             );
           } else {
             // Remove the matching live game if now unreachable
-            const gid = data.page.match(/[a-zA-Z0-9]+$/)[0];
+            const gid = page.match(/[a-zA-Z0-9]+$/)[0];
             // Corr games are always reachable:
             if (!gid.match(/^[0-9]+$/)) {
-              const gidx = this.games.findIndex(g => g.id == gid);
-              // NOTE: gidx should always be >= 0 (TODO?)
-              if (gidx >= 0) {
-                const game = this.games[gidx];
-                ArrayFun.remove(game.rids, rid => rid == data.from);
-                if (game.rids.length == 0) this.games.splice(gidx, 1);
+              // Live games are reachable as long as someone is on the game page
+              if (Object.values(this.people).every(p =>
+                p.pages.every(pg => pg.path != page))) {
+                ArrayFun.remove(this.games, g => g.id == gid);
               }
             }
           }
-          const page = data.page || "/";
-          ArrayFun.remove(this.people[data.from].pages, p => p.path == page);
-          if (this.people[data.from].pages.length == 0)
-            this.$delete(this.people, data.from);
           break;
         }
         case "getfocus":
@@ -776,32 +773,27 @@ export default {
         case "game": // Individual request
         case "newgame": {
           const game = data.data;
-          // Ignore games where I play (will go in MyGames page)
-          if (game.players.every(p =>
-            p.sid != this.st.user.sid && p.id != this.st.user.id))
-          {
-            let locGame = this.games.find(g => g.id == game.id);
-            if (!locGame) {
-              let newGame = game;
-              newGame.type = this.classifyObject(game);
-              newGame.vname = this.getVname(game.vid);
-              if (!game.score)
-                // New game from Hall
-                newGame.score = "*";
-              newGame.rids = [game.rid];
-              delete newGame["rid"];
-              this.games.push(newGame);
-              if (
-                (newGame.type == "live" && this.gdisplay == "corr") ||
-                (newGame.type == "corr" && this.gdisplay == "live")
-              ) {
-                document
-                  .getElementById("btnG" + newGame.type)
-                  .classList.add("somethingnew");
-              }
-            } else {
-              // Append rid (if not already in list)
-              if (!locGame.rids.includes(game.rid)) locGame.rids.push(game.rid);
+          // Ignore games where I play (will go in MyGames page),
+          // and also games that I already received.
+          if (
+            game.players.every(p =>
+              p.sid != this.st.user.sid && p.id != this.st.user.id) &&
+            this.games.findIndex(g => g.id == game.id) == -1
+          ) {
+            let newGame = game;
+            newGame.type = this.classifyObject(game);
+            newGame.vname = this.getVname(game.vid);
+            if (!game.score)
+              // New game from Hall
+              newGame.score = "*";
+            this.games.push(newGame);
+            if (
+              (newGame.type == "live" && this.gdisplay == "corr") ||
+              (newGame.type == "corr" && this.gdisplay == "live")
+            ) {
+              document
+                .getElementById("btnG" + newGame.type)
+                .classList.add("somethingnew");
             }
           }
           break;
index 4907965..f904311 100644 (file)
@@ -155,22 +155,42 @@ module.exports = function(wss) {
         case "askidentity":
         case "asklastate":
         case "askchallenges":
-        case "askgame":
-        case "askfullgame": {
+        case "askgame": {
           const pg = obj.page || page; //required for askidentity and askgame
-          // In cas askfullgame to wrong SID for example, would crash:
           if (!!clients[pg] && !!clients[pg][obj.target]) {
-            const tmpIds = Object.keys(clients[pg][obj.target]);
+            let tmpIds = Object.keys(clients[pg][obj.target]);
             if (obj.target == sid) {
               // Targetting myself
               const idx_myTmpid = tmpIds.findIndex(x => x == tmpId);
               if (idx_myTmpid >= 0) tmpIds.splice(idx_myTmpid, 1);
             }
-            const tmpId_idx = Math.floor(Math.random() * tmpIds.length);
-            send(
-              clients[pg][obj.target][tmpIds[tmpId_idx]].socket,
-              { code: obj.code, from: [sid,tmpId,page] }
-            );
+            if (tmpIds.length > 0) {
+              const ttmpId = tmpIds[Math.floor(Math.random() * tmpIds.length)];
+              send(
+                clients[pg][obj.target][ttmpId].socket,
+                { code: obj.code, from: [sid,tmpId,page] }
+              );
+            }
+          }
+          break;
+        }
+
+        // Special situation of the previous "case":
+        // Full game can be asked to any observer.
+        case "askfullgame": {
+          if (!!clients[page]) {
+            let sids = Object.keys(clients[page]).filter(k => k != sid);
+            if (sids.length > 0) {
+              // Pick a SID at random in this set, and ask full game:
+              const rid = sids[Math.floor(Math.random() * sids.length)];
+              // ..to a random tmpId:
+              const tmpIds = Object.keys(clients[page][rid]);
+              const rtmpId = tmpIds[Math.floor(Math.random() * tmpIds.length)];
+              send(
+                clients[page][rid][rtmpId].socket,
+                { code: "askfullgame", from: [sid,tmpId] }
+              );
+            }
           }
           break;
         }