Some fixes. TODO: check clocks update, and continue work on resign/abort/draw
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 19 Jun 2019 16:26:53 +0000 (18:26 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 19 Jun 2019 16:26:53 +0000 (18:26 +0200)
client/src/components/BaseGame.vue
client/src/utils/gameStorage.js [new file with mode: 0644]
client/src/utils/storage.js [deleted file]
client/src/views/Game.vue
client/src/views/Hall.vue

index 4a40a3a..254741d 100644 (file)
       a#download(href="#")
       .button-group
         button#downloadBtn(@click="download") {{ st.tr["Download PGN"] }}
-        
         // TODO: Import game button copy game locally in IndexedDB
         //button Import game
-
-
-// TODO: do not use localStorage for current game, but directly indexedDB
-// update function is similar
-// ==> retrieval functions must filter on score, and potential "imported" tag
-// ==> this should allow several simultaneous games
-
-
     //MoveList(v-if="showMoves"
       :moves="moves" :cursor="cursor" @goto-move="gotoMove")
 </template>
diff --git a/client/src/utils/gameStorage.js b/client/src/utils/gameStorage.js
new file mode 100644 (file)
index 0000000..94c1c30
--- /dev/null
@@ -0,0 +1,136 @@
+// Game object: {
+//   // Static informations:
+//   gameId: string
+//   vname: string,
+//   fenStart: string,
+//   players: array of sid+id+name,
+//   timeControl: string,
+//   increment: integer (seconds),
+//   mode: string ("live" or "corr")
+//   imported: boolean (optional, default false)
+//   // Game (dynamic) state:
+//   fen: string,
+//   moves: array of Move objects,
+//   clocks: array of integers,
+//   initime: integer (when clock start running),
+//   score: string (several options; '*' == running),
+// }
+
+function dbOperation(callback)
+{
+  let db = null;
+  let DBOpenRequest = window.indexedDB.open("vchess", 4);
+
+  DBOpenRequest.onerror = function(event) {
+    alert("Database error: " + event.target.errorCode);
+  };
+
+  DBOpenRequest.onsuccess = function(event) {
+    db = DBOpenRequest.result;
+    callback(db);
+    db.close();
+  };
+
+  DBOpenRequest.onupgradeneeded = function(event) {
+    let db = event.target.result;
+    db.onerror = function(event) {
+      alert("Error while loading database: " + event.target.errorCode);
+    };
+    // Create objectStore for vchess->games
+    db.createObjectStore("games", { keyPath: "gameId" });
+  }
+}
+
+export const GameStorage =
+{
+  // Optional callback to get error status
+  add: function(game, callback)
+  {
+    dbOperation((db) => {
+      let transaction = db.transaction("games", "readwrite");
+      if (callback)
+      {
+        transaction.oncomplete = function() {
+          callback({}); //everything's fine
+        }
+        transaction.onerror = function() {
+          callback({errmsg: "addGame failed: " + transaction.error});
+        };
+      }
+      let objectStore = transaction.objectStore("games");
+      objectStore.add(game);
+    });
+  },
+
+  // TODO: also option to takeback a move ?
+  // NOTE: for live games only (all on server for corr)
+  update: function(gameId, obj) //colorIdx, move, fen, addTime, initime, score
+  {
+    dbOperation((db) => {
+      let objectStore = db.transaction("games", "readwrite").objectStore("games");
+      objectStore.get(gameId).onsuccess = function(event) {
+        const game = event.target.result;
+        if (!!obj.move)
+        {
+          game.moves.push(obj.move);
+          game.fen = obj.fen;
+          if (!!obj.addTime) //NaN if first move in game
+            game.clocks[obj.colorIdx] += obj.addTime;
+        }
+        if (!!obj.initime) //just a flag (true)
+          game.initime = Date.now();
+        if (!!obj.score)
+          game.score = obj.score;
+        objectStore.put(game); //save updated data
+      }
+    });
+  },
+
+  // Retrieve any live game from its identifiers (locally, running or not)
+  // NOTE: need callback because result is obtained asynchronously
+  get: function(gameId, callback)
+  {
+    dbOperation((db) => {
+      let objectStore = db.transaction('games').objectStore('games');
+      if (!gameId) //retrieve all
+      {
+        let games = [];
+        objectStore.openCursor().onsuccess = function(event) {
+          let cursor = event.target.result;
+          // if there is still another cursor to go, keep running this code
+          if (cursor)
+          {
+            games.push(cursor.value);
+            cursor.continue();
+          }
+          else
+            callback(games);
+        }
+      }
+      else //just one game
+      {
+        objectStore.get(gameId).onsuccess = function(event) {
+          callback(event.target.result);
+        }
+      }
+    });
+  },
+
+  // Delete a game in indexedDB
+  remove: function(gameId, callback)
+  {
+    dbOperation((db) => {
+      let transaction = db.transaction(["games"], "readwrite");
+      if (callback)
+      {
+        transaction.oncomplete = function() {
+          callback({}); //everything's fine
+        }
+        transaction.onerror = function() {
+          callback({errmsg: "removeGame failed: " + transaction.error});
+        };
+      }
+      transaction.objectStore("games").delete(gameId);
+    });
+  },
+};
diff --git a/client/src/utils/storage.js b/client/src/utils/storage.js
deleted file mode 100644 (file)
index 2e7a84f..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-import { extractTime } from "@/utils/timeControl";
-
-// TODO: show game structure
-//const newItem = [
-//     { gameId: "", players: [], timeControl: "", clocks: [] }
-//];
-
-function dbOperation(callback)
-{
-  let db = null;
-  let DBOpenRequest = window.indexedDB.open("vchess", 4);
-
-  DBOpenRequest.onerror = function(event) {
-    alert("Database error: " + event.target.errorCode);
-  };
-
-  DBOpenRequest.onsuccess = function(event) {
-    db = DBOpenRequest.result;
-    callback(db);
-    db.close();
-  };
-
-  DBOpenRequest.onupgradeneeded = function(event) {
-    let db = event.target.result;
-    db.onerror = function(event) {
-      alert("Error while loading database: " + event.target.errorCode);
-    };
-    // Create objectStore for vchess->games
-    db.createObjectStore("games", { keyPath: "gameId" });
-  }
-}
-
-// Optional callback to get error status
-function addGame(game, callback)
-{
-  dbOperation((db) => {
-    let transaction = db.transaction(["games"], "readwrite");
-    if (callback)
-    {
-      transaction.oncomplete = function() {
-        callback({}); //everything's fine
-      }
-      transaction.onerror = function() {
-        callback({errmsg: "addGame failed: " + transaction.error});
-      };
-    }
-    let objectStore = transaction.objectStore("games");
-    objectStore.add(game);
-  });
-}
-
-// Clear current live game from localStorage
-function clear() {
-  localStorage.removeItem("gameInfo");
-  localStorage.removeItem("gameState");
-}
-
-// Current live game:
-function getCurrent()
-{
-  return Object.assign({},
-    JSON.parse(localStorage.getItem("gameInfo")),
-    JSON.parse(localStorage.getItem("gameState")));
-}
-
-// Only called internally after a score update
-function transferToDb()
-{
-  addGame(getCurrent(), (err) => {
-    if (!!err.errmsg)
-      return err;
-    clear();
-  });
-}
-
-export const GameStorage =
-{
-  // localStorage:
-  init: function(o)
-  {
-    // Extract times (in [milli]seconds), set clocks, store in localStorage
-    const tc = extractTime(o.timeControl);
-
-    // game infos: constant
-    const gameInfo =
-    {
-      gameId: o.gameId,
-      vname: o.vname,
-      fenStart: o.fenStart,
-      players: o.players,
-      timeControl: o.timeControl,
-      increment: tc.increment,
-      mode: "live", //function for live games only
-    };
-
-    // game state: will be updated
-    const gameState =
-    {
-      fen: o.fenStart,
-      moves: [],
-      clocks: [...Array(o.players.length)].fill(tc.mainTime),
-      initime: (o.initime ? Date.now() : undefined),
-      score: "*",
-    };
-
-    localStorage.setItem("gameInfo", JSON.stringify(gameInfo));
-    localStorage.setItem("gameState", JSON.stringify(gameState));
-  },
-
-  getInitime: function()
-  {
-    const gameState = JSON.parse(localStorage.getItem("gameState"));
-    return gameState.initime;
-  },
-
-  // localStorage:
-  // TODO: also option to takeback a move ?
-  // NOTE: for live games only (all on server for corr)
-  update: function(o) //colorIdx, move, fen, addTime, initime, score
-  {
-    let gameState = JSON.parse(localStorage.getItem("gameState"));
-    if (!!o.move)
-    {
-      gameState.moves.push(o.move);
-      gameState.fen = o.fen;
-      if (!!o.addTime) //NaN if first move in game
-        gameState.clocks[o.colorIdx] += o.addTime;
-    }
-    if (!!o.initime) //just a flag (true)
-      gameState.initime = Date.now();
-    if (!!o.score)
-      gameState.score = o.score;
-    localStorage.setItem("gameState", JSON.stringify(gameState));
-    if (!!o.score && o.score != "*")
-      transferToDb(); //game is over
-  },
-
-  // indexedDB:
-  // Since DB requests are asynchronous, require a callback using the result
-  // TODO: option for remote retrieval (third arg, or just "gameRef")
-  getLocal: function(gameId, callback)
-  {
-    dbOperation((db) => {
-      let objectStore = db.transaction('games').objectStore('games');
-      if (!gameId) //retrieve all
-      {
-        let games = [];
-        objectStore.openCursor().onsuccess = function(event) {
-          let cursor = event.target.result;
-          // if there is still another cursor to go, keep running this code
-          if (cursor)
-          {
-            games.push(cursor.value);
-            cursor.continue();
-          }
-          else
-            callback(games);
-        }
-      }
-      else //just one game
-      {
-        objectStore.get(gameId).onsuccess = function(event) {
-          callback(event.target.result);
-        }
-      }
-    });
-  },
-
-  // Delete a game in indexedDB
-  remove: function(gameId, callback)
-  {
-    dbOperation((db) => {
-      let transaction = db.transaction(["games"], "readwrite");
-      if (callback)
-      {
-        transaction.oncomplete = function() {
-          callback({}); //everything's fine
-        }
-        transaction.onerror = function() {
-          callback({errmsg: "game removal failed: " + transaction.error});
-        };
-      }
-      transaction.objectStore("games").delete(gameId);
-    });
-  },
-
-  // Retrieve any live game from its identifiers (remote or not, running or not)
-  // NOTE: need callback because result might be obtained asynchronously
-  get: function(gameRef, callback)
-  {
-    const gid = gameRef.id;
-    const rid = gameRef.rid; //may be blank
-    if (!!rid)
-    {
-      // TODO: send request to server which forward to user sid == rid,
-      // need to listen to "remote game" event in main hall ?
-      return callback({}); //means "the game will arrive later" (TODO...)
-    }
-
-    const gameInfoStr = localStorage.getItem("gameInfo");
-    if (gameInfoStr)
-    {
-      const gameInfo = JSON.parse(gameInfoStr);
-      if (gameInfo.gameId == gid)
-      {
-        const gameState = JSON.parse(localStorage.getItem("gameState"));
-        return callback(Object.assign({}, gameInfo, gameState));
-      }
-    }
-
-    // Game is local and not running
-    GameStorage.getLocal(gid, callback);
-  },
-};
index 9be8806..ab6ae7a 100644 (file)
@@ -25,7 +25,7 @@ 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";
+import { GameStorage } from "@/utils/gameStorage";
 
 export default {
   name: 'my-game',
@@ -250,13 +250,12 @@ export default {
       // Next line will trigger a "gameover" event, bubbling up till here
       this.$refs["basegame"].endGame(this.game.mycolor=="w" ? "0-1" : "1-0");
     },
-    // 4 cases for loading a game:
-    //  - from localStorage (one running game I play)
-    //  - from indexedDB (one completed live game)
+    // 3 cases for loading a game:
+    //  - from indexedDB (running or completed live game I play)
     //  - 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) => {
+    loadGame: function(game) {
+      const afterRetrieval = async (game) => {
         const vModule = await import("@/variants/" + game.vname + ".js");
         window.V = vModule.VariantRules;
         this.vr = new V(game.fenStart);
@@ -266,7 +265,23 @@ export default {
           {mycolor: [undefined,"w","b"][1 + game.players.findIndex(
             p => p.sid == this.st.user.sid)]},
         );
-      });
+      };
+      if (!!game)
+        return afterRetrival(game);
+      if (!!this.gameRef.rid)
+      {
+        // TODO: just send a game request message to the remote player,
+        // and when receiving answer just call loadGame(received_game)
+        // + remote peer should have registered us as an observer
+        // (send moves updates + resign/abort/draw actions)
+        return;
+      }
+      else
+      {
+        GameStorage.get(this.gameRef.id, async (game) => {
+          afterRetrieval(game);
+        });
+      }
 //    // 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))
