From: Benjamin Auder Date: Thu, 30 May 2019 23:11:40 +0000 (+0200) Subject: GameStorage: experimental refactor X-Git-Url: https://git.auder.net/variants/Chakart/doc/scripts/assets/R.css?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); }, };