Corr games: almost there. Then remote games + abort/resign/draw/message
authorBenjamin Auder <benjamin.auder@somewhere>
Fri, 6 Dec 2019 19:47:35 +0000 (20:47 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Fri, 6 Dec 2019 19:47:35 +0000 (20:47 +0100)
client/src/components/BaseGame.vue
client/src/store.js
client/src/utils/gameStorage.js
client/src/views/Game.vue
client/src/views/Hall.vue
server/db/create.sql
server/models/Game.js
server/routes/games.js
server/sockets.js

index 8e9ac36..19aaf15 100644 (file)
@@ -223,8 +223,6 @@ export default {
       this.lastMove = move;
       if (this.st.settings.sound == 2)
         new Audio("/sounds/move.mp3").play().catch(err => {});
-      if (!this.analyze)
-        this.$emit("newmove", move); //post-processing (e.g. computer play)
       if (!navigate)
       {
         move.fen = this.vr.getFen();
@@ -237,6 +235,8 @@ export default {
             this.moves = this.moves.slice(0,this.cursor).concat([move]);
         }
       }
+      if (!this.analyze)
+        this.$emit("newmove", move); //post-processing (e.g. computer play)
       // Is opponent in check?
       this.incheck = this.vr.getCheckSquares(this.vr.turn);
       const score = this.vr.getCurrentScore();
index 3b280d9..2728aaf 100644 (file)
@@ -36,8 +36,8 @@ export const store =
         this.state.user.notify = res.notify;
       });
     }
-    this.state.conn = new WebSocket(
-      params.socketUrl + "/?sid=" + mysid + "&page=" + page);
+    this.state.conn = new WebSocket(params.socketUrl + "/?sid=" + mysid +
+      "&page=" + encodeURIComponent(page));
     // Settings initialized with values from localStorage
     this.state.settings = {
       bcolor: localStorage["bcolor"] || "lichess",
index 0284763..0b3a7da 100644 (file)
@@ -79,8 +79,7 @@ export const GameStorage =
           gid: gameId,
           newObj:
           {
-            // TODO: I think stringify isn't requuired here (see ajax() )
-            move: JSON.stringify(obj.move), //may be undefined...
+            move: obj.move, //may be undefined...
             fen: obj.fen,
             score: obj.score,
           }
index c292252..b66c6ef 100644 (file)
@@ -11,7 +11,7 @@
         button(@click="abortGame") {{ st.tr["Game is too boring"] }}
     BaseGame(:game="game" :vr="vr" ref="basegame"
       @newmove="processMove" @gameover="gameOver")
-    // TODO: also show players names
+    div Names: {{ game.players[0].name }} - {{ game.players[1].name }}
     div Time: {{ virtualClocks[0] }} - {{ virtualClocks[1] }}
     .button-group(v-if="game.mode!='analyze' && game.score=='*'")
       button(@click="offerDraw") Draw
@@ -38,6 +38,7 @@ import { store } from "@/store";
 import { GameStorage } from "@/utils/gameStorage";
 import { ppt } from "@/utils/datetime";
 import { extractTime } from "@/utils/timeControl";
+import { ArrayFun } from "@/utils/array";
 
 export default {
   name: 'my-game',
@@ -52,7 +53,7 @@ export default {
         id: "",
         rid: ""
       },
-      game: {}, //passed to BaseGame
+      game: {players:[{name:""},{name:""}]}, //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
@@ -109,14 +110,6 @@ export default {
         }
       }, 1000);
     },