@@ -300,7 +315,7 @@ export default {
       let addTime = undefined;
       if (move.color == this.game.mycolor)
       {
-        const elapsed = Date.now() - GameStorage.getInitime();
+        const elapsed = Date.now() - this.game.initime;
         this.game.players.forEach(p => {
           if (p.sid != this.st.user.sid)
           {
@@ -315,13 +330,20 @@ export default {
         // elapsed time is measured in milliseconds
         addTime = this.game.increment - elapsed/1000;
       }
-      GameStorage.update({
+      const myTurnNow = (this.vr.turn == this.game.mycolor);
+      GameStorage.update(this.gameRef.id,
+      {
         colorIdx: colorIdx,
         move: filtered_move,
         fen: move.fen,
         addTime: addTime,
-        initime: (this.vr.turn == this.game.mycolor), //my turn now?
+        initime: myTurnNow,
       });
+      // Also update current game object:
+      this.game.moves.push(move);
+      this.game.fen = move.fen;
+      this.game.clocks[colorIdx] += (!!addTime ? addTime : 0);
+      this.game.initime = (myTurnNow ? Date.now() : undefined);
     },
     // TODO: this update function should also work for corr games
     gameOver: function(score) {
index dd4621b..22aa53e 100644 (file)
@@ -81,7 +81,8 @@ import { ajax } from "@/utils/ajax";
 import { getRandString, shuffle } from "@/utils/alea";
 import GameList from "@/components/GameList.vue";
 import ChallengeList from "@/components/ChallengeList.vue";
-import { GameStorage } from "@/utils/storage";
+import { GameStorage } from "@/utils/gameStorage";
+import { extractTime } from "@/utils/timeControl";
 export default {
   name: "my-hall",
   components: {
@@ -292,20 +293,21 @@ export default {
         }
         case "askgame":
         {
-          // Send my current live game (if any)
-          if (!!localStorage["gid"])
-          {
-            const myGame =
-            {
-              // Minimal game informations: (fen+clock not required)
-              id: localStorage["gid"],
-              players: JSON.parse(localStorage["players"]), //array sid+id+name
-              vname: localStorage["vname"],
-              timeControl: localStorage["timeControl"],
-            };
-            this.st.conn.send(JSON.stringify({code:"game",
-              game:myGame, target:data.from}));
-          }
+          // Send my current live games (if any)
+          // TODO: from indexedDB, through GameStorage.
+//          if (!!localStorage["gid"])
+//          {
+//            const myGame =
+//            {
+//              // Minimal game informations: (fen+clock not required)
+//              id: localStorage["gid"],
+//              players: JSON.parse(localStorage["players"]), //array sid+id+name
+//              vname: localStorage["vname"],
+//              timeControl: localStorage["timeControl"],
+//            };
+//            this.st.conn.send(JSON.stringify({code:"game",
+//              game:myGame, target:data.from}));
+//          }
           break;
         }
         case "identity":
@@ -331,6 +333,7 @@ export default {
         {
           // Receive game from some player (+sid)
           // NOTE: it may be correspondance (if newgame while we are connected)
+          // TODO: ambiguous naming "newGame" ==> rename function ?
           let newGame = data.game;
           newGame.type = this.classifyObject(data.game);
           newGame.vname = newGame.vname;
@@ -586,14 +589,27 @@ export default {
     },
     // NOTE: for live games only (corr games are launched on server)
     newGame: function(gameInfo) {
-      GameStorage.init({
+      // Extract times (in [milli]seconds), set clocks
+      const tc = extractTime(gameInfo.timeControl);
+      const IPlayFirst = (gameInfo.players[0].sid == this.st.user.sid);
+      const game =
+      {
+        // Game infos: constant
         gameId: gameInfo.gameId,
         vname: this.getVname(gameInfo.vid),
         fenStart: gameInfo.fen,
         players: gameInfo.players,
         timeControl: gameInfo.timeControl,
-        initime: (gameInfo.players[0].sid == this.st.user.sid),
-      });
+        increment: tc.increment,
+        mode: "live", //function for live games only
+        // Game state: will be updated
+        fen: gameInfo.fen,
+        moves: [],
+        clocks: [...Array(gameInfo.players.length)].fill(tc.mainTime),
+        initime: (IPlayFirst ? Date.now() : undefined),
+        score: "*",
+      };
+      GameStorage.add(game);
       if (this.st.settings.sound >= 1)
         new Audio("/sounds/newgame.mp3").play().catch(err => {});
       // TODO: redirect to game
@@ -605,3 +621,9 @@ export default {
 <style lang="sass">
 // TODO
 </style>
+
+<!--
+// TODO:
+// Remove duplicates if several players of one game send their game info (Hall)
+// When click on it, assign a random rid among online players (max. 4).
+-->