From: Benjamin Auder <benjamin.auder@somewhere>
Date: Thu, 10 Jan 2019 22:39:44 +0000 (+0100)
Subject: Reorganize and completed board component. Now need to finish game component
X-Git-Url: https://git.auder.net/doc/html/assets/pieces/app_dev.php/DESCRIPTION?a=commitdiff_plain;h=e5dc87e0e8f2d53a910b2b42ed2a0a39ea6787aa;p=vchess.git

Reorganize and completed board component. Now need to finish game component
---

diff --git a/public/javascripts/components/board.js b/public/javascripts/components/board.js
index 2861cf7f..6110cfb8 100644
--- a/public/javascripts/components/board.js
+++ b/public/javascripts/components/board.js
@@ -1,3 +1,12 @@
+Vue.component('my-board', {
+	// Last move cannot be guessed from here, and is required to highlight squares
+	// gotoMove : juste set FEN depuis FEN stocké dans le coup (TODO)
+	// send event after each move (or undo), to notify what was played
+	// also notify end of game (which returns here later through prop...)
+	props: ["fen","moveToPlay","moveToUndo",
+		"analyze","lastMove","orientation","userColor","gameOver"],
+	data: function () {
+		return {
 			hints: (!localStorage["hints"] ? true : localStorage["hints"] === "1"),
 			bcolor: localStorage["bcolor"] || "lichess", //lichess, chesscom or chesstempo
 			possibleMoves: [], //filled after each valid click/dragstart
@@ -6,236 +15,241 @@
 			incheck: [],
 			start: {}, //pixels coordinates + id of starting square (click or drag)
 			vr: null, //object to check moves, store them, FEN..
-	orientation: "w", //useful if click on "flip board"	
-	
-
-// TODO: watch for property change "fen"
-// send event after each move, to notify what was played
-	
-	const [sizeX,sizeY] = [V.size.x,V.size.y];
+		};
+	},
+	watch: {
+		// NOTE: maybe next 3 should be encapsulated in object to be watched (?)
+		fen: function(newFen) {
+			this.vr = new VariantRules(newFen);
+		},
+		moveToPlay: function(move) {
+			this.play(move, "animate");
+		},
+		moveToUndo: function(move) {
+			this.undo(move);
+		},
+	},
+	created: function() {
+		this.vr = new VariantRules(this.fen);
+	},
+	render(h) {
+		const [sizeX,sizeY] = [V.size.x,V.size.y];
 		// Precompute hints squares to facilitate rendering
 		let hintSquares = doubleArray(sizeX, sizeY, false);
 		this.possibleMoves.forEach(m => { hintSquares[m.end.x][m.end.y] = true; });
 		// Also precompute in-check squares
 		let incheckSq = doubleArray(sizeX, sizeY, false);
 		this.incheck.forEach(sq => { incheckSq[sq[0]][sq[1]] = true; });
-			const choices = h('div',
-				{
-					attrs: { "id": "choices" },
-					'class': { 'row': true },
-					style: {
-						"display": this.choices.length>0?"block":"none",
-						"top": "-" + ((sizeY/2)*squareWidth+squareWidth/2) + "px",
-						"width": (this.choices.length * squareWidth) + "px",
-						"height": squareWidth + "px",
-					},
+		const choices = h(
+			'div',
+			{
+				attrs: { "id": "choices" },
+				'class': { 'row': true },
+				style: {
+					"display": this.choices.length>0?"block":"none",
+					"top": "-" + ((sizeY/2)*squareWidth+squareWidth/2) + "px",
+					"width": (this.choices.length * squareWidth) + "px",
+					"height": squareWidth + "px",
 				},
-				this.choices.map( m => { //a "choice" is a move
-					return h('div',
-						{
-							'class': {
-								'board': true,
-								['board'+sizeY]: true,
-							},
-							style: {
-								'width': (100/this.choices.length) + "%",
-								'padding-bottom': (100/this.choices.length) + "%",
-							},
+			},
+			this.choices.map(m => { //a "choice" is a move
+				return h('div',
+					{
+						'class': {
+							'board': true,
+							['board'+sizeY]: true,
+						},
+						style: {
+							'width': (100/this.choices.length) + "%",
+							'padding-bottom': (100/this.choices.length) + "%",
 						},
-						[h('img',
-							{
-								attrs: { "src": '/images/pieces/' +
-									VariantRules.getPpath(m.appear[0].c+m.appear[0].p) + '.svg' },
-								'class': { 'choice-piece': true },
-								on: {
-									"click": e => { this.play(m); this.choices=[]; },
-									// NOTE: add 'touchstart' event to fix a problem on smartphones
-									"touchstart": e => { this.play(m); this.choices=[]; },
-								},
-							})
-						]
-					);
-				})
-			);
-			// Create board element (+ reserves if needed by variant or mode)
-			const lm = this.vr.lastMove;
-			const showLight = this.hints && variant.name!="Dark" &&
-				(this.mode != "idle" ||
-					(this.vr.moves.length > 0 && this.cursor==this.vr.moves.length));
-			const gameDiv = h('div',
-				{
-					'class': {
-						'game': true,
-						'clearer': true,
 					},
-				},
-				[_.range(sizeX).map(i => {
-					let ci = (this.mycolor=='w' ? i : sizeX-i-1);
-					return h(
-						'div',
+					[h('img',
 						{
-							'class': {
-								'row': true,
+							attrs: { "src": '/images/pieces/' +
+								V.getPpath(m.appear[0].c+m.appear[0].p) + '.svg' },
+							'class': { 'choice-piece': true },
+							on: {
+								"click": e => { this.play(m); this.choices=[]; },
+								// NOTE: add 'touchstart' event to fix a problem on smartphones
+								"touchstart": e => { this.play(m); this.choices=[]; },
 							},
-							style: { 'opacity': this.choices.length>0?"0.5":"1" },
-						},
-						_.range(sizeY).map(j => {
-							let cj = (this.mycolor=='w' ? j : sizeY-j-1);
-							let elems = [];
-							if (this.vr.board[ci][cj] != VariantRules.EMPTY && (variant.name!="Dark"
-								|| this.score!="*" || this.vr.enlightened[this.mycolor][ci][cj]))
-							{
-								elems.push(
-									h(
-										'img',
-										{
-											'class': {
-												'piece': true,
-												'ghost': !!this.selectedPiece
-													&& this.selectedPiece.parentNode.id == "sq-"+ci+"-"+cj,
-											},
-											attrs: {
-												src: "/images/pieces/" +
-													VariantRules.getPpath(this.vr.board[ci][cj]) + ".svg",
-											},
-										}
-									)
-								);
-							}
-							if (this.hints && hintSquares[ci][cj])
-							{
-								elems.push(
-									h(
-										'img',
-										{
-											'class': {
-												'mark-square': true,
-											},
-											attrs: {
-												src: "/images/mark.svg",
-											},
-										}
-									)
-								);
-							}
-							return h(
-								'div',
-								{
-									'class': {
-										'board': true,
-										['board'+sizeY]: true,
-										'light-square': (i+j)%2==0,
-										'dark-square': (i+j)%2==1,
-										[this.bcolor]: true,
-										'in-shadow': variant.name=="Dark" && this.score=="*"
-											&& !this.vr.enlightened[this.mycolor][ci][cj],
-										'highlight': showLight && !!lm && _.isMatch(lm.end, {x:ci,y:cj}),
-										'incheck': showLight && incheckSq[ci][cj],
-									},
-									attrs: {
-										id: this.getSquareId({x:ci,y:cj}),
-									},
-								},
-								elems
-							);
 						})
-					);
-				}), choices]
-			);
-			if (!!this.vr.reserve)
+					]
+				);
+			})
+		);
+		// Create board element (+ reserves if needed by variant or mode)
+		const lm = this.lastMove;
+		const showLight = this.hints && variant.name != "Dark";
+		const gameDiv = h(
+			'div',
 			{
-				const shiftIdx = (this.mycolor=="w" ? 0 : 1);
-				let myReservePiecesArray = [];
-				for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++)
-				{
-					myReservePiecesArray.push(h('div',
+				'class': {
+					'game': true,
+					'clearer': true,
+				},
+			},
+			[_.range(sizeX).map(i => {
+				let ci = (this.orientation=='w' ? i : sizeX-i-1);
+				return h(
+					'div',
 					{
-						'class': {'board':true, ['board'+sizeY]:true},
-						attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) }
+						'class': {
+							'row': true,
+						},
+						style: { 'opacity': this.choices.length>0?"0.5":"1" },
 					},
-					[
-						h('img',
+					_.range(sizeY).map(j => {
+						let cj = (this.orientation=='w' ? j : sizeY-j-1);
+						let elems = [];
+						if (this.vr.board[ci][cj] != V.EMPTY && (variant.name!="Dark"
+							|| this.gameOver || this.vr.enlightened[this.userColor][ci][cj]))
 						{
-							'class': {"piece":true, "reserve":true},
-							attrs: {
-								"src": "/images/pieces/" +
-									this.vr.getReservePpath(this.mycolor,i) + ".svg",
-							}
-						}),
-						h('sup',
-							{"class": { "reserve-count": true } },
-							[ this.vr.reserve[this.mycolor][VariantRules.RESERVE_PIECES[i]] ]
-						)
-					]));
-				}
-				let oppReservePiecesArray = [];
-				const oppCol = this.vr.getOppCol(this.mycolor);
-				for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++)
-				{
-					oppReservePiecesArray.push(h('div',
-					{
-						'class': {'board':true, ['board'+sizeY]:true},
-						attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) }
-					},
-					[
-						h('img',
+							elems.push(
+								h(
+									'img',
+									{
+										'class': {
+											'piece': true,
+											'ghost': !!this.selectedPiece
+												&& this.selectedPiece.parentNode.id == "sq-"+ci+"-"+cj,
+										},
+										attrs: {
+											src: "/images/pieces/" +
+												V.getPpath(this.vr.board[ci][cj]) + ".svg",
+										},
+									}
+								)
+							);
+						}
+						if (this.hints && hintSquares[ci][cj])
 						{
-							'class': {"piece":true, "reserve":true},
-							attrs: {
-								"src": "/images/pieces/" +
-									this.vr.getReservePpath(oppCol,i) + ".svg",
-							}
-						}),
-						h('sup',
-							{"class": { "reserve-count": true } },
-							[ this.vr.reserve[oppCol][VariantRules.RESERVE_PIECES[i]] ]
-						)
-					]));
-				}
-				let reserves = h('div',
-					{
-						'class':{
-							'game': true,
-							"reserve-div": true,
-						},
-					},
-					[
-						h('div',
+							elems.push(
+								h(
+									'img',
+									{
+										'class': {
+											'mark-square': true,
+										},
+										attrs: {
+											src: "/images/mark.svg",
+										},
+									}
+								)
+							);
+						}
+						return h(
+							'div',
 							{
 								'class': {
-									'row': true,
-									"reserve-row-1": true,
+									'board': true,
+									['board'+sizeY]: true,
+									'light-square': (i+j)%2==0,
+									'dark-square': (i+j)%2==1,
+									[this.bcolor]: true,
+									'in-shadow': variant.name=="Dark" && !this.gameOver
+										&& !this.vr.enlightened[this.userColor][ci][cj],
+									'highlight': showLight && !!lm && _.isMatch(lm.end, {x:ci,y:cj}),
+									'incheck': showLight && incheckSq[ci][cj],
+								},
+								attrs: {
+									id: this.getSquareId({x:ci,y:cj}),
 								},
 							},
-							myReservePiecesArray
-						),
-						h('div',
-							{ 'class': { 'row': true }},
-							oppReservePiecesArray
-						)
-					]
+							elems
+						);
+					})
 				);
-				elementArray.push(reserves);
+			}), choices]
+		);
+		let elementArray = [choices, gameDiv];
+		if (!!this.vr.reserve)
+		{
+			const shiftIdx = (this.userColor=="w" ? 0 : 1);
+			let myReservePiecesArray = [];
+			for (let i=0; i<V.RESERVE_PIECES.length; i++)
+			{
+				myReservePiecesArray.push(h('div',
+				{
+					'class': {'board':true, ['board'+sizeY]:true},
+					attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) }
+				},
+				[
+					h('img',
+					{
+						'class': {"piece":true, "reserve":true},
+						attrs: {
+							"src": "/images/pieces/" +
+								this.vr.getReservePpath(this.userColor,i) + ".svg",
+						}
+					}),
+					h('sup',
+						{"class": { "reserve-count": true } },
+						[ this.vr.reserve[this.userColor][V.RESERVE_PIECES[i]] ]
+					)
+				]));
+			}
+			let oppReservePiecesArray = [];
+			const oppCol = this.vr.getOppCol(this.userColor);
+			for (let i=0; i<V.RESERVE_PIECES.length; i++)
+			{
+				oppReservePiecesArray.push(h('div',
+				{
+					'class': {'board':true, ['board'+sizeY]:true},
+					attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) }
+				},
+				[
+					h('img',
+					{
+						'class': {"piece":true, "reserve":true},
+						attrs: {
+							"src": "/images/pieces/" +
+								this.vr.getReservePpath(oppCol,i) + ".svg",
+						}
+					}),
+					h('sup',
+						{"class": { "reserve-count": true } },
+						[ this.vr.reserve[oppCol][V.RESERVE_PIECES[i]] ]
+					)
+				]));
 			}
