From edcd679ab1fe609641451586ef1e9484925c4f83 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Thu, 27 Dec 2018 22:05:20 +0100 Subject: [PATCH] Some debug, plan several short + long term TODOs --- TODO | 17 +++++ public/javascripts/base_rules.js | 30 +++++--- public/javascripts/components/game.js | 93 +++++++++++------------- public/javascripts/variants/Marseille.js | 38 ++++++---- public/stylesheets/variant.sass | 3 +- sockets.js | 9 ++- views/rules/Marseille/en.pug | 2 + views/rules/Marseille/fr.pug | 2 + views/translations/en.pug | 2 +- views/translations/es.pug | 2 +- views/translations/fr.pug | 2 +- 11 files changed, 115 insertions(+), 85 deletions(-) diff --git a/TODO b/TODO index 37961d74..efdd2596 100644 --- a/TODO +++ b/TODO @@ -5,3 +5,20 @@ Promotions: increase pieces sizes, better background. Code: use two spaces instead of tabs, everywhere. Increase code line length to 100 or more? (http://katafrakt.me/2017/09/16/80-characters-line-length-limit/) +Chat button should be more apparent after game ends (color ?) +Reinforce security for problems upload (how ?) +The mode switch between human/computer/friend (+ problem) is a mess +(example: finished computer game, ongoing friend game, reload, friend game is unreachable) + +Later: +Let choice of time control, allow correspondance play, several games at the same time +==> need to use indexedDB instead of localStorage. Maybe with Dexie https://dexie.org/ +Each user would have a unique identifier stored in the client DB. +Allow to cancel games (if opponent doesn't connect again) +Identity would be browser-based: different games on smartphone, home computer, work computer... (why not ?) +Index might still look the same, and variant page would have another tab "Games" +==> running, and finished (which can be deleted from local memory) +(A true analysis mode could be implemented also, to navigate in completed games --> use a button) +Allow challenging a specific player (by his chosen name) +But keep the random pairings as main playing way + always playing in ZEN mode, +except when accepting an individual challenge. diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 50766b07..8f875e4f 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -186,7 +186,8 @@ class ChessRules // Argument is a move: const move = moveOrSquare; const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x]; - if (move.appear[0].p == V.PAWN && Math.abs(sx - ex) == 2) + // TODO: next conditions are first for Atomic, and third for Checkered + if (move.appear.length > 0 && move.appear[0].p == V.PAWN && ["w","b"].includes(move.appear[0].c) && Math.abs(sx - ex) == 2) { return { x: (sx + ex)/2, @@ -1372,15 +1373,22 @@ class ChessRules getPGN(mycolor, score, fenStart, mode) { let pgn = ""; - pgn += '[Site "vchess.club"]
'; + pgn += '[Site "vchess.club"]\n'; const opponent = mode=="human" ? "Anonymous" : "Computer"; - pgn += '[Variant "' + variant + '"]
'; - pgn += '[Date "' + getDate(new Date()) + '"]
'; - pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]
'; - pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]
'; - pgn += '[FenStart "' + fenStart + '"]
'; - pgn += '[Fen "' + this.getFen() + '"]
'; - pgn += '[Result "' + score + '"]

'; + pgn += '[Variant "' + variant + '"]\n'; + pgn += '[Date "' + getDate(new Date()) + '"]\n'; + // TODO: later when users are a bit less anonymous, use better names + const whiteName = ["human","computer"].includes(mode) + ? (mycolor=='w'?'Myself':opponent) + : "analyze"; + const blackName = ["human","computer"].includes(mode) + ? (mycolor=='b'?'Myself':opponent) + : "analyze"; + pgn += '[White "' + whiteName + '"]\n'; + pgn += '[Black "' + blackName + '"]\n'; + pgn += '[FenStart "' + fenStart + '"]\n'; + pgn += '[Fen "' + this.getFen() + '"]\n'; + pgn += '[Result "' + score + '"]\n\n'; // Standard PGN for (let i=0; i 0) - { - elementArray.push( - h('div', - { - attrs: { id: "pgn-div" }, - "class": { "section-content": true }, - }, - [ - h('a', - { - attrs: { - id: "download", - href: "#", - } - } - ), - h('p', - { - attrs: { id: "pgn-game" }, - domProps: { innerHTML: this.pgnTxt } - } - ), - h('button', - { - attrs: { "id": "downloadBtn" }, - on: { click: this.download }, - domProps: { innerHTML: translations["Download game"] }, - } - ), - ] - ) - ); - } - else if (this.mode != "idle") + if (!!this.vr) { if (this.mode == "problem") { @@ -949,6 +915,31 @@ Vue.component('my-game', { ) ); } + elementArray.push( + h('div', + { + attrs: { id: "pgn-div" }, + "class": { "section-content": true }, + }, + [ + h('a', + { + attrs: { + id: "download", + href: "#", + } + } + ), + h('button', + { + attrs: { "id": "downloadBtn" }, + on: { click: this.download }, + domProps: { innerHTML: translations["Download PGN"] }, + } + ), + ] + ) + ); } return h( 'div', @@ -1008,7 +999,7 @@ Vue.component('my-game', { }; const socketMessageListener = msg => { const data = JSON.parse(msg.data); - const L = (!!this.vr ? this.vr.moves.length : 0); + let L = undefined; switch (data.code) { case "oppname": @@ -1028,7 +1019,7 @@ Vue.component('my-game', { break; case "newgame": //opponent found // oppid: opponent socket ID - this.newGame("human", data.fen, data.color, data.oppid); + this.newGame("human", data.fen, data.color, data.oppid, data.gameid); break; case "newmove": //..he played! this.play(data.move, (variant!="Dark" ? "animate" : null)); @@ -1038,6 +1029,7 @@ Vue.component('my-game', { break; //games IDs don't match: definitely over... this.oppConnected = true; // Send our "last state" informations to opponent + L = this.vr.moves.length; this.conn.send(JSON.stringify({ code: "lastate", oppid: this.oppid, @@ -1047,10 +1039,11 @@ Vue.component('my-game', { })); break; case "lastate": //got opponent infos about last move + L = this.vr.moves.length; if (this.gameId != data.gameId) break; //games IDs don't match: nothing we can do... // OK, opponent still in game (which might be over) - if (this.mode != "human") + if (this.score != "*") { // We finished the game (any result possible) this.conn.send(JSON.stringify({ @@ -1068,6 +1061,7 @@ Vue.component('my-game', { this.conn.send(JSON.stringify({ code: "lastate", oppid: this.oppid, + gameId: this.gameId, lastMove: this.vr.moves[L-1], movesCount: L, })); @@ -1164,13 +1158,12 @@ Vue.component('my-game', { : "none"; }, download: function() { - let content = document.getElementById("pgn-game").innerHTML; - content = content.replace(/
/g, "\n"); + // Variants may have special PGN structure (so next function isn't defined here) + const content = this.vr.getPGN(this.mycolor, this.score, this.fenStart, this.mode); // Prepare and trigger download link let downloadAnchor = document.getElementById("download"); downloadAnchor.setAttribute("download", "game.pgn"); - downloadAnchor.href = "data:text/plain;charset=utf-8," + - encodeURIComponent(content); + downloadAnchor.href = "data:text/plain;charset=utf-8," + encodeURIComponent(content); downloadAnchor.click(); }, showScoreMsg: function() { @@ -1186,8 +1179,6 @@ Vue.component('my-game', { localStorage.setItem(prefix+"score", score); } this.showScoreMsg(); - // Variants may have special PGN structure (so next function isn't defined here) - this.pgnTxt = this.vr.getPGN(this.mycolor, this.score, this.fenStart, this.mode); if (this.mode == "human" && this.oppConnected) { // Send our nickname to opponent @@ -1311,7 +1302,7 @@ Vue.component('my-game', { } this.endGame(this.mycolor=="w"?"0-1":"1-0"); }, - newGame: function(mode, fenInit, color, oppId) { + newGame: function(mode, fenInit, color, oppId, gameId) { const fen = fenInit || VariantRules.GenRandInitFen(); console.log(fen); //DEBUG if (mode=="human" && !oppId) @@ -1325,7 +1316,7 @@ Vue.component('my-game', { } // Send game request and wait.. try { - this.conn.send(JSON.stringify({code:"newgame", fen:fen})); + this.conn.send(JSON.stringify({code:"newgame", fen:fen, gameid: getRandString() })); } catch (INVALID_STATE_ERR) { return; //nothing achieved } @@ -1374,12 +1365,10 @@ Vue.component('my-game', { this.mode = mode; this.incheck = []; this.fenStart = V.ParseFen(fen).position; //this is enough - if (mode != "problem") - this.setStorage(); //store game state in case of interruptions if (mode=="human") { // Opponent found! - this.gameId = getRandString(); + this.gameId = gameId; this.oppid = oppId; this.oppConnected = true; this.mycolor = color; @@ -1398,6 +1387,8 @@ Vue.component('my-game', { else if (mode == "friend") this.mycolor = "w"; //convention... //else: problem solving: nothing more to do + if (mode != "problem") + this.setStorage(); //store game state in case of interruptions }, continueGame: function(mode) { this.mode = mode; diff --git a/public/javascripts/variants/Marseille.js b/public/javascripts/variants/Marseille.js index 2eb42606..f372698d 100644 --- a/public/javascripts/variants/Marseille.js +++ b/public/javascripts/variants/Marseille.js @@ -70,7 +70,6 @@ class MarseilleRules extends ChessRules const firstRank = (color == 'w' ? sizeX-1 : 0); const startRank = (color == "w" ? sizeX-2 : 1); const lastRank = (color == "w" ? 0 : sizeX-1); - const pawnColor = this.getColor(x,y); //can be different for checkered const finalPieces = x + shiftX == lastRank ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN] : [V.PAWN]; @@ -81,7 +80,7 @@ class MarseilleRules extends ChessRules for (let piece of finalPieces) { moves.push(this.getBasicMove([x,y], [x+shiftX,y], - {c:pawnColor,p:piece})); + {c:color,p:piece})); } // Next condition because pawns on 1st rank can generally jump if ([startRank,firstRank].includes(x) @@ -101,7 +100,7 @@ class MarseilleRules extends ChessRules for (let piece of finalPieces) { moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY], - {c:pawnColor,p:piece})); + {c:color,p:piece})); } } } @@ -118,6 +117,7 @@ class MarseilleRules extends ChessRules }); if (epSqs.length == 0) return moves; + const oppCol = this.getOppCol(color); for (let sq of epSqs) { if (this.subTurn == 1 || (epSqs.length == 2 && @@ -125,14 +125,16 @@ class MarseilleRules extends ChessRules // (Or maybe the opponent filled the en-passant square with a piece) this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY)) { - if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1) + if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1 + // Add condition "enemy pawn must be present" + && this.getPiece(x,sq.y) == V.PAWN && this.getColor(x,sq.y) == oppCol) { let epMove = this.getBasicMove([x,y], [sq.x,sq.y]); epMove.vanish.push({ x: x, y: sq.y, p: 'p', - c: this.getColor(x,sq.y) + c: oppCol }); moves.push(epMove); } @@ -320,15 +322,21 @@ class MarseilleRules extends ChessRules getPGN(mycolor, score, fenStart, mode) { let pgn = ""; - pgn += '[Site "vchess.club"]
'; + pgn += '[Site "vchess.club"]\n'; const opponent = mode=="human" ? "Anonymous" : "Computer"; - pgn += '[Variant "' + variant + '"]
'; - pgn += '[Date "' + getDate(new Date()) + '"]
'; - pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]
'; - pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]
'; - pgn += '[FenStart "' + fenStart + '"]
'; - pgn += '[Fen "' + this.getFen() + '"]
'; - pgn += '[Result "' + score + '"]

