Some improvements (multi-tabs on same game seem fixed)
authorBenjamin Auder <benjamin.auder@somewhere>
Fri, 6 Mar 2020 13:31:55 +0000 (14:31 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Fri, 6 Mar 2020 13:31:55 +0000 (14:31 +0100)
client/public/images/pieces/Hidden/wp.svg
client/src/App.vue
client/src/components/BaseGame.vue
client/src/components/Board.vue
client/src/components/MoveList.vue
client/src/utils/gameStorage.js
client/src/variants/Benedict.js
client/src/variants/Hidden.js
client/src/views/Game.vue

index e501622..fa9a146 100644 (file)
@@ -23,7 +23,7 @@
         <dc:format>image/svg+xml</dc:format>
         <dc:type
            rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title></dc:title>
+        <dc:title />
       </cc:Work>
     </rdf:RDF>
   </metadata>
@@ -41,7 +41,7 @@
      id="namedview8"
      showgrid="false"
      inkscape:zoom="0.26767309"
-     inkscape:cx="347.21094"
+     inkscape:cx="362.15454"
      inkscape:cy="440.83624"
      inkscape:window-x="0"
      inkscape:window-y="20"
      d="m 394.23381,791.93859 c -7.96934,-3.85734 -18.71233,-12.01208 -23.87329,-18.12165 -8.15598,-9.6551 -9.38357,-13.27828 -9.38357,-27.69552 0,-9.12297 1.93363,-20.47373 4.29695,-25.22392 13.36268,-26.85857 62.82698,-39.13548 96.68415,-23.99674 36.37643,16.26519 46.19804,54.40328 20.80685,80.79473 -20.75542,21.57308 -60.25727,27.92824 -88.53109,14.2431 z"
      id="path3745"
      inkscape:connector-curvature="0" />
+  <path
+     style="fill:#b3b3b3;stroke-width:3.7359004"
+     d="m 365.9488,597.18368 c 0.11267,-48.68499 14.3471,-85.81016 43.53171,-113.53622 7.87401,-7.4805 29.17201,-25.42354 47.32889,-39.87344 49.29838,-39.23343 69.61923,-70.81828 69.68826,-108.31699 0.062,-33.66397 -6.82367,-52.77547 -25.83347,-71.70242 -20.24535,-20.15711 -35.65297,-25.37053 -76.63848,-25.93192 -24.85407,-0.34043 -32.86588,1.16304 -49.45902,9.28131 -32.89031,16.09173 -56.24732,51.66167 -62.85419,95.71944 l -2.61177,17.41658 -20.95255,1.25795 c -11.5239,0.69188 -43.52542,0.66722 -71.11448,-0.0548 l -50.16193,-1.31277 2.58937,-37.59577 c 5.53759,-80.40202 17.10702,-107.98772 65.40863,-155.95793 52.78629,-52.42411 95.12919,-67.741257 187.07901,-67.674067 105.09067,0.07679 156.07116,18.007557 208.61014,73.372017 29.73724,31.33649 44.30385,59.64812 51.69858,100.48133 10.32047,56.98905 0.77206,103.79195 -31.02897,152.09321 -16.75005,25.44093 -33.63789,41.34497 -85.49503,80.51446 -50.8195,38.38574 -68.16712,61.80338 -71.79048,96.91027 l -2.0773,20.12709 h -62.98764 -62.98764 l 0.0583,-25.21733 z"
+     id="path24"
+     inkscape:connector-curvature="0" />
+  <path
+     style="fill:#b3b3b3;stroke-width:3.7359004"
+     d="m 397.49104,791.17525 c -20.95037,-9.80667 -35.3365,-27.83288 -35.3365,-44.27759 0,-35.74584 26.39876,-55.39922 71.02605,-52.87755 27.60114,1.5596 39.28935,7.38936 53.81087,26.83935 10.16576,13.61593 9.89605,34.89996 -0.62957,49.68183 -16.99337,23.86499 -60.42541,33.949 -88.87085,20.63396 z"
+     id="path26"
+     inkscape:connector-curvature="0" />
+  <path
+     style="fill:#ffffff;stroke-width:3.7359004"
+     d="m 403.24944,622.60368 c -36.38827,-1.92078 -39.17141,-2.61132 -38.45943,-9.54243 0.42214,-4.10949 1.50538,-17.85214 2.4072,-30.53922 0.90182,-12.68709 2.46017,-24.45517 3.46302,-26.15131 2.57808,-4.36035 8.00395,-28.67348 7.1496,-32.0371 -0.39143,-1.54106 1.06868,-2.80192 3.24469,-2.80192 2.17601,0 2.91737,-1.68116 1.64747,-3.7359 -1.2699,-2.05475 -0.88831,-3.7359 0.84799,-3.7359 1.73629,0 8.27294,-6.65656 14.52589,-14.79238 6.25294,-8.13578 23.30338,-24.78803 37.88985,-37.00499 14.58648,-12.21692 25.52098,-23.21249 24.2989,-24.43457 -1.22208,-1.22208 -0.41492,-2.22197 1.79371,-2.22197 3.92111,0 38.32452,-31.56196 38.32452,-35.15921 0,-0.99816 3.91834,-6.0428 8.70743,-11.21031 4.78909,-5.16751 10.67313,-15.95639 13.07565,-23.97529 5.67096,-18.92796 5.60375,-54.92166 -0.12197,-65.32718 -2.46962,-4.4881 -4.85065,-10.31649 -5.29119,-12.95199 -1.2878,-7.70425 -37.91199,-42.88766 -42.00848,-40.35588 -2.05279,1.26869 -4.80526,0.57072 -6.11659,-1.55106 -1.31133,-2.12178 -3.94923,-2.89056 -5.86199,-1.70841 -1.91277,1.18216 -3.47776,0.57187 -3.47776,-1.35618 0,-1.94132 -15.00318,-3.40002 -33.62311,-3.26905 -18.4927,0.13008 -33.6231,1.70757 -33.6231,3.50555 0,1.79798 -1.56499,2.30184 -3.47776,1.11968 -1.91276,-1.18215 -4.55066,-0.41337 -5.86199,1.70841 -1.31133,2.12178 -3.75874,3.00829 -5.43869,1.97002 -1.67995,-1.03826 -5.13195,0.61547 -7.6711,3.67496 -2.53915,3.0595 -6.42025,5.56272 -8.62465,5.56272 -5.78758,0 -24.96431,19.19055 -24.96431,24.98231 0,2.69769 -1.36979,4.90489 -3.04397,4.90489 -4.40736,0 -16.15589,26.02284 -13.60902,30.14378 1.18269,1.91363 0.47446,3.47933 -1.57385,3.47933 -2.04831,0 -3.7782,2.94202 -3.8442,6.53782 -0.25374,13.82485 -5.19841,31.47973 -9.2177,32.91169 -8.64121,3.07863 -125.72058,3.75356 -131.07759,0.75563 -4.48656,-2.51081 -5.1839,-8.76582 -3.56055,-31.93746 3.12633,-44.62479 4.26178,-54.4689 7.07348,-61.32536 1.43422,-3.49741 1.51891,-8.12059 0.1882,-10.27373 -1.33072,-2.15314 -0.8351,-3.9148 1.10138,-3.9148 5.14874,0 7.77321,-18.11697 2.91495,-20.12218 -2.2698,-0.93684 -1.18488,-1.83607 2.41093,-1.99828 3.5958,-0.16222 6.53782,-1.7415 6.53782,-3.50951 0,-1.76801 4.15222,-9.33321 9.22716,-16.81155 5.07494,-7.47834 8.46177,-13.59699 7.52628,-13.59699 -0.93549,0 13.24042,-13.97801 31.50202,-31.06225 31.49366,-29.4632 72.11791,-54.86345 87.74663,-54.86345 3.40814,0 7.2184,-2.66269 8.46723,-5.91709 1.69135,-4.4076 2.99484,-4.745247 5.10955,-1.32357 1.76537,2.85643 5.45391,3.49238 9.75383,1.6817 3.80318,-1.6015 12.98731,-2.57524 20.40917,-2.16386 8.3573,0.46323 15.82375,-1.58148 19.61348,-5.371212 3.38714,-3.387144 6.11917,-4.245293 6.11917,-1.922073 0,2.769732 9.84655,4.097318 28.95323,3.903696 38.43716,-0.389512 83.92361,5.558839 114.33409,14.951679 13.60895,4.20337 25.83551,7.43725 27.17012,7.18638 1.33461,-0.25087 3.45507,0.95306 4.71221,2.6754 1.25709,1.72233 9.26409,7.86327 17.79334,13.64652 22.67042,15.37163 62.40127,57.86451 72.23595,77.25764 4.68904,9.24635 9.56054,17.65213 10.82556,18.6795 1.26505,1.02737 2.75242,6.07084 3.30526,11.2077 0.55288,5.13686 3.584,11.2714 6.73587,13.6323 3.15183,2.3609 4.30076,4.88263 2.55318,5.60385 -1.74757,0.72122 -2.257,19.52756 -1.13197,41.79187 1.12499,22.26431 1.23337,41.28889 0.24096,42.27683 -0.99251,0.98794 -3.86471,8.77353 -6.38263,17.3013 -3.57541,12.10911 -3.58931,16.13347 -0.0635,18.37434 3.25651,2.06965 2.95296,2.88773 -1.08913,2.93539 -3.08212,0.0362 -10.50177,9.43955 -16.4881,20.89601 -11.68122,22.35509 -49.73888,68.03746 -57.08411,68.52066 -2.45191,0.16139 -6.65479,3.4296 -9.33975,7.26289 -2.68492,3.8333 -6.56285,6.96395 -8.6176,6.957 -2.05475,-0.007 -5.43021,3.29674 -7.50102,7.34156 -2.07081,4.04483 -5.66945,6.71947 -7.99695,5.94363 -2.32747,-0.77583 -7.93035,3.29137 -12.45079,9.03819 -4.52047,5.74683 -8.48703,9.93242 -8.81463,9.30135 -0.32764,-0.63107 -9.41611,7.35442 -20.1967,17.74553 -10.78059,10.3911 -18.6516,18.89293 -17.49113,18.89293 1.16047,0 -1.48131,5.04347 -5.87064,11.2077 -4.38932,6.16424 -6.6893,11.2077 -5.11106,11.2077 1.57824,0 0.95856,1.91095 -1.37706,4.2466 -2.6249,2.62488 -3.5066,11.5415 -2.30885,23.34938 1.12464,11.08718 0.59214,17.22601 -1.26908,14.63012 -2.11023,-2.94326 -5.08934,-3.28218 -8.71205,-0.99121 -5.67563,3.58927 -15.14596,3.59648 -82.09121,0.0628 z"
+     id="path31"
+     inkscape:connector-curvature="0" />
+  <path
+     style="fill:#ffffff;stroke-width:3.7359004"
+     d="m 425.47976,794.25242 c -32.5155,-2.48227 -47.40124,-12.19763 -62.5006,-40.79177 -3.27677,-6.20531 -3.57039,-10.25943 -0.93398,-12.89584 2.11489,-2.11489 3.84526,-6.54472 3.84526,-9.84406 0,-7.56404 14.97605,-27.42941 22.4154,-29.73348 3.08212,-0.95458 19.05309,-2.3631 35.49105,-3.13005 34.9352,-1.62998 49.65764,4.15346 62.18915,24.42986 14.18057,22.94465 5.1724,50.08583 -21.08542,63.52935 -6.16909,3.15846 -12.0571,6.76894 -13.08447,8.02329 -1.02738,1.25436 -12.87875,1.44007 -26.33639,0.4127 z"
+     id="path33"
+     inkscape:connector-curvature="0" />
 </svg>
index e9ece04..368982b 100644 (file)
@@ -193,11 +193,14 @@ nav
     border: none
     & > label.drawer-toggle
       cursor: pointer
-      font-size: 32px
       position: absolute
-      top: 5px
-      line-height: 1em
+      top: 0
+      left: 5px
+      line-height: 42px
+      height: 42px
       padding: 0
+    & > label.drawer-toggle:before
+      font-size: 42px
     & > #menuBar
       z-index: 5000 //to hide currently selected piece if any
 
@@ -206,14 +209,14 @@ nav
 
 [type=checkbox].drawer+* .drawer-close
   top: 0
-  left: 10px
+  left: 5px
   padding: 0
-  width: 42px
-  height: 42px
+  height: 50px
+  width: 50px
+  line-height: 50px
 
 [type=checkbox].drawer+* .drawer-close:before
   font-size: 50px
-  line-height: 1em
 
 @media screen and (max-width: 767px)
   .button-group
index 3c14a7f..5c55656 100644 (file)
@@ -87,12 +87,13 @@ export default {
       orientation: "w",
       score: "*", //'*' means 'unfinished'
       moves: [],
-      // TODO: later, use subCursor to navigate intra-multimoves?
       cursor: -1, //index of the move just played
       lastMove: null,
       firstMoveNumber: 0, //for printing
       incheck: [], //for Board
-      inMultimove: false
+      inMultimove: false,
+      inPlay: false,
+      stackToPlay: []
     };
   },
   watch: {
@@ -316,8 +317,15 @@ export default {
       }
     },
     // "light": if gotoMove() or gotoEnd()
-    // data: some custom data (addTime) to be re-emitted
-    play: function(move, received, light, data) {
+    play: function(move, received, light, noemit) {
+      if (!!noemit) {
+        if (this.inPlay) {
+          // Received moves in observed games can arrive too fast:
+          this.stackToPlay.unshift(move);
+          return;
+        }
+        this.inPlay = true;
+      }
       const navigate = !move;
       const playSubmove = (smove) => {
         if (!navigate) smove.notation = this.vr.getNotation(smove);
@@ -384,8 +392,15 @@ export default {
           }
           if (!navigate && this.game.mode != "analyze") {
             const L = this.moves.length;
-            // Post-processing (e.g. computer play)
-            this.$emit("newmove", this.moves[L-1], data);
+            if (!noemit)
+              // Post-processing (e.g. computer play)
+              this.$emit("newmove", this.moves[L-1]);
+            else {
+              this.inPlay = false;
+              if (this.stackToPlay.length > 0)
+                // Move(s) arrived in-between
+                this.play(this.stackToPlay.pop(), received, light, noemit);
+            }
           }
         }
       };
index 640ccca..86c2acc 100644 (file)
@@ -48,7 +48,10 @@ export default {
     });
 
     const lm = this.lastMove;
-    const showLight = this.settings.highlight && V.ShowMoves == "all";
+    const showLight = (
+      this.settings.highlight &&
+      ["all","highlight"].includes(V.ShowMoves)
+    );
     const orientation = !V.CanFlip ? "w" : this.orientation;
     // Ensure that squares colors do not change when board is flipped
     const lightSquareMod = (sizeX + sizeY) % 2;
