From c605216162dfc09cbbe61b0969f8890f28625372 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 27 Nov 2018 00:52:03 +0100
Subject: [PATCH] Draft of Grand chess. Not bad, but style to adjust (10%), and
 castle + pawn captures to fix

---
 public/javascripts/base_rules.js     |  16 ++-
 public/javascripts/variants/Grand.js | 199 +++++++++++++++++++++++++--
 2 files changed, 197 insertions(+), 18 deletions(-)

diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js
index f466624d..d4315c73 100644
--- a/public/javascripts/base_rules.js
+++ b/public/javascripts/base_rules.js
@@ -395,7 +395,8 @@ class ChessRules
 	getCastleMoves([x,y])
 	{
 		const c = this.getColor(x,y);
-		if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c])
+		const [sizeX,sizeY] = VariantRules.size;
+		if (x != (c=="w" ? sizeX-1 : 0) || y != this.INIT_COL_KING[c])
 			return []; //x isn't first rank, or king has moved (shortcut)
 
 		const V = VariantRules;
@@ -404,7 +405,7 @@ class ChessRules
 		const oppCol = this.getOppCol(c);
 		let moves = [];
 		let i = 0;
-		const finalSquares = [ [2,3], [6,5] ]; //king, then rook
+		const finalSquares = [ [2,3], [sizeY-2,sizeY-3] ]; //king, then rook
 		castlingCheck:
 		for (let castleSide=0; castleSide < 2; castleSide++) //large, then small
 		{
@@ -489,8 +490,8 @@ class ChessRules
 	{
 		const color = this.turn;
 		const oppCol = this.getOppCol(color);
-		var potentialMoves = [];
-		let [sizeX,sizeY] = VariantRules.size;
+		let potentialMoves = [];
+		const [sizeX,sizeY] = VariantRules.size;
 		for (var i=0; i<sizeX; i++)
 		{
 			for (var j=0; j<sizeY; j++)
@@ -510,7 +511,7 @@ class ChessRules
 	{
 		const color = this.turn;
 		const oppCol = this.getOppCol(color);
-		let [sizeX,sizeY] = VariantRules.size;
+		const [sizeX,sizeY] = VariantRules.size;
 		for (let i=0; i<sizeX; i++)
 		{
 			for (let j=0; j<sizeY; j++)
@@ -666,7 +667,8 @@ class ChessRules
 	{
 		const piece = this.getPiece(move.start.x,move.start.y);
 		const c = this.getColor(move.start.x,move.start.y);
-		const firstRank = (c == "w" ? 7 : 0);
+		const [sizeX,sizeY] = VariantRules.size;
+		const firstRank = (c == "w" ? sizeX-1 : 0);
 
 		// Update king position + flags
 		if (piece == VariantRules.KING && move.appear.length > 0)
@@ -677,7 +679,7 @@ class ChessRules
 			return;
 		}
 		const oppCol = this.getOppCol(c);
-		const oppFirstRank = 7 - firstRank;
+		const oppFirstRank = (sizeX-1) - firstRank;
 		if (move.start.x == firstRank //our rook moves?
 			&& this.INIT_COL_ROOK[c].includes(move.start.y))
 		{
diff --git a/public/javascripts/variants/Grand.js b/public/javascripts/variants/Grand.js
index 63108dee..faf7f63f 100644
--- a/public/javascripts/variants/Grand.js
+++ b/public/javascripts/variants/Grand.js
@@ -4,24 +4,39 @@ class GrandRules extends ChessRules
 	static getPpath(b)
 	{
 		const V = VariantRules;
-		return ([V.CAMEL,V.WILDEBEEST].includes(b[1]) ? "Grand/" : "") + b;
+		return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
 	}
 
+	initVariables(fen)
+	{
+		super.initVariables(fen);
+		this.captures = { "w": {}, "b": {} }; //for promotions
+	}
+
+	static get size() { return [10,10]; }
+
 	static get MARSHALL() { return 'm'; } //rook+knight
 	static get CARDINAL() { return 'c'; } //bishop+knight
 
-	// ------> pas les règles exactes, on démarre en 2 avec 1,2 ou 3 cases + enPassant comme Wildebeest
-// TODO: IN epSquares (return array), not return singleton. Easy. Adapt
-// just here for now...
+	// En-passant after 2-sq or 3-sq jumps
 	getEpSquare(move)
 	{
 		const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
-		if (this.getPiece(sx,sy) == VariantRules.PAWN && Math.abs(sx - ex) == 2)
+		if (this.getPiece(sx,sy) == VariantRules.PAWN && Math.abs(sx - ex) >= 2)
 		{
-			return {
-				x: (sx + ex)/2,
+			const step = (ex-sx) / Math.abs(ex-sx);
+			let res = [{
+				x: sx + step,
 				y: sy
-			};
+			}];
+			if (sx + 2*step != ex) //3-squares move
+			{
+				res.push({
+					x: sx + 2*step,
+					y: sy
+				});
+			}
+			return res;
 		}
 		return undefined; //default
 	}
@@ -39,9 +54,84 @@ class GrandRules extends ChessRules
 		}
 	}
 
-	// TODO: quite many changes! Especially promotions, only to friendly pieces already captured. ==> keep track of captured material in initVariables()......
+	// Special pawn rules: promotions to captured friendly pieces,
+	// optional on ranks 8-9 and mandatory on rank 10.
 	getPotentialPawnMoves([x,y])
 	{
+		const color = this.turn;
+		let moves = [];
+		const V = VariantRules;
+		const [sizeX,sizeY] = VariantRules.size;
+		const shift = (color == "w" ? -1 : 1);
+		const startRanks = (color == "w" ? [sizeY-2,sizeY-3] : [1,2]);
+		const lastRanks = (color == "w" ? [0,1,2] : [sizeY-1,sizeY-2,sizeY-3]);
+
+		if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRanks[0])
+		{
+			// Normal moves
+			if (this.board[x+shift][y] == V.EMPTY)
+			{
+				moves.push(this.getBasicMove([x,y], [x+shift,y]));
+				if (startRanks.includes(x) && this.board[x+2*shift][y] == V.EMPTY)
+				{
+					// Two squares jump
+					moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
+					if (x == startRanks[0] && this.board[x+2*shift][y] == V.EMPTY)
+					{
+						// 3-squares jump
+						moves.push(this.getBasicMove([x,y], [x+3*shift,y]));
+					}
+				}
+			}
+			// Captures
+			if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
+				moves.push(this.getBasicMove([x,y], [x+shift,y-1]));
+			if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
+				moves.push(this.getBasicMove([x,y], [x+shift,y+1]));
+		}
+
+		if (lastRanks.includes(x+shift))
+		{
+			// Promotion
+			let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+			promotionPieces.forEach(p => {
+				if (!this.captures[color][p] || this.captures[color][p]==0)
+					return;
+				// Normal move
+				if (this.board[x+shift][y] == V.EMPTY)
+					moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
+				// Captures
+				if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
+					moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:p}));
+				if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
+					moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:p}));
+			});
+		}
+
+		// En passant
+		const Lep = this.epSquares.length;
+		const epSquare = Lep>0 ? this.epSquares[Lep-1] : undefined;
+		if (!!epSquare)
+		{
+			for (let epsq of epSquare)
+			{
+				// TODO: some redundant checks
+				if (epsq.x == x+shift && Math.abs(epsq.y - y) == 1)
+				{
+					let epStep = epsq.y - y;
+					var enpassantMove = this.getBasicMove([x,y], [x+shift,y+epStep]);
+					enpassantMove.vanish.push({
+						x: x,
+						y: y+epStep,
+						p: 'p',
+						c: this.getColor(x,y+epStep)
+					});
+					moves.push(enpassantMove);
+				}
+			}
+		}
+
+		return moves;
 	}
 
 	getPotentialMarshallMoves(sq)