'; + pgn += '[Variant "' + variant + '"]\n'; + pgn += '[Date "' + getDate(new Date()) + '"]\n'; + const whiteName = ["human","computer"].includes(mode) + ? (mycolor=='w'?'Myself':opponent) + : "analyze"; + const blackName = ["human","computer"].includes(mode) + ? (mycolor=='b'?'Myself':opponent) + : "analyze"; + pgn += '[White "' + whiteName + '"]\n'; + pgn += '[Black "' + blackName + '"]\n'; + pgn += '[FenStart "' + fenStart + '"]\n'; + pgn += '[Fen "' + this.getFen() + '"]\n'; + pgn += '[Result "' + score + '"]\n\n'; let counter = 1; let i = 0; @@ -344,7 +352,7 @@ class MarseilleRules extends ChessRules pgn += move + (i < this.moves.length-1 ? " " : ""); } } - pgn += "

"; + pgn += "\n\n"; // "Complete moves" PGN (helping in ambiguous cases) counter = 1; @@ -362,7 +370,7 @@ class MarseilleRules extends ChessRules } } - return pgn; + return pgn + "\n"; } } diff --git a/public/stylesheets/variant.sass b/public/stylesheets/variant.sass index df26d584..15c1996d 100644 --- a/public/stylesheets/variant.sass +++ b/public/stylesheets/variant.sass @@ -248,6 +248,7 @@ img.ghost #fen-string margin-top: 0 + margin-bottom: 10px #pgn-game margin-top: 0 @@ -261,7 +262,7 @@ img.ghost #pgn-div > a display: none -#fen-div > p +//#fen-div > p margin-left: 0 margin-right: 0 diff --git a/sockets.js b/sockets.js index 11fe91cc..e411050d 100644 --- a/sockets.js +++ b/sockets.js @@ -100,20 +100,21 @@ module.exports = function(wss) { // Start a new game const oppId = games[page]["id"]; const fen = games[page]["fen"]; + const gameId = games[page]["gameid"]; delete games[page]; - const mycolor = Math.random() < 0.5 ? 'w' : 'b'; + const mycolor = (Math.random() < 0.5 ? 'w' : 'b'); socket.send(JSON.stringify( - {code:"newgame",fen:fen,oppid:oppId,color:mycolor})); + {code:"newgame",fen:fen,oppid:oppId,color:mycolor,gameid:gameId})); if (!!clients[page][oppId]) { clients[page][oppId].send( JSON.stringify( - {code:"newgame",fen:fen,oppid:sid,color:mycolor=="w"?"b":"w"}), + {code:"newgame",fen:fen,oppid:sid,color:mycolor=="w"?"b":"w",gameid:gameId}), noop); } } else - games[page] = {id:sid, fen:obj.fen}; //wait for opponent + games[page] = {id:sid, fen:obj.fen, gameid:obj.gameid}; //wait for opponent break; case "cancelnewgame": //if a user cancel his seek delete games[page]; diff --git a/views/rules/Marseille/en.pug b/views/rules/Marseille/en.pug index a390e9d9..503d51ea 100644 --- a/views/rules/Marseille/en.pug +++ b/views/rules/Marseille/en.pug @@ -54,6 +54,8 @@ p. Note: if a pawn 2-squares jump was made and then a piece landed at the en-passant square at the second move, a pawn capture on this square takes only the piece. + And, if a pawn advanced twice then en-passant capture + on its first movement is impossible (the pawn is now "too far"). h3 More information diff --git a/views/rules/Marseille/fr.pug b/views/rules/Marseille/fr.pug index 51c0fcd0..c805a629 100644 --- a/views/rules/Marseille/fr.pug +++ b/views/rules/Marseille/fr.pug @@ -46,6 +46,8 @@ p. Note : si un pion se déplace de deux cases puis qu'une pièce occupe la case de prise en passant au second coup d'un tour, une capture sur cette case ne prendra que la pièce. + Et, si un pion a avancé deux fois la prise en passant sur son premier + déplacement est impossible (le pion est "trop loin" désormais). h3 Plus d'information diff --git a/views/translations/en.pug b/views/translations/en.pug index f07764b0..10a82cd7 100644 --- a/views/translations/en.pug +++ b/views/translations/en.pug @@ -59,7 +59,7 @@ "Chat with ": "Chat with ", "Type here": "Type here", "Send": "Send", - "Download game": "Download game", + "Download PGN": "Download PGN", "Show solution": "Show solution", "Load previous problems": "Load previous problems", "Load next problems": "Load next problems", diff --git a/views/translations/es.pug b/views/translations/es.pug index b589fb16..98d8b06d 100644 --- a/views/translations/es.pug +++ b/views/translations/es.pug @@ -59,7 +59,7 @@ "Chat with ": "Hablar con ", "Type here": "Escribe aqui", "Send": "Enviar", - "Download game": "Descargar la partida", + "Download PGN": "Descargar el PGN", "Show solution": "Mostrar la solucion", "Load previous problems": "Cargar los problemas anteriores", "Load next problems": "Cargar los siguientes problemas", diff --git a/views/translations/fr.pug b/views/translations/fr.pug index 8a86df38..b76e4d25 100644 --- a/views/translations/fr.pug +++ b/views/translations/fr.pug @@ -59,7 +59,7 @@ "Chat with ": "Discuter avec ", "Type here": "Écrivez ici", "Send": "Envoyer", - "Download game": "Télécharger la partie", + "Download PGN": "Télécharger le PGN", "Show solution": "Montrer la solution", "Load previous problems": "Charger les problèmes précédents", "Load next problems": "Charger les problèmes suivants", -- 2.44.0