From 3d55deea9a2011c38d8d0067bd57fc889958bec2 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 3 Dec 2019 02:29:15 +0100
Subject: [PATCH] Draft game update. TODO: debug, finish view/Game.vue

---
 client/src/utils/gameStorage.js |  49 ++++++++++-----
 client/src/views/Game.vue       |   7 ++-
 client/src/views/Hall.vue       | 105 ++++++++++++++++----------------
 server/models/Game.js           |  21 +++++++
 server/routes/games.js          |  38 +++---------
 5 files changed, 125 insertions(+), 95 deletions(-)

diff --git a/client/src/utils/gameStorage.js b/client/src/utils/gameStorage.js
index 2ccb494f..0284763a 100644
--- a/client/src/utils/gameStorage.js
+++ b/client/src/utils/gameStorage.js
@@ -67,22 +67,43 @@ export const GameStorage =
   },
 
   // TODO: also option to takeback a move ?
-  // NOTE: for live games only (all on server for corr)
   update: function(gameId, obj) //move, fen, clocks, score, initime, ...
   {
-    dbOperation((db) => {
-      let objectStore = db.transaction("games", "readwrite").objectStore("games");
-      objectStore.get(gameId).onsuccess = function(event) {
-        const game = event.target.result;
-        Object.keys(obj).forEach(k => {
-          if (k == "move")
-            game.moves.push(obj[k]);
-          else
-            game[k] = obj[k];
-        });
-        objectStore.put(game); //save updated data
-      }
-    });
+    if (Number.isInteger(gameId) || !isNaN(parseInt(gameId)))
+    {
+      // corr: only move, fen and score
+      ajax(
+        "/games",
+        "PUT",
+        {
+          gid: gameId,
+          newObj:
+          {
+            // TODO: I think stringify isn't requuired here (see ajax() )
+            move: JSON.stringify(obj.move), //may be undefined...
+            fen: obj.fen,
+            score: obj.score,
+          }
+        }
+      );
+    }
+    else
+    {
+      // live
+      dbOperation((db) => {
+        let objectStore = db.transaction("games", "readwrite").objectStore("games");
+        objectStore.get(gameId).onsuccess = function(event) {
+          const game = event.target.result;
+          Object.keys(obj).forEach(k => {
+            if (k == "move")
+              game.moves.push(obj[k]);
+            else
+              game[k] = obj[k];
+          });
+          objectStore.put(game); //save updated data
+        }
+      });
+    }
   },
 
   // Retrieve all local games (running, completed, imported...)
diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue
index f00dc92b..67af3b9f 100644
--- a/client/src/views/Game.vue
+++ b/client/src/views/Game.vue
@@ -319,7 +319,12 @@ export default {
           // TODO: compute clocks + initime
         }
         const tc = extractTime(game.timeControl);
-        const myIdx = game.players.findIndex(p => p.sid == this.st.user.sid);
+        // 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
         {
           game.clocks = [tc.mainTime, tc.mainTime];
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index dadb597f..82330ef1 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -5,7 +5,7 @@ main
     .card.smallpad.small-modal.text-center
       label.modal-close(for="modalInfo")
       h3#infoMessage.section
-        p New game started: #[a(href="/game/" + {{ newGameId }})]
+        p(v-html="infoMessage")
   input#modalNewgame.modal(type="checkbox")
   div(role="dialog" aria-labelledby="titleFenedit")
     .card.smallpad
@@ -91,7 +91,7 @@ export default {
       games: [],
       challenges: [],
       people: [], //(all) online players
-      newGameId: 0,
+      infoMessage: "",
       newchallenge: {
         fen: "",
         vid: 0,
@@ -143,50 +143,47 @@ export default {
       else
         localStorage.removeItem("challenge");
     }
-    if (this.st.user.id > 0)
-    {
-      // Ask server for current corr games (all but mines)
-      ajax(
-        "/games",
-        "GET",
-        {uid: this.st.user.id, excluded: true},
-        response => {
-          this.games = this.games.concat(response.games.map(g => {
-            const type = this.classifyObject(g);
-            const vname = this.getVname(g.vid);
-            return Object.assign({}, g, {type: type, vname: vname});
-          }));
-        }
-      );
-      // Also ask for corr challenges (open + sent to me)
-      ajax(
-        "/challenges",
-        "GET",
-        {uid: this.st.user.id},
-        response => {
-          // Gather all senders names, and then retrieve full identity:
-          // (TODO [perf]: some might be online...)
-          const uids = response.challenges.map(c => { return c.uid });
-          ajax("/users",
-            "GET",
-            { ids: uids.join(",") },
-            response2 => {
-              let names = {};
-              response2.users.forEach(u => {names[u.id] = u.name});
-              this.challenges = this.challenges.concat(
-                response.challenges.map(c => {
-                  // (just players names in fact)
-                  const from = {name: names[c.uid], id: c.uid};
-                  const type = this.classifyObject(c);
-                  const vname = this.getVname(c.vid);
-                  return Object.assign({}, c, {type: type, vname: vname, from: from});
-                })
-              )
-            }
-          );
-        }
-      );
-    }
+    // Ask server for current corr games (all but mines)
+    ajax(
+      "/games",
+      "GET",
+      {uid: this.st.user.id, excluded: true},
+      response => {
+        this.games = this.games.concat(response.games.map(g => {
+          const type = this.classifyObject(g);
+          const vname = this.getVname(g.vid);
+          return Object.assign({}, g, {type: type, vname: vname});
+        }));
+      }
+    );
+    // Also ask for corr challenges (open + sent to me)
+    ajax(
+      "/challenges",
+      "GET",
+      {uid: this.st.user.id},
+      response => {
+        // Gather all senders names, and then retrieve full identity:
+        // (TODO [perf]: some might be online...)
+        const uids = response.challenges.map(c => { return c.uid });
+        ajax("/users",
+          "GET",
+          { ids: uids.join(",") },
+          response2 => {
+            let names = {};
+            response2.users.forEach(u => {names[u.id] = u.name});
+            this.challenges = this.challenges.concat(
+              response.challenges.map(c => {
+                // (just players names in fact)
+                const from = {name: names[c.uid], id: c.uid};
+                const type = this.classifyObject(c);
+                const vname = this.getVname(c.vid);
+                return Object.assign({}, c, {type: type, vname: vname, from: from});
+              })
+            )
+          }
+        );
+      }
+    );
     // 0.1] Ask server for room composition:
     const funcPollClients = () => {
       this.st.conn.send(JSON.stringify({code:"pollclients"}));
@@ -383,10 +380,12 @@ export default {
             this.startNewGame(data.gameInfo);
           else
           {
-            this.newGameId = data.gameInfo.gameId;
+            this.infoMessage = "New game started: " +
+              "<a href='/game/" + data.gameInfo.gameId + "'>" +
+              "/game/" + data.gameInfo.gameId + "</a>";
             let modalBox = document.getElementById("modalInfo");
             modalBox.checked = true;
-            setTimeout(() => { modalBox.checked = false; }, 2500);
+            setTimeout(() => { modalBox.checked = false; }, 3000);
           }
           break;
         }
@@ -490,14 +489,12 @@ export default {
       }
     },
     clickChallenge: function(c) {
-      // In all cases, the challenge is consumed:
-      ArrayFun.remove(this.challenges, ch => ch.id == c.id);
-      // NOTE: deletechallenge event might be redundant (but it's easier this way)
-      this.sendSomethingTo((!!c.to ? c.from : null), "deletechallenge", {cid:c.id});
       const myChallenge = (c.from.sid == this.st.user.sid //live
         || (this.st.user.id > 0 && c.from.id == this.st.user.id)); //corr
       if (!myChallenge)
       {
+        if (c.type == "corr" && this.st.user.id <= 0)
+          return alert("Please log in to accept corr challenges");
         c.accepted = true;
         if (!!c.to) //c.to == this.st.user.name (connected)
         {
@@ -528,6 +525,10 @@ export default {
           );
         }
       }
+      // In (almost) all cases, the challenge is consumed:
+      ArrayFun.remove(this.challenges, ch => ch.id == c.id);
+      // NOTE: deletechallenge event might be redundant (but it's easier this way)
+      this.sendSomethingTo((!!c.to ? c.from : null), "deletechallenge", {cid:c.id});
     },
     // NOTE: when launching game, the challenge is already deleted
     launchGame: async function(c) {
diff --git a/server/models/Game.js b/server/models/Game.js
index f99dbdef..315b5e2b 100644
--- a/server/models/Game.js
+++ b/server/models/Game.js
@@ -112,6 +112,27 @@ const GameModel =
 		});
 	},
 
+  // obj can have fields move, fen and/or score
+  update: function(id, obj, cb)
+  {
+		db.serialize(function() {
+      let query =
+        "UPDATE Games " +
+        "SET ";
+      if (!!obj.move)
+        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);
+      });
+    });
+  },
+
 	remove: function(id)
 	{
 		db.parallelize(function() {
diff --git a/server/routes/games.js b/server/routes/games.js
index 24e97471..73a8bf70 100644
--- a/server/routes/games.js
+++ b/server/routes/games.js
@@ -51,35 +51,17 @@ router.get("/games", access.ajax, (req,res) => {
   }
 });
 
-//////////////////////////////////
-
-// TODO: new move
-router.put("/games", access.logged, access.ajax, (req,res) => {
-	let gid = ObjectId(req.body.gid);
-	let result = req.body.result;
-	// NOTE: only game-level life update is "gameover"
-	GameModel.gameOver(gid, result, ObjectId(req.userId), (err,game) => {
-		access.checkRequest(res, err, game, "Cannot find game", () => {
-			res.json({});
-		});
-	});
-});
-
+// New move + fen update + score, potentially
 // TODO: if newmove fail, takeback in GUI
-// TODO: check move structure
-// TODO: move should contain an optional "message" field ("corr chat" !)
-router.post("/moves", access.logged, access.ajax, (req,res) => {
-	let gid = ObjectId(req.body.gid);
-	let fen = req.body.fen;
-	let vname = req.body.vname; //defined only if !!offlineOpp
-	// NOTE: storing the moves encoded lead to double stringify --> error at parsing
-	let move = JSON.parse(req.body.move);
-	GameModel.addMove(gid, move, fen, req._user._id, (err,game) => {
-		access.checkRequest(res, err, game, "Cannot find game", () => {
-			if (!!req.body.offlineOpp)
-				UserModel.tryNotify(ObjectId(req.body.offlineOpp), gid, vname, "New move");
-			res.json({});
-		});
+router.put("/games", access.logged, access.ajax, (req,res) => {
+	const gid = req.body.gid;
+	const obj = req.body.newObj;
+	GameModel.update(gid, obj, (err) => {
+		if (!!err)
+      return res.json(err);
+    if (!!req.body.offlineOpp) //TODO: refresh this...
+      UserModel.tryNotify(req.body.offlineOpp, "New move in game " + gid);
+    res.json({});
 	});
 });
 
-- 
2.44.0