@@ -60,7 +150,7 @@ class GrandRules extends ChessRules
 
 	isAttacked(sq, colors)
 	{
-		return (super.isAttacked(sq, colors)
+		return super.isAttacked(sq, colors)
 			|| this.isAttackedByMarshall(sq, colors)
 			|| this.isAttackedByCardinal(sq, colors);
 	}
@@ -79,6 +169,32 @@ class GrandRules extends ChessRules
 			|| this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
 	}
 
+	play(move, ingame)
+	{
+		super.play(move, ingame);
+		if (move.vanish.length==2 && move.appear.length==1
+			&& move.vanish[1].p != VariantRules.PAWN)
+		{
+			// Capture: update this.captures
+			if (!this.captures[move.vanish[1].c][move.vanish[1].p])
+				this.captures[move.vanish[1].c][move.vanish[1].p] = 1;
+			else
+				this.captures[move.vanish[1].c][move.vanish[1].p]++;
+		}
+	}
+
+	undo(move)
+	{
+		super.undo(move);
+		if (move.vanish.length==2 && move.appear.length==1
+			&& move.vanish[1].p != VariantRules.PAWN)
+		{
+			// Capture: update this.captures
+			this.captures[move.vanish[1].c][move.vanish[1].p] =
+				Math.max(0, this.captures[move.vanish[1].c][move.vanish[1].p]-1);
+		}
+	}
+
 	static get VALUES() {
 		return Object.assign(
 			ChessRules.VALUES,
@@ -86,8 +202,69 @@ class GrandRules extends ChessRules
 		);
 	}
 
