X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fviews%2FGame.vue;h=ffb7564ec97fed55ba238b3649a7fd8e75c94e6e;hb=978fa11c8c9838018124a36ec14e06856d948d1e;hp=0d9cba228c04785202efb8f86833116168654eca;hpb=5b18515f0b7dbfab8a2770d9b0fc7aace09267dc;p=vchess.git diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 0d9cba22..ffb7564e 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -7,7 +7,7 @@ main ) .card label.modal-close(for="modalRules") - h4#variantNameInGame(@click="gotoRules") {{ game.vname }} + a#variantNameInGame(:href="'/#/variants/'+game.vname") {{ game.vname }} div(v-html="rulesContent") input#modalScore.modal(type="checkbox") div#scoreDiv( @@ -84,39 +84,42 @@ main @click="showNextGame()" ) | {{ st.tr["Next_g"] }} - button#chatBtn.tooltip( + button#chatBtn( + :class="btnTooltipClass()" onClick="window.doClick('modalChat')" aria-label="Chat" ) img(src="/images/icons/chat.svg") #actions(v-if="game.score=='*'") - button.tooltip( + button( @click="clickDraw()" - :class="{['draw-' + drawOffer]: true}" + :class="btnTooltipClass('draw')" :aria-label="st.tr['Draw']" ) img(src="/images/icons/draw.svg") - button.tooltip( + button( v-if="!!game.mycolor" + :class="btnTooltipClass()" @click="abortGame()" :aria-label="st.tr['Abort']" ) img(src="/images/icons/abort.svg") - button.tooltip( + button( v-if="!!game.mycolor" + :class="btnTooltipClass()" @click="resign()" :aria-label="st.tr['Resign']" ) img(src="/images/icons/resign.svg") - button.tooltip( + button( v-else + :class="btnTooltipClass('rematch')" @click="clickRematch()" - :class="{['rematch-' + rematchOffer]: true}" :aria-label="st.tr['Rematch']" ) img(src="/images/icons/rematch.svg") #playersInfo - p(v-if="isLargeScreen()") + div(v-if="isLargeScreen()") span.name(:class="{connected: isConnected(0)}") | {{ game.players[0].name || "@nonymous" }} span.time( @@ -138,30 +141,24 @@ main span.time-separator(v-if="!!virtualClocks[1][1]") : span.time-right(v-if="!!virtualClocks[1][1]") | {{ virtualClocks[1][1] }} - p(v-else) + div(v-else) span.name(:class="{connected: isConnected(0)}") | {{ game.players[0].name || "@nonymous" }} span.split-names - span.name(:class="{connected: isConnected(1)}") | {{ game.players[1].name || "@nonymous" }} - br - 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.separator - 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] }} + div(v-if="game.score=='*'") + span.time(: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.separator + span.time(: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" @@ -215,9 +212,6 @@ export default { curDiag: "", //for corr moves confirmation 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, gotMoveIdx: -1, //last move index received @@ -270,12 +264,6 @@ export default { .addEventListener("click", processModalClick); } ); - if ("ontouchstart" in window) { - // Disable tooltips on smartphones: - document.querySelectorAll("#aboveBoard .tooltip").forEach(elt => { - elt.classList.remove("tooltip"); - }); - } }, beforeDestroy: function() { this.cleanBeforeDestroy(); @@ -296,11 +284,6 @@ export default { visibilityChange: function() { // TODO: Use document.hidden? https://webplatform.news/issues/2019-03-27 this.focus = (document.visibilityState == "visible"); - if (!this.focus && !!this.rematchOffer) { - this.rematchOffer = ""; - this.send("rematchoffer", { data: false }); - // Do not remove rematch offer from (local) storage - } this.send(this.focus ? "getfocus" : "losefocus"); }, onFocus: function() { @@ -309,17 +292,20 @@ export default { }, onBlur: function() { this.focus = false; - if (!!this.rematchOffer) { - this.rematchOffer = ""; - this.send("rematchoffer", { data: false }); - } this.send("losefocus"); }, isLargeScreen: function() { - return window.innerWidth >= 500; + return window.innerWidth >= 768; }, - gotoRules: function() { - this.$router.push("/variants/" + this.game.vname); + btnTooltipClass: function(thing) { + let append = {}; + if (!!thing) append = { [thing + "-" + this[thing + "Offer"]]: true }; + return ( + Object.assign( + { tooltip: !("ontouchstart" in window) }, + append + ) + ); }, participateInChat: function(p) { return Object.keys(p.tmpIds).some(x => p.tmpIds[x].focus) && !!p.name; @@ -368,8 +354,6 @@ export default { this.rematchOffer = ""; this.lastate = undefined; this.roomInitialized = false; - this.askGameTime = 0; - this.gameIsLoading = false; this.gotLastate = false; this.gotMoveIdx = -1; this.opponentGotMove = false; @@ -437,10 +421,14 @@ export default { isConnected: function(index) { const player = this.game.players[index]; // Is it me ? In this case no need to bother with focus - if (this.st.user.sid == player.sid || this.st.user.id == player.id) + if ( + this.st.user.sid == player.sid || + (!!player.name && this.st.user.id == player.id) + ) { // Still have to check for name (because of potential multi-accounts // on same browser, although this should be rare...) return (!this.st.user.name || this.st.user.name == player.name); + } // Try to find a match in people: return ( ( @@ -454,7 +442,7 @@ export default { ) || ( - !!player.id && + player.id > 0 && Object.values(this.people).some(p => { return ( p.id == player.id && @@ -543,27 +531,6 @@ export default { this.$router.push( "/game/" + nextGid + "/?next=" + JSON.stringify(this.nextIds)); }, - askGameAgain: function() { - this.gameIsLoading = true; - const currentUrl = document.location.href; - const doAskGame = () => { - if (document.location.href != currentUrl) return; //page change - this.fetchGame((game) => { - if (!!game) - // This is my game: just reload. - this.loadGame(game); - else - // Just ask fullgame again (once!), this is much simpler. - // If this fails, the user could just reload page :/ - this.send("askfullgame"); - }); - }; - // 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; const data = JSON.parse(msg.data); @@ -678,7 +645,6 @@ export default { !this.gotLastate && !!this.game.mycolor && this.game.type == "live" && - this.game.score == "*" && this.game.players.some(p => p.sid == user.sid) ) { this.send("asklastate", { target: user.sid }); @@ -754,10 +720,15 @@ export default { if (!this.game || !this.game.moves) this.lastateAsked = true; else this.sendLastate(data.from); break; + // TODO: possible bad scenario: reload page while oppponent sends a + // move => get both lastate and newmove, process both, add move twice. + // Confirm scenario? Fix? case "lastate": { // Got opponent infos about last move this.gotLastate = true; this.lastate = data.data; + if (this.lastate.movesCount - 1 > this.gotMoveIdx) + this.gotMoveIdx = this.lastate.movesCount - 1; if (this.game.rendered) // Game is rendered (Board component) this.processLastate(); @@ -765,63 +736,77 @@ export default { break; } case "newmove": { + +// DEBUG: +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; - if (movePlus.index > movesCount) { - // This can only happen if I'm an observer and missed a move. - if (this.gotMoveIdx < movePlus.index) - this.gotMoveIdx = movePlus.index; - if (!this.gameIsLoading) this.askGameAgain(); + if ( + movePlus.index < movesCount || + this.gotMoveIdx >= movePlus.index + ) { + // Opponent re-send but we already have the move: + // (maybe he didn't receive our pingback...) + this.send("gotmove", {data: movePlus.index, target: data.from}); } else { - if ( - movePlus.index < movesCount || - this.gotMoveIdx >= movePlus.index - ) { - // Opponent re-send but we already have the move: - // (maybe he didn't receive our pingback...) - this.send("gotmove", {data: movePlus.index, target: data.from}); - } else { - this.gotMoveIdx = movePlus.index; - const receiveMyMove = (movePlus.color == this.game.mycolor); - const moveColIdx = ["w", "b"].indexOf(movePlus.color); - if (!receiveMyMove && !!this.game.mycolor) { - // Notify opponent that I got the move: - this.send( - "gotmove", - { data: movePlus.index, target: data.from } + this.gotMoveIdx = movePlus.index; + const receiveMyMove = (movePlus.color == this.game.mycolor); + const moveColIdx = ["w", "b"].indexOf(movePlus.color); + if (!receiveMyMove && !!this.game.mycolor) { + // Notify opponent that I got the move: + this.send( + "gotmove", + { data: movePlus.index, target: data.from } + ); + // And myself if I'm elsewhere: + if (!this.focus) { + notify( + "New move", + { + body: + (this.game.players[moveColIdx].name || "@nonymous") + + " just played." + } ); - // And myself if I'm elsewhere: - if (!this.focus) { - notify( - "New move", - { - body: - (this.game.players[moveColIdx].name || "@nonymous") + - " just played." - } - ); - } } - if (movePlus.cancelDrawOffer) { - // Opponent refuses draw - this.drawOffer = ""; - // NOTE for corr games: drawOffer reset by player in turn - if ( - this.game.type == "live" && - !!this.game.mycolor && - !receiveMyMove - ) { - GameStorage.update(this.gameRef, { drawOffer: "" }); - } + } + if (movePlus.cancelDrawOffer) { + // Opponent refuses draw + this.drawOffer = ""; + // NOTE for corr games: drawOffer reset by player in turn + if ( + this.game.type == "live" && + !!this.game.mycolor && + !receiveMyMove + ) { + GameStorage.update(this.gameRef, { drawOffer: "" }); } - this.$refs["basegame"].play(movePlus.move, "received"); - this.game.clocks[moveColIdx] = movePlus.clock; - this.processMove( - movePlus.move, - { receiveMyMove: receiveMyMove } - ); } + this.$refs["basegame"].play(movePlus.move, "received"); + // Freeze time while the move is being play + // (TODO: a callback would be cleaner here) + clearInterval(this.clockUpdate); + this.clockUpdate = null; + const freezeDuration = ["all", "highlight"].includes(V.ShowMoves) + // 250 = length of animation, 500 = delay between sub-moves + ? 250 + 750 * + (Array.isArray(movePlus.move) ? movePlus.move.length - 1 : 0) + // Incomplete information: no move animation + : 0; + setTimeout( + () => { + this.game.clocks[moveColIdx] = movePlus.clock; + this.processMove( + movePlus.move, + { receiveMyMove: receiveMyMove } + ); + }, + freezeDuration + ); } break; } @@ -862,7 +847,7 @@ export default { if (!!this.game.mycolor && this.game.type == "live") { GameStorage.update( this.gameRef, - { rematchOffer: V.GetOppCol(this.game.mycolor) } + { rematchOffer: data.data ? V.GetOppCol(this.game.mycolor) : "" } ); } break; @@ -877,6 +862,7 @@ export default { this.addAndGotoLiveGame(gameInfo); } 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); @@ -929,8 +915,8 @@ export default { clock: this.game.clocks[myIdx], // Since we played a move (or abort or resign), // only drawOffer=="sent" is possible - drawSent: this.drawOffer == "sent", - rematchSent: this.rematchOffer == "sent", + drawSent: this.drawOffer == "sent" ? true : undefined, + rematchSent: this.rematchOffer == "sent" ? true : undefined, score: this.game.score != "*" ? this.game.score : undefined, scoreMsg: this.game.score != "*" ? this.game.scoreMsg : undefined, movesCount: L @@ -941,23 +927,47 @@ export default { processLastate: function() { const data = this.lastate; this.lastate = undefined; //security... - const L = this.game.moves.length; - const oppIdx = 1 - ["w", "b"].indexOf(this.game.mycolor); - this.game.clocks[oppIdx] = data.clock; - if (data.movesCount > L) { - // Just got last move from him - this.$refs["basegame"].play(data.lastMove, "received"); - this.processMove(data.lastMove); - } else { - if (!!this.clockUpdate) clearInterval(this.clockUpdate); - this.re_setClocks(); - } - if (data.drawSent) this.drawOffer = "received"; - if (data.rematchSent) this.rematchOffer = "received"; if (!!data.score) { - this.drawOffer = ""; - if (this.game.score == "*") - this.gameOver(data.score, data.scoreMsg); + const oppCol = V.GetOppCol(this.game.mycolor); + if (!!data.rematchSent) { + if (this.game.rematchOffer != oppCol) { + // Opponent sended rematch offer while we were offline: + this.rematchOffer = "received"; + GameStorage.update( + this.gameRef, + { rematchOffer: oppCol } + ); + } + } + else { + if (this.game.rematchOffer == oppCol) { + // Opponent cancelled rematch offer while we were offline: + this.rematchOffer = ""; + GameStorage.update( + this.gameRef, + { rematchOffer: "" } + ); + } + } + } + else { + const L = this.game.moves.length; + const oppIdx = 1 - ["w", "b"].indexOf(this.game.mycolor); + this.game.clocks[oppIdx] = data.clock; + if (data.movesCount > L) { + // Just got last move from him + this.$refs["basegame"].play(data.lastMove, "received"); + this.processMove(data.lastMove); + } else { + if (!!this.clockUpdate) clearInterval(this.clockUpdate); + this.re_setClocks(); + } + if (!!data.drawSent) this.drawOffer = "received"; + if (!!data.score) { + this.drawOffer = ""; + if (this.game.score == "*") + this.gameOver(data.score, data.scoreMsg); + } } }, clickDraw: function() { @@ -999,6 +1009,7 @@ export default { // Game state (including FEN): will be updated moves: [], clocks: [-1, -1], //-1 = unstarted + chats: [], score: "*" } ); @@ -1019,7 +1030,7 @@ export default { let gameInfo = { id: getRandString(), //ignored if corr fen: V.GenRandInitFen(this.game.randomness), - players: this.game.players.reverse(), + players: [this.game.players[1], this.game.players[0]], vid: this.game.vid, cadence: this.game.cadence }; @@ -1089,7 +1100,10 @@ export default { const gtype = game.type || this.getGameType(game); const tc = extractTime(game.cadence); const myIdx = game.players.findIndex(p => { - return p.sid == this.st.user.sid || p.id == this.st.user.id; + return ( + p.sid == this.st.user.sid || + (!!p.name && p.id == this.st.user.id) + ); }); // Sometimes the name isn't stored yet (TODO: why?) if ( @@ -1181,8 +1195,9 @@ export default { if ( (game.rematchOffer == "w" && myIdx == 0) || (game.rematchOffer == "b" && myIdx == 1) - ) + ) { this.rematchOffer = "sent"; + } else this.rematchOffer = "received"; } } @@ -1214,30 +1229,22 @@ export default { game ); this.$refs["basegame"].re_setVariables(this.game); - if (!this.gameIsLoading) { - // Initial loading: - this.gotMoveIdx = game.moves.length - 1; - // If we arrive here after 'nextGame' action, the board might be hidden - let boardDiv = document.querySelector(".game"); - if (!!boardDiv && boardDiv.style.visibility == "hidden") - boardDiv.style.visibility = "visible"; - } + // Initial loading: + this.gotMoveIdx = game.moves.length - 1; + // If we arrive here after 'nextGame' action, the board might be hidden + let boardDiv = document.querySelector(".game"); + if (!!boardDiv && boardDiv.style.visibility == "hidden") + boardDiv.style.visibility = "visible"; this.re_setClocks(); this.$nextTick(() => { this.game.rendered = true; // Did lastate arrive before game was rendered? - if (this.lastate) this.processLastate(); + if (!!this.lastate) this.processLastate(); }); if (this.lastateAsked) { this.lastateAsked = false; this.sendLastate(game.oppsid); } - if (this.gameIsLoading) { - this.gameIsLoading = false; - if (this.gotMoveIdx >= game.moves.length) - // Some moves arrived meanwhile... - this.askGameAgain(); - } if (!!callback) callback(); }, loadVariantThenGame: async function(game, callback) { @@ -1465,7 +1472,7 @@ export default { this.opponentGotMove = false; this.send("newmove", {data: sendMove}); // If the opponent doesn't reply gotmove soon enough, re-send move: - // Do this at most 2 times, because mpore would mean network issues, + // Do this at most 2 times, because more would mean network issues, // opponent would then be expected to disconnect/reconnect. let counter = 1; const currentUrl = document.location.href; @@ -1497,6 +1504,7 @@ export default { }; if ( this.game.type == "corr" && + V.CorrConfirm && moveCol == this.game.mycolor && !data.receiveMyMove ) { @@ -1540,7 +1548,8 @@ export default { }); document.querySelector("#confirmDiv > .card").style.width = boardDiv.offsetWidth + "px"; - } else { + } + else { // Incomplete information: just ask confirmation // Hide the board, because otherwise it could reveal infos boardDiv.style.visibility = "hidden"; @@ -1572,7 +1581,10 @@ export default { document.getElementById("modalScore").checked = true; this.$set(this.game, "scoreMsg", scoreMsg); const myIdx = this.game.players.findIndex(p => { - return p.sid == this.st.user.sid || p.id == this.st.user.id; + return ( + p.sid == this.st.user.sid || + (!!p.name && p.id == this.st.user.id) + ); }); if (myIdx >= 0) { // OK, I play in this game @@ -1687,10 +1699,14 @@ span.separator span.name font-size: 1.5rem + @media screen and (max-width: 767px) + font-size: 1.2rem padding: 0 3px span.time font-size: 2rem + @media screen and (max-width: 767px) + font-size: 1.5rem display: inline-block .time-left margin-left: 10px @@ -1726,19 +1742,19 @@ span.yourturn background-color: lightyellow .draw-received, .draw-received:hover - background-color: lightgreen + background-color: #73C6B6 .draw-threerep, .draw-threerep:hover - background-color: #e4d1fc + background-color: #D2B4DE .rematch-sent, .rematch-sent:hover background-color: lightyellow .rematch-received, .rematch-received:hover - background-color: lightgreen + background-color: #48C9B0 .somethingnew - background-color: #c5fefe + background-color: #D2B4DE .diagram margin: 0 auto @@ -1755,11 +1771,13 @@ button.acceptBtn button.refuseBtn background-color: red -h4#variantNameInGame - cursor: pointer +a#variantNameInGame + color: var(--card-fore-color) text-align: center - text-decoration: underline font-weight: bold + font-size: calc(1rem * var(--heading-ratio)) + line-height: 1.2 + margin: calc(1.5 * var(--universal-margin))