From dfeb96ea90e880a2557cbb5953dbb7258c912283 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Wed, 5 Feb 2020 12:15:05 +0100
Subject: [PATCH] TODO: finish draw offer logic + fix inCheck bug (no
 highlight)

---
 client/src/components/Board.vue    | 10 +++++-----
 client/src/components/Settings.vue |  7 +++++--
 client/src/store.js                | 14 ++++++++------
 client/src/translations/en.js      |  1 +
 client/src/utils/gameStorage.js    |  3 ++-
 client/src/views/Game.vue          | 22 +++++++++++++++++++---
 server/db/create.sql               |  2 +-
 server/models/Game.js              |  8 +++++---
 server/routes/games.js             | 25 +++++++++++++++++--------
 9 files changed, 63 insertions(+), 29 deletions(-)

diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue
index 1b7fd6bc..6a20d54b 100644
--- a/client/src/components/Board.vue
+++ b/client/src/components/Board.vue
@@ -1,6 +1,7 @@
 <script>
 import { getSquareId, getSquareFromId } from "@/utils/squareId";
 import { ArrayFun } from "@/utils/array";
+import { store } from "@/store";
 
 export default {
   name: 'my-board',
@@ -10,13 +11,12 @@ export default {
   props: ["vr","lastMove","analyze","orientation","userColor","vname"],
   data: function () {
     return {
-      hints: (!localStorage["hints"] ? true : localStorage["hints"] === "1"),
-      bcolor: localStorage["bcolor"] || "lichess", //lichess, chesscom or chesstempo
       possibleMoves: [], //filled after each valid click/dragstart
       choices: [], //promotion pieces, or checkered captures... (as moves)
       selectedPiece: null, //moving piece (or clicked piece)
       incheck: [],
       start: {}, //pixels coordinates + id of starting square (click or drag)
+      settings: store.state.settings,
     };
   },
   render(h) {
@@ -83,7 +83,7 @@ export default {
     );
     // Create board element (+ reserves if needed by variant or mode)
     const lm = this.lastMove;
-    const showLight = this.hints && this.vname != "Dark";
+    const showLight = this.settings.highlight && this.vname != "Dark";
     const gameDiv = h(
       'div',
       {
@@ -126,7 +126,7 @@ export default {
                 )
               );
             }
-            if (this.hints && hintSquares[ci][cj])
+            if (this.settings.hints && hintSquares[ci][cj])
             {
               elems.push(
                 h(
@@ -150,7 +150,7 @@ export default {
                   ['board'+sizeY]: true,
                   'light-square': (i+j)%2==0,
                   'dark-square': (i+j)%2==1,
-                  [this.bcolor]: true,
+                  [this.settings.bcolor]: true,
                   'in-shadow': this.vname=="Dark" && !this.analyze
                     && (!this.userColor
                       || !this.vr.enlightened[this.userColor][ci][cj]),
diff --git a/client/src/components/Settings.vue b/client/src/components/Settings.vue
index 003dc15f..b9a2b637 100644
--- a/client/src/components/Settings.vue
+++ b/client/src/components/Settings.vue
@@ -39,9 +39,12 @@ export default {
     updateSettings: function(event) {
       const propName =
         event.target.id.substr(3).replace(/^\w/, c => c.toLowerCase())
-      localStorage[propName] = ["bcolor","sound"].includes(propName)
+      let value = (["bcolor","sound"].includes(propName)
         ? event.target.value
-        : event.target.checked;
+        : event.target.checked);
+      if (propName == "sound")
+        value = parseInt(value);
+      store.updateSetting(propName, value);
     },
   },
 };
diff --git a/client/src/store.js b/client/src/store.js
index 0234807f..798251f9 100644
--- a/client/src/store.js
+++ b/client/src/store.js
@@ -42,12 +42,10 @@ export const store =
       "&page=" + encodeURIComponent(page));
     // Settings initialized with values from localStorage
     this.state.settings = {
-      bcolor: localStorage["bcolor"] || "lichess",
-      sound: parseInt(localStorage["sound"]) || 2,
-      hints: parseInt(localStorage["hints"]) || 1,
-      coords: !!eval(localStorage["coords"]),
-      highlight: !!eval(localStorage["highlight"]),
-      sqSize: parseInt(localStorage["sqSize"]),
+      bcolor: localStorage.getItem("bcolor") || "lichess",
+      sound: parseInt(localStorage.getItem("sound")) || 1,
+      hints: localStorage.getItem("hints") == "true",
+      highlight: localStorage.getItem("highlight") == "true",
     };
     this.socketCloseListener = () => {
       // Next line may fail at first, but should retry and eventually success (TODO?)
@@ -62,6 +60,10 @@ export const store =
         : "en");
     this.setTranslations();
   },
+  updateSetting: function(propName, value) {
+    this.state.settings[propName] = value;
+    localStorage.setItem(propName, value);
+  },
   setTranslations: async function() {
     // Import translations from "./translations/$lang.js"
     const tModule = await import("@/translations/" + this.state.lang + ".js");
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 0a7f93db..9b4e1372 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -24,6 +24,7 @@ export const translations =
   "Database error:": "Database error:",
   "Download PGN": "Download PGN",
   "Draw": "Draw",
+  "Draw offer only in your turn": "Draw offer only in your turn",
   "Email": "Email",
   "Email sent!": "Email sent!",
   "Empty message": "Empty message",
diff --git a/client/src/utils/gameStorage.js b/client/src/utils/gameStorage.js
index 7fadbedd..bf5dfba4 100644
--- a/client/src/utils/gameStorage.js
+++ b/client/src/utils/gameStorage.js
@@ -81,8 +81,9 @@ export const GameStorage =
           gid: gameId,
           newObj:
           {
+            // Some fields may be undefined:
             chat: obj.chat,
-            move: obj.move, //may be undefined...
+            move: obj.move,
             fen: obj.fen,
             score: obj.score,
             scoreMsg: obj.scoreMsg,
diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue
index 6ea6cdd9..ec1962ee 100644
--- a/client/src/views/Game.vue
+++ b/client/src/views/Game.vue
@@ -320,6 +320,8 @@ export default {
       }
       else if (this.drawOffer == "") //no effect if drawOffer == "sent"
       {
+        if (this.game.mycolor != this.vr.turn)
+          return alert(this.st.tr["Draw offer only in your turn"]);
         if (!confirm(this.st.tr["Offer draw?"]))
           return;
         this.drawOffer = "sent";
@@ -327,7 +329,7 @@ export default {
           if (sid != this.st.user.sid)
             this.st.conn.send(JSON.stringify({code:"drawoffer", target:sid}));
         });
-        GameStorage.update(this.gameRef.id, {drawOffer: true});
+        GameStorage.update(this.gameRef.id, {drawOffer: this.game.mycolor});
       }
     },
     abortGame: function() {
@@ -396,8 +398,6 @@ export default {
             }
             if (L >= 1)
               game.initime[L%2] = game.moves[L-1].played;
-            if (game.drawOffer)
-              this.drawOffer = "received";
           }
           // Now that we used idx and played, re-format moves as for live games
           game.moves = game.moves.map( (m) => {
@@ -432,6 +432,22 @@ export default {
             }
           }
         }
+
+
+
+        // TODO: (and also when receiving / sending a move ?)
+//        if (!!game.drawOffer)
+//        {
+//          if (game.drawOffer == "w")
+//          {
+//            if (myIdx == 0)
+//            {
+//              this.drawOffer = "sent";
+
+
+
+
+
         this.game = Object.assign({},
           game,
           // NOTE: assign mycolor here, since BaseGame could also be VS computer
diff --git a/server/db/create.sql b/server/db/create.sql
index d18a3105..21c7728b 100644
--- a/server/db/create.sql
+++ b/server/db/create.sql
@@ -41,7 +41,7 @@ create table Games (
   scoreMsg varchar,
   timeControl varchar,
   created datetime, --used only for DB cleaning
-  drawOffer boolean,
+  drawOffer character,
   foreign key (vid) references Variants(id)
 );
 
diff --git a/server/models/Game.js b/server/models/Game.js
index da59b4a6..a3ddc3c4 100644
--- a/server/models/Game.js
+++ b/server/models/Game.js
@@ -11,7 +11,7 @@ const UserModel = require("./User");
  *   score: varchar (result)
  *   scoreMsg: varchar ("Time", "Mutual agreement"...)
  *   created: datetime
- *   drawOffer: boolean
+ *   drawOffer: char ('w','b' or '' for none)
  *
  * Structure table Players:
  *   gid: ref game id
@@ -56,7 +56,7 @@ const GameModel =
         "INSERT INTO Games"
         + " (vid, fenStart, fen, score, timeControl, created, drawOffer)"
         + " VALUES (" + vid + ",'" + fen + "','" + fen + "','*','"
-        + timeControl + "'," + Date.now() + "," + false + ")";
+        + timeControl + "'," + Date.now() + ",'')";
       db.run(query, function(err) {
         if (!!err)
           return cb(err);
@@ -200,7 +200,9 @@ const GameModel =
       let modifs = "";
       if (!!obj.message)
         modifs += "message = message || ' ' || '" + obj.message + "',";
-      if ([true,false].includes(obj.drawOffer))
+      // NOTE: if drawOffer is true, we should check that it's player's turn
+      // A bit overcomplicated. Let's trust the client on that for now...
+      if (!!obj.drawOffer)
         modifs += "drawOffer = " + obj.drawOffer + ",";
       if (!!obj.fen)
         modifs += "fen = '" + obj.fen + "',";
diff --git a/server/routes/games.js b/server/routes/games.js
index 24bfc82c..c6e25a6a 100644
--- a/server/routes/games.js
+++ b/server/routes/games.js
@@ -76,14 +76,23 @@ router.put("/games", access.logged, access.ajax, (req,res) => {
   GameModel.update(gid, obj, (err) => {
     if (!!err)
       return res.json(err);
-    // Notify opponent if he enabled notifications:
-    GameModel.getPlayers(gid, (err2,players) => {
-      if (!!err2)
-        return res.json(err);
-      const oppid = (players[0].id == req.userId ? players[1].id : players[0].id);
-      UserModel.tryNotify(oppid,
-        "New move in game: " + params.siteURL + "/game/" + gid);
-    });
+    if (!!obj.move || !!obj.score)
+    {
+      // Notify opponent if he enabled notifications:
+      GameModel.getPlayers(gid, (err2,players) => {
+        if (!err2)
+        {
+          const oppid = (players[0].id == req.userId
+            ? players[1].id
+            : players[0].id);
+          const messagePrefix = (!!obj.move
+            ? "New move in game: "
+            : "Game ended: ");
+          UserModel.tryNotify(oppid,
+            messagePrefix + params.siteURL + "/game/" + gid);
+        }
+      });
+    }
     res.json({});
   });
 });
-- 
2.44.0