-				// Show current FEN (just below board, lower right corner)
-// (if mode != Dark ...)
-				elementArray.push(
+			let reserves = h('div',
+				{
+					'class':{
+						'game': true,
+						"reserve-div": true,
+					},
+				},
+				[
 					h('div',
 						{
-							attrs: { id: "fen-div" },
-							"class": { "section-content": true },
+							'class': {
+								'row': true,
+								"reserve-row-1": true,
+							},
 						},
-						[
-							h('p',
-								{
-									attrs: { id: "fen-string" },
-									domProps: { innerHTML: this.vr.getBaseFen() },
-									"class": { "text-center": true },
-								}
-							)
-						]
+						myReservePiecesArray
+					),
+					h('div',
+						{ 'class': { 'row': true }},
+						oppReservePiecesArray
 					)
-				);
+				]
+			);
+			elementArray.push(reserves);
+		}
+		return h(
+			'div',
+			{
+				'class': {
+					"col-sm-12":true,
+					"col-md-10":true,
+					"col-md-offset-1":true,
+					"col-lg-8":true,
+					"col-lg-offset-2":true,
+				},
+				// NOTE: click = mousedown + mouseup
 				on: {
 					mousedown: this.mousedown,
 					mousemove: this.mousemove,
@@ -244,10 +258,12 @@
 					touchmove: this.mousemove,
 					touchend: this.mouseup,
 				},
-
-
-		// TODO: "chessground-like" component
-		// Get the identifier of a HTML table cell from its numeric coordinates o.x,o.y.
+			},
+			elementArray
+		);
+	},
+	methods: {
+		// Get the identifier of a HTML square from its numeric coordinates o.x,o.y.
 		getSquareId: function(o) {
 			// NOTE: a separator is required to allow any size of board
 			return  "sq-" + o.x + "-" + o.y;
@@ -289,18 +305,11 @@
 				this.selectedPiece.style.zIndex = 3000;
 				const startSquare = this.getSquareFromId(e.target.parentNode.id);
 				this.possibleMoves = [];
-				if (this.score == "*")
-				{
-
-// TODO: essentially adapt this (all other things do not change much)
-// if inside a real game, mycolor should be provided ? (simplest way)
-
-					const color = ["friend","problem"].includes(this.mode)
-						? this.vr.turn
-						: this.mycolor;
-					if (this.vr.canIplay(color,startSquare))
-						this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
-				}
+				const color = this.analyze || this.gameOver
+					? this.vr.turn
+					: this.userColor;
+				if (this.vr.canIplay(color,startSquare))
+					this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
 				// Next line add moving piece just after current image
 				// (required for Crazyhouse reserve)
 				e.target.parentNode.insertBefore(this.selectedPiece, e.target.nextSibling);
@@ -389,3 +398,26 @@
 				this.play(move);
 			}, 250);
 		},
