From: Benjamin Auder <benjamin.auder@somewhere>
Date: Thu, 30 May 2019 23:11:40 +0000 (+0200)
Subject: GameStorage: experimental refactor
X-Git-Url: https://git.auder.net/variants/Chakart/img/scripts/doc/current/git-favicon.png?a=commitdiff_plain;h=8f5780d8cc67245c01cb381853a56cb510ad9ab2;p=vchess.git

GameStorage: experimental refactor
---

diff --git a/client/src/utils/storage.js b/client/src/utils/storage.js
index 2e243a83..2c2d4be2 100644
--- a/client/src/utils/storage.js
+++ b/client/src/utils/storage.js
@@ -1,124 +1,205 @@
-// TODO: general methods to access/retrieve from storage, to be generalized
-// https://developer.mozilla.org/fr/docs/Web/API/API_IndexedDB
-// https://dexie.org/
-
 import { extractTime } from "@/utils/timeControl";
 import { shuffle } from "@/utils/alea";
 
+// 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.deleteItem("gameInfo");
+  localStorage.deleteItem("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)
   {
-    localStorage.setItem("gameId", o.gameId);
-    localStorage.setItem("vname", o.vname);
     // NOTE: when >= 3 players, better use an array + shuffle for mycolor
     const mycolor = (Math.random() < 0.5 ? "w" : "b");
-    localStorage.setItem("mycolor", mycolor);
-    localStorage.setItem("fenStart", o.fenStart);
-    localStorage.setItem("fen", o.fenStart);
-    localStorage.setItem("moves", JSON.stringify([]));
     // Shuffle players order (white then black then other colors).
-    localStorage.setItem("players", JSON.stringify(shuffle(o.players)));
+    const players = shuffle(o.players);
     // Extract times (in [milli]seconds), set clocks, store in localStorage
     const tc = extractTime(o.timeControl);
-    localStorage.setItem("timeControl", o.timeControl);
-    localStorage.setItem("clocks", JSON.stringify(
-      [...Array(o.players.length)].fill(tc.mainTime)));
-    localStorage.setItem("increment", tc.increment);
-    localStorage.setItem("started", JSON.stringify(
-      [...Array(o.players.length)].fill(false)));
-    localStorage.setItem("score", "*");
-    localStorage.setItem("started", JSON.stringify(
-      [...Array(o.players.length)].fill(false)));
-    localStorage.setItem("clocks", JSON.stringify(
-      [...Array(o.players.length)].fill(0)));
-    localStorage.setItem("mode", "live"); //function for live games only
+
+    // game infos: constant
+    const gameInfo =
+    {
+      gameId: o.gameId,
+      vname: o.vname,
+      mycolor: mycolor,
+      fenStart: o.fenStart,
+      players: 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),
+      started: [...Array(o.players.length)].fill(false),
+      score: "*",
+    };
+
+    localStorage.setItem("gameInfo", JSON.stringify(gameInfo));
+    localStorage.setItem("gameState", JSON.stringify(gameState));
   },
 
-  // TODO: also option to takeback a move ?
+  // localStorage:
+  // TODO: also option to takeback a move ? Is fen included in move ?
   // NOTE: for live games only (all on server for corr)
-  update: function(move, score) //game ID is not required
+  update: function(fen, moves, clocks, started, score)
   {
-    if (!!move)
+    let gameState = JSON.parse(localStorage.getItem("gameState"));
+    if (!!fen)
     {
-      let moves = JSON.parse(localStorage.getItem("moves"));
-      moves.push(move);
-      localStorage.setItem("moves", JSON.stringify(moves));
+      gameState.moves = moves;
+      gameState.fen = fen;
+      gameState.clocks = clocks;
     }
+    if (!!started)
+      gameState.started = started;
     if (!!score)
-      localStorage.setItem("score", score);
+      gameState.score = score;
+    localStorage.setItem("gameState", JSON.stringify(gameState));
+    if (!!score && score != "*")
+      transferToDb(); //game is over
   },
 
