X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fviews%2FGame.vue;h=68c8c94d4362f4324b6eefd109a965c2add58195;hb=f54f4c26b4c820b14aca298e94644efb20beeed6;hp=0182352a494fe4f2d3085fd8eec5d04e8c0e2be7;hpb=f14572c4a22425033735253eabbaa2d8dbb53d05;p=vchess.git diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 0182352a..68c8c94d 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -14,7 +14,7 @@ main | {{ st.tr["Rematch in progress"] }} input#modalChat.modal( type="checkbox" - @click="resetChatColor()" + @click="toggleChat()" ) div#chatWrap( role="dialog" @@ -37,7 +37,6 @@ main ref="chatcomp" :players="game.players" :pastChats="game.chats" - :newChat="newChat" @mychat="processChat" @chatcleared="clearChat" ) @@ -154,11 +153,8 @@ export default { data: function() { return { st: store.state, - gameRef: { - // rid = remote (socket) ID - id: "", - rid: "" - }, + // gameRef can point to a corr game, local game or remote live game + gameRef: "", nextIds: [], game: {}, //passed to BaseGame // virtualClocks will be initialized from true game.clocks @@ -172,7 +168,6 @@ export default { lastate: undefined, //used if opponent send lastate before game is ready repeat: {}, //detect position repetition curDiag: "", //for corr moves confirmation - newChat: "", conn: null, roomInitialized: false, // If newmove has wrong index: ask fullgame again: @@ -207,10 +202,8 @@ export default { this.atCreation(); } else { // Same game ID - this.gameRef.id = to.params["id"]; - this.gameRef.rid = to.query["rid"]; this.nextIds = JSON.parse(this.$route.query["next"] || "[]"); - this.loadGame(); + this.loadGame(this.game); } } }, @@ -246,11 +239,8 @@ export default { }, atCreation: function() { // 0] (Re)Set variables - this.gameRef.id = this.$route.params["id"]; - // rid = remote ID to find an observed live game, - // next = next corr games IDs to navigate faster - // (Both might be undefined) - this.gameRef.rid = this.$route.query["rid"]; + this.gameRef = this.$route.params["id"]; + // next = next corr games IDs to navigate faster (if applicable) this.nextIds = JSON.parse(this.$route.query["next"] || "[]"); // Always add myself to players' list const my = this.st.user; @@ -276,7 +266,6 @@ export default { this.lastateAsked = false; this.rematchOffer = ""; this.lastate = undefined; - this.newChat = ""; this.roomInitialized = false; this.askGameTime = 0; this.gameIsLoading = false; @@ -310,18 +299,18 @@ export default { callback(); else // Socket not ready yet (initial loading) - // NOTE: it's important to call callback without arguments, - // otherwise first arg is Websocket object and loadGame fails. + // NOTE: first arg is Websocket object, unused here: this.conn.onopen = () => callback(); }; - if (!this.gameRef.rid) - // Game stored locally or on server - this.loadGame(null, () => socketInit(this.roomInit)); - else - // Game stored remotely: need socket to retrieve it - // NOTE: the callback "roomInit" will be lost, so we don't provide it. - // --> It will be given when receiving "fullgame" socket event. - socketInit(this.loadGame); + this.fetchGame((game) => { + if (!!game) + this.loadVariantThenGame(game, () => socketInit(this.roomInit)); + else + // Live game stored remotely: need socket to retrieve it + // NOTE: the callback "roomInit" will be lost, so we don't provide it. + // --> It will be given when receiving "fullgame" socket event. + socketInit(() => { this.send("askfullgame"); }); + }); }, cleanBeforeDestroy: function() { if (!!this.askLastate) @@ -380,8 +369,12 @@ export default { if (!!oppsid && !!this.people[oppsid]) return oppsid; return null; }, - resetChatColor: function() { - // TODO: this is called twice, once on opening an once on closing + toggleChat: function() { + if (document.getElementById("modalChat").checked) + // Entering chat + document.getElementById("inputChat").focus(); + // TODO: next line is only required when exiting chat, + // but the event for now isn't well detected. document.getElementById("chatBtn").classList.remove("somethingnew"); }, processChat: function(chat) { @@ -431,13 +424,15 @@ export default { const currentUrl = document.location.href; const doAskGame = () => { if (document.location.href != currentUrl) return; //page change - 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 :/ - this.send("askfullgame", { target: this.gameRef.rid }); + 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(); @@ -562,8 +557,7 @@ export default { players: this.game.players, vid: this.game.vid, cadence: this.game.cadence, - score: this.game.score, - rid: this.st.user.sid //useful in Hall if I'm an observer + score: this.game.score }; this.send("game", { data: myGame, target: data.from }); } @@ -586,7 +580,7 @@ export default { break; case "fullgame": // Callback "roomInit" to poll clients only after game is loaded - this.loadGame(data.data, this.roomInit); + this.loadVariantThenGame(data.data, this.roomInit); break; case "asklastate": // Sending informative last state if I played a move or score != "*" @@ -638,7 +632,7 @@ export default { !!this.game.mycolor && !receiveMyMove ) { - GameStorage.update(this.gameRef.id, { drawOffer: "" }); + GameStorage.update(this.gameRef, { drawOffer: "" }); } } this.$refs["basegame"].play(movePlus.move, "received", null, true); @@ -662,8 +656,8 @@ export default { break; } case "resign": - const score = data.side == "b" ? "1-0" : "0-1"; - const side = data.side == "w" ? "White" : "Black"; + const score = (data.data == "b" ? "1-0" : "0-1"); + const side = (data.data == "w" ? "White" : "Black"); this.gameOver(score, side + " surrender"); break; case "abort": @@ -675,10 +669,22 @@ export default { case "drawoffer": // NOTE: observers don't know who offered draw this.drawOffer = "received"; + if (this.game.type == "live") { + GameStorage.update( + this.gameRef, + { drawOffer: V.GetOppCol(this.game.mycolor) } + ); + } break; case "rematchoffer": // NOTE: observers don't know who offered rematch this.rematchOffer = data.data ? "received" : ""; + if (this.game.type == "live") { + GameStorage.update( + this.gameRef, + { rematchOffer: V.GetOppCol(this.game.mycolor) } + ); + } break; case "newgame": { // A game started, redirect if I'm playing in @@ -695,23 +701,13 @@ export default { ) { this.$router.push("/game/" + gameInfo.id); } else { - let urlRid = ""; - if (gameInfo.cadence.indexOf('d') === -1) { - urlRid = "/?rid="; - // Select sid of any of the online players: - let onlineSid = []; - gameInfo.players.forEach(p => { - if (!!this.people[p.sid]) onlineSid.push(p.sid); - }); - urlRid += onlineSid[Math.floor(Math.random() * onlineSid.length)]; - } - this.rematchId = gameInfo.id + urlRid; + this.rematchId = gameInfo.id; document.getElementById("modalInfo").checked = true; } break; } case "newchat": - this.newChat = data.data; + this.$refs["chatcomp"].newChat(data.data); if (!document.getElementById("modalChat").checked) document.getElementById("chatBtn").classList.add("somethingnew"); break; @@ -728,7 +724,7 @@ export default { "PUT", { data: { - gid: this.gameRef.id, + gid: this.gameRef, newObj: obj }, success: () => { @@ -803,7 +799,7 @@ export default { this.send("drawoffer"); if (this.game.type == "live") { GameStorage.update( - this.gameRef.id, + this.gameRef, { drawOffer: this.game.mycolor } ); } else this.updateCorrGame({ drawOffer: this.game.mycolor }); @@ -878,7 +874,7 @@ export default { this.send("rematchoffer", { data: true }); if (this.game.type == "live") { GameStorage.update( - this.gameRef.id, + this.gameRef, { rematchOffer: this.game.mycolor } ); } else this.updateCorrGame({ rematchOffer: this.game.mycolor }); @@ -888,7 +884,7 @@ export default { this.send("rematchoffer", { data: false }); if (this.game.type == "live") { GameStorage.update( - this.gameRef.id, + this.gameRef, { rematchOffer: '' } ); } else this.updateCorrGame({ rematchOffer: 'n' }); @@ -903,189 +899,180 @@ export default { if (!this.game.mycolor || !confirm(this.st.tr["Resign the game?"])) return; this.send("resign", { data: this.game.mycolor }); - const score = this.game.mycolor == "w" ? "0-1" : "1-0"; - const side = this.game.mycolor == "w" ? "White" : "Black"; + const score = (this.game.mycolor == "w" ? "0-1" : "1-0"); + const side = (this.game.mycolor == "w" ? "White" : "Black"); this.gameOver(score, side + " surrender"); }, - // 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) loadGame: function(game, callback) { - const afterRetrieval = async (game) => { - const vModule = await import("@/variants/" + game.vname + ".js"); - window.V = vModule.VariantRules; - this.vr = new V(game.fen); - const gtype = 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; - }); - const mycolor = [undefined, "w", "b"][myIdx + 1]; //undefined for observers - if (!game.chats) game.chats = []; //live games don't have chat history - if (gtype == "corr") { - // NOTE: clocks in seconds, initime in milliseconds - game.moves.sort((m1, m2) => m1.idx - m2.idx); //in case of - game.clocks = [tc.mainTime, tc.mainTime]; - const L = game.moves.length; - if (game.score == "*") { - // Set clocks + initime - game.initime = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]; - if (L >= 1) game.initime[L % 2] = game.moves[L-1].played; - // NOTE: game.clocks shouldn't be computed right now: - // job will be done in re_setClocks() called soon below. - } - // Sort chat messages from newest to oldest - game.chats.sort((c1, c2) => { - return c2.added - c1.added; - }); - if (myIdx >= 0 && game.score == "*" && game.chats.length > 0) { - // Did a chat message arrive after my last move? - let dtLastMove = 0; - if (L == 1 && myIdx == 0) - dtLastMove = game.moves[0].played; - else if (L >= 2) { - if (L % 2 == 0) { - // It's now white turn - dtLastMove = game.moves[L-1-(1-myIdx)].played; - } else { - // Black turn: - dtLastMove = game.moves[L-1-myIdx].played; - } - } - if (dtLastMove < game.chats[0].added) - document.getElementById("chatBtn").classList.add("somethingnew"); - } - // Now that we used idx and played, re-format moves as for live games - game.moves = game.moves.map(m => m.squares); - } - if (gtype == "live" && game.clocks[0] < 0) { - // Game is unstarted. clocks and initime are ignored until move 2 - game.clocks = [tc.mainTime, tc.mainTime]; + this.vr = new V(game.fen); + const gtype = 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; + }); + const mycolor = [undefined, "w", "b"][myIdx + 1]; //undefined for observers + if (!game.chats) game.chats = []; //live games don't have chat history + if (gtype == "corr") { + // NOTE: clocks in seconds, initime in milliseconds + game.moves.sort((m1, m2) => m1.idx - m2.idx); //in case of + game.clocks = [tc.mainTime, tc.mainTime]; + const L = game.moves.length; + if (game.score == "*") { + // Set clocks + initime game.initime = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]; - if (myIdx >= 0) { - // I play in this live game - GameStorage.update(game.id, { - clocks: game.clocks, - initime: game.initime - }); - } + if (L >= 1) game.initime[L % 2] = game.moves[L-1].played; + // NOTE: game.clocks shouldn't be computed right now: + // job will be done in re_setClocks() called soon below. } - // TODO: merge next 2 "if" conditions - if (!!game.drawOffer) { - if (game.drawOffer == "t") - // Three repetitions - this.drawOffer = "threerep"; - else { - // Draw offered by any of the players: - if (myIdx < 0) this.drawOffer = "received"; - else { - // I play in this game: - if ( - (game.drawOffer == "w" && myIdx == 0) || - (game.drawOffer == "b" && myIdx == 1) - ) - this.drawOffer = "sent"; - else this.drawOffer = "received"; + // Sort chat messages from newest to oldest + game.chats.sort((c1, c2) => { + return c2.added - c1.added; + }); + if (myIdx >= 0 && game.score == "*" && game.chats.length > 0) { + // Did a chat message arrive after my last move? + let dtLastMove = 0; + if (L == 1 && myIdx == 0) + dtLastMove = game.moves[0].played; + else if (L >= 2) { + if (L % 2 == 0) { + // It's now white turn + dtLastMove = game.moves[L-1-(1-myIdx)].played; + } else { + // Black turn: + dtLastMove = game.moves[L-1-myIdx].played; } } + if (dtLastMove < game.chats[0].added) + document.getElementById("chatBtn").classList.add("somethingnew"); + } + // Now that we used idx and played, re-format moves as for live games + game.moves = game.moves.map(m => m.squares); + } + if (gtype == "live" && game.clocks[0] < 0) { + // Game is unstarted. clocks and initime are ignored until move 2 + game.clocks = [tc.mainTime, tc.mainTime]; + game.initime = [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]; + if (myIdx >= 0) { + // I play in this live game + GameStorage.update(game.id, { + clocks: game.clocks, + initime: game.initime + }); } - if (!!game.rematchOffer) { - if (myIdx < 0) this.rematchOffer = "received"; + } + // TODO: merge next 2 "if" conditions + if (!!game.drawOffer) { + if (game.drawOffer == "t") + // Three repetitions + this.drawOffer = "threerep"; + else { + // Draw offered by any of the players: + if (myIdx < 0) this.drawOffer = "received"; else { // I play in this game: if ( - (game.rematchOffer == "w" && myIdx == 0) || - (game.rematchOffer == "b" && myIdx == 1) + (game.drawOffer == "w" && myIdx == 0) || + (game.drawOffer == "b" && myIdx == 1) ) - this.rematchOffer = "sent"; - else this.rematchOffer = "received"; + this.drawOffer = "sent"; + else this.drawOffer = "received"; } } - this.repeat = {}; //reset: scan past moves' FEN: - let repIdx = 0; - let vr_tmp = new V(game.fenStart); - let curTurn = "n"; - game.moves.forEach(m => { - playMove(m, vr_tmp); - const fenIdx = vr_tmp.getFen().replace(/ /g, "_"); - this.repeat[fenIdx] = this.repeat[fenIdx] - ? this.repeat[fenIdx] + 1 - : 1; - }); - if (this.repeat[repIdx] >= 3) this.drawOffer = "threerep"; - this.game = Object.assign( - // NOTE: assign mycolor here, since BaseGame could also be VS computer - { - type: gtype, - increment: tc.increment, - mycolor: mycolor, - // opponent sid not strictly required (or available), but easier - // at least oppsid or oppid is available anyway: - oppsid: myIdx < 0 ? undefined : game.players[1 - myIdx].sid, - oppid: myIdx < 0 ? undefined : game.players[1 - myIdx].id - }, - 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"; - } - this.re_setClocks(); - this.$nextTick(() => { - this.game.rendered = true; - // Did lastate arrive before game was rendered? - 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(); - }; - if (!!game) { - afterRetrieval(game); - return; } - if (this.gameRef.rid) { - // Remote live game: forgetting about callback func... (TODO: design) - this.send("askfullgame", { target: this.gameRef.rid }); - } else { - // Local or corr game on server. - // NOTE: afterRetrieval() is never called if game not found - const gid = this.gameRef.id; - if (Number.isInteger(gid) || !isNaN(parseInt(gid))) { - // corr games identifiers are integers - ajax( - "/games", - "GET", - { - data: { gid: gid }, - success: (res) => { - let g = res.game; - g.moves.forEach(m => { - m.squares = JSON.parse(m.squares); - }); - afterRetrieval(g); - } - } - ); + if (!!game.rematchOffer) { + if (myIdx < 0) this.rematchOffer = "received"; + else { + // I play in this game: + if ( + (game.rematchOffer == "w" && myIdx == 0) || + (game.rematchOffer == "b" && myIdx == 1) + ) + this.rematchOffer = "sent"; + else this.rematchOffer = "received"; } - else - // Local game - GameStorage.get(this.gameRef.id, afterRetrieval); } + this.repeat = {}; //reset: scan past moves' FEN: + let repIdx = 0; + let vr_tmp = new V(game.fenStart); + let curTurn = "n"; + game.moves.forEach(m => { + playMove(m, vr_tmp); + const fenIdx = vr_tmp.getFen().replace(/ /g, "_"); + this.repeat[fenIdx] = this.repeat[fenIdx] + ? this.repeat[fenIdx] + 1 + : 1; + }); + if (this.repeat[repIdx] >= 3) this.drawOffer = "threerep"; + this.game = Object.assign( + // NOTE: assign mycolor here, since BaseGame could also be VS computer + { + type: gtype, + increment: tc.increment, + mycolor: mycolor, + // opponent sid not strictly required (or available), but easier + // at least oppsid or oppid is available anyway: + oppsid: myIdx < 0 ? undefined : game.players[1 - myIdx].sid, + oppid: myIdx < 0 ? undefined : game.players[1 - myIdx].id + }, + 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"; + } + this.re_setClocks(); + this.$nextTick(() => { + this.game.rendered = true; + // Did lastate arrive before game was rendered? + 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) { + await import("@/variants/" + game.vname + ".js") + .then((vModule) => { + window.V = vModule[game.vname + "Rules"]; + this.loadGame(game, callback); + }); + }, + // 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))) { + // corr games identifiers are integers + ajax( + "/games", + "GET", + { + data: { gid: this.gameRef }, + success: (res) => { + res.game.moves.forEach(m => { + m.squares = JSON.parse(m.squares); + }); + callback(res.game); + } + } + ); + } else + // Local game (or live remote) + GameStorage.get(this.gameRef, callback); }, re_setClocks: function() { if (this.game.moves.length < 2 || this.game.score != "*") { @@ -1150,19 +1137,18 @@ export default { playMove(move, this.vr); // The move is played: stop clock clearInterval(this.clockUpdate); - if (!data.score) { - // Received move, score has not been computed in BaseGame (!!noemit) - const score = this.vr.getCurrentScore(); - if (score != "*") this.gameOver(score); - } + if (!data.score) + // Received move, score is computed in BaseGame, but maybe not yet. + // ==> Compute it here, although this is redundant (TODO) + data.score = this.vr.getCurrentScore(); + if (data.score != "*") this.gameOver(data.score); this.game.moves.push(move); this.game.fen = this.vr.getFen(); if (this.game.type == "live") { if (!!data.clock) this.game.clocks[colorIdx] = data.clock; else this.game.clocks[colorIdx] += addTime; - } - // In corr games, just reset clock to mainTime: - else { + } else { + // In corr games, just reset clock to mainTime: this.game.clocks[colorIdx] = extractTime(this.game.cadence).mainTime; } // NOTE: opponent's initime is reset after "gotmove" is received @@ -1190,7 +1176,7 @@ export default { this.notifyMyGames( "turn", { - gid: this.gameRef.id, + gid: this.gameRef, turn: this.vr.turn } ); @@ -1220,7 +1206,6 @@ export default { fen: this.game.fen, move: { squares: filtered_move, - played: Date.now(), idx: origMovescount }, // Code "n" for "None" to force reset (otherwise it's ignored) @@ -1229,7 +1214,7 @@ export default { } else { const updateStorage = () => { - GameStorage.update(this.gameRef.id, { + GameStorage.update(this.gameRef, { fen: this.game.fen, move: filtered_move, moveIdx: origMovescount, @@ -1367,7 +1352,7 @@ export default { scoreMsg: scoreMsg }; if (this.game.type == "live") { - GameStorage.update(this.gameRef.id, scoreObj); + GameStorage.update(this.gameRef, scoreObj); if (!!callback) callback(); } else this.updateCorrGame(scoreObj, callback); @@ -1377,7 +1362,7 @@ export default { this.notifyMyGames( "score", { - gid: this.gameRef.id, + gid: this.gameRef, score: score } );