Finish Pacosako + add GameStat table to know how many live games are played
[vchess.git] / client / src / views / Hall.vue
index 01f588f..ebb4675 100644 (file)
@@ -15,6 +15,8 @@ main
         span.variantName {{ curChallToAccept.vname }} 
         span {{ curChallToAccept.cadence }} 
         span {{ st.tr["with"] + " " + curChallToAccept.from.name }}
+      p.text-center(v-if="!!curChallToAccept.color")
+        | {{ st.tr["Your color:"] + " " + invColor(curChallToAccept.color) }}
       .diagram(
         v-if="!!curChallToAccept.fen"
         v-html="tchallDiag"
@@ -88,7 +90,13 @@ main
             type="text"
             v-model="newchallenge.to"
           )
-        fieldset(v-if="st.user.id > 0 && newchallenge.to.length > 0")
+        fieldset(v-show="st.user.id > 0 && newchallenge.to.length > 0")
+          label(for="selectColor") {{ st.tr["Color"] }}
+          select#selectColor(v-model="newchallenge.color")
+            option(value='') 
+            option(value='w') {{ st.tr["White"] }}
+            option(value='b') {{ st.tr["Black"] }}
+          br
           input#inputFen(
             placeholder="FEN"
             @input="trySetNewchallDiag()"
@@ -113,14 +121,15 @@ main
             v-for="sid in Object.keys(people)"
             v-if="!!people[sid].name"
           )
-            span {{ people[sid].name }}
+            UserBio.user-bio(:uid="people[sid].id" :uname="people[sid].name")
             button.player-action(
               v-if="isGamer(sid)"
               @click="watchGame(sid)"
             )
               | {{ st.tr["Observe"] }}
             button.player-action(
-              v-else-if="isFocusedOnHall(sid)"
+              v-else-if="st.user.sid != sid"
+              :class="{focused: isFocusedOnHall(sid)}"
               @click="challenge(sid)"
             )
               | {{ st.tr["Challenge"] }}
@@ -166,7 +175,7 @@ main
           button.tabbtn#btnClive(@click="setDisplay('c','live',$event)")
             | {{ st.tr["Live challenges"] }}
           button.tabbtn#btnCcorr(@click="setDisplay('c','corr',$event)")
-            | {{ st.tr["Correspondance challenges"] }}
+            | {{ st.tr["Correspondence challenges"] }}
         ChallengeList(
           v-show="cdisplay=='live'"
           :challenges="filterChallenges('live')"
@@ -182,7 +191,7 @@ main
           button.tabbtn#btnGlive(@click="setDisplay('g','live',$event)")
             | {{ st.tr["Live games"] }}
           button.tabbtn#btnGcorr(@click="setDisplay('g','corr',$event)")
-            | {{ st.tr["Correspondance games"] }}
+            | {{ st.tr["Correspondence games"] }}
         GameList(
           v-show="gdisplay=='live'"
           :games="filterGames('live')"
@@ -212,6 +221,7 @@ import params from "@/parameters";
 import { getRandString, shuffle, randInt } from "@/utils/alea";
 import { getDiagram } from "@/utils/printDiagram";
 import Chat from "@/components/Chat.vue";
+import UserBio from "@/components/UserBio.vue";
 import GameList from "@/components/GameList.vue";
 import ChallengeList from "@/components/ChallengeList.vue";
 import { GameStorage } from "@/utils/gameStorage";
@@ -220,6 +230,7 @@ export default {
   name: "my-hall",
   components: {
     Chat,
+    UserBio,
     GameList,
     ChallengeList
   },
@@ -238,12 +249,13 @@ export default {
       infoMessage: "",
       newchallenge: {
         fen: "",
-        vid: parseInt(localStorage.getItem("vid")) || 0,
+        vid: parseInt(localStorage.getItem("vid"), 10) || 0,
         to: "", //name of challenged player (if any)
+        color: '',
         cadence: localStorage.getItem("cadence") || "",
         randomness:
           // Warning: randomness can be 0, then !!randomness is false
-          (parseInt(localStorage.getItem("challRandomness"))+1 || 3) - 1,
+          (parseInt(localStorage.getItem("challRandomness"),10)+1 || 3) - 1,
         // VariantRules object, stored to not interfere with
         // diagrams of targetted challenges:
         V: null,
@@ -253,7 +265,7 @@ export default {
       },
       focus: true,
       tchallDiag: "",
-      curChallToAccept: {from: {}},
+      curChallToAccept: { from: {} },
       presetChalls: JSON.parse(localStorage.getItem("presetChalls") || "[]"),
       conn: null,
       connexionString: "",
@@ -299,6 +311,28 @@ export default {
     const connectAndPoll = () => {
       this.send("connect");
       this.send("pollclientsandgamers");
+      if (!!this.$route.query["challenge"]) {
+        // Automatic challenge sending, for tournaments
+        this.loadNewchallVariant(
+          () => {
+            this.newchallenge = {
+              fen: "",
+              vid:
+                this.st.variants
+                .find(v => v.name == this.$route.query["variant"])
+                .id,
+              to: this.$route.query["challenge"],
+              color: this.$route.query["color"] || '',
+              cadence: this.$route.query["cadence"],
+              // Tournament: no randomness (TODO: for now at least)
+              randomness: 0,
+              memorize: false
+            };
+            this.issueNewChallenge();
+          },
+          this.$route.query["variant"]
+        );
+      }
     };
     // Initialize connection
     this.connexionString =
@@ -407,7 +441,8 @@ export default {
                 }
               }
             );
-          } else addChallenges();
+          }
+          else addChallenges();
         }
       }
     );
