socket rooms correspnding to pages. TODO: Hall+Game (split live and corr? shared...
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 4 Dec 2019 10:26:36 +0000 (11:26 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 4 Dec 2019 10:26:36 +0000 (11:26 +0100)
client/src/App.vue
client/src/router.js
client/src/views/About.vue [new file with mode: 0644]
client/src/views/Game.vue
client/src/views/Hall.vue
client/src/views/Rules.vue
server/db/create.sql
server/sockets.js

index c5008f2..a9c1d4c 100644 (file)
     .row
       .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
         footer
-          a(href="https://github.com/yagu0/vchess") {{ st.tr["Source code"] }}
+          router-link.menuitem(to="/about") {{ st.tr["About"] }}
           p.clickable(onClick="doClick('modalContact')")
-            | {{ st.tr["Contact form"] }}
-  //my-game(:game-ref="gameRef" :mode="mode" :settings="settings" @game-over="archiveGame")
-  //// TODO: add only the necessary icons to mini-css custom build
+            | {{ st.tr["Contact"] }}
+  // TODO: add only the necessary icons to mini-css custom build
   //script(src="//unpkg.com/feather-icons")
 </template>
 
@@ -62,10 +61,6 @@ export default {
       st: store.state,
     };
   },
-//    // TODO: $route: ...
-//    gameRef: function() {
-//      this.loadGame();
-//    },
   computed: {
     flagImage: function() {
       return `/images/flags/${this.st.lang}.svg`;
@@ -151,7 +146,7 @@ footer
   display: inline-flex
   align-items: center
   justify-content: center
-  & > a
+  & > .menuitem
     display: inline-block
     margin: 0 10px 0 0
     &:link
index 184fede..380ccc6 100644 (file)
@@ -46,7 +46,7 @@ const router = new Router({
               localStorage["myname"] = res.name;
               localStorage["myid"] = res.id;
             }
-            next();
+            next("/");
           }
         );
       },
@@ -58,30 +58,26 @@ const router = new Router({
       name: "game",
       component: loadView("Game"),
     },
-//    {
-//      path: "/about",
-//      name: "about",
-//      // route level code-splitting
-//      // this generates a separate chunk (about.[hash].js) for this route
-//      // which is lazy-loaded when the route is visited.
-//      component: loadView('About'),
-//                             //function() {
-//        //   return import(/* webpackChunkName: "about" */ "./views/About.vue");
-//                             //}
-//    },
-    // TODO: gameRef, problemId: https://router.vuejs.org/guide/essentials/dynamic-matching.html
+    {
+      path: "/about",
+      name: "about",
+      component: loadView("About"),
+    },
+    // TODO: myGames, problemId: https://router.vuejs.org/guide/essentials/dynamic-matching.html
   ]
 });
 
 router.beforeEach((to, from, next) => {
-  window.scrollTo(0, 0); //TODO: check if a live game is running; if yes, call next('/game')
-  //https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
+  window.scrollTo(0, 0);
+  if (!!store.state.conn) //uninitialized at first page
+  {
+    // Notify WebSockets server (TODO: path or fullPath?)
+    store.state.conn.send(JSON.stringify({code: "pagechange", page: to.path}));
+  }
   next();
-    //TODO: si une partie en cours dans storage, rediriger vers cette partie
-    //(à condition que l'URL n'y corresponde pas déjà !)
-    // (l'identifiant de l'utilisateur si connecté)
-//    if (!!localStorage["variant"])
-//      location.hash = "#game?id=" + localStorage["gameId"];
+  // TODO?: redirect to current game (through GameStorage.getCurrent()) if any?
+  // (and if the URL doesn't already match it) (use next("/game/GID"))
+  //https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
 });
 
 export default router;
