From cafe016679ee9c14bf7bf0a37104ade7f74aff89 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 9 Mar 2020 18:24:49 +0100 Subject: [PATCH] MyGames page is now dynamic (experimental, not much tested) --- client/src/components/Board.vue | 17 ++++--- client/src/components/GameList.vue | 2 +- client/src/views/Game.vue | 63 ++++++++++++----------- client/src/views/Hall.vue | 12 +++++ client/src/views/MyGames.vue | 82 ++++++++++++++++++++---------- server/sockets.js | 58 +++++++++++++-------- 6 files changed, 147 insertions(+), 87 deletions(-) diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index 86c2acca..cf3d3e68 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -19,6 +19,7 @@ export default { ], data: function() { return { + mobileBrowser: ("ontouchstart" in window), possibleMoves: [], //filled after each valid click/dragstart choices: [], //promotion pieces, or checkered captures... (as moves) selectedPiece: null, //moving piece (or clicked piece) @@ -292,7 +293,7 @@ export default { } let onEvents = {}; // NOTE: click = mousedown + mouseup - if ("ontouchstart" in window) { + if (this.mobileBrowser) { onEvents = { on: { touchstart: this.mousedown, @@ -343,9 +344,10 @@ export default { mousemove: function(e) { if (!this.selectedPiece) return; // There is an active element: move it around - const [offsetX, offsetY] = e.clientX - ? [e.clientX, e.clientY] //desktop browser - : [e.changedTouches[0].pageX, e.changedTouches[0].pageY]; //smartphone + const [offsetX, offsetY] = + this.mobileBrowser + ? [e.changedTouches[0].pageX, e.changedTouches[0].pageY] + : [e.clientX, e.clientY]; this.selectedPiece.style.left = offsetX - this.start.x + "px"; this.selectedPiece.style.top = offsetY - this.start.y + "px"; }, @@ -353,9 +355,10 @@ export default { if (!this.selectedPiece) return; // There is an active element: obtain the move from start and end squares this.selectedPiece.style.zIndex = -3000; //HACK to find square from final coords - const [offsetX, offsetY] = e.clientX - ? [e.clientX, e.clientY] - : [e.changedTouches[0].pageX, e.changedTouches[0].pageY]; + const [offsetX, offsetY] = + this.mobileBrowser + ? [e.changedTouches[0].pageX, e.changedTouches[0].pageY] + : [e.clientX, e.clientY]; let landing = document.elementFromPoint(offsetX, offsetY); this.selectedPiece.style.zIndex = 3000; // Next condition: classList.contains(piece) fails because of marks diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue index 451d1bac..183d6463 100644 --- a/client/src/components/GameList.vue +++ b/client/src/components/GameList.vue @@ -82,7 +82,7 @@ export default { : "b"; if (g.score == "*") { priority++; - if (isMyTurn(g, myColor)) priority++; + if (g.turn == myColor || isMyTurn(g, myColor)) priority++; } } if (g.created < minCreated) minCreated = g.created; diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 024847ce..e909f70e 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -165,7 +165,6 @@ export default { rematchOffer: "", lastateAsked: false, people: {}, //players + observers - onMygames: [], //opponents (or me) on "MyGames" page lastate: undefined, //used if opponent send lastate before game is ready repeat: {}, //detect position repetition curDiag: "", //for corr moves confirmation @@ -271,7 +270,6 @@ export default { this.drawOffer = ""; this.lastateAsked = false; this.rematchOffer = ""; - this.onMygames = []; this.lastate = undefined; this.newChat = ""; this.roomInitialized = false; @@ -290,6 +288,8 @@ export default { params.socketUrl + "/?sid=" + this.st.user.sid + + "&id=" + + this.st.user.id + "&tmpId=" + getRandString() + "&page=" + @@ -401,17 +401,17 @@ export default { getGameType: function(game) { return game.cadence.indexOf("d") >= 0 ? "corr" : "live"; }, - // Notify turn after a new move (to opponent and me on MyGames page) - notifyTurn: function(sid) { - const player = this.people[sid]; - const colorIdx = this.game.players.findIndex( - p => p.sid == sid || p.uid == player.id); - const color = ["w","b"][colorIdx]; - const movesCount = this.game.moves.length; - const yourTurn = - (color == "w" && movesCount % 2 == 0) || - (color == "b" && movesCount % 2 == 1); - this.send("turnchange", { target: sid, yourTurn: yourTurn }); + // Notify something after a new move (to opponent and me on MyGames page) + notifyMyGames: function(thing, data) { + this.send( + "notify" + thing, + { + data: data, + targets: this.game.players.map(p => { + return { sid: p.sid, uid: p.uid }; + }) + } + ); }, showNextGame: function() { // Did I play in current game? If not, add it to nextIds list @@ -462,22 +462,6 @@ export default { case "disconnect": this.$delete(this.people, data.from); break; - case "mconnect": { - // TODO: from MyGames page : send mconnect message with the list of gid (live and corr) - // Either me (another tab) or opponent - const sid = data.from; - if (!this.onMygames.some(s => s == sid)) - { - this.onMygames.push(sid); - this.notifyTurn(sid); //TODO: this may require server ID (so, notify after receiving identity) - } - break; - if (!this.people[sid]) - this.send("askidentity", { target: sid }); - } - case "mdisconnect": - ArrayFun.remove(this.onMygames, sid => sid == data.from); - break; case "getfocus": { let player = this.people[data.from]; if (!!player) { @@ -867,6 +851,8 @@ export default { const notifyNewGame = () => { let oppsid = this.getOppsid(); //may be null this.send("rnewgame", { data: gameInfo, oppsid: oppsid }); + // Also to MyGames page: + this.notifyMyGames("newgame", gameInfo); }; if (this.game.type == "live") this.addAndGotoLiveGame(gameInfo, notifyNewGame); @@ -1185,7 +1171,6 @@ export default { const score = this.vr.getCurrentScore(); if (score != "*") this.gameOver(score); } -// TODO: notifyTurn: "changeturn" message this.game.moves.push(move); this.game.fen = this.vr.getFen(); if (this.game.type == "live") { @@ -1216,6 +1201,16 @@ export default { // NOTE: 'var' to see that variable outside this block var filtered_move = getFilteredMove(move); } + if (moveCol == this.game.mycolor && !data.receiveMyMove) { + // Notify turn on MyGames page: + this.notifyMyGames( + "turn", + { + gid: this.gameRef.id, + turn: this.vr.turn + } + ); + } // Since corr games are stored at only one location, update should be // done only by one player for each move: if ( @@ -1394,6 +1389,14 @@ export default { else this.updateCorrGame(scoreObj, callback); // Notify the score to main Hall. TODO: only one player (currently double send) this.send("result", { gid: this.game.id, score: score }); + // Also to MyGames page (TODO: doubled as well...) + this.notifyMyGames( + "score", + { + gid: this.gameRef.id, + score: score + } + ); } else if (!!callback) callback(); } diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index ac2e26ec..5217c34d 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -324,6 +324,8 @@ export default { params.socketUrl + "/?sid=" + this.st.user.sid + + "&id=" + + this.st.user.id + "&tmpId=" + getRandString() + "&page=" + @@ -977,6 +979,16 @@ export default { // Send game info (only if live) to everyone except me and opponent // TODO: this double message send could be avoided. this.send("newgame", { data: gameInfo, oppsid: oppsid }); + // Also to MyGames page: + this.send( + "notifynewgame", + { + data: gameInfo, + targets: gameInfo.players.map(p => { + return { sid: p.sid, uid: p.uid }; + }) + } + ); }; if (c.type == "live") { notifyNewgame(); diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index 5148c3cf..5d862073 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -74,6 +74,8 @@ export default { params.socketUrl + "/?sid=" + this.st.user.sid + + "&id=" + + this.st.user.id + "&tmpId=" + getRandString() + "&page=" + @@ -100,9 +102,59 @@ export default { elt.previousElementSibling.classList.remove("active"); else elt.nextElementSibling.classList.remove("active"); }, - // TODO: classifyObject is redundant (see Hall.vue) - classifyObject: function(o) { - return o.cadence.indexOf("d") === -1 ? "live" : "corr"; + tryShowNewsIndicator: function(type) { + if ( + (type == "live" && this.display == "corr") || + (type == "corr" && this.display == "live") + ) { + document + .getElementById(type + "Games") + .classList.add("somethingnew"); + } + }, + socketMessageListener: function(msg) { + const data = JSON.parse(msg.data); + switch (data.code) { + // NOTE: no need to increment movesCount: unused if turn is provided + case "notifyturn": + case "notifyscore": { + const info = data.data; + let games = + !!parseInt(info.gid) + ? this.corrGames + : this.liveGames; + let g = games.find(g => g.id == info.gid); + // "notifything" --> "thing": + const thing = data.code.substr(6); + this.$set(g, thing, info[thing]); + this.tryShowNewsIndicator(g.type); + break; + } + case "notifynewgame": { + const gameInfo = data.data; + // st.variants might be uninitialized, + // if unlucky and newgame right after connect: + const v = this.st.variants.find(v => v.id == gameInfo.vid); + const vname = !!v ? v.name : ""; + const type = gameInfo.cadence.indexOf('d') >= 0 ? "corr": "live"; + const game = Object.assign( + { + vname: vname, + type: type, + score: "*" + }, + gameInfo + ); + this[type + "Games"].push(game); + this.tryShowNewsIndicator(type); + break; + } + } + }, + socketCloseListener: function() { + this.conn = new WebSocket(this.connexionString); + this.conn.addEventListener("message", this.socketMessageListener); + this.conn.addEventListener("close", this.socketCloseListener); }, showGame: function(game) { // TODO: "isMyTurn" is duplicated (see GameList component). myColor also @@ -170,30 +222,6 @@ export default { } ); } - }, - socketMessageListener: function(msg) { - const data = JSON.parse(msg.data); - if (data.code == "changeturn") { - let games = !!parseInt(data.gid) - ? this.corrGames - : this.liveGames; - // NOTE: new move itself is not received, because it wouldn't be used. - let g = games.find(g => g.id == data.gid); - this.$set(g, "movesCount", g.movesCount + 1); - if ( - (g.type == "live" && this.display == "corr") || - (g.type == "corr" && this.display == "live") - ) { - document - .getElementById(g.type + "Games") - .classList.add("somethingnew"); - } - } - }, - socketCloseListener: function() { - this.conn = new WebSocket(this.connexionString); - this.conn.addEventListener("message", this.socketMessageListener); - this.conn.addEventListener("close", this.socketCloseListener); } } }; diff --git a/server/sockets.js b/server/sockets.js index d1489f71..d96b6967 100644 --- a/server/sockets.js +++ b/server/sockets.js @@ -24,9 +24,12 @@ module.exports = function(wss) { // or "/mygames" for Mygames page (simpler: no 'people' array). // tmpId is required if a same user (browser) has different tabs let clients = {}; + let sidToPages = {}; + let idToSid = {}; wss.on("connection", (socket, req) => { const query = getJsonFromUrl(req.url); const sid = query["sid"]; + const id = query["id"]; const tmpId = query["tmpId"]; const page = query["page"]; const notifyRoom = (page,code,obj={}) => { @@ -60,14 +63,22 @@ module.exports = function(wss) { delete clients[page][sid][tmpId]; if (Object.keys(clients[page][sid]).length == 0) { delete clients[page][sid]; + const pgIndex = sidToPages[sid].findIndex(pg => pg == page); + sidToPages[sid].splice(pgIndex, 1); if (Object.keys(clients[page]).length == 0) delete clients[page]; + // Am I totally offline? + if (sidToPages[sid].length == 0) { + delete sidToPages[sid]; + delete idToSid[id]; + } } }; const doDisconnect = () => { deleteConnexion(); - if (!clients[page] || !clients[page][sid]) { + // Nothing to notify when disconnecting from MyGames page: + if (page != "/mygames" && (!clients[page] || !clients[page][sid])) { // I effectively disconnected from this page: notifyRoom(page, "disconnect"); if (page.indexOf("/game/") >= 0) @@ -139,7 +150,7 @@ module.exports = function(wss) { }); // NOTE: a "gamer" could also just be an observer Object.keys(clients).forEach(p => { - if (p != "/") { + if (p.indexOf("/game/") >= 0) { Object.keys(clients[p]).forEach(k => { // 'page' indicator is needed for gamers if (k != sid) sockIds.push({ sid:k, page:p }); @@ -243,26 +254,6 @@ module.exports = function(wss) { notifyRoom("/", "result", { gid: obj.gid, score: obj.score }); break; - case "mconnect": - // Special case: notify some game rooms that - // I'm watching game state from MyGames - // TODO: this code is ignored for now - obj.gids.forEach(gid => { - const pg = "/game/" + gid; - Object.keys(clients[pg]).forEach(s => { - Object.keys(clients[pg][s]).forEach(x => { - send( - clients[pg][s][x].socket, - { code: "mconnect", from: sid } - ); - }); - }); - }); - break; - case "mdisconnect": - // TODO - // Also TODO: pass newgame to MyGames, and gameover (result) - break; case "mabort": { const gamePg = "/game/" + obj.gid; if (!!clients[gamePg] && !!clients[gamePg][obj.target]) { @@ -276,6 +267,24 @@ module.exports = function(wss) { break; } + case "notifyscore": + case "notifyturn": + case "notifynewgame": + if (!!clients["/mygames"]) { + obj.targets.forEach(t => { + const k = t.sid || idToSid[t.uid]; + if (!!clients["/mygames"][k]) { + Object.keys(clients["/mygames"][k]).forEach(x => { + send( + clients["/mygames"][k][x].socket, + { code: obj.code, data: obj.data } + ); + }); + } + }); + } + break; + case "getfocus": case "losefocus": if (page == "/") notifyAllBut("/", obj.code, { page: "/" }, [sid]); @@ -319,6 +328,11 @@ module.exports = function(wss) { clients[page][sid] = { [tmpId]: newElt }; else clients[page][sid][tmpId] = newElt; + // Also update helper correspondances + if (!idToSid[id]) idToSid[id] = sid; + if (!sidToPages[sid]) sidToPages[sid] = []; + const pgIndex = sidToPages[sid].findIndex(pg => pg == page); + if (pgIndex === -1) sidToPages[sid].push(page); socket.on("message", messageListener); socket.on("close", closeListener); }); -- 2.44.0