-    // In case variants array was't loaded when game was retrieved
-    "st.variants": function(variantArray) {
-      if (!!this.game.vname && this.game.vname == "")
-      {
-        this.game.vname = variantArray.filter(v =>
-          v.id == this.game.vid)[0].name;
-      }
-    },
   },
   // TODO: redundant code with Hall.vue (related to people array)
   created: function() {
@@ -179,67 +172,50 @@ export default {
         }
         case "identity":
         {
-          const pIdx = this.people.findIndex(p => p.sid == data.user.sid);
-          this.people[pIdx].id = data.user.id;
-          this.people[pIdx].name = data.user.name;
-          break;
-        }
-        case "newmove":
-          // NOTE: next call will trigger processMove()
-          this.$refs["basegame"].play(data.move,
-            "receive", this.game.vname!="Dark" ? "animate" : null);
-          break;
-        case "pong": //received if we sent a ping (game still alive on our side)
-        {
-          if (this.game.type == "live") //corr games are always complete
+          let player = this.people.find(p => p.sid == data.user.sid);
+          player.id = data.user.id;
+          player.name = data.user.name;
+          // Sending last state only for live games: corr games are complete
+          if (this.game.type == "live" && this.game.oppsid == player.sid)
           {
-            // Send our "last state" informations to opponent(s)
+            // Send our "last state" informations to opponent
             const L = this.game.moves.length;
             this.st.conn.send(JSON.stringify({
               code: "lastate",
-              target: this.getOppSid(), //he's connected for sure
-              gameId: this.gameRef.id,
-              lastMove: (L>0 ? this.game.moves[L-1] : undefined),
-              score: this.game.score,
-              movesCount: L,
-              drawOffer: this.drawOffer,
-              clocks: this.game.clocks,
+              target: player.sid,
+              state:
+              {
+                lastMove: (L>0 ? this.game.moves[L-1] : undefined),
+                score: this.game.score,
+                movesCount: L,
+                drawOffer: this.drawOffer,
+                clocks: this.game.clocks,
+              }
             }));
           }
           break;
         }
+        case "newmove":
+          // NOTE: this call to play() will trigger processMove()
+          this.$refs["basegame"].play(data.move,
+            "receive", this.game.vname!="Dark" ? "animate" : null);
+          break;
         case "lastate": //got opponent infos about last move
         {
           const L = this.game.moves.length;
-          if (this.gameRef.id != data.gameId)
-            break; //games IDs don't match: nothing we can do...
-          // OK, opponent still in game (which might be over)
           if (data.movesCount > L)
           {
             // Just got last move from him
-            this.$refs["basegame"].play(data.lastMove, "receive");
+            this.$refs["basegame"].play(data.lastMove,
+              "receive", this.game.vname!="Dark" ? "animate" : null);
             if (data.score != "*" && this.game.score == "*")
             {
               // Opponent resigned or aborted game, or accepted draw offer
               // (this is not a stalemate or checkmate)
               this.$refs["basegame"].endGame(data.score, "Opponent action");
             }
-            this.game.clocks = data.clocks;
-            this.drawOffer = data.drawOffer;
-          }
-          else if (data.movesCount < L)
-          {
-            // We must tell last move to opponent
-            this.st.conn.send(JSON.stringify({
-              code: "lastate",
-              target: this.getOppSid(), //we know he is connected
-              gameId: this.gameRef.id,
-              lastMove: (L>0 ? this.game.moves[L-1] : undefined),
-              score: this.game.score,
-              movesCount: L,
-              drawOffer: this.drawOffer,
-              clocks: this.game.clocks,
-            }));
+            this.game.clocks = data.clocks; //TODO: check this?
+            this.drawOffer = data.drawOffer; //does opponent offer draw?
           }
           break;
         }
@@ -274,7 +250,6 @@ export default {
         // ==> on "newmove", check "drawOffer" field
         case "connect":
         {
-          // TODO: if opponent connect, trigger lastate chain of events...
           this.people.push({name:"", id:0, sid:data.sid});
           this.st.conn.send(JSON.stringify({code:"askidentity", target:data.sid}));
           break;
@@ -357,21 +332,19 @@ export default {
     //  - from remote peer (one live game I don't play, finished or not)
     loadGame: function(game) {
       const afterRetrieval = async (game) => {
-        // NOTE: variants array might not be available yet, thus the two next lines
-        const variantCell = this.st.variants.filter(v => v.id == game.vid);
-        const vname = (variantCell.length > 0 ? variantCell[0].name : "");
-        if (!game.fen)
-          game.fen = game.fenStart; //game wasn't started
+        const vModule = await import("@/variants/" + game.vname + ".js");
+        window.V = vModule.VariantRules;
+        this.vr = new V(game.fen);
         const gtype = (game.timeControl.indexOf('d') >= 0 ? "corr" : "live");
-        // 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;
-        });
         const tc = extractTime(game.timeControl);
         if (gtype == "corr")
         {
+          if (game.players[0].color == "b")
+          {
+            // Adopt the same convention for live and corr games: [0] = white
+            [ game.players[0], game.players[1] ] =
+              [ game.players[1], game.players[0] ];
+          }
           // corr game: needs to compute the clocks + initime
           game.clocks = [tc.mainTime, tc.mainTime];
           game.initime = [0, 0];
@@ -390,6 +363,9 @@ export default {
           if (L >= 1)
             game.initime[L%2] = game.moves[L-1].played;
         }
+        const myIdx = game.players.findIndex(p => {
+          return p.sid == this.st.user.sid || p.uid == this.st.user.id;
+        });
         if (gtype == "live" && game.clocks[0] < 0) //game unstarted
         {
           game.clocks = [tc.mainTime, tc.mainTime];
@@ -404,16 +380,12 @@ export default {
             });
           }
         }
-        const vModule = await import("@/variants/" + vname + ".js");
-        window.V = vModule.VariantRules;
-        this.vr = new V(game.fen);
         this.game = Object.assign({},
           game,
           // NOTE: assign mycolor here, since BaseGame could also be VS computer
           {
             type: gtype,
             increment: tc.increment,
-            vname: vname,
             mycolor: [undefined,"w","b"][myIdx+1],
             // opponent sid not strictly required (or available), but easier
             // at least oppsid or oppid is available anyway:
@@ -421,23 +393,20 @@ export default {
             oppid: (myIdx < 0 ? undefined : game.players[1-myIdx].uid),
           }
         );
-        if (!!this.game.oppid)
-        {
-          // Send ping to server (answer pong if players[s] are connected)
-          this.st.conn.send(JSON.stringify({code:"ping", target:this.game.oppid}));
-        }
       };
       if (!!game)
         return afterRetrival(game);
       if (!!this.gameRef.rid)
       {
+        // Remote live game
         this.st.conn.send(JSON.stringify(
           {code:"askfullgame", target:this.gameRef.rid}));
         // (send moves updates + resign/abort/draw actions)
       }
       else
       {
-        GameStorage.get(this.gameRef.id, async (game) => {
+        // Local or corr game
+        GameStorage.get(this.gameRef.id, (game) => {
           afterRetrieval(game);
         });
       }
@@ -477,8 +446,15 @@ export default {
       const nextIdx = ["w","b"].indexOf(this.vr.turn);
       GameStorage.update(this.gameRef.id,
       {
-        move: filtered_move,
         fen: move.fen,
+        move:
+        {
+          squares: filtered_move,
+          message: this.corrMsg, //TODO
+          played: Date.now(), //TODO: on server?
+          idx: this.game.moves.length - 1,
+          color: move.color,
+        },
         clocks: this.game.clocks.map((t,i) => i==colorIdx
           ? this.game.clocks[i] + addTime
           : this.game.clocks[i]),
index a8dfa98..1ad88b4 100644 (file)
@@ -110,7 +110,7 @@ export default {
       });
       this.games.forEach(g => {
         if (g.vname == "")
-          g.vname = this.getVname(g.vid)
+          g.vname = this.getVname(g.vid);
       });
     },
   },
@@ -224,10 +224,10 @@ export default {
       }
       this.$router.push(url);
     },
-    // TODO: ...filter(...)[0].name, one-line, just remove this function
     getVname: function(vid) {
-      const vIdx = this.st.variants.findIndex(v => v.id == vid);
-      return vIdx >= 0 ? this.st.variants[vIdx].name : "";
+      const variant = this.st.variants.find(v => v.id == vid);
+      // this.st.variants might be uninitialized (variant == null)
+      return (!!variant ? variant.name : "");
     },
     getSid: function(pname) {
       const pIdx = this.people.findIndex(pl => pl.name == pname);
@@ -452,7 +452,8 @@ export default {
         chall.vname = vname;
         chall.from = this.people[0]; //avoid sending email
         this.challenges.push(chall);
-        localStorage.setItem("challenge", JSON.stringify(chall));
+        if (ctype == "live")
+          localStorage.setItem("challenge", JSON.stringify(chall));
         document.getElementById("modalNewgame").checked = false;
       };
       const cIdx = this.challenges.findIndex(
@@ -515,7 +516,6 @@ export default {
       }
       else //my challenge
       {
-        localStorage.removeItem("challenge");
         if (c.type == "corr")
         {
           ajax(
@@ -524,6 +524,8 @@ export default {
             {id: c.id}
           );
         }
+        else //live
+          localStorage.removeItem("challenge");
       }
       // In (almost) all cases, the challenge is consumed:
       ArrayFun.remove(this.challenges, ch => ch.id == c.id);
@@ -541,6 +543,7 @@ export default {
         fen: c.fen || V.GenRandInitFen(),
         players: shuffle([c.from, c.seat]), //white then black
         vid: c.vid,
+        vname: c.vname, //theoretically vid is enough, but much easier with vname
         timeControl: c.timeControl,
       };
       let target = c.from.sid; //may not be defined if corr + offline opp
index 3cfcbae..0251c17 100644 (file)
@@ -65,7 +65,7 @@ create table Players (
 
 create table Moves (
   gid integer,
-  move varchar,
+  squares varchar, --description, appear/vanish/from/to
   message varchar,
   played datetime, --when was this move played?
   idx integer, --index of the move in the game
index 72ee0dc..ced5611 100644 (file)
@@ -13,11 +13,10 @@ var db = require("../utils/database");
  *   gid: ref game id
  *   uid: ref user id
  *   color: character
- *   rtime: real (remaining time)
  *
  * Structure table Moves:
  *   gid: ref game id
- *   move: varchar (description)
+ *   squares: varchar (description)
  *   message: text
  *   played: datetime
  *   idx: integer
@@ -30,8 +29,8 @@ const GameModel =
        {
                db.serialize(function() {
                        let query =
-                               "INSERT INTO Games (vid, fenStart, score, timeControl) " +
-                               "VALUES (" + vid + ",'" + fen + "','*','" + timeControl + "')";
+                               "INSERT INTO Games (vid, fenStart, fen, score, timeControl) VALUES " +
+        "(" + vid + ",'" + fen + "','" + fen + "','*','" + timeControl + "')";
       db.run(query, function(err) {
         if (!!err)
           return cb(err);
@@ -39,8 +38,7 @@ const GameModel =
           const color = (idx==0 ? "w" : "b");
           query =
             "INSERT INTO Players VALUES " +
-            // Remaining time = -1 means "unstarted"
-            "(" + this.lastID + "," + p.id + ",'" + color + "', -1)";
+            "(" + this.lastID + "," + p.id + ",'" + color + "')";
           db.run(query);
         });
         cb(null, {gid: this.lastID});
@@ -52,22 +50,28 @@ const GameModel =
        getOne: function(id, cb)
        {
                db.serialize(function() {
+      // TODO: optimize queries?
                        let query =
-                               "SELECT * " +
-                               "FROM Games " +
-                               "WHERE id = " + id;
+                               "SELECT g.id, g.vid, g.fen, g.fenStart, g.timeControl, g.score, " +
+          "v.name AS vname " +
+                               "FROM Games g " +
+        "JOIN Variants v " +
+        "  ON g.vid = v.id " +
+                               "WHERE g.id = " + id;
                        db.get(query, (err,gameInfo) => {
                                if (!!err)
                                        return cb(err);
                                query =
-                                       "SELECT uid, color, rtime " +
-                                       "FROM Players " +
-                                       "WHERE gid = " + id;
+                                       "SELECT p.uid, p.color, u.name " +
+                                       "FROM Players p " +
+          "JOIN Users u " +
+          "  ON p.uid = u.id " +
+                                       "WHERE p.gid = " + id;
                                db.all(query, (err2,players) => {
                                        if (!!err2)
                                                return cb(err2);
                                        query =
-                                               "SELECT move, message, played, idx, color " +
+                                               "SELECT squares, message, played, idx, color " +
                                                "FROM Moves " +
                                                "WHERE gid = " + id;
                                        db.all(query, (err3,moves) => {
@@ -126,26 +130,35 @@ const GameModel =
   },
 
   // obj can have fields move, fen and/or score
-  update: function(id, obj, cb)
+  update: function(id, obj)
   {
-               db.serialize(function() {
+
+
+
+console.log(id);
+    console.log(obj);
+
+
+               db.parallelize(function() {
       let query =
         "UPDATE Games " +
         "SET ";
-      if (!!obj.move)
-      {
-        move.played = Date.now();
-        query += "move = " + obj.move + ","; //TODO: already stringified?!
-      }
       if (!!obj.fen)
         query += "fen = " + obj.fen + ",";
       if (!!obj.score)
         query += "score = " + obj.score + ",";
       query = query.slice(0,-1); //remove last comma
       query += " WHERE gameId = " + id;
-      db.run(query, (err) => {
-        cb(err);
-      });
+      db.run(query);
+      if (!!obj.move)
+      {
+        const m  =obj.move;
+        query =
+          "INSERT INTO Moves (gid,squares,message,played,idx,color) VALUES " +
+          "(" + id + ",'" + JSON.stringify(m.squares) + "','" + m.message +
+            "'" + m.played + "," + m.idx + ",'" + m.color + "')";
+        db.run(query);
+      }
     });
   },
 
index 3b91e8b..128d9ee 100644 (file)
@@ -54,8 +54,7 @@ router.get("/games", access.ajax, (req,res) => {
 // New move + fen update + score, potentially
 // TODO: if newmove fail, takeback in GUI
 router.put("/games", access.logged, access.ajax, (req,res) => {
-       const gid = req.body.gid;
-       const oppId = req.body.oppId;
+  const gid = req.body.gid;
        const obj = req.body.newObj;
        GameModel.update(gid, obj, (err) => {
                if (!!err)
index fe82f6e..3637c8f 100644 (file)
@@ -35,9 +35,10 @@ module.exports = function(wss) {
       switch (obj.code)
       {
         case "pollclients":
+          const curPage = clients[sid].page;
           socket.send(JSON.stringify({code:"pollclients",
             sockIds: Object.keys(clients).filter(k =>
-              k != sid && clients[k].page == obj.page)}));
+              k != sid && clients[k].page == curPage)}));
           break;
         case "pagechange":
           notifyRoom(clients[sid].page, "disconnect");
@@ -45,28 +46,28 @@ module.exports = function(wss) {
           notifyRoom(obj.page, "connect");
           break;
         case "askidentity":
-          clients[obj.target].sock.send(
-            JSON.stringify({code:"askidentity",from:sid}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"askidentity",from:sid}));
           break;
         case "askchallenge":
-          clients[obj.target].sock.send(
-            JSON.stringify({code:"askchallenge",from:sid}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"askchallenge",from:sid}));
           break;
         case "askgame":
-          clients[obj.target].sock.send(
-            JSON.stringify({code:"askgame",from:sid}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"askgame",from:sid}));
           break;
         case "identity":
-          clients[obj.target].sock.send(
-            JSON.stringify({code:"identity",user:obj.user}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"identity",user:obj.user}));
           break;
         case "refusechallenge":
-          clients[obj.target].sock.send(
-            JSON.stringify({code:"refusechallenge", cid:obj.cid, from:sid}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"refusechallenge", cid:obj.cid, from:sid}));
           break;
         case "deletechallenge":
-          clients[obj.target].sock.send(
-            JSON.stringify({code:"deletechallenge", cid:obj.cid, from:sid}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"deletechallenge", cid:obj.cid, from:sid}));
           break;
         case "newgame":
           clients[obj.target].sock.send(JSON.stringify(
@@ -81,32 +82,33 @@ module.exports = function(wss) {
             {code:"game", game:obj.game, from:sid}));
           break;
         case "newchat":
-          clients[obj.target].sock.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].sock.send(JSON.stringify({code:"newmove",move:obj.move}));
-          break;
-        case "ping":
-          // If this code is reached, then obj.target is connected
-          socket.send(JSON.stringify({code:"pong"}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"newmove",move:obj.move}));
           break;
         case "lastate":
-          const oppId = obj.target;
-          obj.oppid = sid; //I'm the opponent of my opponent
-          clients[oppId].sock.send(JSON.stringify(obj));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"lastate", state:obj.state}));
           break;
         case "resign":
-          clients[obj.target].sock.send(JSON.stringify({code:"resign"}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"resign"}));
           break;
         case "abort":
-          clients[obj.target].sock.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].sock.send(JSON.stringify({code:"drawoffer"}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"drawoffer"}));
           break;
         case "draw":
-          clients[obj.target].sock.send(JSON.stringify({code:"draw"}));
+          clients[obj.target].sock.send(JSON.stringify(
+            {code:"draw"}));
           break;
       }
     });