Fix a few things: ready to work on basic live games
[vchess.git] / client / src / views / Game.vue
index d0e601c..22cf5bf 100644 (file)
@@ -1,80 +1,88 @@
 <!-- TODO: component Game, + handle players + observers connect/disconnect
   event = "gameconnect" ...etc
   connect/disconnect with sid+name (ID not required); name slightly redundant but easier
-
 quand on arrive dans la partie, on poll les sids pour savoir qui est en ligne (ping)
 (éventuel échange lastate avec les connectés, pong ...etc)
 ensuite quand qqun se deco il suffit d'écouter "disconnect"
 pareil quand quelqu'un reco.
 (c'est assez rudimentaire et écoute trop de messages, mais dans un premier temps...)
       // TODO: [in game] send move + elapsed time (in milliseconds); in case of "lastate" message too
+// TODO: if I'm an observer and player(s) disconnect/reconnect, how to find me ?
+//      onClick :: ask full game to remote player, and register as an observer in game
+//      (use gameId to communicate)
+//      on landing on game :: if gameId not found locally, check remotely
+//      ==> il manque un param dans game : "remoteId"
 -->
 <template lang="pug">
 .row
   .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
-    BaseGame(:variant="variant.name" @game-over=".....TODO")
-
-
-
-localStorage["score"] = score;
-
-
-    .button-group(v-if="mode!='analyze'")
+    BaseGame(:game="game" :vr="vr" ref="basegame" @newmove="processMove")
+    .button-group(v-if="game.mode!='analyze'")
       button(@click="offerDraw") Draw
       button(@click="abortGame") Abort
       button(@click="resign") Resign
-    div(v-if="mode=='corr'")
-      textarea(v-show="score=='*' && vr.turn==mycolor" v-model="corrMsg")
+    div(v-if="game.mode=='corr'")
+      textarea(v-show="score=='*' && vr.turn==game.mycolor" v-model="corrMsg")
       div(v-show="cursor>=0") {{ moves[cursor].message }}
 </template>
 
 <script>
-// TODO: if I'm an observer and player(s) disconnect/reconnect, how to find me ?
-//      onClick :: ask full game to remote player, and register as an observer in game
-//      (use gameId to communicate)
-//      on landing on game :: if gameId not found locally, check remotely
-//      ==> il manque un param dans game : "remoteId"
-import Board from "@/components/Board.vue";
+import BaseGame from "@/components/BaseGame.vue";
 //import Chat from "@/components/Chat.vue";
 //import MoveList from "@/components/MoveList.vue";
 import { store } from "@/store";
+import { GameStorage } from "@/utils/storage";
 
 export default {
   name: 'my-game',
   components: {
     BaseGame,
   },
-  // gameId: to find the game in storage (assumption: it exists)
-  // mode: "live" or "corr" (correspondance game), or "analyze"
-  // gameRef in URL hash (listen for changes)
-  props: ["gid","mode","variant"],
+  // gameRef: to find the game in (potentially remote) storage
   data: function() {
     return {
       st: store.state,
-      myname: store.state.user.name, //may be anonymous (thus no name)
-      opponents: {}, //filled later (potentially 2 or 3 opponents)
-      drawOfferSent: false, //did I just ask for draw?
-      people: [], //observers
+      gameRef: { //given in URL (rid = remote ID)
+        id: "",
+        rid: ""
+      },
+      game: { }, //passed to BaseGame
+      vr: null, //"variant rules" object initialized from FEN
+      drawOfferSent: false, //did I just ask for draw? (TODO: draw variables?)
+      people: [ ], //potential observers (TODO)
     };
   },
   watch: {
-    gid: function() {
-      this.launchGame();
+    '$route' (to, from) {
+      if (!!to.params["id"])
+      {
+        this.gameRef.id = to.params["id"];
+        this.gameRef.rid = to.query["rid"];
+        this.loadGame();
+      }
     },
   },
-  // Modal end of game, and then sub-components
   created: function() {
-    if (!!this.gid)
-      this.launchGame();
-    // TODO: if I'm one of the players in game, then:
-    // Send ping to server (answer pong if opponent[s] is connected)
-    if (true && !!this.conn && !!this.gameRef)
+    if (!!this.$route.params["id"])
     {
-      this.conn.onopen = () => {
-        this.conn.send(JSON.stringify({
-          code:"ping",oppid:this.oppid,gameId:this.gameRef.id}));
-      };
+      this.gameRef.id = this.$route.params["id"];
+      this.gameRef.rid = this.$route.query["rid"];
+      this.loadGame();
     }
+    // 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...
+    // detect multiple tabs connected (when connect ask server if my SID is already in use)
+// 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
+
+
     // TODO: also handle "draw accepted" (use opponents array?)
     // --> must give this info also when sending lastState...
     // and, if all players agree then OK draw (end game ...etc)
@@ -83,17 +91,20 @@ export default {
       let L = undefined;
       switch (data.code)
       {
-        case "newmove": //..he played!
-          this.play(data.move, this.variant.name!="Dark" ? "animate" : null);
+        case "newmove":
+          // TODO: observer on dark games must see all board ? Or alternate ? (seems better)
+          // ...or just see nothing as on buho21
+          this.$refs["basegame"].play(
+            data.move, this.game.vname!="Dark" ? "animate" : null);
           break;
         case "pong": //received if we sent a ping (game still alive on our side)
           if (this.gameRef.id != data.gameId)
-            break; //games IDs don't match: definitely over...
+            break; //games IDs don't match: the game is definitely over...
           this.oppConnected = true;
           // Send our "last state" informations to opponent(s)
           L = this.vr.moves.length;
           Object.keys(this.opponents).forEach(oid => {
-            this.conn.send(JSON.stringify({
+            this.st.conn.send(JSON.stringify({
               code: "lastate",
               oppid: oid,
               gameId: this.gameRef.id,
@@ -102,7 +113,7 @@ export default {
             }));
           });
           break;
-        // TODO: refactor this, because at 3 or 4 players we may have missed 2 or 3 moves (not just one)
+        // TODO: refactor this, because at 3 or 4 players we may have missed 2 or 3 moves
         case "lastate": //got opponent infos about last move
           L = this.vr.moves.length;
           if (this.gameRef.id != data.gameId)
@@ -111,7 +122,7 @@ export default {
           if (this.score != "*")
           {
             // We finished the game (any result possible)
-            this.conn.send(JSON.stringify({
+            this.st.conn.send(JSON.stringify({
               code: "lastate",
               oppid: data.oppid,
               gameId: this.gameRef.id,
@@ -123,7 +134,7 @@ export default {
           else if (data.movesCount < L)
           {
             // We must tell last move to opponent
-            this.conn.send(JSON.stringify({
+            this.st.conn.send(JSON.stringify({
               code: "lastate",
               oppid: this.opponent.id,
               gameId: this.gameRef.id,
@@ -135,7 +146,7 @@ export default {
             this.play(data.lastMove, "animate");
           break;
         case "resign": //..you won!
-          this.endGame(this.mycolor=="w"?"1-0":"0-1");
+          this.endGame(this.game.mycolor=="w"?"1-0":"0-1");
           break;
         // TODO: also use (dis)connect info to count online players?
         case "gameconnect":
@@ -159,14 +170,11 @@ export default {
       }
     };
     const socketCloseListener = () => {
-      this.conn.addEventListener('message', socketMessageListener);
-      this.conn.addEventListener('close', socketCloseListener);
+      this.st.conn.addEventListener('message', socketMessageListener);
+      this.st.conn.addEventListener('close', socketCloseListener);
     };
-    if (!!this.conn)
-    {
-      this.conn.onmessage = socketMessageListener;
-      this.conn.onclose = socketCloseListener;
-    }
+    this.st.conn.onmessage = socketMessageListener;
+    this.st.conn.onclose = socketCloseListener;
   },
   // dans variant.js (plutôt room.js) conn gère aussi les challenges
   // et les chats dans chat.js. Puis en webRTC, repenser tout ça.
@@ -186,7 +194,7 @@ export default {
           if (!!o.online)
           {
             try {
-              this.conn.send(JSON.stringify({code: "draw", oppid: o.id}));
+              this.st.conn.send(JSON.stringify({code: "draw", oppid: o.id}));
             } catch (INVALID_STATE_ERR) {
               return;
             }
@@ -205,6 +213,7 @@ export default {
         return;
       //+ bouton "abort" avec score == "?" + demander confirmation pour toutes ces actions,
       //send message: "gameOver" avec score "?"
+      // ==> BaseGame must listen to game.score change, and call "endgame(score)" in this case
     },
     resign: function(e) {
       if (!confirm("Resign the game?"))
@@ -212,34 +221,44 @@ export default {
       if (this.mode == "human" && this.oppConnected(this.oppid))
       {
         try {
-          this.conn.send(JSON.stringify({code: "resign", oppid: this.oppid}));
+          this.st.conn.send(JSON.stringify({code: "resign", oppid: this.oppid}));
         } catch (INVALID_STATE_ERR) {
           return;
         }
       }
       this.endGame(this.mycolor=="w"?"0-1":"1-0");
     },
-    launchGame: async function() {
-      const vModule = await import("@/variants/" + this.variant.name + ".js");
-      window.V = vModule.VariantRules;
-      this.loadGame(this.gid);
-    },
-    loadGame: function(gid) {
-      // TODO: ask game to remote peer if this.remoteId is set
-      // (or just if game not found locally)
-      // NOTE: if it's a corr game, ask it from server
-      const game = getGameFromStorage(gid); //, this.gameRef.uid); //uid may be blank
-      this.opponent.id = game.oppid; //opponent ID in case of running HH game
-      this.opponent.name = game.oppname; //maye be blank (if anonymous)
-      this.score = game.score;
-      this.mycolor = game.mycolor;
-      this.fenStart = game.fenStart;
-      this.moves = game.moves;
-      this.cursor = game.moves.length-1;
-      this.lastMove = (game.moves.length > 0 ? game.moves[this.cursor] : null);
+    // 4 cases for loading a game:
+    //  - from localStorage (one running game I play)
+    //  - from indexedDB (one completed live game)
+    //  - from server (one correspondance game I play[ed] or not)
+    //  - from remote peer (one live game I don't play, finished or not)
+    loadGame: function() {
+      GameStorage.get(this.gameRef, async (game) => {
+        this.game = Object.assign({},
+          game,
+          // NOTE: assign mycolor here, since BaseGame could also bs VS computer
+          {mycolor: ["w","b"][game.players.findIndex(p => p.sid == this.st.user.sid)]},
+        );
+        const vModule = await import("@/variants/" + game.vname + ".js");
+        window.V = vModule.VariantRules;
+        this.vr = new V(game.fen);
+      });
+//    // Poll all players except me (if I'm playing) to know online status.
+//    // --> Send ping to server (answer pong if players[s] are connected)
+//    if (this.gameInfo.players.some(p => p.sid == this.st.user.sid))
+//    {
+//      this.game.players.forEach(p => {
+//        if (p.sid != this.st.user.sid)
+//          this.st.conn.send(JSON.stringify({code:"ping", oppid:p.sid}));
+//      });
+//    }
     },
     oppConnected: function(uid) {
-      return this.opponents.any(o => o.id == uidi && o.online);
+      return this.opponents.some(o => o.id == uid && o.online);
+    },
+    processMove: function(move) {
+      // TODO: process some opponent's move
     },
   },
 };