From: Benjamin Auder Date: Fri, 6 Mar 2020 13:31:55 +0000 (+0100) Subject: Some improvements (multi-tabs on same game seem fixed) X-Git-Url: https://git.auder.net/?p=vchess.git;a=commitdiff_plain;h=57eb158fe8e37daaae11685df846003cda4aba19 Some improvements (multi-tabs on same game seem fixed) --- diff --git a/client/public/images/pieces/Hidden/wp.svg b/client/public/images/pieces/Hidden/wp.svg index e5016228..fa9a146d 100644 --- a/client/public/images/pieces/Hidden/wp.svg +++ b/client/public/images/pieces/Hidden/wp.svg @@ -23,7 +23,7 @@ image/svg+xml - + @@ -41,7 +41,7 @@ id="namedview8" showgrid="false" inkscape:zoom="0.26767309" - inkscape:cx="347.21094" + inkscape:cx="362.15454" inkscape:cy="440.83624" inkscape:window-x="0" inkscape:window-y="20" @@ -87,4 +87,24 @@ d="m 394.23381,791.93859 c -7.96934,-3.85734 -18.71233,-12.01208 -23.87329,-18.12165 -8.15598,-9.6551 -9.38357,-13.27828 -9.38357,-27.69552 0,-9.12297 1.93363,-20.47373 4.29695,-25.22392 13.36268,-26.85857 62.82698,-39.13548 96.68415,-23.99674 36.37643,16.26519 46.19804,54.40328 20.80685,80.79473 -20.75542,21.57308 -60.25727,27.92824 -88.53109,14.2431 z" id="path3745" inkscape:connector-curvature="0" /> + + + + diff --git a/client/src/App.vue b/client/src/App.vue index e9ece044..368982b4 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -193,11 +193,14 @@ nav border: none & > label.drawer-toggle cursor: pointer - font-size: 32px position: absolute - top: 5px - line-height: 1em + top: 0 + left: 5px + line-height: 42px + height: 42px padding: 0 + & > label.drawer-toggle:before + font-size: 42px & > #menuBar z-index: 5000 //to hide currently selected piece if any @@ -206,14 +209,14 @@ nav [type=checkbox].drawer+* .drawer-close top: 0 - left: 10px + left: 5px padding: 0 - width: 42px - height: 42px + height: 50px + width: 50px + line-height: 50px [type=checkbox].drawer+* .drawer-close:before font-size: 50px - line-height: 1em @media screen and (max-width: 767px) .button-group diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index 3c14a7fd..5c556563 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -87,12 +87,13 @@ export default { orientation: "w", score: "*", //'*' means 'unfinished' moves: [], - // TODO: later, use subCursor to navigate intra-multimoves? cursor: -1, //index of the move just played lastMove: null, firstMoveNumber: 0, //for printing incheck: [], //for Board - inMultimove: false + inMultimove: false, + inPlay: false, + stackToPlay: [] }; }, watch: { @@ -316,8 +317,15 @@ export default { } }, // "light": if gotoMove() or gotoEnd() - // data: some custom data (addTime) to be re-emitted - play: function(move, received, light, data) { + play: function(move, received, light, noemit) { + if (!!noemit) { + if (this.inPlay) { + // Received moves in observed games can arrive too fast: + this.stackToPlay.unshift(move); + return; + } + this.inPlay = true; + } const navigate = !move; const playSubmove = (smove) => { if (!navigate) smove.notation = this.vr.getNotation(smove); @@ -384,8 +392,15 @@ export default { } if (!navigate && this.game.mode != "analyze") { const L = this.moves.length; - // Post-processing (e.g. computer play) - this.$emit("newmove", this.moves[L-1], data); + if (!noemit) + // Post-processing (e.g. computer play) + this.$emit("newmove", this.moves[L-1]); + else { + this.inPlay = false; + if (this.stackToPlay.length > 0) + // Move(s) arrived in-between + this.play(this.stackToPlay.pop(), received, light, noemit); + } } } }; diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index 640ccca1..86c2acca 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -48,7 +48,10 @@ export default { }); const lm = this.lastMove; - const showLight = this.settings.highlight && V.ShowMoves == "all"; + const showLight = ( + this.settings.highlight && + ["all","highlight"].includes(V.ShowMoves) + ); const orientation = !V.CanFlip ? "w" : this.orientation; // Ensure that squares colors do not change when board is flipped const lightSquareMod = (sizeX + sizeY) % 2; diff --git a/client/src/components/MoveList.vue b/client/src/components/MoveList.vue index cc8e24c6..c0a15dff 100644 --- a/client/src/components/MoveList.vue +++ b/client/src/components/MoveList.vue @@ -21,7 +21,7 @@ div #scoreInfo(v-if="score!='*'") p {{ score }} p {{ st.tr[message] }} - .moves-list(v-if="show != 'none'") + .moves-list(v-if="!['none','highlight'].includes(show)") .tr(v-for="moveIdx in evenNumbers") .td {{ firstNum + moveIdx / 2 + 1 }} .td(v-if="moveIdx < moves.length-1 || show == 'all'" diff --git a/client/src/utils/gameStorage.js b/client/src/utils/gameStorage.js index 2e789942..480c73ba 100644 --- a/client/src/utils/gameStorage.js +++ b/client/src/utils/gameStorage.js @@ -84,6 +84,8 @@ export const GameStorage = { // Ignoring error silently: shouldn't happen now. TODO? if (event.target.result) { let game = event.target.result; + // Hidden tabs are delayed, to prevent multi-updates: + if (obj.moveIdx < game.moves.length) return; Object.keys(obj).forEach(k => { if (k == "move") game.moves.push(obj[k]); else game[k] = obj[k]; diff --git a/client/src/variants/Benedict.js b/client/src/variants/Benedict.js index e87827f4..4a31fceb 100644 --- a/client/src/variants/Benedict.js +++ b/client/src/variants/Benedict.js @@ -290,4 +290,15 @@ export const VariantRules = class BenedictRules extends ChessRules { // Stalemate: return "1/2"; } + + getNotation(move) { + // Just remove flips: + const basicMove = { + appear: [move.appear[0]], + vanish: [move.vanish[0]], + start: move.start, + end: move.end + }; + return super.getNotation(basicMove); + } }; diff --git a/client/src/variants/Hidden.js b/client/src/variants/Hidden.js index 5ccc3d7e..046fe99e 100644 --- a/client/src/variants/Hidden.js +++ b/client/src/variants/Hidden.js @@ -16,9 +16,9 @@ export const VariantRules = class HiddenRules extends ChessRules { return false; } - // Moves are revealed only when game ends + // Moves are revealed only when game ends, but are highlighted on board static get ShowMoves() { - return "none"; + return "highlight"; } static get HIDDEN_DECODE() { diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index fbdf4e63..9bca38ee 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -51,11 +51,23 @@ main p span.name(:class="{connected: isConnected(0)}") | {{ game.players[0].name || "@nonymous" }} - span.time(v-if="game.score=='*'") {{ virtualClocks[0] }} + span.time( + v-if="game.score=='*'" + :class="{yourturn: !!vr && vr.turn == 'w'}" + ) + span.time-left {{ virtualClocks[0][0] }} + span.time-separator(v-if="!!virtualClocks[0][1]") : + span.time-right(v-if="!!virtualClocks[0][1]") {{ virtualClocks[0][1] }} span.split-names - span.name(:class="{connected: isConnected(1)}") | {{ game.players[1].name || "@nonymous" }} - span.time(v-if="game.score=='*'") {{ virtualClocks[1] }} + span.time( + v-if="game.score=='*'" + :class="{yourturn: !!vr && vr.turn == 'b'}" + ) + span.time-left {{ virtualClocks[1][0] }} + span.time-separator(v-if="!!virtualClocks[1][1]") : + span.time-right(v-if="!!virtualClocks[1][1]") {{ virtualClocks[1][1] }} BaseGame( ref="basegame" :game="game" @@ -100,7 +112,7 @@ export default { chats: [], rendered: false }, - virtualClocks: [0, 0], //initialized with true game.clocks + virtualClocks: [[0,0], [0,0]], //initialized with true game.clocks vr: null, //"variant rules" object initialized from FEN drawOffer: "", people: {}, //players + observers @@ -111,6 +123,7 @@ export default { conn: null, roomInitialized: false, // If newmove has wrong index: ask fullgame again: + askGameTime: 0, gameIsLoading: false, // If asklastate got no reply, ask again: gotLastate: false, @@ -243,19 +256,26 @@ export default { }, askGameAgain: function() { this.gameIsLoading = true; - if (!this.gameRef.rid) - // This is my game: just reload. - this.loadGame(); - else { - // Just ask fullgame again (once!), this is much simpler. - // If this fails, the user could just reload page :/ - let self = this; - (function askIfPeerConnected() { - if (!!self.people[self.gameRef.rid]) - self.send("askfullgame", { target: self.gameRef.rid }); - else setTimeout(askIfPeerConnected, 1000); - })(); - } + const doAskGame = () => { + if (!this.gameRef.rid) + // This is my game: just reload. + this.loadGame(); + else { + // Just ask fullgame again (once!), this is much simpler. + // If this fails, the user could just reload page :/ + let self = this; + (function askIfPeerConnected() { + if (!!self.people[self.gameRef.rid]) + self.send("askfullgame", { target: self.gameRef.rid }); + else setTimeout(askIfPeerConnected, 1000); + })(); + } + }; + // Delay of at least 2s between two game requests + const now = Date.now(); + const delay = Math.max(2000 - (now - this.askGameTime), 0); + this.askGameTime = now; + setTimeout(doAskGame, delay); }, socketMessageListener: function(msg) { if (!this.conn) return; @@ -341,7 +361,7 @@ export default { if (!self.gotLastate && !!self.people[user.sid]) askLastate(); }, - 750 + 1000 ); })(); } @@ -415,7 +435,8 @@ export default { const movesCount = this.game.moves.length; if (movePlus.index > movesCount) { // This can only happen if I'm an observer and missed a move. - this.gotMoveIdx = movePlus.index; + if (this.gotMoveIdx < movePlus.index) + this.gotMoveIdx = movePlus.index; if (!this.gameIsLoading) this.askGameAgain(); } else { @@ -444,10 +465,9 @@ export default { GameStorage.update(this.gameRef.id, { drawOffer: "" }); } } - this.$refs["basegame"].play( + this.$refs["basegame"].play(movePlus.move, "received", null, true); + this.processMove( movePlus.move, - "received", - null, { addTime: movePlus.addTime, receiveMyMove: receiveMyMove @@ -667,7 +687,7 @@ export default { ); if (this.gameIsLoading) // Re-load game because we missed some moves: - // artificially reset BaseGame (required if moves arrive too quickly) + // artificially reset BaseGame (required if moves arrived in wrong order) this.$refs["basegame"].re_setVariables(); this.re_setClocks(); this.$nextTick(() => { @@ -699,7 +719,7 @@ export default { re_setClocks: function() { if (this.game.moves.length < 2 || this.game.score != "*") { // 1st move not completed yet, or game over: freeze time - this.virtualClocks = this.game.clocks.map(s => ppt(s)); + this.virtualClocks = this.game.clocks.map(s => ppt(s).split(':')); return; } const currentTurn = this.vr.turn; @@ -711,7 +731,7 @@ export default { this.virtualClocks = [0, 1].map(i => { const removeTime = i == colorIdx ? (Date.now() - this.game.initime[colorIdx]) / 1000 : 0; - return ppt(this.game.clocks[i] - removeTime); + return ppt(this.game.clocks[i] - removeTime).split(':'); }); let clockUpdate = setInterval(() => { if ( @@ -729,11 +749,11 @@ export default { this.$set( this.virtualClocks, colorIdx, - ppt(Math.max(0, --countdown)) + ppt(Math.max(0, --countdown)).split(':') ); }, 1000); }, - // Post-process a (potentially partial) move (which was just played in BaseGame) + // Update variables and storage after a move: processMove: function(move, data) { if (!data) data = {}; const moveCol = this.vr.turn; @@ -809,15 +829,21 @@ export default { drawOffer: drawCode || "n" }); } - else if (!document.hidden) { - // Live game: consider only the active tab - GameStorage.update(this.gameRef.id, { - fen: this.game.fen, - move: filtered_move, - clocks: this.game.clocks, - initime: this.game.initime, - drawOffer: drawCode - }); + else { + const updateStorage = () => { + GameStorage.update(this.gameRef.id, { + fen: this.game.fen, + move: filtered_move, + moveIdx: origMovescount, + clocks: this.game.clocks, + initime: this.game.initime, + drawOffer: drawCode + }); + }; + // The active tab can update storage immediately + if (!document.hidden) updateStorage(); + // Small random delay otherwise + else setTimeout(updateStorage, 500 + 1000 * Math.random()); } } // Send move ("newmove" event) to people in the room (if our turn) @@ -833,22 +859,25 @@ export default { this.opponentGotMove = false; this.send("newmove", {data: sendMove}); // If the opponent doesn't reply gotmove soon enough, re-send move: - let retrySendmove = setInterval( () => { - if (this.opponentGotMove) { - clearInterval(retrySendmove); - return; - } - let oppsid = this.game.players[nextIdx].sid; - if (!oppsid) { - oppsid = Object.keys(this.people).find( - sid => this.people[sid].id == this.game.players[nextIdx].uid - ); - } - if (!oppsid || !this.people[oppsid]) - // Opponent is disconnected: he'll ask last state - clearInterval(retrySendmove); - else this.send("newmove", {data: sendMove, target: oppsid}); - }, 750); + let retrySendmove = setInterval( + () => { + if (this.opponentGotMove) { + clearInterval(retrySendmove); + return; + } + let oppsid = this.game.players[nextIdx].sid; + if (!oppsid) { + oppsid = Object.keys(this.people).find( + sid => this.people[sid].id == this.game.players[nextIdx].uid + ); + } + if (!oppsid || !this.people[oppsid]) + // Opponent is disconnected: he'll ask last state + clearInterval(retrySendmove); + else this.send("newmove", {data: sendMove, target: oppsid}); + }, + 1000 + ); } }; if ( @@ -939,14 +968,29 @@ export default { font-weight: bold padding-right: 10px -.name +span.name font-size: 1.5rem - padding: 1px + padding: 0 3px -.time +span.time font-size: 2rem display: inline-block - margin-left: 10px + .time-left + margin-left: 10px + .time-right + margin-left: 5px + .time-separator + margin-left: 5px + position: relative + top: -1px + +span.yourturn + color: #831B1B + .time-separator + animation: blink-animation 2s steps(3, start) infinite +@keyframes blink-animation + to + visibility: hidden .split-names display: inline-block