index cc8e24c..c0a15df 100644 (file)
@@ -21,7 +21,7 @@ div
   #scoreInfo(v-if="score!='*'")
     p {{ score }}
     p {{ st.tr[message] }}
-  .moves-list(v-if="show != 'none'")
+  .moves-list(v-if="!['none','highlight'].includes(show)")
     .tr(v-for="moveIdx in evenNumbers")
       .td {{ firstNum + moveIdx / 2 + 1 }}
       .td(v-if="moveIdx < moves.length-1 || show == 'all'"
index 2e78994..480c73b 100644 (file)
@@ -84,6 +84,8 @@ export const GameStorage = {
           // Ignoring error silently: shouldn't happen now. TODO?
           if (event.target.result) {
             let game = event.target.result;
+            // Hidden tabs are delayed, to prevent multi-updates:
+            if (obj.moveIdx < game.moves.length) return;
             Object.keys(obj).forEach(k => {
               if (k == "move") game.moves.push(obj[k]);
               else game[k] = obj[k];
index e87827f..4a31fce 100644 (file)
@@ -290,4 +290,15 @@ export const VariantRules = class BenedictRules extends ChessRules {
     // Stalemate:
     return "1/2";
   }
+
+  getNotation(move) {
+    // Just remove flips:
+    const basicMove = {
+      appear: [move.appear[0]],
+      vanish: [move.vanish[0]],
+      start: move.start,
+      end: move.end
+    };
+    return super.getNotation(basicMove);
+  }
 };
index 5ccc3d7..046fe99 100644 (file)
@@ -16,9 +16,9 @@ export const VariantRules = class HiddenRules extends ChessRules {
     return false;
   }
 
-  // Moves are revealed only when game ends
+  // Moves are revealed only when game ends, but are highlighted on board
   static get ShowMoves() {
-    return "none";
+    return "highlight";
   }
 
   static get HIDDEN_DECODE() {
index fbdf4e6..9bca38e 100644 (file)
@@ -51,11 +51,23 @@ main
         p
           span.name(:class="{connected: isConnected(0)}")
             | {{ game.players[0].name || "@nonymous" }}
-          span.time(v-if="game.score=='*'") {{ virtualClocks[0] }}
+          span.time(
+            v-if="game.score=='*'"
+            :class="{yourturn: !!vr && vr.turn == 'w'}"
+          )
+            span.time-left {{ virtualClocks[0][0] }}
+            span.time-separator(v-if="!!virtualClocks[0][1]") :
+            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" }}
-          span.time(v-if="game.score=='*'") {{ virtualClocks[1] }}
+          span.time(
+            v-if="game.score=='*'"
+            :class="{yourturn: !!vr && vr.turn == 'b'}"
+          )
+            span.time-left {{ virtualClocks[1][0] }}
+            span.time-separator(v-if="!!virtualClocks[1][1]") :
+            span.time-right(v-if="!!virtualClocks[1][1]") {{ virtualClocks[1][1] }}
   BaseGame(
     ref="basegame"
     :game="game"
@@ -100,7 +112,7 @@ export default {
         chats: [],
         rendered: false
       },
-      virtualClocks: [0, 0], //initialized with true game.clocks
+      virtualClocks: [[0,0], [0,0]], //initialized with true game.clocks
       vr: null, //"variant rules" object initialized from FEN
       drawOffer: "",
       people: {}, //players + observers
@@ -111,6 +123,7 @@ export default {
       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,
@@ -243,19 +256,26 @@ export default {
     },
     askGameAgain: function() {
       this.gameIsLoading = true;
-      if (!this.gameRef.rid)
-        // This is my game: just reload.
-        this.loadGame();
-      else {
-        // Just ask fullgame again (once!), this is much simpler.
-        // If this fails, the user could just reload page :/
-        let self = this;
-        (function askIfPeerConnected() {
-          if (!!self.people[self.gameRef.rid])
-            self.send("askfullgame", { target: self.gameRef.rid });
-          else setTimeout(askIfPeerConnected, 1000);
-        })();
-      }
+      const doAskGame = () => {
+        if (!this.gameRef.rid)
+          // This is my game: just reload.
+          this.loadGame();
+        else {
+          // Just ask fullgame again (once!), this is much simpler.
+          // If this fails, the user could just reload page :/
+          let self = this;
+          (function askIfPeerConnected() {
+            if (!!self.people[self.gameRef.rid])
+              self.send("askfullgame", { target: self.gameRef.rid });
+            else setTimeout(askIfPeerConnected, 1000);
+          })();
+        }
+      };
+      // 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;
@@ -341,7 +361,7 @@ export default {
                     if (!self.gotLastate && !!self.people[user.sid])
                       askLastate();
                   },
-                  750
+                  1000
                 );
               })();
             }
@@ -415,7 +435,8 @@ export default {
           const movesCount = this.game.moves.length;
           if (movePlus.index > movesCount) {
             // This can only happen if I'm an observer and missed a move.
-            this.gotMoveIdx = movePlus.index;
+            if (this.gotMoveIdx < movePlus.index)
+              this.gotMoveIdx = movePlus.index;
             if (!this.gameIsLoading) this.askGameAgain();
           }
           else {
@@ -444,10 +465,9 @@ export default {
                   GameStorage.update(this.gameRef.id, { drawOffer: "" });
                 }
               }
-              this.$refs["basegame"].play(
+              this.$refs["basegame"].play(movePlus.move, "received", null, true);
+              this.processMove(
                 movePlus.move,
-                "received",
-                null,
                 {
                   addTime: movePlus.addTime,
                   receiveMyMove: receiveMyMove
@@ -667,7 +687,7 @@ export default {
         );
         if (this.gameIsLoading)
           // Re-load game because we missed some moves:
-          // artificially reset BaseGame (required if moves arrive too quickly)
+          // artificially reset BaseGame (required if moves arrived in wrong order)
           this.$refs["basegame"].re_setVariables();
         this.re_setClocks();
         this.$nextTick(() => {
@@ -699,7 +719,7 @@ export default {
     re_setClocks: function() {
       if (this.game.moves.length < 2 || this.game.score != "*") {
         // 1st move not completed yet, or game over: freeze time
-        this.virtualClocks = this.game.clocks.map(s => ppt(s));
+        this.virtualClocks = this.game.clocks.map(s => ppt(s).split(':'));
         return;
       }
       const currentTurn = this.vr.turn;
@@ -711,7 +731,7 @@ export default {
       this.virtualClocks = [0, 1].map(i => {
         const removeTime =
           i == colorIdx ? (Date.now() - this.game.initime[colorIdx]) / 1000 : 0;
-        return ppt(this.game.clocks[i] - removeTime);
+        return ppt(this.game.clocks[i] - removeTime).split(':');
       });
       let clockUpdate = setInterval(() => {
         if (
@@ -729,11 +749,11 @@ export default {
           this.$set(
             this.virtualClocks,
             colorIdx,
-            ppt(Math.max(0, --countdown))
+            ppt(Math.max(0, --countdown)).split(':')
           );
       }, 1000);
     },
-    // Post-process a (potentially partial) move (which was just played in BaseGame)
+    // Update variables and storage after a move:
     processMove: function(move, data) {
       if (!data) data = {};
       const moveCol = this.vr.turn;
@@ -809,15 +829,21 @@ export default {
               drawOffer: drawCode || "n"
             });
           }
-          else if (!document.hidden) {
-            // Live game: consider only the active tab
-            GameStorage.update(this.gameRef.id, {
-              fen: this.game.fen,
-              move: filtered_move,
-              clocks: this.game.clocks,
-              initime: this.game.initime,
-              drawOffer: drawCode
-            });
+          else {
+            const updateStorage = () => {
+              GameStorage.update(this.gameRef.id, {
+                fen: this.game.fen,
+                move: filtered_move,
+                moveIdx: origMovescount,
+                clocks: this.game.clocks,
+                initime: this.game.initime,
+                drawOffer: drawCode
+              });
+            };
+            // The active tab can update storage immediately
+            if (!document.hidden) updateStorage();
+            // Small random delay otherwise
+            else setTimeout(updateStorage, 500 + 1000 * Math.random());
           }
         }
         // Send move ("newmove" event) to people in the room (if our turn)
@@ -833,22 +859,25 @@ export default {
           this.opponentGotMove = false;
           this.send("newmove", {data: sendMove});
           // If the opponent doesn't reply gotmove soon enough, re-send move:
-          let retrySendmove = setInterval( () => {
-            if (this.opponentGotMove) {
-              clearInterval(retrySendmove);
-              return;
-            }
-            let oppsid = this.game.players[nextIdx].sid;
-            if (!oppsid) {
-              oppsid = Object.keys(this.people).find(
-                sid => this.people[sid].id == this.game.players[nextIdx].uid
-              );
-            }
-            if (!oppsid || !this.people[oppsid])
-              // Opponent is disconnected: he'll ask last state
-              clearInterval(retrySendmove);
-            else this.send("newmove", {data: sendMove, target: oppsid});
-          }, 750);
+          let retrySendmove = setInterval(
+            () => {
+              if (this.opponentGotMove) {
+                clearInterval(retrySendmove);
+                return;
+              }
+              let oppsid = this.game.players[nextIdx].sid;
+              if (!oppsid) {
+                oppsid = Object.keys(this.people).find(
+                  sid => this.people[sid].id == this.game.players[nextIdx].uid
+                );
+              }
+              if (!oppsid || !this.people[oppsid])
+                // Opponent is disconnected: he'll ask last state
+                clearInterval(retrySendmove);
+              else this.send("newmove", {data: sendMove, target: oppsid});
+            },
+            1000
+          );
         }
       };
       if (
@@ -939,14 +968,29 @@ export default {
   font-weight: bold
   padding-right: 10px
 
-.name
+span.name
   font-size: 1.5rem
-  padding: 1px
+  padding: 0 3px
 
-.time
+span.time
   font-size: 2rem
   display: inline-block
-  margin-left: 10px
+  .time-left
+    margin-left: 10px
+  .time-right
+    margin-left: 5px
+  .time-separator
+    margin-left: 5px
+    position: relative
+    top: -1px
+
+span.yourturn
+  color: #831B1B
+  .time-separator
+    animation: blink-animation 2s steps(3, start) infinite
+@keyframes blink-animation
+  to
+    visibility: hidden
 
 .split-names
   display: inline-block