X-Git-Url: https://git.auder.net/?p=vchess.git;a=blobdiff_plain;f=client%2Fsrc%2Fviews%2FGame.vue;h=c3f211984e3b0b649e8cff54dac618ba1cee4ada;hp=6722f3341d8b0c1102fcc9adaed3d3f38216bfc2;hb=16d06164d56332bd00fb22acc5b2b2997b942d73;hpb=d060d8cb355367f3cd9533c9ad37552802090502 diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 6722f334..c3f21198 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -7,7 +7,7 @@ main ) .card label.modal-close(for="modalRules") - a#variantNameInGame(:href="'/#/variants/'+game.vname") {{ game.vname }} + a#variantNameInGame(:href="'/#/variants/'+game.vname") {{ game.vdisp }} div(v-html="rulesContent") input#modalScore.modal(type="checkbox") div#scoreDiv( @@ -78,7 +78,9 @@ main .row #aboveBoard.col-sm-12 span.variant-cadence(v-if="game.type!='import'") {{ game.cadence }} - span.variant-name {{ game.vname }} + span.variant-name + | {{ game.vname }} + | {{ !!vr ? vr.constructor.AbbreviateOptions(game.options) : '' }} span#nextGame( v-if="nextIds.length > 0" @click="showNextGame()" @@ -196,6 +198,7 @@ import { getDiagram, replaceByDiag } from "@/utils/printDiagram"; import { processModalClick } from "@/utils/modalClick"; import { playMove, getFilteredMove } from "@/utils/playUndo"; import { ArrayFun } from "@/utils/array"; +import afterRawLoad from "@/utils/afterRawLoad"; import params from "@/parameters"; export default { name: "my-game", @@ -233,7 +236,8 @@ export default { // If newmove got no pingback, send again: opponentGotMove: false, connexionString: "", - socketCloseListener: 0, + reopenTimeout: 0, + reconnectTimeout: 0, // Incomplete info games: show move played moveNotation: "", // Intervals from setInterval(): @@ -286,14 +290,13 @@ export default { }, methods: { cleanBeforeDestroy: function() { - clearInterval(this.socketCloseListener); document.removeEventListener('visibilitychange', this.visibilityChange); window.removeEventListener('focus', this.onFocus); window.removeEventListener('blur', this.onBlur); if (!!this.askLastate) clearInterval(this.askLastate); if (!!this.retrySendmove) clearInterval(this.retrySendmove); if (!!this.clockUpdate) clearInterval(this.clockUpdate); - this.conn.removeEventListener("message", this.socketMessageListener); + clearTimeout(this.reopenTimeout); this.send("disconnect"); this.conn = null; }, @@ -330,6 +333,29 @@ export default { ) ); }, + requestLastate: function(sid) { + // TODO: maybe also find opponent SID ? + //const oppSid = + // this.game.players.find(p => p.sid != this.st.user.sid).sid; + this.send("asklastate", { target: sid }); + let counter = 1; + this.askLastate = setInterval( + () => { + // Ask at most 3 times: + // if no reply after that there should be a network issue. + if ( + counter < 3 && + !this.gotLastate && + !!this.people[sid] + ) { + this.send("asklastate", { target: sid }); + counter++; + } + else clearInterval(this.askLastate); + }, + 1500 + ); + }, atCreation: function() { document.addEventListener('visibilitychange', this.visibilityChange); window.addEventListener('focus', this.onFocus); @@ -348,7 +374,7 @@ export default { id: my.id, name: my.name, tmpIds: { - tmpId: { focus: true } + [tmpId]: { focus: true } } } ); @@ -383,32 +409,52 @@ export default { "&page=" + // Discard potential "/?next=[...]" for page indication: encodeURIComponent(this.$route.path.match(/\/game\/[a-zA-Z0-9]+/)[0]); + openConnection(); + }, + openConnection: function() { this.conn = new WebSocket(this.connexionString); - this.conn.addEventListener("message", this.socketMessageListener); - this.socketCloseListener = setInterval( - () => { - if (this.conn.readyState == 3) { - this.conn.removeEventListener( - "message", this.socketMessageListener); - this.conn = new WebSocket(this.connexionString); - this.conn.addEventListener("message", this.socketMessageListener); - } - }, - 1000 - ); + const onOpen = () => { + this.reconnectTimeout = 250; + const oppSid = this.getOppsid(); + if (!!oppSid) this.requestLastate(oppSid); //in case of + }; + this.conn.onopen = onOpen; + this.conn.onmessage = this.socketMessageListener; + const closeConnection = () => { + this.reopenTimeout = setTimeout( + () => { + this.openConnection(); + this.reconnectTimeout = Math.min(2*this.reconnectTimeout, 30000); + }, + this.reconnectTimeout + ); + }; + this.conn.onerror = closeConnection; + this.conn.onclose = closeConnection; // Socket init required before loading remote game: const socketInit = callback => { if (this.conn.readyState == 1) // 1 == OPEN state callback(); - else + else { // Socket not ready yet (initial loading) // NOTE: first arg is Websocket object, unused here: - this.conn.onopen = () => callback(); + this.conn.onopen = () => { + onOpen(); + callback(); + }; + } }; this.fetchGame((game) => { - if (!!game) + if (!!game) { + if (!game.options) { + // Patch for retro-compatibility (TODO: remove it) + game.options = { randomness: game.randomness }; + delete game["randomness"]; + } + else game.options = JSON.parse(game.options); this.loadVariantThenGame(game, () => socketInit(this.roomInit)); + } else // Live game stored remotely: need socket to retrieve it // NOTE: the callback "roomInit" will be lost, so it's not provided. @@ -513,7 +559,8 @@ export default { "DELETE", { data: { gid: this.game.id } } ); - } else { + } + else { // Live game GameStorage.update(this.gameRef, { delchat: true }); } @@ -522,7 +569,7 @@ export default { }, getGameType: function(game) { if (!!game.id.toString().match(/^i/)) return "import"; - return game.cadence.indexOf("d") >= 0 ? "corr" : "live"; + return (game.cadence.indexOf("d") >= 0 ? "corr" : "live"); }, // Notify something after a new move (to opponent and me on MyGames page) notifyMyGames: function(thing, data) { @@ -577,7 +624,8 @@ export default { // For self multi-connects tests: this.newConnect[data.from[0]] = true; this.send("askidentity", { target: data.from[0] }); - } else { + } + else { this.people[data.from[0]].tmpIds[data.from[1]] = { focus: true }; this.$forceUpdate(); //TODO: shouldn't be required } @@ -660,25 +708,7 @@ export default { this.game.type == "live" && this.game.players.some(p => p.sid == user.sid) ) { - this.send("asklastate", { target: user.sid }); - let counter = 1; - this.askLastate = setInterval( - () => { - // Ask at most 3 times: - // if no reply after that there should be a network issue. - if ( - counter < 3 && - !this.gotLastate && - !!this.people[user.sid] - ) { - this.send("asklastate", { target: user.sid }); - counter++; - } else { - clearInterval(this.askLastate); - } - }, - 1500 - ); + this.requestLastate(user.sid); } break; } @@ -705,7 +735,7 @@ export default { const gameToSend = Object.keys(this.game) .filter(k => [ - "id","fen","players","vid","cadence","fenStart","vname", + "id","fen","players","vid","cadence","fenStart","options", "moves","clocks","score","drawOffer","rematchOffer" ].includes(k)) .reduce( @@ -751,9 +781,9 @@ export default { case "newmove": { // DEBUG: -console.log("Receive move"); -console.log(data.data); -//moveslist not updated when receiving a move? (see in baseGame) +//console.log("Receive move"); +//console.log(data.data); +//moveslist not updated when receiving a move? (see in BaseGame) const movePlus = data.data; const movesCount = this.game.moves.length; @@ -873,13 +903,15 @@ console.log(data.data); gameInfo.players.some(p => p.sid == this.st.user.sid) ) { this.addAndGotoLiveGame(gameInfo); - } else if ( + } + else if ( gameType == "corr" && this.st.user.id > 0 && gameInfo.players.some(p => p.id == this.st.user.id) ) { this.$router.push("/game/" + gameInfo.id); - } else { + } + else { this.rematchId = gameInfo.id; document.getElementById("modalRules").checked = false; document.getElementById("modalScore").checked = false; @@ -971,7 +1003,8 @@ console.log(data.data); // Just got last move from him this.$refs["basegame"].play(data.lastMove, "received"); this.processMove(data.lastMove); - } else { + } + else { if (!!this.clockUpdate) clearInterval(this.clockUpdate); this.re_setClocks(); } @@ -993,7 +1026,8 @@ console.log(data.data); : "Three repetitions"; this.send("draw", { data: message }); this.gameOver("1/2", message); - } else if (this.drawOffer == "") { + } + else if (this.drawOffer == "") { // No effect if drawOffer == "sent" if (this.game.mycolor != this.vr.turn) { alert(this.st.tr["Draw offer only in your turn"]); @@ -1007,7 +1041,8 @@ console.log(data.data); this.gameRef, { drawOffer: this.game.mycolor } ); - } else this.updateCorrGame({ drawOffer: this.game.mycolor }); + } + else this.updateCorrGame({ drawOffer: this.game.mycolor }); } }, addAndGotoLiveGame: function(gameInfo, callback) { @@ -1017,7 +1052,6 @@ console.log(data.data); { // (other) Game infos: constant fenStart: gameInfo.fen, - vname: this.game.vname, created: Date.now(), // Game state (including FEN): will be updated moves: [], @@ -1042,39 +1076,51 @@ console.log(data.data); // Start a new game! let gameInfo = { id: getRandString(), //ignored if corr - fen: V.GenRandInitFen(this.game.randomness), + fen: V.GenRandInitFen(this.game.options), + options: JSON.stringify(this.game.options), players: [this.game.players[1], this.game.players[0]], vid: this.game.vid, cadence: this.game.cadence }; const notifyNewGame = () => { - const oppsid = this.getOppsid(); //may be null - this.send("rnewgame", { data: gameInfo, oppsid: oppsid }); + this.send("rnewgame", { data: gameInfo }); // To main Hall if corr game: if (this.game.type == "corr") this.send("newgame", { data: gameInfo, page: "/" }); // Also to MyGames page: this.notifyMyGames("newgame", gameInfo); }; - if (this.game.type == "live") + if (this.game.type == "live") { + GameStorage.update( + this.gameRef, + { rematchOffer: "" } + ); + // Increment game stats counter in DB + ajax( + "/gamestat", + "POST", + { data: { vid: gameInfo.vid } } + ); this.addAndGotoLiveGame(gameInfo, notifyNewGame); + } else { // corr game + this.updateCorrGame({ rematchOffer: 'n' }); ajax( "/games", "POST", { - // cid is useful to delete the challenge: data: { gameInfo: gameInfo }, success: (response) => { - gameInfo.id = response.gameId; + gameInfo.id = response.id; notifyNewGame(); - this.$router.push("/game/" + response.gameId); + this.$router.push("/game/" + response.id); } } ); } - } else if (this.rematchOffer == "") { + } + else if (this.rematchOffer == "") { this.rematchOffer = "sent"; this.send("rematchoffer", { data: true }); if (this.game.type == "live") { @@ -1082,8 +1128,10 @@ console.log(data.data); this.gameRef, { rematchOffer: this.game.mycolor } ); - } else this.updateCorrGame({ rematchOffer: this.game.mycolor }); - } else if (this.rematchOffer == "sent") { + } + else this.updateCorrGame({ rematchOffer: this.game.mycolor }); + } + else if (this.rematchOffer == "sent") { // Toggle rematch offer (on --> off) this.rematchOffer = ""; this.send("rematchoffer", { data: false }); @@ -1092,7 +1140,8 @@ console.log(data.data); this.gameRef, { rematchOffer: '' } ); - } else this.updateCorrGame({ rematchOffer: 'n' }); + } + else this.updateCorrGame({ rematchOffer: 'n' }); } }, abortGame: function() { @@ -1161,11 +1210,10 @@ console.log(data.data); { clocks: game.clocks } ); } - } else { - if (!!game.initime) - // It's my turn: clocks not updated yet - game.clocks[myIdx] -= (Date.now() - game.initime) / 1000; } + else if (!!game.initime) + // It's my turn: clocks not updated yet + game.clocks[myIdx] -= (Date.now() - game.initime) / 1000; } else // gtype == "import" @@ -1261,31 +1309,45 @@ console.log(data.data); if (!!callback) callback(); }, loadVariantThenGame: async function(game, callback) { - await import("@/variants/" + game.vname + ".js") - .then((vModule) => { - window.V = vModule[game.vname + "Rules"]; - this.loadGame(game, callback); - }); - // (AJAX) Request to get rules content (plain text, HTML) - this.rulesContent = - require( - "raw-loader!@/translations/rules/" + - game.vname + "/" + - this.st.lang + ".pug" - ) - // Next two lines fix a weird issue after last update (2019-11) - .replace(/\\n/g, " ") - .replace(/\\"/g, '"') - .replace('module.exports = "', "") - .replace(/"$/, "") - .replace(/(fen:)([^:]*):/g, replaceByDiag); + const afterSetVname = async () => { + await import("@/variants/" + game.vname + ".js") + .then((vModule) => { + window.V = vModule[game.vname + "Rules"]; + this.loadGame(game, callback); + }); + this.rulesContent = + afterRawLoad( + require( + "raw-loader!@/translations/rules/" + + game.vname + "/" + this.st.lang + ".pug" + ).default + ).replace(/(fen:)([^:]*):/g, replaceByDiag); + }; + let variant = undefined; + const trySetVname = setInterval( + () => { + // this.st.variants might be uninitialized (variant == null) + variant = this.st.variants.find(v => { + return v.id == game.vid || v.name == game.vname + }); + if (!!variant) { + clearInterval(trySetVname); + game.vname = variant.name; + game.vdisp = variant.display; + afterSetVname(); + } + }, 500 + ); }, // 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) fetchGame: function(callback) { - if (Number.isInteger(this.gameRef) || !isNaN(parseInt(this.gameRef))) { + if ( + Number.isInteger(this.gameRef) || + !isNaN(parseInt(this.gameRef, 10)) + ) { // corr games identifiers are integers ajax( "/games", @@ -1331,7 +1393,8 @@ console.log(data.data); currentTurn == "w" ? "0-1" : "1-0", "Time" ); - } else { + } + else { this.$set( this.virtualClocks, colorIdx, @@ -1387,14 +1450,20 @@ console.log(data.data); // In corr games, just reset clock to mainTime: this.game.clocks[colorIdx] = extractTime(this.game.cadence).mainTime; } - // If repetition detected, consider that a draw offer was received: - const fenObj = this.vr.getFenForRepeat(); - this.repeat[fenObj] = - !!this.repeat[fenObj] - ? this.repeat[fenObj] + 1 - : 1; - if (this.repeat[fenObj] >= 3) this.drawOffer = "threerep"; - else if (this.drawOffer == "threerep") this.drawOffer = ""; + if (!V.IgnoreRepetition) { + // If repetition detected, consider that a draw offer was received: + const fenObj = this.vr.getFenForRepeat(); + this.repeat[fenObj] = + !!this.repeat[fenObj] + ? this.repeat[fenObj] + 1 + : 1; + if (this.repeat[fenObj] >= 3) { + if (this.vr.loseOnRepetition()) + this.gameOver(moveCol == "w" ? "0-1" : "1-0", "Repetition"); + else this.drawOffer = "threerep"; + } + else if (this.drawOffer == "threerep") this.drawOffer = ""; + } if (!!this.game.mycolor && !data.receiveMyMove) { // NOTE: 'var' to see that variable outside this block var filtered_move = getFilteredMove(move); @@ -1517,7 +1586,6 @@ console.log(data.data); }; if ( this.game.type == "corr" && - V.CorrConfirm && moveCol == this.game.mycolor && !data.receiveMyMove ) { @@ -1533,6 +1601,10 @@ console.log(data.data); if (data.score == "*") this.re_setClocks(); } }; + if (!V.CorrConfirm) { + afterSetScore(); + return; + } let el = document.querySelector("#buttonsConfirm > .acceptBtn"); // We may play several moves in a row: in case of, remove listener: let elClone = el.cloneNode(true); @@ -1557,7 +1629,9 @@ console.log(data.data); if (["all","byrow"].includes(V.ShowMoves)) { this.curDiag = getDiagram({ position: position, - orientation: V.CanFlip ? this.game.mycolor : "w" + orientation: V.CanFlip ? this.game.mycolor : "w", + color: this.game.mycolor, + score: "*" }); document.querySelector("#confirmDiv > .card").style.width = boardDiv.offsetWidth + "px"; @@ -1587,7 +1661,7 @@ console.log(data.data); // In corr games, callback to change page only after score is set: gameOver: function(score, scoreMsg, callback) { this.game.score = score; - if (!scoreMsg) scoreMsg = getScoreMessage(score); + if (!scoreMsg) scoreMsg = getScoreMessage(score, V.ReverseColors); this.game.scoreMsg = scoreMsg; document.getElementById("modalRules").checked = false; // Display result in a un-missable way: