Rollback last (bad) improving attempt
[vchess.git] / client / src / views / Game.vue
index 2beb10c..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(
@@ -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
@@ -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()"
@@ -120,8 +122,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 +136,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 +150,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 +182,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 +198,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 +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: "",
@@ -212,9 +230,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 +260,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 +326,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 =>
@@ -320,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);
@@ -338,7 +374,7 @@ export default {
           id: my.id,
           name: my.name,
           tmpIds: {
-            tmpId: { focus: true }
+            [tmpId]: { focus: true }
           }
         }
       );
@@ -357,8 +393,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;
@@ -384,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
@@ -399,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.
@@ -505,7 +548,8 @@ export default {
             "DELETE",
             { data: { gid: this.game.id } }
           );
-        } else {
+        }
+        else {
           // Live game
           GameStorage.update(this.gameRef, { delchat: true });
         }
@@ -514,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) {
@@ -536,27 +580,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 +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
           }
@@ -673,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;
         }
@@ -718,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(
@@ -764,82 +770,75 @@ 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;
-          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;
         }
@@ -893,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;
@@ -991,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();
         }
@@ -1013,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"]);
@@ -1027,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) {
@@ -1037,11 +1041,11 @@ 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: [],
           clocks: [-1, -1], //-1 = unstarted
+          chats: [],
           score: "*"
         }
       );
@@ -1061,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") {
@@ -1101,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 });
@@ -1111,7 +1129,8 @@ console.log(data.data);
             this.gameRef,
             { rematchOffer: '' }
           );
-        } else this.updateCorrGame({ rematchOffer: 'n' });
+        }
+        else this.updateCorrGame({ rematchOffer: 'n' });
       }
     },
     abortGame: function() {
@@ -1180,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"
@@ -1261,14 +1279,12 @@ console.log(data.data);
         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;
@@ -1279,40 +1295,48 @@ console.log(data.data);
         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) {
-      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",
@@ -1358,7 +1382,8 @@ console.log(data.data);
                 currentTurn == "w" ? "0-1" : "1-0",
                 "Time"
               );
-          } else {
+          }
+          else {
             this.$set(
               this.virtualClocks,
               colorIdx,
@@ -1414,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);
@@ -1512,7 +1543,7 @@ console.log(data.data);
           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;
@@ -1559,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);
@@ -1583,11 +1618,14 @@ 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";
-        } else {
+        }
+        else {
           // Incomplete information: just ask confirmation
           // Hide the board, because otherwise it could reveal infos
           boardDiv.style.visibility = "hidden";
@@ -1612,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:
@@ -1716,6 +1754,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
 
@@ -1735,12 +1780,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)