From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 14 Jan 2019 15:52:55 +0000 (+0100)
Subject: Fix moveList + progress on game navigation system
X-Git-Url: https://git.auder.net/variants/Chakart/doc/css/current/scripts/DESCRIPTION?a=commitdiff_plain;h=7d9e99bc177972c5af8b1b45f4bfb043d8306f30;p=vchess.git

Fix moveList + progress on game navigation system
---

diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js
index 4b0b4705..7db4cfde 100644
--- a/public/javascripts/base_rules.js
+++ b/public/javascripts/base_rules.js
@@ -1053,6 +1053,8 @@ class ChessRules
 			move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
 		if (V.HasEnpassant)
 			this.epSquares.push( this.getEpSquare(move) );
+		if (!move.color)
+			move.color = this.turn; //for interface
 		V.PlayOnBoard(this.board, move);
 		this.turn = V.GetOppCol(this.turn);
 		this.movesCount++;
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 594c8c22..4048a3d4 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -24,8 +24,10 @@ Vue.component('my-game', {
 			vr: null, //VariantRules object, describing the game state + rules
 			endgameMessage: "",
 			orientation: "w",
+			fenStart: "",
 			
 			moves: [], //TODO: initialize if gameId is defined...
+			cursor: 0,
 			// orientation :: button flip
 			// userColor: given by gameId, or fen (if no game Id)
 			// gameOver: known if gameId; otherwise assue false
@@ -47,6 +49,7 @@ Vue.component('my-game', {
 			return this.allowChat && this.mode=='human' && this.score != '*';
 		},
 		showMoves: function() {
+			return true;
 			return this.allowMovelist && window.innerWidth >= 768;
 		},
 		showFen: function() {
@@ -81,6 +84,13 @@ Vue.component('my-game', {
 			</my-chat>
 			<my-board v-bind:vr="vr" :mode="mode" :orientation="orientation" :user-color="mycolor" @play-move="play">
 			</my-board>
+			<div class="button-group">
+				<button @click="() => play()">Play</button>
+				<button @click="() => undo()">Undo</button>
+				<button @click="flip">Flip</button>
+				<button @click="gotoBegin">GotoBegin</button>
+				<button @click="gotoEnd">GotoEnd</button>
+			</div>
 			<div v-if="showFen && !!vr" id="fen-div" class="section-content">
 				<p id="fen-string" class="text-center">
 					{{ vr.getFen() }}
@@ -93,18 +103,18 @@ Vue.component('my-game', {
 					{{ translate("Download PGN") }}
 				</button>
 			</div>
-			<my-move-list v-if="showMoves" :moves="moves">
+			<my-move-list v-if="showMoves" :moves="moves" :cursor="cursor" @goto-move="gotoMove">
 			</my-move-list>
 		</div>
 	`,
 	created: function() {
-
-//		console.log(this.fen);
-//		console.log(this.gameId);
 		if (!!this.gameId)
 			this.loadGame();
 		else if (!!this.fen)
+		{
 			this.vr = new VariantRules(this.fen);
+			this.fenStart = this.fen;
+		}
 		// TODO: after game, archive in indexedDB
 		// TODO: this events listener is central. Refactor ? How ?
 		const socketMessageListener = msg => {
@@ -316,12 +326,25 @@ Vue.component('my-game', {
 			}, 250);
 		},
 		play: function(move, programmatic) {
+			// Forbid playing outside analyze mode when cursor isn't at moves.length-1
+			if (this.mode != "analyze" && this.cursor < this.moves.length-1)
+				return;
+			let navigate = !move;
+			if (navigate)
+			{
+				if (this.cursor == this.moves.length)
+					return; //no more moves
+				move = this.moves[this.cursor];
+			}
 			if (!!programmatic) //computer or human opponent
 				return this.animateMove(move);
 			// Not programmatic, or animation is over
 			if (!move.notation)
 				move.notation = this.vr.getNotation(move);
+			if (!move.color)
+				move.color = this.vr.turn;
 			this.vr.play(move);
+			this.cursor++;
 			if (!move.fen)
 				move.fen = this.vr.getFen();
 			if (this.sound == 2)
@@ -337,10 +360,13 @@ Vue.component('my-game', {
 				// Send the move to web worker (including his own moves)
 				this.compWorker.postMessage(["newmove",move]);
 			}
-			if (this.score == "*" || this.mode == "analyze")
+			if (!navigate && (this.score == "*" || this.mode == "analyze"))
 			{
-				// Stack move on movesList
-				this.moves.push(move);
+				// Stack move on movesList at current cursor
+				if (this.cursor == this.moves.length)
+					this.moves.push(move);
+				else
+					this.moves = this.moves.slice(0,this.cursor-1).concat([move]);
 			}
 			// Is opponent in check?
 			this.incheck = this.vr.getCheckSquares(this.vr.turn);
@@ -355,18 +381,45 @@ Vue.component('my-game', {
 			}
 			else if (this.mode == "computer" && this.vr.turn != this.userColor)
 				this.playComputerMove();
+			if (navigate)
+				this.$children[0].$forceUpdate(); //TODO!?
 		},
 		undo: function(move) {
+			let navigate = !move;
+			if (navigate)
+			{
+				if (this.cursor == 0)
+					return; //no more moves
+				move = this.moves[this.cursor-1];
+			}
 			this.vr.undo(move);
+			this.cursor--;
+			if (navigate)
+				this.$children[0].$forceUpdate(); //TODO!?
 			if (this.sound == 2)
 				new Audio("/sounds/undo.mp3").play().catch(err => {});
 			this.incheck = this.vr.getCheckSquares(this.vr.turn);
-			if (this.mode == "analyze")
+			if (!navigate && this.mode == "analyze")
 				this.moves.pop();
+			if (navigate)
+				this.$forceUpdate(); //TODO!?
+		},
+		gotoMove: function(index) {
+			this.vr = new VariantRules(this.moves[index].fen);
+			this.cursor = index+1;
+		},
+		gotoBegin: function() {
+			this.vr = new VariantRules(this.fenStart);
+			this.cursor = 0;
+		},
+		gotoEnd: function() {
+			this.gotoMove(this.moves.length-1);
+		},
+		flip: function() {
+			this.orientation = V.GetNextCol(this.orientation);
 		},
 	},
 })
-// cursor + ........
 //TODO: confirm dialog with "opponent offers draw", avec possible bouton "prevent future offers" + bouton "proposer nulle"
 //+ bouton "abort" avec score == "?" + demander confirmation pour toutes ces actions,
 //comme sur lichess
diff --git a/public/javascripts/components/moveList.js b/public/javascripts/components/moveList.js
index 3687864c..b3c4255f 100644
--- a/public/javascripts/components/moveList.js
+++ b/public/javascripts/components/moveList.js
@@ -1,12 +1,12 @@
 //TODO: component for moves list on the right
 // TODO: generic "getPGN" in the same way (following move.color)
 Vue.component('my-move-list', {
-	props: ["moves"], //TODO: other props for e.g. players names + connected indicator
+	props: ["moves","cursor"], //TODO: other props for e.g. players names + connected indicator
 	// --> we could also add turn indicator here
+	// + missing "cursor" prop
 	data: function() {
 		return {
-			// if oppid == "computer" then mode = "computer" (otherwise human)
-			myid: "", //TODO
+			something: "", //TODO
 		};
 	},
 	// TODO: extend rendering for more than 2 colors: would be a parameter
@@ -14,8 +14,9 @@ Vue.component('my-move-list', {
 		if (this.moves.length == 0)
 			return;
 		const nbColors = 2;
-		if (this.moves[0].color == "black")
-			this.moves.unshift({color: "white", notation: "..."});
+		// TODO: name colors "white", "black", "red", "yellow" ?
+		if (this.moves[0].color == "b")
+			this.moves.unshift({color: "w", notation: "..."});
 		let tableContent = [];
 		let moveCounter = 0;
 		let tableRow = undefined;
@@ -23,45 +24,62 @@ Vue.component('my-move-list', {
 		let curCellContent = "";
 		for (let i=0; i<this.moves.length; i++)
 		{
-			if (this.moves[i].color == "white")
+			if (this.moves[i].color == "w")
 			{
-				if (i == 0 || i>0 && this.moves[i-1].color=="black")
+				if (i == 0 || i>0 && this.moves[i-1].color=="b")
 				{
 					if (!!tableRow)
+					{
+						tableRow.children = moveCells;
 						tableContent.push(tableRow);
+					}
 					moveCells = [
 						h(
 							"td",
-							{ attrs: { innerHTML: (++moveCounter) + "." } }
+							{ domProps: { innerHTML: (++moveCounter) + "." } }
 						)
 					];
 					tableRow = h(
 						"tr",
-						{ },
-						moveCells
+						{ }
 					);
 					curCellContent = "";
 				}
-				curCellContent += this.moves[i].notation + ",";
+			}
+			curCellContent += this.moves[i].notation;
+			if (i < this.moves.length-1 && this.moves[i+1].color == this.moves[i].color)
+				curCellContent += ",";
+			else //color change
+			{
 				moveCells.push(
 					h(
 						"td",
-						{ attrs: ..............
+						{
+							domProps: { innerHTML: curCellContent },
+							on: { click: () => this.gotoMove(i) },
+							"class": { "highlight-lm": this.cursor-1 == i },
+						}
+					)
+				);
+				curCellContent = "";
 			}
 		}
 		// Complete last row, which might not be full:
-		if (tableRow.length-1 < nbColors)
+		if (moveCells.length-1 < nbColors)
 		{
 			const delta = nbColors - (moveCells.length-1);
 			for (let i=0; i<delta; i++)
 			{
 				moveCells.push(
-					"td"
-					{ attrs: { innerHTML: "" } }
+					h(
+						"td",
+						{ domProps: { innerHTML: "" } }
+					)
 				);
 			}
-			tableContent.push(tableRow);
 		}
+		tableRow.children = moveCells;
+		tableContent.push(tableRow);
 		const movesTable = h(
 			"table",
 			{ },
@@ -69,6 +87,9 @@ Vue.component('my-move-list', {
 		);
 		return movesTable;
 	},
-//	methods: {
-//	},
-}
+	methods: {
+		gotoMove: function(index) {
+			this.$emit("goto-move", index);
+		},
+	},
+})
diff --git a/public/javascripts/variant.js b/public/javascripts/variant.js
index 74c4298c..f074baeb 100644
--- a/public/javascripts/variant.js
+++ b/public/javascripts/variant.js
@@ -12,7 +12,7 @@ new Vue({
 		userColor: "w",
 
 		allowChat: false,
-		allowMovelist: false,
+		allowMovelist: true,
 		fen: V.GenRandInitFen(),
 	},
 	created: function() {
diff --git a/public/javascripts/variants/Marseille.js b/public/javascripts/variants/Marseille.js
index 1adb83f4..d38d177f 100644
--- a/public/javascripts/variants/Marseille.js
+++ b/public/javascripts/variants/Marseille.js
@@ -19,8 +19,6 @@ class MarseilleRules extends ChessRules
 
 	getTurnFen()
 	{
-		if (this.startAtFirstMove && this.moves.length==0)
-			return "w";
 		return this.turn + this.subTurn;
 	}
 
@@ -56,9 +54,8 @@ class MarseilleRules extends ChessRules
 		// Extract subTurn from turn indicator: "w" (first move), or
 		// "w1" or "w2" white subturn 1 or 2, and same for black
 		const fullTurn = V.ParseFen(fen).turn;
-		this.startAtFirstMove = (fullTurn == "w");
 		this.turn = fullTurn[0];
-		this.subTurn = (fullTurn[1] || 1);
+		this.subTurn = (fullTurn[1] || 0); //"w0" = special code for first move in game
 	}
 
 	getPotentialPawnMoves([x,y])
@@ -144,18 +141,13 @@ class MarseilleRules extends ChessRules
 		return moves;
 	}
 
-	play(move, ingame)
+	play(move)
 	{
-		if (!!ingame)
-		{
-			move.notation = [this.getNotation(move), this.getLongNotation(move)];
-			// In this special case, we also need the "move color":
-			move.color = this.turn;
-		}
 		move.flags = JSON.stringify(this.aggregateFlags());
+		move.turn = this.turn + this.subturn;
 		V.PlayOnBoard(this.board, move);
 		const epSq = this.getEpSquare(move);
-		if (this.startAtFirstMove && this.moves.length == 0)
+		if (this.subTurn == 0) //first move in game
 		{
 			this.turn = "b";
 			this.epSquares.push([epSq]);
@@ -179,40 +171,22 @@ class MarseilleRules extends ChessRules
 				this.epSquares.push([epSq]);
 			this.subTurn = 3 - this.subTurn;
 		}
-		this.moves.push(move);
 		this.updateVariables(move);
-		if (!!ingame)
-			move.hash = hex_md5(this.getFen());
 	}
 
 	undo(move)
 	{
 		this.disaggregateFlags(JSON.parse(move.flags));
 		V.UndoOnBoard(this.board, move);
-		if (this.startAtFirstMove && this.moves.length == 1)
-		{
-			this.turn = "w";
+		if (move.turn[1] == '0' || move.checkOnSubturn1 || this.subTurn == 2)
 			this.epSquares.pop();
-		}
-		else if (move.checkOnSubturn1)
+		else //this.subTurn == 1
 		{
-			this.turn = V.GetOppCol(this.turn);
-			this.subTurn = 1;
-			this.epSquares.pop();
-		}
-		else
-		{
-			if (this.subTurn == 1)
-			{
-				this.turn = V.GetOppCol(this.turn);
-				let lastEpsq = this.epSquares[this.epSquares.length-1];
-				lastEpsq.pop();
-			}
-			else
-				this.epSquares.pop();
-			this.subTurn = 3 - this.subTurn;
+			let lastEpsq = this.epSquares[this.epSquares.length-1];
+			lastEpsq.pop();
 		}
-		this.moves.pop();
+		this.turn = move.turn[0];
+		this.subTurn = parseInt(move.turn[1]);
 		this.unupdateVariables(move);
 	}
 
@@ -319,6 +293,7 @@ class MarseilleRules extends ChessRules
 		return selected;
 	}
 
+	// TODO: put this generic version elsewhere
 	getPGN(mycolor, score, fenStart, mode)
 	{
 		let pgn = "";
diff --git a/public/stylesheets/variant.sass b/public/stylesheets/variant.sass
index 78570856..299320df 100644
--- a/public/stylesheets/variant.sass
+++ b/public/stylesheets/variant.sass
@@ -67,6 +67,9 @@ label.drawer-toggle
 
 // Game section:
 
+td.highlight-lm
+  background-color: purple
+
 button.play
   height: 24px
   margin: 0
diff --git a/views/variant.pug b/views/variant.pug
index dbdbabae..76d79ae8 100644
--- a/views/variant.pug
+++ b/views/variant.pug
@@ -54,5 +54,6 @@ block javascripts
 	script(src="/javascripts/components/board.js")
 	//script(src="/javascripts/components/problemPreview.js")
 	//script(src="/javascripts/components/problems.js")
+	script(src="/javascripts/components/moveList.js")
 	script(src="/javascripts/components/game.js")
 	script(src="/javascripts/variant.js")