@@ -452,9 +487,14 @@ export default {
       this.focus = false;
       this.send("losefocus");
     },
+    invColor: function(c) {
+      if (c == 'w') return this.st.tr["Black"];
+      return this.tr.tr["White"];
+    },
     partialResetNewchallenge: function() {
       // Reset potential target and custom FEN:
       this.newchallenge.to = "";
+      this.newchallenge.color = '';
       this.newchallenge.fen = "";
       this.newchallenge.diag = "";
       this.newchallenge.memorize = false;
@@ -497,6 +537,7 @@ export default {
       if (!this.newchallenge.to) {
         // Reset potential FEN + diagram
         this.newchallenge.fen = "";
+        this.newchallenge.color = '';
         this.newchallenge.diag = "";
       }
     },
@@ -522,9 +563,8 @@ export default {
     },
     // o: challenge or game
     classifyObject: function(o) {
-      // Consider imports as live games (TODO)
-      if (!!o.id && !!o.id.toString().match(/^i/)) return "live";
-      return o.cadence.indexOf("d") === -1 ? "live" : "corr";
+      // No imported games here
+      return (o.cadence.indexOf("d") >= 0 ? "corr" : "live");
     },
     setDisplay: function(letter, type, e) {
       this[letter + "display"] = type;
@@ -572,14 +612,12 @@ export default {
         }
       });
       const gid = gids[Math.floor(Math.random() * gids.length)];
-      const game = this.games.find(g => g.id == gid);
-      if (!!game) this.showGame(game);
-      else this.$router.push("/game/" + gid); //game vs. me
+      window.open("/#/game/" + gid, "_blank");
     },
     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
-      this.$router.push("/game/" + g.id);
+      window.open("/#/game/" + g.id, "_blank");
     },
     toggleSocialColor: function(action) {
       if (!action && document.getElementById("modalPeople").checked)
@@ -664,7 +702,8 @@ export default {
             // For self multi-connects tests:
             this.newConnect[data.from[0]] = true;
             this.send("askidentity", { target: data.from[0], page: page });
-          } else {
+          }
+          else {
             this.people[data.from[0]].tmpIds[data.from[1]] =
               { page: page, focus: true };
             this.$forceUpdate(); //TODO: shouldn't be required
@@ -696,7 +735,8 @@ export default {
                 "all"
               );
             }
-          } else {
+          }
+          else {
             // Remove the matching live game if now unreachable
             const gid = data.page.match(/[a-zA-Z0-9]+$/)[0];
             // Corr games are always reachable:
@@ -861,7 +901,7 @@ export default {
             this.startNewGame(gameInfo);
           else {
             this.infoMessage =
-              this.st.tr["New correspondance game:"] + " " +
+              this.st.tr["New correspondence game:"] + " " +
               "<a href='#/game/" + gameInfo.id + "'>" +
               "#/game/" + gameInfo.id + "</a>";
             document.getElementById("modalInfo").checked = true;
@@ -911,7 +951,8 @@ export default {
                 );
               });
               this.games = this.games.concat(moreGames);
-            } else this.hasMore = false;
+            }
+            else this.hasMore = false;
           }
         }
       );
