From f9c36b2da005b596ad656f4b6cc4e09ef3c656f1 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Thu, 5 Mar 2020 10:30:44 +0100 Subject: [PATCH] Some fixes + draft newmove pingback logic (unfinished, not working) --- client/public/images/pieces/Alice/bc.svg | 58 +++--- client/public/images/pieces/Alice/bl.svg | 97 ++++++---- client/public/images/pieces/Alice/bo.svg | 77 ++++---- client/public/images/pieces/Alice/bs.svg | 15 +- client/public/images/pieces/Alice/bt.svg | 81 ++++++--- client/public/images/pieces/Alice/bu.svg | 72 ++++---- client/public/images/pieces/Alice/wc.svg | 95 ++++++---- client/public/images/pieces/Alice/wl.svg | 103 +++++++---- client/public/images/pieces/Alice/wo.svg | 36 ++-- client/public/images/pieces/Alice/ws.svg | 25 ++- client/public/images/pieces/Alice/wt.svg | 201 +++++++++++++-------- client/public/images/pieces/Alice/wu.svg | 107 +++++++---- client/src/base_rules.js | 23 ++- client/src/components/BaseGame.vue | 1 + client/src/components/GameList.vue | 8 +- client/src/components/MoveList.vue | 6 +- client/src/translations/rules/Alice/en.pug | 3 +- client/src/translations/rules/Alice/es.pug | 3 +- client/src/translations/rules/Alice/fr.pug | 3 +- client/src/variants/Alice.js | 5 +- client/src/variants/Crazyhouse.js | 11 ++ client/src/variants/Grand.js | 10 + client/src/variants/Recycle.js | 10 + client/src/variants/Shatranj.js | 3 - client/src/variants/Wormhole.js | 10 +- client/src/views/Game.vue | 151 +++++++++++----- client/src/views/Hall.vue | 44 +++-- client/src/views/MyGames.vue | 3 +- server/sockets.js | 33 +++- 29 files changed, 825 insertions(+), 469 deletions(-) diff --git a/client/public/images/pieces/Alice/bc.svg b/client/public/images/pieces/Alice/bc.svg index cf2df27a..03c42738 100644 --- a/client/public/images/pieces/Alice/bc.svg +++ b/client/public/images/pieces/Alice/bc.svg @@ -13,7 +13,7 @@ viewBox="0 0 2048 2048" id="svg16" sodipodi:docname="bc.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> @@ -49,44 +49,46 @@ inkscape:window-maximized="0" inkscape:current-layer="svg16" /> + fill="#000" + id="path2" /> + fill-rule="nonzero" + fill="#fff" + id="g14"> + id="path4" /> + id="path6" /> + id="path8" /> + id="path10" /> + id="path12" /> + diff --git a/client/public/images/pieces/Alice/bl.svg b/client/public/images/pieces/Alice/bl.svg index 4680d888..965d8dec 100644 --- a/client/public/images/pieces/Alice/bl.svg +++ b/client/public/images/pieces/Alice/bl.svg @@ -11,11 +11,11 @@ width="100%" version="1.1" viewBox="0 0 2048 2048" - id="svg40" + id="svg18" sodipodi:docname="bl.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + id="metadata24"> @@ -27,7 +27,7 @@ + id="defs22" /> + inkscape:current-layer="svg18" /> + fill-rule="nonzero" + fill="#fff" + id="g8"> + id="path2" /> + id="path4" /> + id="path6" /> + fill-rule="nonzero" + id="g16"> + fill="#000" + id="path10" /> + fill="#fff" + id="path12" /> + fill="#fff" + id="path14" /> + + + + + diff --git a/client/public/images/pieces/Alice/bo.svg b/client/public/images/pieces/Alice/bo.svg index b8c0a462..a3e425a5 100644 --- a/client/public/images/pieces/Alice/bo.svg +++ b/client/public/images/pieces/Alice/bo.svg @@ -11,11 +11,11 @@ width="100%" version="1.1" viewBox="0 0 2048 2048" - id="svg64" + id="svg18" sodipodi:docname="bo.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + id="metadata24"> @@ -27,7 +27,7 @@ + id="defs22" /> + inkscape:current-layer="svg18" /> + fill="#000" + id="path2" /> + fill-rule="nonzero" + fill="#fff" + id="g16"> + id="path4" /> + id="path6" /> + id="path8" /> + id="path10" /> + id="path12" /> + id="path14" /> + diff --git a/client/public/images/pieces/Alice/bs.svg b/client/public/images/pieces/Alice/bs.svg index abe7c2ed..fab7c213 100644 --- a/client/public/images/pieces/Alice/bs.svg +++ b/client/public/images/pieces/Alice/bs.svg @@ -11,7 +11,7 @@ version="1.1" id="svg4" sodipodi:docname="bs.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> @@ -47,9 +47,16 @@ inkscape:window-maximized="0" inkscape:current-layer="svg4" /> + diff --git a/client/public/images/pieces/Alice/bt.svg b/client/public/images/pieces/Alice/bt.svg index faf44577..311d5fd7 100644 --- a/client/public/images/pieces/Alice/bt.svg +++ b/client/public/images/pieces/Alice/bt.svg @@ -11,11 +11,11 @@ width="100%" version="1.1" viewBox="0 0 2048 2048" - id="svg22" + id="svg12" sodipodi:docname="bt.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + id="metadata18"> @@ -27,7 +27,7 @@ + id="defs16" /> + inkscape:current-layer="svg12" /> + fill="#000" + id="path2" /> + fill-rule="nonzero" + fill="#fff" + id="g10"> + id="path4" /> + id="path6" /> + id="path8" /> + + + + + + diff --git a/client/public/images/pieces/Alice/bu.svg b/client/public/images/pieces/Alice/bu.svg index fdc0ee59..bce3eed1 100644 --- a/client/public/images/pieces/Alice/bu.svg +++ b/client/public/images/pieces/Alice/bu.svg @@ -11,11 +11,11 @@ width="100%" version="1.1" viewBox="0 0 2048 2048" - id="svg44" + id="svg16" sodipodi:docname="bu.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + id="metadata22"> @@ -27,7 +27,7 @@ + id="defs20" /> + inkscape:current-layer="svg16" /> + fill="#000" + id="path2" /> + fill-rule="nonzero" + fill="#fff" + id="g14"> + id="path4" /> + id="path6" /> + id="path8" /> + id="path10" /> + id="path12" /> + diff --git a/client/public/images/pieces/Alice/wc.svg b/client/public/images/pieces/Alice/wc.svg index fd766a8b..b51dea5a 100644 --- a/client/public/images/pieces/Alice/wc.svg +++ b/client/public/images/pieces/Alice/wc.svg @@ -13,7 +13,7 @@ viewBox="0 0 2048 2048" id="svg18" sodipodi:docname="wc.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> @@ -22,6 +22,7 @@ image/svg+xml + @@ -40,7 +41,7 @@ inkscape:window-height="1060" id="namedview20" showgrid="false" - inkscape:zoom="0.11523438" + inkscape:zoom="0.16296602" inkscape:cx="1041.3559" inkscape:cy="1024" inkscape:window-x="0" @@ -48,50 +49,78 @@ inkscape:window-maximized="0" inkscape:current-layer="svg18" /> + fill="#fff" + id="path2" /> + fill="#000" + id="path4" /> + fill-rule="nonzero" + fill="#fff" + id="g16"> + id="path6" /> + id="path8" /> + id="path10" /> + id="path12" /> + id="path14" /> + + + + + + diff --git a/client/public/images/pieces/Alice/wl.svg b/client/public/images/pieces/Alice/wl.svg index 89ad619d..322c54c3 100644 --- a/client/public/images/pieces/Alice/wl.svg +++ b/client/public/images/pieces/Alice/wl.svg @@ -11,22 +11,23 @@ width="100%" version="1.1" viewBox="0 0 2048 2048" - id="svg42" + id="svg18" sodipodi:docname="wl.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + id="metadata24"> image/svg+xml + + id="defs22" /> + inkscape:current-layer="svg18" /> + fill="#000" + id="path2" /> + fill-rule="nonzero" + fill="#fff" + id="g16"> + id="path4" /> + id="path6" /> + id="path8" /> + id="path10" /> + id="path12" /> + id="path14" /> + + + + + + diff --git a/client/public/images/pieces/Alice/wo.svg b/client/public/images/pieces/Alice/wo.svg index 83f0c4b9..0e3bd47e 100644 --- a/client/public/images/pieces/Alice/wo.svg +++ b/client/public/images/pieces/Alice/wo.svg @@ -11,11 +11,11 @@ width="100%" version="1.1" viewBox="0 0 2048 2048" - id="svg54" + id="svg6" sodipodi:docname="wo.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + id="metadata12"> @@ -27,7 +27,7 @@ + id="defs10" /> + inkscape:current-layer="svg6" /> + fill="#fff" + id="path2" /> + diff --git a/client/public/images/pieces/Alice/ws.svg b/client/public/images/pieces/Alice/ws.svg index df257304..dfcd791e 100644 --- a/client/public/images/pieces/Alice/ws.svg +++ b/client/public/images/pieces/Alice/ws.svg @@ -13,7 +13,7 @@ viewBox="0 0 2048 2048" id="svg6" sodipodi:docname="ws.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> @@ -22,6 +22,7 @@ image/svg+xml + @@ -48,15 +49,23 @@ inkscape:window-maximized="0" inkscape:current-layer="svg6" /> + fill="#000" + id="path2" /> + diff --git a/client/public/images/pieces/Alice/wt.svg b/client/public/images/pieces/Alice/wt.svg index 5cbb7337..e2a5a230 100644 --- a/client/public/images/pieces/Alice/wt.svg +++ b/client/public/images/pieces/Alice/wt.svg @@ -11,22 +11,23 @@ width="100%" version="1.1" viewBox="0 0 2048 2048" - id="svg46" + id="svg34" sodipodi:docname="wt.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + id="metadata40"> image/svg+xml + + id="defs38" /> + inkscape:current-layer="svg34" /> + fill="#000" + id="path2" /> + fill-rule="nonzero" + fill="#fff" + id="g32"> + id="path4" /> + id="path6" /> + id="path8" /> + id="path10" /> + id="path12" /> + id="path14" /> + id="path16" /> + id="path18" /> + id="path20" /> + id="path22" /> + id="path24" /> + id="path26" /> + id="path28" /> + id="path30" /> + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Alice/wu.svg b/client/public/images/pieces/Alice/wu.svg index bf9f16ad..b2bfd221 100644 --- a/client/public/images/pieces/Alice/wu.svg +++ b/client/public/images/pieces/Alice/wu.svg @@ -11,22 +11,23 @@ width="100%" version="1.1" viewBox="0 0 2048 2048" - id="svg70" + id="svg18" sodipodi:docname="wu.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + id="metadata24"> image/svg+xml + + id="defs22" /> + inkscape:current-layer="svg18" /> + fill="#000" + id="path2" /> + fill-rule="nonzero" + fill="#fff" + id="g12"> + id="path4" /> + id="path6" /> + id="path8" /> + id="path10" /> + fill="#fff" + id="path14" /> + + + + + + diff --git a/client/src/base_rules.js b/client/src/base_rules.js index d2b8a7fd..b39fe9c4 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -197,12 +197,13 @@ export const ChessRules = class ChessRules { const move = moveOrSquare; const s = move.start, e = move.end; - // NOTE: next conditions are first for Atomic, and last for Checkered + // NOTE: next conditions are first for Crazyhouse, and last for Checkered + // TODO: Checkered exceptions are too weird and should move in its own file. if ( - move.appear.length > 0 && + move.vanish.length > 0 && Math.abs(s.x - e.x) == 2 && s.y == e.y && - move.appear[0].p == V.PAWN && + move.vanish[0].p == V.PAWN && ["w", "b"].includes(move.appear[0].c) ) { return { @@ -319,16 +320,24 @@ export const ChessRules = class ChessRules { // Return current fen (game state) getFen() { return ( - this.getBaseFen() + - " " + - this.getTurnFen() + - " " + + this.getBaseFen() + " " + + this.getTurnFen() + " " + this.movesCount + (V.HasFlags ? " " + this.getFlagsFen() : "") + (V.HasEnpassant ? " " + this.getEnpassantFen() : "") ); } + getFenForRepeat() { + // Omit movesCount, only variable allowed to differ + return ( + this.getBaseFen() + "_" + + this.getTurnFen() + + (V.HasFlags ? "_" + this.getFlagsFen() : "") + + (V.HasEnpassant ? "_" + this.getEnpassantFen() : "") + ); + } + // Position part of the FEN string getBaseFen() { let position = ""; diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index 0c20b0bc..3c14a7fd 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -274,6 +274,7 @@ export default { // Animate an elementary move animateMove: function(move, callback) { let startSquare = document.getElementById(getSquareId(move.start)); + if (!startSquare) return; //shouldn't happen but... let endSquare = document.getElementById(getSquareId(move.end)); let rectStart = startSquare.getBoundingClientRect(); let rectEnd = endSquare.getBoundingClientRect(); diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue index 39ad9bf2..98eaf33f 100644 --- a/client/src/components/GameList.vue +++ b/client/src/components/GameList.vue @@ -134,7 +134,13 @@ export default { return res; }, deleteGame: function(game, e) { - if (game.score != "*") { + if ( + game.score != "*" && + game.players.some(p => + p.sid == this.st.user.sid || + p.uid == this.st.user.id + ) + ) { if (confirm(this.st.tr["Remove game?"])) { GameStorage.remove( game.id, diff --git a/client/src/components/MoveList.vue b/client/src/components/MoveList.vue index f41e7e2e..cc8e24c6 100644 --- a/client/src/components/MoveList.vue +++ b/client/src/components/MoveList.vue @@ -138,12 +138,12 @@ export default { border-bottom: 1px solid lightgrey & > .td float: left - padding: 2% 0 2% 1% + padding: 2% 0 2% 2% &:first-child color: grey - width: 15% + width: 13% &:not(first-child) - width: 41% + width: 40.5% @media screen and (max-width: 767px) .moves-list diff --git a/client/src/translations/rules/Alice/en.pug b/client/src/translations/rules/Alice/en.pug index 0ee71b51..63e61ff9 100644 --- a/client/src/translations/rules/Alice/en.pug +++ b/client/src/translations/rules/Alice/en.pug @@ -4,7 +4,8 @@ p.boxed. p. Two boards are used in this variant (represented on only one). - Upside-down pieces appear at every "normal" pieces moves: they live on + Colored pieces appear at every "normal" pieces moves + (yellow for white, red for black): they live on another board. When moved, they return to the initial board. Orthodox rules apply on each board. In addition, the final square should not be occupied by a piece from the diff --git a/client/src/translations/rules/Alice/es.pug b/client/src/translations/rules/Alice/es.pug index 77ede730..017a53b3 100644 --- a/client/src/translations/rules/Alice/es.pug +++ b/client/src/translations/rules/Alice/es.pug @@ -5,7 +5,8 @@ p.boxed. p. En esta variante se utilizan dos tableros (representados en un unico tablero). - Piezas devueltas aparecen en cada jugada de pieza "normal": ellas viven + Piezas coloridas aparecen en cada jugada de pieza "normal" + (amarillos para las blancas, rojos para las negras): ellas viven en un otro tablero. Vuelven al tablero inicial cuando se mueven. Las reglas ortodoxas se aplican en cada tablero. Además, la casilla de llegada no debe estar ocupado por una pieza en el otro diff --git a/client/src/translations/rules/Alice/fr.pug b/client/src/translations/rules/Alice/fr.pug index 5950f256..7df67113 100644 --- a/client/src/translations/rules/Alice/fr.pug +++ b/client/src/translations/rules/Alice/fr.pug @@ -5,7 +5,8 @@ p.boxed. p. Deux échiquiers sont utilisés dans cette variante. - Des pièces à l'envers apparaissent à chaque coup de pièce "normale" : + Des pièces colorées apparaissent à chaque coup de pièce "normale" + (jaunes pour les blancs, rouges pour les noirs) : ces dernières vivent sur un autre échiquier. Quand elles se déplacent à nouveau, elles reviennent sur l'échiquier initial. Les règles usuelles s'appliquent sur chaque échiquier. diff --git a/client/src/variants/Alice.js b/client/src/variants/Alice.js index aa956928..5e9ff93d 100644 --- a/client/src/variants/Alice.js +++ b/client/src/variants/Alice.js @@ -126,11 +126,12 @@ export const VariantRules = class AliceRules extends ChessRules { // If the move is computed on board1, m.appear change for Alice pieces. if (mirrorSide == 1) { m.appear.forEach(psq => { - //forEach: castling taken into account + // forEach: castling taken into account psq.p = V.ALICE_CODES[psq.p]; //goto board2 }); - } //move on board2: mark vanishing pieces as Alice + } else { + // Move on board2: mark vanishing pieces as Alice m.vanish.forEach(psq => { psq.p = V.ALICE_CODES[psq.p]; }); diff --git a/client/src/variants/Crazyhouse.js b/client/src/variants/Crazyhouse.js index 7dc53466..984c902f 100644 --- a/client/src/variants/Crazyhouse.js +++ b/client/src/variants/Crazyhouse.js @@ -38,6 +38,17 @@ export const VariantRules = class CrazyhouseRules extends ChessRules { ); } + getFenForRepeat() { + return ( + this.getBaseFen() + "_" + + this.getTurnFen() + "_" + + this.getFlagsFen() + "_" + + this.getEnpassantFen() + "_" + + this.getReserveFen() + "_" + + this.getPromotedFen() + ); + } + getReserveFen() { let counts = new Array(10); for ( diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js index 359671b6..4ea521e0 100644 --- a/client/src/variants/Grand.js +++ b/client/src/variants/Grand.js @@ -39,6 +39,16 @@ export const VariantRules = class GrandRules extends ChessRules { return super.getFen() + " " + this.getCapturedFen(); } + getFenForRepeat() { + return ( + this.getBaseFen() + "_" + + this.getTurnFen() + "_" + + this.getFlagsFen() + "_" + + this.getEnpassantFen() + "_" + + this.getCapturedFen() + ); + } + getCapturedFen() { let counts = [...Array(14).fill(0)]; let i = 0; diff --git a/client/src/variants/Recycle.js b/client/src/variants/Recycle.js index a234350b..9f43ad80 100644 --- a/client/src/variants/Recycle.js +++ b/client/src/variants/Recycle.js @@ -28,6 +28,16 @@ export const VariantRules = class RecycleRules extends ChessRules { ); } + getFenForRepeat() { + return ( + this.getBaseFen() + "_" + + this.getTurnFen() + "_" + + this.getFlagsFen() + "_" + + this.getEnpassantFen() + "_" + + this.getReserveFen() + ); + } + getReserveFen() { let counts = new Array(10); for ( diff --git a/client/src/variants/Shatranj.js b/client/src/variants/Shatranj.js index ef6f65b5..b45d915a 100644 --- a/client/src/variants/Shatranj.js +++ b/client/src/variants/Shatranj.js @@ -1,6 +1,3 @@ -// TODO: bishop OK, but queen should move vertical/horizontal and capture diagonally. -// ==> then the pawn promotion is a real promotion (enhancement). - import { ChessRules } from "@/base_rules"; export const VariantRules = class ShatranjRules extends ChessRules { diff --git a/client/src/variants/Wormhole.js b/client/src/variants/Wormhole.js index 76f6d127..04699b25 100644 --- a/client/src/variants/Wormhole.js +++ b/client/src/variants/Wormhole.js @@ -154,16 +154,16 @@ export const VariantRules = class WormholeRules extends ChessRules { } } // Captures - const finalPieces = x + shiftX == lastRank - ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] - : [V.PAWN]; for (let shiftY of [-1, 1]) { const sq = this.getSquareAfter([x,y], [shiftX,shiftY]); if ( - sq && + !!sq && this.board[sq[0]][sq[1]] != V.EMPTY && this.canTake([x, y], [sq[0], sq[1]]) ) { + const finalPieces = sq[0] == lastRank + ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] + : [V.PAWN]; for (let piece of finalPieces) { moves.push( this.getBasicMove([x, y], [sq[0], sq[1]], { @@ -295,7 +295,7 @@ export const VariantRules = class WormholeRules extends ChessRules { const piece = this.getPiece(move.start.x, move.start.y); // Indicate start square + dest square, because holes distort the board let notation = - piece.toUpperCase() + + (piece != V.PAWN ? piece.toUpperCase() : "") + V.CoordsToSquare(move.start) + (move.vanish.length > move.appear.length ? "x" : "") + V.CoordsToSquare(move.end); diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 8f49c5d7..f012c69e 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -109,6 +109,13 @@ export default { repeat: {}, //detect position repetition newChat: "", conn: null, + roomInitialized: false, + // If newmove has wrong index: ask fullgame again: + fullGamerequested: false, + // If asklastate got no reply, ask again: + gotLastate: false, + // If newmove got no pingback, send again: + opponentGotMove: false, connexionString: "", // Related to (killing of) self multi-connects: newConnect: {}, @@ -171,13 +178,18 @@ export default { }, methods: { roomInit: function() { - // Notify the room only now that I connected, because - // messages might be lost otherwise (if game loading is slow) - this.send("connect"); - this.send("pollclients"); + if (!this.roomInitialized) { + // Notify the room only now that I connected, because + // messages might be lost otherwise (if game loading is slow) + this.send("connect"); + this.send("pollclients"); + // We may ask fullgame several times if some moves are lost, + // but room should be init only once: + this.roomInitialized = true; + } }, send: function(code, obj) { - if (this.conn) + if (!!this.conn) this.conn.send(JSON.stringify(Object.assign({ code: code }, obj))); }, isConnected: function(index) { @@ -211,7 +223,7 @@ export default { clearChat: function() { // Nothing more to do if game is live (chats not recorded) if (this.game.type == "corr") { - if (this.game.mycolor) + if (!!this.game.mycolor) ajax("/chats", "DELETE", {gid: this.game.id}); this.game.chats = []; } @@ -244,7 +256,7 @@ export default { this.send("askidentity", { target: sid }); // Ask potentially missed last state, if opponent and I play if ( - this.game.mycolor && + !!this.game.mycolor && this.game.type == "live" && this.game.score == "*" && this.game.players.some(p => p.sid == sid) @@ -298,21 +310,19 @@ export default { case "identity": { const user = data.data; this.$set(this.people, user.sid, { name: user.name, id: user.id }); - if (user.name) { - // If I multi-connect, kill current connexion if no mark (I'm older) + // If I multi-connect, kill current connexion if no mark (I'm older) + if (this.newConnect[user.sid]) { if ( - this.newConnect[user.sid] && user.id > 0 && user.id == this.st.user.id && - user.sid != this.st.user.sid + user.sid != this.st.user.sid && + !this.killed[this.st.user.sid] ) { - if (!this.killed[this.st.user.sid]) { this.send("killme", { sid: this.st.user.sid }); this.killed[this.st.user.sid] = true; - } } + delete this.newConnect[user.sid]; } - delete this.newConnect[user.sid]; break; } case "askgame": @@ -376,20 +386,66 @@ export default { break; case "newmove": { const move = data.data; - if (move.cancelDrawOffer) { - // Opponent refuses draw - this.drawOffer = ""; - // NOTE for corr games: drawOffer reset by player in turn - if (this.game.type == "live" && !!this.game.mycolor) - GameStorage.update(this.gameRef.id, { drawOffer: "" }); + if (move.index > this.game.movesCount && !this.fullGameRequested) { + // This can only happen if I'm an observer and missed a move: + // just ask fullgame again, this is much simpler. + (function askIfPeerConnected() { + if (!!this.people[this.gameRef.rid]) + this.send("askfullgame", { target: this.gameRef.rid }); + else setTimeout(askIfPeerConnected, 1000); + })(); + this.fullGameRequested = true; + } else { + if ( + move.index < this.game.movesCount || + this.gotMoveIdx >= move.index + ) { + // Opponent re-send but we already have the move: + // (maybe he didn't receive our pingback...) + // TODO: currently, all opponent game tabs will receive this + this.send("gotmove", {index: move.index, target: data.from}); + } else { + const receiveMyMove = ( + !!this.game.mycolor && + move.index == this.game.movesCount + ); + if (!receiveMyMove && !!this.game.mycolor) + // Notify opponent that I got the move: + this.send("gotmove", {index: move.index, target: data.from}); + if (move.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.id, { drawOffer: "" }); + } + } + this.$refs["basegame"].play( + move.move, + "received", + null, + { + addTime: move.addTime, + receiveMyMove: receiveMyMove + } + ); + } } - this.$refs["basegame"].play( - move.move, - "received", - null, - {addTime: move.addTime}); break; } + case "gotmove": { + this.opponentGotMove = true; + break; + } +/// TODO: same strategy for askLastate +// --> the message could not have been received, +// or maybe we ddn't receive it back. + + case "resign": const score = data.side == "b" ? "1-0" : "0-1"; const side = data.side == "w" ? "White" : "Black"; @@ -448,7 +504,7 @@ export default { this.gameOver("1/2", message); } else if (this.drawOffer == "") { // No effect if drawOffer == "sent" - if (this.game.mycolor != this.vr.turn) { + if (!!this.game.mycolor != this.vr.turn) { alert(this.st.tr["Draw offer only in your turn"]); return; } @@ -593,6 +649,9 @@ export default { }, game, ); + if (this.fullGameRequested) + // Second (or more) time the full game is asked: + this.fullGameRequested = false; this.re_setClocks(); this.$nextTick(() => { this.game.rendered = true; @@ -601,7 +660,7 @@ export default { }); if (callback) callback(); }; - if (game) { + if (!!game) { afterRetrieval(game); return; } @@ -652,21 +711,18 @@ export default { }, 1000); }, // Post-process a (potentially partial) move (which was just played in BaseGame) - // TODO?: wait for AJAX return to finish processing a move, - // and for opponent pingback in case of live game : if none received after e.g. 500ms, re-send newmove - // ...and provide move index with newmove event for basic check after receiving processMove: function(move, data) { const moveCol = this.vr.turn; const doProcessMove = () => { const colorIdx = ["w", "b"].indexOf(moveCol); const nextIdx = 1 - colorIdx; - if (this.game.mycolor) { + if (!!this.game.mycolor && !data.receiveMyMove) { // NOTE: 'var' to see that variable outside this block var filtered_move = getFilteredMove(move); } // Send move ("newmove" event) to people in the room (if our turn) let addTime = (data && this.game.type == "live") ? data.addTime : 0; - if (moveCol == this.game.mycolor) { + if (moveCol == this.game.mycolor && !data.receiveMyMove) { if (this.drawOffer == "received") // I refuse draw this.drawOffer = ""; @@ -678,18 +734,20 @@ export default { } const sendMove = { move: filtered_move, + index: this.game.movesCount, addTime: addTime, //undefined for corr games - cancelDrawOffer: this.drawOffer == "", - // Players' SID required for /mygames page - // TODO: precompute and add this field to game object? - players: this.game.players.map(p => p.sid) + cancelDrawOffer: this.drawOffer == "" }; + this.opponentGotMove = false; this.send("newmove", { data: sendMove }); + +// TODO: setInterval 500ms to 1s (750?) : if !gotMove (with the right index), re-send + } // Update current game object (no need for moves stack): playMove(move, this.vr); this.game.movesCount++; - // TODO: notifyTurn +// TODO: notifyTurn: "changeturn" message // (add)Time indication: useful in case of lastate infos requested this.game.moves.push(this.game.type == "live" ? {move:move, addTime:addTime} @@ -702,16 +760,15 @@ export default { this.game.initime[nextIdx] = (data && data.initime) ? data.initime : Date.now(); this.re_setClocks(); // If repetition detected, consider that a draw offer was received: - const fenObj = V.ParseFen(this.game.fen); - let repIdx = fenObj.position + "_" + fenObj.turn; - if (fenObj.flags) repIdx += "_" + fenObj.flags; - this.repeat[repIdx] = this.repeat[repIdx] ? this.repeat[repIdx] + 1 : 1; - if (this.repeat[repIdx] >= 3) this.drawOffer = "threerep"; + const fenObj = this.vr.getFenForRepeat(); + this.repeat[fenObj] = this.repeat[fenObj] ? this.repeat[fenObj] + 1 : 1; + if (this.repeat[fenObj] >= 3) this.drawOffer = "threerep"; else if (this.drawOffer == "threerep") this.drawOffer = ""; // Since corr games are stored at only one location, update should be // done only by one player for each move: if ( - this.game.mycolor && + !!this.game.mycolor && + !data.receiveMyMove && (this.game.type == "live" || moveCol == this.game.mycolor) ) { let drawCode = ""; @@ -750,8 +807,14 @@ export default { } } }; - if (this.game.type == "corr" && moveCol == this.game.mycolor) { + if ( + this.game.type == "corr" && + moveCol == this.game.mycolor && + !data.receiveMyMove + ) { setTimeout(() => { + // TODO: remplacer cette confirm box par qqch de plus discret + // (et de même pour challenge accepté / refusé) if ( !confirm( this.st.tr["Move played:"] + diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 2b0a1483..6ecc5527 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -89,7 +89,7 @@ main #players p( v-for="sid in Object.keys(people)" - v-if="people[sid].name" + v-if="!!people[sid].name" ) span {{ people[sid].name }} button.player-action( @@ -98,7 +98,7 @@ main ) | {{ st.tr["Observe"] }} button.player-action( - v-else-if="st.user.id > 0 && sid!=st.user.sid" + v-else-if="st.user.id > 0 && sid != st.user.sid" @click="challenge(sid)" ) | {{ st.tr["Challenge"] }} @@ -211,7 +211,7 @@ export default { "st.variants": function() { // Set potential challenges and games variant names: this.challenges.concat(this.games).forEach(o => { - if (o.vname == "") o.vname = this.getVname(o.vid); + if (!o.vname) o.vname = this.getVname(o.vid); }); if (!this.newchallenge.V && this.newchallenge.vid > 0) this.loadNewchallVariant(); @@ -254,7 +254,7 @@ export default { let names = {}; response.challenges.forEach(c => { if (c.uid != this.st.user.id) names[c.uid] = ""; - else if (c.target && c.target != this.st.user.id) + else if (!!c.target && c.target != this.st.user.id) names[c.target] = ""; }); const addChallenges = () => { @@ -337,7 +337,7 @@ export default { document.getElementById("cadence").focus(); }, send: function(code, obj) { - if (this.conn) { + if (!!this.conn) { this.conn.send(JSON.stringify(Object.assign({ code: code }, obj))); } }, @@ -367,7 +367,7 @@ export default { : document.getElementById("btn" + letter.toUpperCase() + type); elt.classList.add("active"); elt.classList.remove("somethingnew"); //in case of - if (elt.previousElementSibling) + if (!!elt.previousElementSibling) elt.previousElementSibling.classList.remove("active"); else elt.nextElementSibling.classList.remove("active"); }, @@ -385,11 +385,11 @@ export default { let gids = []; this.people[sid].pages.forEach(p => { const matchGid = p.match(/[a-zA-Z0-9]+$/); - if (matchGid) gids.push(matchGid[0]); + if (!!matchGid) gids.push(matchGid[0]); }); const gid = gids[Math.floor(Math.random() * gids.length)]; const game = this.games.find(g => g.id == gid); - if (game) this.showGame(game); + if (!!game) this.showGame(game); else this.$router.push("/game/" + gid); //game vs. me }, showGame: function(g) { @@ -524,21 +524,19 @@ export default { name: user.name, pages: this.people[user.sid].pages }); - if (user.name) { - // If I multi-connect, kill current connexion if no mark (I'm older) + // If I multi-connect, kill current connexion if no mark (I'm older) + if (this.newConnect[user.sid]) { if ( - this.newConnect[user.sid] && user.id > 0 && user.id == this.st.user.id && - user.sid != this.st.user.sid + user.sid != this.st.user.sid && + !this.killed[this.st.user.sid] ) { - if (!this.killed[this.st.user.sid]) { this.send("killme", { sid: this.st.user.sid }); this.killed[this.st.user.sid] = true; - } } + delete this.newConnect[user.sid]; } - delete this.newConnect[user.sid]; break; } case "askchallenge": { @@ -642,7 +640,7 @@ export default { } case "result": { let g = this.games.find(g => g.id == data.gid); - if (g) g.score = data.score; + if (!!g) g.score = data.score; break; } case "startgame": { @@ -683,7 +681,7 @@ export default { const vModule = await import("@/variants/" + vname + ".js"); this.newchallenge.V = vModule.VariantRules; this.newchallenge.vname = vname; - if (cb) + if (!!cb) cb(); }, trySetNewchallDiag: function() { @@ -695,7 +693,7 @@ export default { window.V = this.newchallenge.V; if ( this.newchallenge.vid > 0 && - this.newchallenge.fen && + !!this.newchallenge.fen && V.IsGoodFen(this.newchallenge.fen) ) { const parsedFen = V.ParseFen(this.newchallenge.fen); @@ -715,7 +713,7 @@ export default { error = this.st.tr["Please select a variant"]; else if (ctype == "corr" && this.st.user.id <= 0) error = this.st.tr["Please log in to play correspondance games"]; - else if (this.newchallenge.to) { + else if (!!this.newchallenge.to) { if (this.newchallenge.to == this.st.user.name) error = this.st.tr["Self-challenge is forbidden"]; else if ( @@ -810,7 +808,7 @@ export default { this.launchGame(c); } else { const oppsid = this.getOppsid(c); - if (oppsid) + if (!!oppsid) this.send("refusechallenge", { data: c.id, target: oppsid }); if (c.type == "corr") ajax("/challenges", "DELETE", { id: c.id }); @@ -829,9 +827,9 @@ export default { c.accepted = true; const vModule = await import("@/variants/" + c.vname + ".js"); window.V = vModule.VariantRules; - if (c.to) { + if (!!c.to) { // c.to == this.st.user.name (connected) - if (c.fen) { + if (!!c.fen) { const parsedFen = V.ParseFen(c.fen); c.mycolor = V.GetOppCol(parsedFen.turn); this.tchallDiag = getDiagram({ @@ -874,7 +872,7 @@ export default { }; const notifyNewgame = () => { const oppsid = this.getOppsid(c); - if (oppsid) + if (!!oppsid) //opponent is online this.send("startgame", { data: gameInfo, target: oppsid }); // Send game info (only if live) to everyone except me in this tab diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index c9c65659..c1430f89 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -92,8 +92,7 @@ export default { }, socketMessageListener: function(msg) { const data = JSON.parse(msg.data); - // Only event is newmove, and received only: - if (data.code == "newmove") { + if (data.code == "changeturn") { let games = !!parseInt(data.gid) ? this.corrGames : this.liveGames; diff --git a/server/sockets.js b/server/sockets.js index 3854ca2f..614696f3 100644 --- a/server/sockets.js +++ b/server/sockets.js @@ -14,7 +14,7 @@ function getJsonFromUrl(url) { // Helper to safe-send some message through a (web-)socket: function send(socket, message) { - if (socket && socket.readyState == 1) + if (!!socket && socket.readyState == 1) socket.send(JSON.stringify(message)); } @@ -145,7 +145,7 @@ module.exports = function(wss) { case "askfullgame": { const pg = obj.page || page; //required for askidentity and askgame // In cas askfullgame to wrong SID for example, would crash: - if (clients[pg] && clients[pg][obj.target]) { + if (!!clients[pg] && !!clients[pg][obj.target]) { const tmpIds = Object.keys(clients[pg][obj.target]); if (obj.target == sid) { // Targetting myself @@ -183,7 +183,30 @@ module.exports = function(wss) { case "abort": case "drawoffer": case "draw": - notifyRoom(page, obj.code, {data: obj.data}); + // TODO: if newmove, change "from" field to fully specified sid + tmpId + // ==> allow "gotmove" messages to be fully targetted + // Special case re-send newmove only to opponent: + if (!!obj.target) { + Object.keys(clients[page][obj.target]).forEach(x => { + send( + clients[page][obj.target][x], + {code: "newmove", data: obj.data} + ); + }); + } + else notifyRoom(page, obj.code, {data: obj.data}); + break; + + case "gotmove": + // TODO: should fully specify the target and be included in the last case below + if (!!clients[page][obj.target]) { + Object.keys(clients[page][obj.target]).forEach(x => { + send( + clients[pg][obj.target][x], + {code: "gotmove", data: obj.data} + ); + }); + } break; case "result": @@ -222,8 +245,8 @@ module.exports = function(wss) { { const pg = obj.target[2] || page; //required for identity and game // NOTE: if in game we ask identity to opponent still in Hall, - // but leaving Hall, clients[pg] or clients[pg][target] could be ndefined - if (clients[pg] && clients[pg][obj.target[0]]) + // but leaving Hall, clients[pg] or clients[pg][target] could be undefined + if (!!clients[pg] && !!clients[pg][obj.target[0]]) send(clients[pg][obj.target[0]][obj.target[1]], {code:obj.code, data:obj.data}); break; } -- 2.44.0