From edcd679ab1fe609641451586ef1e9484925c4f83 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
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"]<br>';
+		pgn += '[Site "vchess.club"]\n';
 		const opponent = mode=="human" ? "Anonymous" : "Computer";
-		pgn += '[Variant "' + variant + '"]<br>';
-		pgn += '[Date "' + getDate(new Date()) + '"]<br>';
-		pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]<br>';
-		pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]<br>';
-		pgn += '[FenStart "' + fenStart + '"]<br>';
-		pgn += '[Fen "' + this.getFen() + '"]<br>';
-		pgn += '[Result "' + score + '"]<br><br>';
+		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<this.moves.length; i++)
@@ -1389,7 +1397,7 @@ class ChessRules
 				pgn += ((i/2)+1) + ".";
 			pgn += this.moves[i].notation[0] + " ";
 		}
-		pgn += "<br><br>";
+		pgn += "\n\n";
 
 		// "Complete moves" PGN (helping in ambiguous cases)
 		for (let i=0; i<this.moves.length; i++)
@@ -1399,6 +1407,6 @@ class ChessRules
 			pgn += this.moves[i].notation[1] + " ";
 		}
 
-		return pgn;
+		return pgn + "\n";
 	}
 }
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 4c369e4c..58fd365a 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -57,7 +57,7 @@ Vue.component('my-game', {
 					"tooltip": true,
 					"play": true,
 					"seek": this.seek,
-					"playing": this.mode == "human",
+					"playing": this.mode == "human" && this.score == "*",
 				},
 			},
 			[h('i', { 'class': { "material-icons": true } }, "accessibility")])
@@ -73,7 +73,7 @@ Vue.component('my-game', {
 					'class': {
 						"tooltip":true,
 						"play": true,
-						"playing": this.mode == "computer",
+						"playing": this.mode == "computer" && this.score == "*",
 						"spaceleft": true,
 					},
 				},
@@ -865,41 +865,7 @@ Vue.component('my-game', {
 			actionArray
 		);
 		elementArray.push(actions);
-		if (this.score != "*" && this.pgnTxt.length > 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(/<br>/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"]<br>';
+		pgn += '[Site "vchess.club"]\n';
 		const opponent = mode=="human" ? "Anonymous" : "Computer";
-		pgn += '[Variant "' + variant + '"]<br>';
-		pgn += '[Date "' + getDate(new Date()) + '"]<br>';
-		pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]<br>';
-		pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]<br>';
-		pgn += '[FenStart "' + fenStart + '"]<br>';
-		pgn += '[Fen "' + this.getFen() + '"]<br>';
-		pgn += '[Result "' + score + '"]<br><br>';
+		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 += "<br><br>";
+		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