@@ -952,8 +993,8 @@ export default {
         }
       }
     },
-    loadNewchallVariant: async function(cb) {
-      const vname = this.getVname(this.newchallenge.vid);
+    loadNewchallVariant: async function(cb, vname) {
+      vname = vname || this.getVname(this.newchallenge.vid);
       await import("@/variants/" + vname + ".js")
       .then((vModule) => {
         window.V = vModule[vname + "Rules"];
@@ -976,10 +1017,11 @@ export default {
       ) {
         const parsedFen = V.ParseFen(this.newchallenge.fen);
         this.newchallenge.diag = getDiagram({
-          position: parsedFen.position,
-          orientation: parsedFen.turn
+          position: parsedFen.position
+          //,orientation: parsedFen.turn
         });
-      } else this.newchallenge.diag = "";
+      }
+      else this.newchallenge.diag = "";
     },
     newChallFromPreset(pchall) {
       this.partialResetNewchallenge();
@@ -997,7 +1039,9 @@ export default {
       if (!this.newchallenge.vid)
         error = this.st.tr["Please select a variant"];
       else if (ctype == "corr" && this.st.user.id <= 0)
-        error = this.st.tr["Please log in to play correspondance games"];
+        error = this.st.tr["Please log in to play correspondence games"];
+      else if (!this.newchallenge.to && !!this.newchallenge.color)
+        error = this.st.tr["Color option only for targeted challenge"];
       else if (!!this.newchallenge.to) {
         if (this.newchallenge.to == this.st.user.name)
           error = this.st.tr["Self-challenge is forbidden"];
@@ -1007,8 +1051,14 @@ export default {
         ) {
           error = this.newchallenge.to + " " + this.st.tr["is not online"];
         }
+        if (
+          !!this.newchallenge.color &&
+          !['w', 'b'].includes(this.newchallenge.color)
+        ) {
+          error = this.st.tr["Wrong color"];
+        }
       }
-      if (error) {
+      if (!!error) {
         alert(error);
         return;
       }
@@ -1020,13 +1070,18 @@ export default {
       }
       // NOTE: "from" information is not required here
       let chall = Object.assign({}, this.newchallenge);
-      // Add only if not already issued (not counting target or FEN):
+      // Add only if not already issued (not counting FEN):
       if (this.challenges.some(c =>
         (
           c.from.sid == this.st.user.sid ||
           (c.from.id > 0 && c.from.id == this.st.user.id)
         )
         &&
+        (
+          (!c.to && !chall.to) ||
+          c.to == chall.to
+        )
+        &&
         c.vid == chall.vid &&
         c.cadence == chall.cadence &&
         c.randomness == chall.randomness
@@ -1104,8 +1159,9 @@ export default {
       if (ctype == "live") {
         // Live challenges have a random ID
         finishAddChallenge(null);
-      } else {
-        // Correspondance game: send challenge to server
+      }
+      else {
+        // Correspondence game: send challenge to server
         ajax(
           "/challenges",
           "POST",
@@ -1143,7 +1199,8 @@ export default {
         else
           // Corr challenge: just remove the challenge
           this.send("deletechallenge_s", { data: { cid: c.id } });
-      } else {
+      }
+      else {
         const oppsid = this.getOppsid(c);
         if (!!oppsid)
           this.send("refusechallenge", { data: c.id, target: oppsid });
@@ -1167,25 +1224,26 @@ export default {
           alert(this.st.tr["Please log in to accept corr challenges"]);
           return;
         }
-        c.accepted = true;
-        await import("@/variants/" + c.vname + ".js")
-        .then((vModule) => {
-          window.V = vModule[c.vname + "Rules"];
-          if (!!c.to) {
-            // c.to == this.st.user.name (connected)
-            if (!!c.fen) {
-              const parsedFen = V.ParseFen(c.fen);
-              c.mycolor = V.GetOppCol(parsedFen.turn);
-              this.tchallDiag = getDiagram({
-                position: parsedFen.position,
-                orientation: c.mycolor
-              });
+        else {
+          c.accepted = true;
+          await import("@/variants/" + c.vname + ".js")
+          .then((vModule) => {
+            window.V = vModule[c.vname + "Rules"];
+            if (!!c.to) {
+              // c.to == this.st.user.name (connected)
+              if (!!c.fen) {
+                const parsedFen = V.ParseFen(c.fen);
+                this.tchallDiag = getDiagram({
+                  position: parsedFen.position,
+                  orientation: parsedFen.turn
+                });
+              }
+              this.curChallToAccept = c;
+              document.getElementById("modalAccept").checked = true;
             }
-            this.curChallToAccept = c;
-            document.getElementById("modalAccept").checked = true;
-          }
-          else this.finishProcessingChallenge(c);
-        });
+            else this.finishProcessingChallenge(c);
+          });
+        }
       }
       else {
         // My challenge
@@ -1205,8 +1263,8 @@ export default {
     launchGame: function(c) {
       // White player index 0, black player index 1:
       let players =
-        !!c.mycolor
-          ? (c.mycolor == "w" ? [c.seat, c.from] : [c.from, c.seat])
+        !!c.color
+          ? (c.color == "w" ? [c.from, c.seat] : [c.seat, c.from])
           : shuffle([c.from, c.seat]);
       players.forEach(p => {
         if (!!p["tmpIds"]) delete p["tmpIds"];
@@ -1249,7 +1307,14 @@ export default {
       if (c.type == "live") {
         notifyNewgame();
         this.startNewGame(gameInfo);
-      } else {
+        // Increment game stats counter in DB
+        ajax(
+          "/gamestat",
+          "POST",
+          { data: { vid: gameInfo.vid } }
+        );
+      }
+      else {
         // corr: game only on server
         ajax(
           "/games",
@@ -1290,22 +1355,22 @@ export default {
         () => {
           const myIdx = (game.players[0].sid == this.st.user.sid ? 0 : 1);
           GameStorage.add(game, (err) => {
-            // If an error occurred, game is not added: a tab already
-            // added the game. Maybe a focused one, maybe not.
-            // We know for sure that it emitted the gong start sound.
-            // ==> Do not play it again.
-            if (!err && this.st.settings.sound)
-              new Audio("/sounds/newgame.flac").play().catch(() => {});
+            // If an error occurred, game is not added: the focused tab
+            // already added the game.
             if (!this.focus) {
+              if (this.st.settings.sound)
+                // This will be played several times if several hidden tabs
+                // on Hall... TODO: fix that (how ?!)
+                new Audio("/sounds/newgame.flac").play().catch(() => {});
               notify(
                 "New live game",
-                { body: "vs " + game.players[1-myIdx].name || "@nonymous" }
+                { body: "vs " + (game.players[1-myIdx].name || "@nonymous") }
               );
             }
             this.$router.push("/game/" + gameInfo.id);
           });
         },
-        this.focus ? 500 + 1000 * Math.random() : 0
+        this.focus ? 0 : 500 + 1000 * Math.random()
       );
     }
   }
@@ -1314,7 +1379,7 @@ export default {
 
 <style lang="sass" scoped>
 .active
-  color: #42a983
+  color: #388e3c
 
 #infoDiv > .card
   padding: 15px 0
@@ -1366,9 +1431,11 @@ div#peopleWrap > .card
 
 button.player-action
   margin-left: 32px
+  &.focused
+    background-color: #E6D271
 
 .somethingnew
-  background-color: #c5fefe !important
+  background-color: #90C4EC !important
 
 .tabbtn
   background-color: #f9faee
@@ -1402,6 +1469,9 @@ button.refuseBtn
   #div2, #div3
     margin-top: 0
 
+.user-bio
+  display: inline
+
 tr > td
   &.random-0
     background-color: #FF5733