TODO: finish draw offer logic + fix inCheck bug (no highlight)
[vchess.git] / client / src / views / Game.vue
index 30fbc39..ec1962e 100644 (file)
@@ -1,25 +1,33 @@
 <template lang="pug">
 main
-  input#modalChat.modal(type="checkbox" @change="toggleChat")
-  div#chatWrap(role="dialog" data-checkbox="modalChat"
-      aria-labelledby="inputChat")
+  input#modalChat.modal(type="checkbox" @click="resetChatColor")
+  div#chatWrap(role="dialog" data-checkbox="modalChat" aria-labelledby="inputChat")
     #chat.card
       label.modal-close(for="modalChat")
+      #participants
+        span {{ Object.keys(people).length }} st.tr["participant(s):"] 
+        span(v-for="p in Object.values(people)" v-if="!!p.name")
+          | {{ p.name }} 
+        span.anonymous(v-if="Object.values(people).some(p => !p.name)")
+          | + @nonymous
       Chat(:players="game.players" :pastChats="game.chats"
         @newchat-sent="finishSendChat" @newchat-received="processChat")
   .row
     #aboveBoard.col-sm-12.col-md-9.col-md-offset-3.col-lg-10.col-lg-offset-2
       button#chatBtn(onClick="doClick('modalChat')") Chat
-      #actions(v-if="game.mode!='analyze' && game.score=='*'")
-        button(@click="clickDraw" :class="{['draw-' + drawOffer]: true}") Draw
-        button(@click="abortGame") Abort
-        button(@click="resign") Resign
+      #actions(v-if="game.score=='*'")
+        button(@click="clickDraw" :class="{['draw-' + drawOffer]: true}")
+          | {{ st.tr["Draw"] }}
+        button(@click="abortGame") {{ st.tr["Abort"] }}
+        button(@click="resign") {{ st.tr["Resign"] }}
       #playersInfo
         p
-          span.name(:class="{connected: isConnected(0)}") {{ game.players[0].name }}
+          span.name(:class="{connected: isConnected(0)}")
+            | {{ game.players[0].name || "@nonymous" }}
           span.time(v-if="game.score=='*'") {{ virtualClocks[0] }}
           span.split-names -
-          span.name(:class="{connected: isConnected(1)}") {{ game.players[1].name }}
+          span.name(:class="{connected: isConnected(1)}")
+            | {{ game.players[1].name || "@nonymous" }}
           span.time(v-if="game.score=='*'") {{ virtualClocks[1] }}
   BaseGame(:game="game" :vr="vr" ref="basegame"
     @newmove="processMove" @gameover="gameOver")