+		play: function(move, programmatic) {
+			if (!!programmatic) //computer or human opponent
+				return this.animateMove(move);
+			// Not programmatic, or animation is over
+			this.vr.play(move);
+			if (this.sound == 2)
+				new Audio("/sounds/move.mp3").play().catch(err => {});
+			// Is opponent in check?
+			this.incheck = this.vr.getCheckSquares(this.vr.turn);
+			const eog = this.vr.getCurrentScore();
+			if (eog != "*")
+			{
+				// TODO: notify end of game (give score)
+			}
+		},
+		undo: function(move) {
+			this.vr.undo(move);
+			if (this.sound == 2)
+				new Audio("/sounds/undo.mp3").play().catch(err => {});
+			this.incheck = this.vr.getCheckSquares(this.vr.turn);
+		},
+	},
+})
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 84088c05..2b527d93 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -54,6 +54,25 @@ Vue.component('my-game', {
 			// TODO: controls: abort, clear, resign, draw (avec confirm box)
 			// et si partie terminée : (mode analyse) just clear, back / play
 			// + flip button toujours disponible
+				// Show current FEN (just below board, lower right corner)
+// (if mode != Dark ...)
+				elementArray.push(
+					h('div',
+						{
+							attrs: { id: "fen-div" },
+							"class": { "section-content": true },
+						},
+						[
+							h('p',
+								{
+									attrs: { id: "fen-string" },
+									domProps: { innerHTML: this.vr.getBaseFen() },
+									"class": { "text-center": true },
+								}
+							)
+						]
+					)
+				);
 			
 			<div id="pgn-div" class="section-content">
 				<a id="download" href: "#"></a>
@@ -264,83 +283,6 @@ Vue.component('my-game', {
 			this.compWorker.postMessage(["askmove"]);
 		},
 		// OK, these last functions can stay here (?!)
-		play: function(move, programmatic) {
-			if (!move)
-			{
-				// Navigate after game is over
-				if (this.cursor >= this.moves.length)
-					return; //already at the end
-				move = this.moves[this.cursor++];
-			}
-			if (!!programmatic) //computer or human opponent
-				return this.animateMove(move);
-			// Not programmatic, or animation is over
-			if (this.mode == "human" && this.vr.turn == this.mycolor)
-				this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid}));
-			
-			
-			// TODO: play move, and stack it on this.moves (if a move was provided; otherwise just navigate)
-			
-			if (this.score == "*") //TODO: I don't like this if()
-			{
-				// Emergency check, if human game started "at the same time"
-				// TODO: robustify this...
-				if (this.mode == "human" && !!move.computer)
-					return;
-				this.vr.play(move, "ingame");
-				// Is opponent in check?
-				this.incheck = this.vr.getCheckSquares(this.vr.turn);
-				if (this.sound == 2)
-					new Audio("/sounds/move.mp3").play().catch(err => {});
-				if (this.mode == "computer")
-				{
-					// Send the move to web worker (TODO: including his own moves?!)
-					this.compWorker.postMessage(["newmove",move]);
-				}
-				const eog = this.vr.getCurrentScore();
-				if (eog != "*")
-				{
-					if (["human","computer"].includes(this.mode))
-						this.endGame(eog);
-					else
-					{
-						// Just show score on screen (allow undo)
-						this.score = eog;
-						this.showScoreMsg();
-					}
-				}
-			}
-//			else
-//			{
-//				VariantRules.PlayOnBoard(this.vr.board, move);
-//				this.$forceUpdate(); //TODO: ?!
-//			}
-			if (["human","computer","friend"].includes(this.mode))
-				this.updateStorage(); //after our moves and opponent moves
-			if (this.mode == "computer" && this.vr.turn != this.mycolor && this.score == "*")
-				this.playComputerMove();
-		},
-		// TODO: merge two next functions
-		undo: function() {
-			// Navigate after game is over
-			if (this.cursor == 0)
-				return; //already at the beginning
-			if (this.cursor == this.vr.moves.length)
-				this.incheck = []; //in case of...
-			const move = this.vr.moves[--this.cursor];
-			VariantRules.UndoOnBoard(this.vr.board, move);
-			this.$forceUpdate(); //TODO: ?!
-		},
-		undoInGame: function() {
-			const lm = this.vr.lastMove;
-			if (!!lm)
-			{
-				this.vr.undo(lm);
-				if (this.sound == 2)
-					new Audio("/sounds/undo.mp3").play().catch(err => {});
-				this.incheck = this.vr.getCheckSquares(this.vr.turn);
-			}
-		},
 	},
 })
 