-	// TODO:
+	// TODO: this function could be generalized and shared better
 	static GenRandInitFen()
 	{
+		let pieces = [new Array(10), new Array(10)];
+		// Shuffle pieces on first and last rank
+		for (let c = 0; c <= 1; c++)
+		{
+			let positions = _.range(10);
+
+			// Get random squares for bishops
+			let randIndex = 2 * _.random(4);
+			let bishop1Pos = positions[randIndex];
+			// The second bishop must be on a square of different color
+			let randIndex_tmp = 2 * _.random(4) + 1;
+			let bishop2Pos = positions[randIndex_tmp];
+			// Remove chosen squares
+			positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+			positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+			// Get random squares for knights
+			randIndex = _.random(7);
+			let knight1Pos = positions[randIndex];
+			positions.splice(randIndex, 1);
+			randIndex = _.random(6);
+			let knight2Pos = positions[randIndex];
+			positions.splice(randIndex, 1);
+
+			// Get random square for queen
+			randIndex = _.random(5);
+			let queenPos = positions[randIndex];
+			positions.splice(randIndex, 1);
+
+			// ...random square for marshall
+			randIndex = _.random(4);
+			let marshallPos = positions[randIndex];
+			positions.splice(randIndex, 1);
+
+			// ...random square for cardinal
+			randIndex = _.random(3);
+			let cardinalPos = positions[randIndex];
+			positions.splice(randIndex, 1);
+
+			// Rooks and king positions are now fixed, because of the ordering rook-king-rook
+			let rook1Pos = positions[0];
+			let kingPos = positions[1];
+			let rook2Pos = positions[2];
+
+			// Finally put the shuffled pieces in the board array
+			pieces[c][rook1Pos] = 'r';
+			pieces[c][knight1Pos] = 'n';
+			pieces[c][bishop1Pos] = 'b';
+			pieces[c][queenPos] = 'q';
+			pieces[c][marshallPos] = 'm';
+			pieces[c][cardinalPos] = 'c';
+			pieces[c][kingPos] = 'k';
+			pieces[c][bishop2Pos] = 'b';
+			pieces[c][knight2Pos] = 'n';
+			pieces[c][rook2Pos] = 'r';
+		}
+		let fen = pieces[0].join("") +
+			"/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
+			pieces[1].join("").toUpperCase() +
+			" 1111";
+		return fen;
 	}
 }
-- 
2.44.0