-  transferToDb: function()
+  // indexedDB:
+  // Since DB requests are asynchronous, require a callback using the result
+  // TODO: option for remote retrieval (third arg, or just "gameRef")
+  getLocal: function(callback, gameId)
   {
-    // TODO: take finished game on localStorage and transfer it to indexedDB
+    let games = [];
+    dbOperation((db) => {
+      // TODO: if gameId is provided, limit search to gameId (just .get(gameId). ...)
+      let objectStore = db.transaction('games').objectStore('games');
+      objectStore.openCursor().onsuccess = function(event) {
+        var cursor = event.target.result;
+        // if there is still another cursor to go, keep runing this code
+        if (cursor)
+        {
+          games.push(cursor.value);
+          cursor.continue();
+        }
+        else
+          callback(games);
+      }
+    });
   },
 
-  // "computer mode" clearing is done through the menu
-  clear: function()
+  // Delete a game in indexedDB
+  remove: function(gameId, callback)
   {
-    localStorage.setItem("gameId", o.gameId);
-    localStorage.setItem("vname", o.vname);
-    // NOTE: when >= 3 players, better use an array + shuffle for mycolor
-    const mycolor = (Math.random() < 0.5 ? "w" : "b");
-    localStorage.setItem("mycolor", mycolor);
-    localStorage.setItem("fenStart", o.fenStart);
-    localStorage.setItem("fen", o.fenStart);
-    localStorage.setItem("moves", JSON.stringify([]));
-    // Shuffle players order (white then black then other colors).
-    localStorage.setItem("players", JSON.stringify(shuffle(o.players)));
-    // Extract times (in [milli]seconds), set clocks, store in localStorage
-    const tc = extractTime(o.timeControl);
-    localStorage.setItem("timeControl", o.timeControl);
-    localStorage.setItem("clocks", JSON.stringify(
-      [...Array(o.players.length)].fill(tc.mainTime)));
-    localStorage.setItem("increment", tc.increment);
-    localStorage.setItem("started", JSON.stringify(
-      [...Array(o.players.length)].fill(false)));
-    localStorage.setItem("score", "*");
-    localStorage.setItem("started", JSON.stringify(
-      [...Array(o.players.length)].fill(false)));
-    localStorage.setItem("clocks", JSON.stringify(
-      [...Array(o.players.length)].fill(0)));
-    localStorage.setItem("mode", "live"); //function for live games only
-    
-
-    // TODO: refresh, and implement "transfert" function (to indexedDB)
-    localStorage["myid"];
-    localStorage["oppid"];
-    delete localStorage["gameId"];
-    delete localStorage["variant"];
-    delete localStorage["mycolor"];
-    delete localStorage["fenStart"];
-    delete localStorage["moves"];
+    dbOperation((db) => {
+      let transaction = db.transaction(["games"], "readwrite");
+      if (callback)
+      {
+        transaction.oncomplete = function() {
+          callback({}); //everything's fine
+        }
+        transaction.onerror = function() {
+          callback({errmsg: "deleteGame failed: " + transaction.error});
+        };
+      }
+      transaction.objectStore("games").delete(gameId);
+    });
   },
 
-  // TODO: game or gameInfo ?! --> when moves are played, it's a game, otherwise info
-  get: function(gameRef)
+  // 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
-    let game = {};
-    if (localStorage.getItem("gameId") === gid)
+    if (!!rid)
     {
-      // Retrieve running game from localStorage
-      game.score = localStorage.getItem("score");
-      game.mycolor = localStorage.getItem("mycolor");
-      game.fenStart = localStorage.getItem("fenStart");
-      game.fen = localStorage.getItem("fen");
-      game.moves = JSON.parse(localStorage.getItem("moves"));
-      game.players = JSON.parse(localStorage.getItem("players"));
-      game.started = JSON.parse(localStorage.getItem("started"));
-      game.clocks = JSON.parse(localStorage.getItem("clocks"));
-      game.timeControl = localStorage.getItem("timeControl");
-      game.increment = localStorage.getItem("increment");
-      game.vname = localStorage.getItem("vname");
-      game.mode = "live";
+      // 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...)
     }
-    else
+
+    const gameInfoStr = localStorage.getItem("gameInfo");
+    if (gameInfoStr)
     {
-      // Find the game in indexedDB, on server or remotely: TODO
+      const gameInfo = JSON.parse(gameInfoStr);
+      if (gameInfo.gameId == gid)
+      {
+        const gameState = JSON.parse(localStorage.getItem("gameState"));
+        return callback(Object.assign({}, gameInfo, gameState));
+      }
     }
-    return game;
+
+    // Game is local and not running
+    getLocal(callback, gid);
   },
 };