@@ -356,3 +298,26 @@ Vue.component('my-game', {
 //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
+			
+// send move from here:
+//if (this.mode == "human" && this.vr.turn == this.mycolor)
+			//this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid}));
+			// TODO: play move, and stack it on this.moves (if a move was provided; otherwise just navigate)
+			
+//			if (["human","computer","friend"].includes(this.mode))
+//				this.updateStorage(); //after our moves and opponent moves
+//			if (this.mode == "computer" && this.vr.turn != this.mycolor && this.score == "*")
+//				this.playComputerMove();
+//			if (this.mode == "computer")
+//			{
+//				// Send the move to web worker (TODO: including his own moves?!)
+//				this.compWorker.postMessage(["newmove",move]);
+//			}
+//				if (["human","computer"].includes(this.mode))
+//					this.endGame(eog);
+//				else
+//				{
+//					// Just show score on screen (allow undo)
+//					this.score = eog;
+//					this.showScoreMsg();
+//				}
diff --git a/public/javascripts/components/problems.js b/public/javascripts/components/problems.js
index 897babbe..9a14c709 100644
--- a/public/javascripts/components/problems.js
+++ b/public/javascripts/components/problems.js
@@ -38,7 +38,8 @@ Vue.component('my-problems', {
 						{{ curProb.instructions }}
 					</p>
 				</div>
-				<my-board :fen="curProb.fen"></my-board>
+				<my-board :fen="curProb.fen" :analyze:"true" .................> //TODO: use my-game in analyze mode ?
+				</my-board>
 				<div id="solution-div" class="section-content">
 					<h3 class="clickable" @click="showSolution = !showSolution">
 						{{ translations["Show solution"] }}