diff --git a/client/src/views/About.vue b/client/src/views/About.vue
new file mode 100644 (file)
index 0000000..e4b7637
--- /dev/null
@@ -0,0 +1,36 @@
+<template lang="pug">
+.row
+  .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
+    p TODO: give github URL, tell website story...
+    a(href="https://github.com/yagu0/vchess") contribute...
+</template>
+
+<style lang="sass">
+.warn
+  padding: 3px
+  color: red
+  background-color: lightgrey
+  font-weight: bold
+
+p.boxed
+  background-color: #FFCC66
+  padding: 5px
+
+.stageDelimiter
+  color: purple
+
+.section-title
+  padding: 0
+
+.section-title > h4
+  padding: 5px
+
+ol, ul:not(.browser-default)
+  padding-left: 20px
+
+ul:not(.browser-default)
+  margin-top: 5px
+
+ul:not(.browser-default) > li
+  list-style-type: disc
+</style>
index 74dd01d..d74002d 100644 (file)
 // ==> après, implémenter/vérifier les passages de challenges + parties en cours
 // observer,
 // + problèmes, habiller et publier. (+ corr...)
-    // TODO: how to know who is observing ? Send message to everyone with game ID ?
-    // and then just listen to (dis)connect events
-    // server always send "connect on " + URL ; then add to observers if game...
-// router when access a game page tell to server I joined + game ID (no need rid)
-// and ask server for current joined (= observers)
 // when send to chat (or a move), reach only this group (send gid along)
-// -> doivent être enregistrés comme observers au niveau du serveur...
-    // non: poll users + events startObserving / stopObserving
-    // (à faire au niveau du routeur ?)
 -->
 
 <script>
@@ -60,13 +52,12 @@ export default {
         id: "",
         rid: ""
       },
-      game: { }, //passed to BaseGame
-      oppConnected: false, //TODO: use for styling
+      game: {}, //passed to BaseGame
       corrMsg: "", //to send offline messages in corr games
       virtualClocks: [0, 0], //initialized with true game.clocks
       vr: null, //"variant rules" object initialized from FEN
       drawOffer: "", //TODO: use for button style
-      people: [ ], //potential observers (TODO)
+      people: [], //players + observers
     };
   },
   watch: {
@@ -123,6 +114,11 @@ export default {
         this.game.vname = variantArray.filter(v => v.id == this.game.vid)[0].name;
     },
   },