@@ -86,7 +94,7 @@ export default {
         {
           clearInterval(clockUpdate);
           if (countdown < 0)
-            this.gameOver(this.vr.turn=="w" ? "0-1" : "1-0", "Time");
+            this.gameOver(this.vr.turn=="w" ? "0-1" : "1-0", this.st.tr["Time"]);
         }
         else
         {
@@ -135,6 +143,9 @@ export default {
   methods: {
     // O.1] Ask server for room composition:
     roomInit: function() {
+      // Notify the room only now that I connected, because
+      // messages might be lost otherwise (if game loading is slow)
+      this.st.conn.send(JSON.stringify({code:"connect"}));
       this.st.conn.send(JSON.stringify({code:"pollclients"}));
     },
     isConnected: function(index) {
@@ -148,14 +159,12 @@ export default {
       switch (data.code)
       {
         case "duplicate":
-          alert("Warning: duplicate 'offline' connection");
+          alert(this.st.tr["Warning: multi-tabs not supported"]);
           break;
         // 0.2] Receive clients list (just socket IDs)
         case "pollclients":
         {
           data.sockIds.forEach(sid => {
-            // TODO: understand clearly what happens here, problems when a
-            // game is quit, and then launch a new game from hall.
             if (!!this.people[sid])
               return;
             this.$set(this.people, sid, {id:0, name:""});
@@ -182,13 +191,12 @@ export default {
         }
         case "identity":
         {
-          let player = this.people[data.user.sid];
           // NOTE: sometimes player.id fails because player is undefined...
           // Probably because the event was meant for Hall?
-          if (!player)
+          if (!this.people[data.user.sid])
             return;
-          player.id = data.user.id;
-          player.name = data.user.name;
+          this.$set(this.people, data.user.sid,
+            {id: data.user.id, name: data.user.name});
           // Sending last state only for live games: corr games are complete
           if (this.game.type == "live" && this.game.oppsid == data.user.sid)
           {
@@ -212,12 +220,14 @@ export default {
           break;
         }
         case "askgame":
-          // Send current (live) game
+          // Send current (live) game if not asked by opponent (!)
+          if (this.game.players.some(p => p.sid == data.from))
+            return;
           const myGame =
           {
             // Minimal game informations:
             id: this.game.id,
-            players: this.game.players.map(p => { return {name:p.name}; }),
+            players: this.game.players,
             vid: this.game.vid,
             timeControl: this.game.timeControl,
           };
@@ -225,7 +235,7 @@ export default {
             game:myGame, target:data.from}));
           break;
         case "newmove":
-          this.$set(this.game, "moveToPlay", data.move); //TODO: Vue3...
+          this.$set(this.game, "moveToPlay", data.move);
           break;
         case "lastate": //got opponent infos about last move
         {
@@ -236,19 +246,21 @@ export default {
           break;
         }
         case "resign":
-          this.gameOver(data.side=="b" ? "1-0" : "0-1", "Resign");
+          this.gameOver(data.side=="b" ? "1-0" : "0-1", this.st.tr["Resign"]);
           break;
         case "abort":
-          this.gameOver("?", "Abort");
+          this.gameOver("?", this.st.tr["Abort"]);
           break;
         case "draw":
-          this.gameOver("1/2", data.message);
+          this.gameOver("1/2", this.st.tr[data.message]);
           break;
         case "drawoffer":
-          this.drawOffer = "received"; //TODO: observers don't know who offered draw
+          // NOTE: observers don't know who offered draw
+          this.drawOffer = "received";
           break;
         case "askfullgame":
-          this.st.conn.send(JSON.stringify({code:"fullgame", game:this.game, target:data.from}));
+          this.st.conn.send(JSON.stringify({code:"fullgame",
+            game:this.game, target:data.from}));
           break;
         case "fullgame":
           // Callback "roomInit" to poll clients only after game is loaded
@@ -292,7 +304,7 @@ export default {
     clickDraw: function() {
       if (["received","threerep"].includes(this.drawOffer))
       {
-        if (!confirm("Accept draw?"))
+        if (!confirm(this.st.tr["Accept draw?"]))
           return;
         const message = (this.drawOffer == "received"
           ? "Mutual agreement"
@@ -304,31 +316,26 @@ export default {
               message:message, target:sid}));
           }
         });
-        this.gameOver("1/2", message);
-      }
-      else if (this.drawOffer == "sent")
-      {
-        this.drawOffer = "";
-        if (this.game.type == "corr")
-          GameStorage.update(this.gameRef.id, {drawOffer: false});
+        this.gameOver("1/2", this.st.tr[message]);
       }
-      else
+      else if (this.drawOffer == "") //no effect if drawOffer == "sent"
       {
-        if (!confirm("Offer draw?"))
+        if (this.game.mycolor != this.vr.turn)
+          return alert(this.st.tr["Draw offer only in your turn"]);
+        if (!confirm(this.st.tr["Offer draw?"]))
           return;
         this.drawOffer = "sent";
         Object.keys(this.people).forEach(sid => {
           if (sid != this.st.user.sid)
             this.st.conn.send(JSON.stringify({code:"drawoffer", target:sid}));
         });
-        if (this.game.type == "corr")
-          GameStorage.update(this.gameRef.id, {drawOffer: true});
+        GameStorage.update(this.gameRef.id, {drawOffer: this.game.mycolor});
       }
     },
     abortGame: function() {
       if (!confirm(this.st.tr["Terminate game?"]))
         return;
-      this.gameOver("?", "Abort");
+      this.gameOver("?", this.st.tr["Abort"]);
       Object.keys(this.people).forEach(sid => {
         if (sid != this.st.user.sid)
         {
@@ -340,7 +347,7 @@ export default {
       });
     },
     resign: function(e) {
-      if (!confirm("Resign the game?"))
+      if (!confirm(this.st.tr["Resign the game?"]))
         return;
       Object.keys(this.people).forEach(sid => {
         if (sid != this.st.user.sid)
@@ -349,7 +356,7 @@ export default {
             side:this.game.mycolor, target:sid}));
         }
       });
-      this.gameOver(this.game.mycolor=="w" ? "0-1" : "1-0", "Resign");
+      this.gameOver(this.game.mycolor=="w" ? "0-1" : "1-0", this.st.tr["Resign"]);
     },
     // 3 cases for loading a game:
     //  - from indexedDB (running or completed live game I play)
@@ -391,8 +398,6 @@ export default {
             }
             if (L >= 1)
               game.initime[L%2] = game.moves[L-1].played;
-            if (game.drawOffer)
-              this.drawOffer = "received";
           }
           // Now that we used idx and played, re-format moves as for live games
           game.moves = game.moves.map( (m) => {
@@ -427,6 +432,22 @@ export default {
             }
           }
         }
+
+
+
+        // TODO: (and also when receiving / sending a move ?)
+//        if (!!game.drawOffer)
+//        {
+//          if (game.drawOffer == "w")
+//          {
+//            if (myIdx == 0)
+//            {
+//              this.drawOffer = "sent";
+
+
+
+
+
         this.game = Object.assign({},
           game,
           // NOTE: assign mycolor here, since BaseGame could also be VS computer
@@ -550,19 +571,20 @@ export default {
       if (this.repeat[repIdx] >= 3)
         this.drawOffer = "threerep";
     },
-    toggleChat: function() {
+    resetChatColor: function() {
+      // TODO: this is called twice, once on opening an once on closing
       document.getElementById("chatBtn").style.backgroundColor = "#e2e2e2";
     },
     finishSendChat: function(chat) {
-      if (this.game.type == "corr")
+      // NOTE: anonymous chats in corr games are not stored on server (TODO?)
+      if (this.game.type == "corr" && this.st.user.id > 0)
         GameStorage.update(this.gameRef.id, {chat: chat});
     },
     processChat: function() {
-      if (!document.getElementById("inputChat").checked)
+      if (!document.getElementById("modalChat").checked)
         document.getElementById("chatBtn").style.backgroundColor = "#c5fefe";
     },
     gameOver: function(score, scoreMsg) {
-      this.game.mode = "analyze";
       this.game.score = score;
       this.game.scoreMsg = scoreMsg;
       const myIdx = this.game.players.findIndex(p => {
@@ -578,10 +600,17 @@ export default {
 };
 </script>
 
-<style lang="sass">
+<style lang="sass" scoped>
 .connected
   background-color: lightgreen
 
+#participants
+  margin-left: 5px
+
+.anonymous
+  color: grey
+  font-style: italic
+
 @media screen and (min-width: 768px)
   #actions
     width: 300px
@@ -600,6 +629,9 @@ export default {
 @media screen and (max-width: 767px)
   #aboveBoard
     text-align: center
+@media screen and (min-width: 768px)
+  #aboveBoard
+    margin-left: 30%
 
 .name
   font-size: 1.5rem