X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fviews%2FGame.vue;h=5199154ac537f5d0f5774c560892bd9eabb65793;hb=7e355d68b5962aad106e28a9d669a47f3cbec43a;hp=09d9df8a3255a47cda09afb04f97c82cbbe89e4f;hpb=ab6f48ea4d9c549830f549f077c597f57ea4a57d;p=vchess.git diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 09d9df8a..5199154a 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -1,21 +1,15 @@ @@ -49,6 +43,7 @@ export default { vr: null, //"variant rules" object initialized from FEN drawOffer: "", //TODO: use for button style people: [], //players + observers + lastate: undefined, //used if opponent send lastate before game is ready }; }, watch: { @@ -58,9 +53,9 @@ export default { this.loadGame(); }, "game.clocks": function(newState) { - if (this.game.moves.length < 2) + if (this.game.moves.length < 2 || this.game.score != "*") { - // 1st move not completed yet: freeze time + // 1st move not completed yet, or game over: freeze time this.virtualClocks = newState.map(s => ppt(s)); return; } @@ -79,10 +74,7 @@ export default { { clearInterval(clockUpdate); if (countdown < 0) - { - this.$refs["basegame"].endGame( - this.vr.turn=="w" ? "0-1" : "1-0", "Time"); - } + this.setScore(this.vr.turn=="w" ? "0-1" : "1-0", "Time"); } else { @@ -99,21 +91,7 @@ export default { this.people.push({sid:my.sid, id:my.id, name:my.name}); this.gameRef.id = this.$route.params["id"]; this.gameRef.rid = this.$route.query["rid"]; //may be undefined - if (!this.gameRef.rid) - this.loadGame(); //local or corr: can load from now - // TODO: mode analyse (/analyze/Atomic/rn - // ... fen = query[], vname=params[] ... - // 0.1] Ask server for room composition: - const initialize = () => { - // Poll clients + load game if stored remotely - this.st.conn.send(JSON.stringify({code:"pollclients"})); - if (!!this.gameRef.rid) - this.loadGame(); - }; - if (!!this.st.conn && this.st.conn.readyState == 1) //1 == OPEN state - initialize(); - else //socket not ready yet (initial loading) - this.st.conn.onopen = initialize; + // Define socket .onmessage() and .onclose() events: this.st.conn.onmessage = this.socketMessageListener; const socketCloseListener = () => { store.socketCloseListener(); //reinitialize connexion (in store.js) @@ -121,18 +99,35 @@ export default { this.st.conn.addEventListener('close', socketCloseListener); }; this.st.conn.onclose = socketCloseListener; + // Socket init required before loading remote game: + const socketInit = (callback) => { + if (!!this.st.conn && this.st.conn.readyState == 1) //1 == OPEN state + callback(); + else //socket not ready yet (initial loading) + this.st.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. + // A more general approach would be to store it somewhere. + socketInit(this.loadGame); + } }, methods: { - getOppSid: function() { - if (!!this.game.oppsid) - return this.game.oppsid; - const opponent = this.people.find(p => p.id == this.game.oppid); - return (!!opponent ? opponent.sid : null); + // O.1] Ask server for room composition: + roomInit: function() { + this.st.conn.send(JSON.stringify({code:"pollclients"})); }, socketMessageListener: function(msg) { const data = JSON.parse(msg.data); switch (data.code) { + case "duplicate": + alert("Warning: duplicate 'offline' connection"); + break; // 0.2] Receive clients list (just socket IDs) case "pollclients": { @@ -168,15 +163,17 @@ export default { { // Send our "last state" informations to opponent const L = this.game.moves.length; + let lastMove = (L>0 ? this.game.moves[L-1] : undefined); + if (!!lastMove && this.drawOffer == "sent") + lastMove.draw = true; this.st.conn.send(JSON.stringify({ code: "lastate", target: player.sid, state: { - lastMove: (L>0 ? this.game.moves[L-1] : undefined), + lastMove: lastMove, score: this.game.score, movesCount: L, - drawOffer: this.drawOffer, clocks: this.game.clocks, } })); @@ -197,52 +194,36 @@ export default { game:myGame, target:data.from})); break; case "newmove": - // NOTE: this call to play() will trigger processMove() - this.$refs["basegame"].play(data.move, - "receive", this.game.vname!="Dark" ? "animate" : null); + this.corrMsg = data.move.message; //may be empty + this.game.moveToPlay = data.move; break; case "lastate": //got opponent infos about last move { - const L = this.game.moves.length; - if (data.movesCount > L) - { - // Just got last move from him - this.$refs["basegame"].play(data.lastMove, - "receive", this.game.vname!="Dark" ? "animate" : null); - if (data.score != "*" && this.game.score == "*") - { - // Opponent resigned or aborted game, or accepted draw offer - // (this is not a stalemate or checkmate) - this.$refs["basegame"].endGame(data.score, "Opponent action"); - } - this.game.clocks = data.clocks; //TODO: check this? - this.drawOffer = data.drawOffer; //does opponent offer draw? - } + this.lastate = data; + if (!!this.game.type) //game is loaded + this.processLastate(); + //else: will be processed when game is ready break; } case "resign": - this.$refs["basegame"].endGame( - this.game.mycolor=="w" ? "1-0" : "0-1", "Resign"); + this.setScore(data.side=="b" ? "1-0" : "0-1", "Resign"); break; case "abort": - this.$refs["basegame"].endGame("?", "Abort: " + data.msg); + this.setScore("?", "Abort"); break; case "draw": - this.$refs["basegame"].endGame("1/2", "Mutual agreement"); + this.setScore("1/2", "Mutual agreement"); break; case "drawoffer": - this.drawOffer = "received"; + this.drawOffer = "received"; //TODO: observers don't know who offered draw break; case "askfullgame": - // TODO: use data.id to retrieve game in indexedDB (but for now only one running game so OK) this.st.conn.send(JSON.stringify({code:"fullgame", game:this.game, target:data.from})); break; case "fullgame": - this.loadGame(data.game); + // Callback "roomInit" to poll clients only after game is loaded + this.loadGame(data.game, this.roomInit); break; - // TODO: drawaccepted (click draw button before sending move - // ==> draw offer in move) - // ==> on "newmove", check "drawOffer" field case "connect": { this.people.push({name:"", id:0, sid:data.from}); @@ -254,78 +235,91 @@ export default { break; } }, + // lastate was received, but maybe game wasn't ready yet: + processLastate: function() { + const data = this.lastate; + this.lastate = undefined; //security... + const L = this.game.moves.length; + if (data.movesCount > L) + { + // Just got last move from him + this.game.moveToPlay = data.lastMove; + if (data.score != "*" && this.game.score == "*") + { + // Opponent resigned or aborted game, or accepted draw offer + // (this is not a stalemate or checkmate) + this.setScore(data.score, "Opponent action"); + } + this.game.clocks = data.clocks; //TODO: check this? + if (!!data.lastMove.draw) + this.drawOffer = "received"; + } + }, + setScore: function(score, message) { + this.game.scoreMsg = message; + this.game.score = score; + }, offerDraw: function() { - // TODO: also for corr games if (this.drawOffer == "received") { if (!confirm("Accept draw?")) return; - const oppsid = this.getOppSid(); - if (!!oppsid) - this.st.conn.send(JSON.stringify({code:"draw", target:oppsid})); - this.$refs["basegame"].endGame("1/2", "Mutual agreement"); + this.people.forEach(p => { + if (p.sid != this.st.user.sid) + this.st.conn.send(JSON.stringify({code:"draw", target:p.sid})); + }); + this.setScore("1/2", "Mutual agreement"); } else if (this.drawOffer == "sent") + { this.drawOffer = ""; + if (this.game.type == "corr") + GameStorage.update(this.gameRef.id, {drawOffer: false}); + } else { if (!confirm("Offer draw?")) return; - const oppsid = this.getOppSid(); - if (!!oppsid) - this.st.conn.send(JSON.stringify({code:"drawoffer", target:oppsid})); + this.drawOffer = "sent"; + this.people.forEach(p => { + if (p.sid != this.st.user.sid) + this.st.conn.send(JSON.stringify({code:"drawoffer", target:p.sid})); + }); + if (this.game.type == "corr") + GameStorage.update(this.gameRef.id, {drawOffer: true}); } }, - // + conn handling: "draw" message ==> agree for draw (if we have "drawOffered" at true) - receiveDrawOffer: function() { - //if (...) - // TODO: ignore if preventDrawOffer is set; otherwise show modal box with option "prevent future offers" - // if accept: send message "draw" - }, - abortGame: function(event) { - let modalBox = document.getElementById("modalAbort"); - if (!event) - { - // First call show options: - modalBox.checked = true; - } - else - { - modalBox.checked = false; //decision made: box disappear - const message = event.target.innerText; - // Next line will trigger a "gameover" event, bubbling up till here - this.$refs["basegame"].endGame("?", "Abort: " + message); - const oppsid = this.getOppSid(); - if (!!oppsid) + abortGame: function() { + if (!confirm(this.st.tr["Terminate game?"])) + return; + this.setScore("?", "Abort"); + this.people.forEach(p => { + if (p.sid != this.st.user.sid) { this.st.conn.send(JSON.stringify({ code: "abort", - msg: message, - target: oppsid, + target: p.sid, })); } - } + }); }, resign: function(e) { if (!confirm("Resign the game?")) return; - const oppsid = this.getOppSid(); - if (!!oppsid) - { - this.st.conn.send(JSON.stringify({ - code: "resign", - target: oppsid, - })); - } - // Next line will trigger a "gameover" event, bubbling up till here - this.$refs["basegame"].endGame( - this.game.mycolor=="w" ? "0-1" : "1-0", "Resign"); + this.people.forEach(p => { + if (p.sid != this.st.user.sid) + { + this.st.conn.send(JSON.stringify({code:"resign", + side:this.game.mycolor, target:p.sid})); + } + }); + this.setScore(this.game.mycolor=="w" ? "0-1" : "1-0", "Resign"); }, // 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) { + loadGame: function(game, callback) { const afterRetrieval = async (game) => { const vModule = await import("@/variants/" + game.vname + ".js"); window.V = vModule.VariantRules; @@ -341,23 +335,29 @@ export default { [ game.players[1], game.players[0] ]; } // corr game: needs to compute the clocks + initime + // NOTE: clocks in seconds, initime in milliseconds game.clocks = [tc.mainTime, tc.mainTime]; - game.initime = [0, 0]; - const L = game.moves.length; game.moves.sort((m1,m2) => m1.idx - m2.idx); //in case of - if (L >= 3) + if (game.score == "*") //otherwise no need to bother with time { - let addTime = [0, 0]; - for (let i=2; i= 3) { - addTime[i%2] += tc.increment - - (game.moves[i].played - game.moves[i-1].played); + let addTime = [0, 0]; + for (let i=2; i= 1) + game.initime[L%2] = game.moves[L-1].played; + if (game.drawOffer) + this.drawOffer = "received"; } - if (L >= 1) - game.initime[L%2] = game.moves[L-1].played; // Now that we used idx and played, re-format moves as for live games game.moves = game.moves.map( (m) => { const s = m.squares; @@ -376,15 +376,18 @@ export default { if (gtype == "live" && game.clocks[0] < 0) //game unstarted { game.clocks = [tc.mainTime, tc.mainTime]; - game.initime[0] = Date.now(); - if (myIdx >= 0) + if (game.score == "*") { - // I play in this live game; corr games don't have clocks+initime - GameStorage.update(game.id, + game.initime[0] = Date.now(); + if (myIdx >= 0) { - clocks: game.clocks, - initime: game.initime, - }); + // I play in this live game; corr games don't have clocks+initime + GameStorage.update(game.id, + { + clocks: game.clocks, + initime: game.initime, + }); + } } } this.game = Object.assign({}, @@ -400,17 +403,17 @@ export default { oppid: (myIdx < 0 ? undefined : game.players[1-myIdx].uid), } ); + if (!!this.lastate) //lastate arrived before game was loaded: + this.processLastate(); + callback(); }; if (!!game) return afterRetrieval(game); if (!!this.gameRef.rid) { - // Remote live game - // (TODO: send game ID as well, and receiver would pick the corresponding - // game in his current games; if allowed to play several) + // Remote live game: forgetting about callback func... (TODO: design) this.st.conn.send(JSON.stringify( {code:"askfullgame", target:this.gameRef.rid})); - // (send moves updates + resign/abort/draw actions) } else { @@ -445,7 +448,6 @@ export default { let sendMove = Object.assign({}, filtered_move, {addTime: addTime}); if (this.game.type == "corr") sendMove.message = this.corrMsg; - const oppsid = this.getOppSid(); this.people.forEach(p => { if (p.sid != this.st.user.sid) { @@ -456,12 +458,6 @@ export default { })); } }); - if (this.game.type == "corr" && this.corrMsg != "") - { - // Add message to last move in BaseGame: - // TODO: not very good style... - this.$refs["basegame"].setCurrentMessage(this.corrMsg); - } } else addTime = move.addTime; //supposed transmitted @@ -475,10 +471,10 @@ export default { GameStorage.update(this.gameRef.id, { fen: move.fen, + message: this.corrMsg, move: { squares: filtered_move, - message: this.corrMsg, played: Date.now(), //TODO: on server? idx: this.game.moves.length, }, @@ -502,12 +498,9 @@ export default { // Also update current game object: this.game.moves.push(move); this.game.fen = move.fen; - //TODO: just this.game.clocks[colorIdx] += addTime; + //TODO: (Vue3) just this.game.clocks[colorIdx] += addTime; this.$set(this.game.clocks, colorIdx, this.game.clocks[colorIdx] + addTime); this.game.initime[nextIdx] = Date.now(); - // Finally reset curMoveMessage if needed - if (this.game.type == "corr" && move.color == this.game.mycolor) - this.corrMsg = ""; }, gameOver: function(score) { this.game.mode = "analyze";