+  computed: {
+    oppConnected: function() {
+      return this.people.indexOf(p => p.id == this.game.oppid) >= 0;
+    },
+  },
   created: function() {
     if (!!this.$route.params["id"])
     {
@@ -312,27 +308,34 @@ export default {
         if (!game.fen)
           game.fen = game.fenStart; //game wasn't started
         const gtype = (game.timeControl.indexOf('d') >= 0 ? "corr" : "live");
+        const tc = extractTime(game.timeControl);
         if (gtype == "corr")
         {
           // corr game: needs to compute the clocks + initime
-          //if (game.players[i].rtime < 0) initime = Date.now(), else compute,
-          //also using move.played fields
-          game.clocks = [-1, -1];
+          game.clocks = [tc.mainTime, tc.mainTime];
           game.initime = [0, 0];
-          // TODO: compute clocks + initime
+          let addTime = [0, 0];
+          for (let i=2; i<game.moves.length; i++)
+          {
+            addTime[i%2] += tc.increment -
+              (game.moves[i].played - game.moves[i-1].played);
+          }
+          for (let i=0; i<=1; i++)
+            game.clocks[i] += addTime[i];
+          const L = game.moves.length;
+          game.initime[L%2] = game.moves[L-1].played;
         }
-        const tc = extractTime(game.timeControl);
         // TODO: this is not really beautiful (uid on corr players...)
         if (gtype == "corr" && game.players[0].color == "b")
           [ game.players[0], game.players[1] ] = [ game.players[1], game.players[0] ];
         const myIdx = game.players.findIndex(p => {
           return p.sid == this.st.user.sid || p.uid == this.st.user.id;
         });
-        if (game.clocks[0] < 0) //game unstarted
+        if (gtype == "live" && game.clocks[0] < 0) //game unstarted
         {
           game.clocks = [tc.mainTime, tc.mainTime];
           game.initime[0] = Date.now();
-          if (myIdx >= 0 && gtype == "live")
+          if (myIdx >= 0)
           {
             // I play in this live game; corr games don't have clocks+initime
             GameStorage.update(game.id,
index 547395d..a8dfa98 100644 (file)
@@ -90,7 +90,7 @@ export default {
       gdisplay: "live",
       games: [],
       challenges: [],
-      people: [], //(all) online players
+      people: [], //people in main hall
       infoMessage: "",
       newchallenge: {
         fen: "",
@@ -581,7 +581,7 @@ export default {
       const game = Object.assign({}, gameInfo, {
         // (other) Game infos: constant
         fenStart: gameInfo.fen,
-        created: Date.now(),
+        added: Date.now(),
         // Game state (including FEN): will be updated
         moves: [],
         clocks: [-1, -1], //-1 = unstarted
index f626cc9..f196e35 100644 (file)
@@ -132,34 +132,34 @@ figure.diagram-container
     padding-top: 5px
     font-size: 0.8em
 
-  p.boxed
-    background-color: #FFCC66
-    padding: 5px
+p.boxed
+  background-color: #FFCC66
+  padding: 5px
 
-  .stageDelimiter
-    color: purple
+.stageDelimiter
+  color: purple
 
-  // To show (new) pieces, and/or there values...
-  figure.showPieces > img
-    width: 50px
+// To show (new) pieces, and/or there values...
+figure.showPieces > img
+  width: 50px
 
-  figure.showPieces > figcaption
-    color: #6C6C6C
+figure.showPieces > figcaption
+  color: #6C6C6C
 
-  .section-title
-    padding: 0
+.section-title
+  padding: 0
 
-  .section-title > h4
-    padding: 5px
+.section-title > h4
+  padding: 5px
 
-  ol, ul:not(.browser-default)
-    padding-left: 20px
+ol, ul:not(.browser-default)
+  padding-left: 20px
 
-  ul:not(.browser-default)
-    margin-top: 5px
+ul:not(.browser-default)
+  margin-top: 5px
 
-  ul:not(.browser-default) > li
-    list-style-type: disc
+ul:not(.browser-default) > li
+  list-style-type: disc
 
 .light-square-diag
   background-color: #e5e5ca
index 0fb7456..3cfcbae 100644 (file)
@@ -43,7 +43,7 @@ create table Challenges (
   foreign key (vid) references Variants(id)
 );
 
--- NOTE: no need for a "created" field, it's deduce from first move playing time
+-- NOTE: no need for a "created" field, it's deduced from first move playing time
 create table Games (
   id integer primary key,
   vid integer,
@@ -59,7 +59,6 @@ create table Players (
   gid integer,
   uid integer,
   color character,
-  rtime integer, --remaining time in milliseconds
   foreign key (gid) references Games(id),
   foreign key (uid) references Users(id)
 );
index 04422f4..fe82f6e 100644 (file)
@@ -20,12 +20,14 @@ module.exports = function(wss) {
     // TODO: later, allow duplicate connections (shouldn't be much more complicated)
     if (!!clients[sid])
       return socket.send(JSON.stringify({code:"duplicate"}));
-    clients[sid] = socket;
-    // Notify room:
-    Object.keys(clients).forEach(k => {
-      if (k != sid)
-        clients[k].send(JSON.stringify({code:"connect",sid:sid}));
-    });
+    clients[sid] = {sock: socket, page: query["page"]};
+    const notifyRoom = (page,code) => {
+      Object.keys(clients).forEach(k => {
+        if (k != sid && clients[k].page == page)
+          clients[k].sock.send(JSON.stringify({code:code,sid:sid}));
+      });
+    };
+    notifyRoom(query["page"],"connect");
     socket.on("message", objtxt => {
       let obj = JSON.parse(objtxt);
       if (!!obj.target && !clients[obj.target])
@@ -34,50 +36,56 @@ module.exports = function(wss) {
       {
         case "pollclients":
           socket.send(JSON.stringify({code:"pollclients",
-            sockIds:Object.keys(clients).filter(k => k != sid)}));
+            sockIds: Object.keys(clients).filter(k =>
+              k != sid && clients[k].page == obj.page)}));
+          break;
+        case "pagechange":
+          notifyRoom(clients[sid].page, "disconnect");
+          clients[sid].page = obj.page;
+          notifyRoom(obj.page, "connect");
           break;
         case "askidentity":
-          clients[obj.target].send(
+          clients[obj.target].sock.send(
             JSON.stringify({code:"askidentity",from:sid}));
           break;
         case "askchallenge":
-          clients[obj.target].send(
+          clients[obj.target].sock.send(
             JSON.stringify({code:"askchallenge",from:sid}));
           break;
         case "askgame":
-          clients[obj.target].send(
+          clients[obj.target].sock.send(
             JSON.stringify({code:"askgame",from:sid}));
           break;
         case "identity":
-          clients[obj.target].send(
+          clients[obj.target].sock.send(
             JSON.stringify({code:"identity",user:obj.user}));
           break;
         case "refusechallenge":
-          clients[obj.target].send(
+          clients[obj.target].sock.send(
             JSON.stringify({code:"refusechallenge", cid:obj.cid, from:sid}));
           break;
         case "deletechallenge":
-          clients[obj.target].send(
+          clients[obj.target].sock.send(
             JSON.stringify({code:"deletechallenge", cid:obj.cid, from:sid}));
           break;
         case "newgame":
-          clients[obj.target].send(JSON.stringify(
+          clients[obj.target].sock.send(JSON.stringify(
             {code:"newgame", gameInfo:obj.gameInfo, cid:obj.cid}));
           break;
         case "challenge":
-          clients[obj.target].send(JSON.stringify(
+          clients[obj.target].sock.send(JSON.stringify(
             {code:"challenge", chall:obj.chall, from:sid}));
           break;
         case "game":
-          clients[obj.target].send(JSON.stringify(
+          clients[obj.target].sock.send(JSON.stringify(
             {code:"game", game:obj.game, from:sid}));
           break;
         case "newchat":
-          clients[obj.target].send(JSON.stringify({code:"newchat",msg:obj.msg}));
+          clients[obj.target].sock.send(JSON.stringify({code:"newchat",msg:obj.msg}));
           break;
         // TODO: WebRTC instead in this case (most demanding?)
         case "newmove":
-          clients[obj.target].send(JSON.stringify({code:"newmove",move:obj.move}));
+          clients[obj.target].sock.send(JSON.stringify({code:"newmove",move:obj.move}));
           break;
         case "ping":
           // If this code is reached, then obj.target is connected
@@ -85,29 +93,27 @@ module.exports = function(wss) {
           break;
         case "lastate":
           const oppId = obj.target;
-          obj.oppid = sid; //I'm the opponent of my opponent(s)
-          clients[oppId].send(JSON.stringify(obj));
+          obj.oppid = sid; //I'm the opponent of my opponent
+          clients[oppId].sock.send(JSON.stringify(obj));
           break;
         case "resign":
-          clients[obj.target].send(JSON.stringify({code:"resign"}));
+          clients[obj.target].sock.send(JSON.stringify({code:"resign"}));
           break;
         case "abort":
-          clients[obj.target].send(JSON.stringify({code:"abort",msg:obj.msg}));
+          clients[obj.target].sock.send(JSON.stringify({code:"abort",msg:obj.msg}));
           break;
         case "drawoffer":
-          clients[obj.target].send(JSON.stringify({code:"drawoffer"}));
+          clients[obj.target].sock.send(JSON.stringify({code:"drawoffer"}));
           break;
         case "draw":
-          clients[obj.target].send(JSON.stringify({code:"draw"}));
+          clients[obj.target].sock.send(JSON.stringify({code:"draw"}));
           break;
       }
     });
     socket.on("close", () => {
+      const page = clients[sid].page;
       delete clients[sid];
-      // Notify every other connected client
-      Object.keys(clients).forEach( k => {
-        clients[k].send(JSON.stringify({code:"disconnect",sid:sid}));
-      });
+      notifyRoom(page, "disconnect");
     });
   });
 }