From a37076f1ac8f4c19d9b34a60cbe89df86b88fa0b Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 26 Nov 2018 16:20:05 +0100
Subject: [PATCH] Early draft of Wildebeest + Grand

---
 public/javascripts/base_rules.js          |  18 +-
 public/javascripts/variants/Antiking.js   |  18 +-
 public/javascripts/variants/Atomic.js     |   5 +-
 public/javascripts/variants/Grand.js      |  93 ++++++++++
 public/javascripts/variants/Wildebeest.js | 211 ++++++++++++++++++++++
 public/javascripts/variants/Zen.js        |  10 +-
 variants.js                               |   4 +-
 7 files changed, 338 insertions(+), 21 deletions(-)
 create mode 100644 public/javascripts/variants/Grand.js
 create mode 100644 public/javascripts/variants/Wildebeest.js

diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js
index 9cfab5ae..f466624d 100644
--- a/public/javascripts/base_rules.js
+++ b/public/javascripts/base_rules.js
@@ -171,7 +171,6 @@ class ChessRules
 			'r': [ [-1,0],[1,0],[0,-1],[0,1] ],
 			'n': [ [-1,-2],[-1,2],[1,-2],[1,2],[-2,-1],[-2,1],[2,-1],[2,1] ],
 			'b': [ [-1,-1],[-1,1],[1,-1],[1,1] ],
-			'q': [ [-1,0],[1,0],[0,-1],[0,1],[-1,-1],[-1,1],[1,-1],[1,1] ]
 		};
 	}
 
@@ -379,14 +378,17 @@ class ChessRules
 	// What are the queen moves from square x,y ?
 	getPotentialQueenMoves(sq)
 	{
-		return this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.QUEEN]);
+		const V = VariantRules;
+		return this.getSlideNJumpMoves(sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
 	}
 
 	// What are the king moves from square x,y ?
 	getPotentialKingMoves(sq)
 	{
+		const V = VariantRules;
 		// Initialize with normal moves
-		let moves = this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.QUEEN], "oneStep");
+		let moves = this.getSlideNJumpMoves(sq,
+			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
 		return moves.concat(this.getCastleMoves(sq));
 	}
 
@@ -586,15 +588,17 @@ class ChessRules
 	// Is square x,y attacked by queens of color c ?
 	isAttackedByQueen(sq, colors)
 	{
-		return this.isAttackedBySlideNJump(sq, colors,
-			VariantRules.QUEEN, VariantRules.steps[VariantRules.QUEEN]);
+		const V = VariantRules;
+		return this.isAttackedBySlideNJump(sq, colors, V.QUEEN,
+			V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
 	}
 
 	// Is square x,y attacked by king of color c ?
 	isAttackedByKing(sq, colors)
 	{
-		return this.isAttackedBySlideNJump(sq, colors,
-			VariantRules.KING, VariantRules.steps[VariantRules.QUEEN], "oneStep");
+		const V = VariantRules;
+		return this.isAttackedBySlideNJump(sq, colors, V.KING,
+			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
 	}
 
 	// Generic method for non-pawn pieces ("sliding or jumping"): is x,y attacked by piece != color ?
diff --git a/public/javascripts/variants/Antiking.js b/public/javascripts/variants/Antiking.js
index 53e55188..f8c7465a 100644
--- a/public/javascripts/variants/Antiking.js
+++ b/public/javascripts/variants/Antiking.js
@@ -59,7 +59,9 @@ class AntikingRules extends ChessRules
 
 	getPotentialAntikingMoves(sq)
 	{
-		return this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.QUEEN], "oneStep");
+		const V = VariantRules;
+		return this.getSlideNJumpMoves(sq,
+			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
 	}
 
 	isAttacked(sq, colors)
@@ -69,18 +71,20 @@ class AntikingRules extends ChessRules
 
 	isAttackedByKing([x,y], colors)
 	{
-		if (this.getPiece(x,y) == VariantRules.ANTIKING)
+		const V = VariantRules;
+		if (this.getPiece(x,y) == V.ANTIKING)
 			return false; //antiking is not attacked by king
-		return this.isAttackedBySlideNJump([x,y], colors,
-			VariantRules.KING, VariantRules.steps[VariantRules.QUEEN], "oneStep");
+		return this.isAttackedBySlideNJump([x,y], colors, V.KING,
+			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
 	}
 
 	isAttackedByAntiking([x,y], colors)
 	{
-		if (this.getPiece(x,y) == VariantRules.KING)
+		const V = VariantRules;
+		if (this.getPiece(x,y) == V.KING)
 			return false; //king is not attacked by antiking
-		return this.isAttackedBySlideNJump([x,y], colors,
-			VariantRules.ANTIKING, VariantRules.steps[VariantRules.QUEEN], "oneStep");
+		return this.isAttackedBySlideNJump([x,y], colors, V.ANTIKING,
+			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
 	}
 
 	underCheck(move)
diff --git a/public/javascripts/variants/Atomic.js b/public/javascripts/variants/Atomic.js
index 2b7b1ac3..b86e7822 100644
--- a/public/javascripts/variants/Atomic.js
+++ b/public/javascripts/variants/Atomic.js
@@ -30,10 +30,11 @@ class AtomicRules extends ChessRules
 
 	getPotentialKingMoves([x,y])
 	{
+		const V = VariantRules;
 		// King cannot capture:
 		let moves = [];
-		let [sizeX,sizeY] = VariantRules.size;
-		const steps = VariantRules.steps[VariantRules.QUEEN];
+		let [sizeX,sizeY] = V.size;
+		const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
 		for (let step of steps)
 		{
 			var i = x + step[0];
diff --git a/public/javascripts/variants/Grand.js b/public/javascripts/variants/Grand.js
new file mode 100644
index 00000000..63108dee
--- /dev/null
+++ b/public/javascripts/variants/Grand.js
@@ -0,0 +1,93 @@
+//https://www.chessvariants.com/large.dir/freeling.html
+class GrandRules extends ChessRules
+{
+	static getPpath(b)
+	{
+		const V = VariantRules;
+		return ([V.CAMEL,V.WILDEBEEST].includes(b[1]) ? "Grand/" : "") + b;
+	}
+
+	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...
+	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)
+		{
+			return {
+				x: (sx + ex)/2,
+				y: sy
+			};
+		}
+		return undefined; //default
+	}
+
+	getPotentialMovesFrom([x,y])
+	{
+		switch (this.getPiece(x,y))
+		{
+			case VariantRules.MARSHALL:
+				return this.getPotentialMarshallMoves([x,y]);
+			case VariantRules.CARDINAL:
+				return this.getPotentialCardinalMoves([x,y]);
+			default:
+				return super.getPotentialMovesFrom([x,y])
+		}
+	}
+
+	// TODO: quite many changes! Especially promotions, only to friendly pieces already captured. ==> keep track of captured material in initVariables()......
+	getPotentialPawnMoves([x,y])
+	{
+	}
+
+	getPotentialMarshallMoves(sq)
+	{
+		const V = VariantRules;
+		return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
+			this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
+	}
+
+	getPotentialCardinalMoves(sq)
+	{
+		const V = VariantRules;
+		return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
+			this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
+	}
+
+	isAttacked(sq, colors)
+	{
+		return (super.isAttacked(sq, colors)
+			|| this.isAttackedByMarshall(sq, colors)
+			|| this.isAttackedByCardinal(sq, colors);
+	}
+
+	isAttackedByMarshall(sq, colors)
+	{
+		const V = VariantRules;
+		return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
+			|| this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
+	}
+
+	isAttackedByCardinal(sq, colors)
+	{
+		const V = VariantRules;
+		return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
+			|| this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
+	}
+
+	static get VALUES() {
+		return Object.assign(
+			ChessRules.VALUES,
+			{'c': 5, 'm': 7} //experimental
+		);
+	}
+
+	// TODO:
+	static GenRandInitFen()
+	{
+	}
+}
diff --git a/public/javascripts/variants/Wildebeest.js b/public/javascripts/variants/Wildebeest.js
new file mode 100644
index 00000000..5f706cf1
--- /dev/null
+++ b/public/javascripts/variants/Wildebeest.js
@@ -0,0 +1,211 @@
+//https://www.chessvariants.com/large.dir/wildebeest.html
+class GrandRules extends ChessRules
+{
+	static getPpath(b)
+	{
+		const V = VariantRules;
+		return ([V.CAMEL,V.WILDEBEEST].includes(b[1]) ? "Wildebeest/" : "") + b;
+	}
+
+	static get CAMEL() { return 'c'; }
+	static get WILDEBEEST() { return 'w'; }
+
+	static get steps() {
+		return Object.assign(
+			ChessRules.steps, //add camel moves:
+			{'c': [ [-3,-1],[-3,1],[-1,-3],[-1,3],[1,-3],[1,3],[3,-1],[3,1] ]}
+		);
+	}
+
+// TODO: IN epSquares (return array), not return singleton. Easy. Adapt
+// just here for now...
+	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)
+		{
+			return {
+				x: (sx + ex)/2,
+				y: sy
+			};
+		}
+		return undefined; //default
+	}
+
+	getPotentialMovesFrom([x,y])
+	{
+		switch (this.getPiece(x,y))
+		{
+			case VariantRules.CAMEL:
+				return this.getPotentialCamelMoves([x,y]);
+			case VariantRules.WILDEBEEST:
+				return this.getPotentialWildebeestMoves([x,y]);
+			default:
+				return super.getPotentialMovesFrom([x,y])
+		}
+	}
+
+	// TODO: several changes (promote to queen or wildebeest)
+	getPotentialPawnMoves([x,y])
+	{
+		const color = this.turn;
+		let moves = [];
+		const V = VariantRules;
+		const [sizeX,sizeY] = VariantRules.size;
+		const shift = (color == "w" ? -1 : 1);
+		const firstRank = (color == 'w' ? sizeY-1 : 0);
+		const startRank = (color == "w" ? sizeY-2 : 1);
+		const lastRank = (color == "w" ? 0 : sizeY-1);
+
+		if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank)
+		{
+			// Normal moves
+			if (this.board[x+shift][y] == V.EMPTY)
+			{
+				moves.push(this.getBasicMove([x,y], [x+shift,y]));
+				// Next condition because variants with pawns on 1st rank generally allow them to jump
+				if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
+				{
+					// Two squares jump
+					moves.push(this.getBasicMove([x,y], [x+2*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 (x+shift == lastRank)
+		{
+			// Promotion
+			let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+			promotionPieces.forEach(p => {
+				// 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 && epSquare.x == x+shift && Math.abs(epSquare.y - y) == 1)
+		{
+			let epStep = epSquare.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;
+	}
+
+	getPotentialCamelMoves(sq)
+	{
+		return this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.CAMEL], "oneStep");
+	}
+
+	getPotentialWildebeestMoves(sq)
+	{
+		const V = VariantRules;
+		return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]));
+	}
+
+	// TODO: getCastleMoves, generalize a bit to include castleSquares as static variables
+	// ==> but this won't be exactly Wildebeest... think about it.
+
+	// TODO: also generalize lastRank ==> DO NOT HARDCODE 7 !!!
+
+	isAttacked(sq, colors)
+	{
+		return (super.isAttacked(sq, colors)
+			|| this.isAttackedByCamel(sq, colors)
+			|| this.isAttackedByWildebeest(sq, colors);
+	}
+
+	isAttackedByCamel(sq, colors)
+	{
+		return this.isAttackedBySlideNJump(sq, colors,
+			VariantRules.CAMEL, VariantRules.steps[VariantRules.CAMEL]);
+	}
+
+	isAttackedByWildebeest(sq, colors)
+	{
+		const V = VariantRules;
+		return this.isAttackedBySlideNJump(sq, colors, V.WILDEBEEST,
+			V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]));
+	}
+
+	static get VALUES() {
+		return Object.assign(
+			ChessRules.VALUES,
+			{'c': 3, 'w': 7} //experimental
+		);
+	}
+
+	// TODO:
+	static GenRandInitFen()
+	{
+		let pieces = [new Array(8), new Array(8)];
+		// Shuffle pieces on first and last rank
+		for (let c = 0; c <= 1; c++)
+		{
+			let positions = _.range(8);
+
+			// Get random squares for bishops
+			let randIndex = 2 * _.random(3);
+			let bishop1Pos = positions[randIndex];
+			// The second bishop must be on a square of different color
+			let randIndex_tmp = 2 * _.random(3) + 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(5);
+			let knight1Pos = positions[randIndex];
+			positions.splice(randIndex, 1);
+			randIndex = _.random(4);
+			let knight2Pos = positions[randIndex];
+			positions.splice(randIndex, 1);
+
+			// Get random square for queen
+			randIndex = _.random(3);
+			let queenPos = 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][kingPos] = 'k';
+			pieces[c][bishop2Pos] = 'b';
+			pieces[c][knight2Pos] = 'n';
+			pieces[c][rook2Pos] = 'r';
+		}
+		let fen = pieces[0].join("") +
+			"/pppppppp/8/8/8/8/PPPPPPPP/" +
+			pieces[1].join("").toUpperCase() +
+			" 1111"; //add flags
+		return fen;
+	}
+}
diff --git a/public/javascripts/variants/Zen.js b/public/javascripts/variants/Zen.js
index b7f229c3..17c922d2 100644
--- a/public/javascripts/variants/Zen.js
+++ b/public/javascripts/variants/Zen.js
@@ -157,7 +157,8 @@ class ZenRules extends ChessRules
 
 	getPotentialKnightMoves(sq)
 	{
-		let noCaptures = this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.KNIGHT], "oneStep");
+		let noCaptures = this.getSlideNJumpMoves(sq,
+			VariantRules.steps[VariantRules.KNIGHT], "oneStep");
 		let captures = this.findCaptures(sq);
 		return noCaptures.concat(captures);
 	}
@@ -171,15 +172,18 @@ class ZenRules extends ChessRules
 
 	getPotentialQueenMoves(sq)
 	{
-		let noCaptures = this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.QUEEN]);
+		const V = VariantRules;
+		let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK.concat(V.steps[V.BISHOP])]);
 		let captures = this.findCaptures(sq);
 		return noCaptures.concat(captures);
 	}
 
 	getPotentialKingMoves(sq)
 	{
+		const V = VariantRules;
 		// Initialize with normal moves
-		let noCaptures = this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.QUEEN], "oneStep");
+		let noCaptures = this.getSlideNJumpMoves(sq,
+			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
 		let captures = this.findCaptures(sq);
 		return noCaptures.concat(captures).concat(this.getCastleMoves(sq));
 	}
diff --git a/variants.js b/variants.js
index 7aef900d..175c4269 100644
--- a/variants.js
+++ b/variants.js
@@ -6,8 +6,8 @@ module.exports = [
   { "name" : "Antiking", "description" : "Keep antiking in check" },
   { "name" : "Magnetic", "description" : "Laws of attraction" },
   { "name" : "Alice", "description" : "Both sides of the mirror" },
-//  { "name" : "Grand", "description" : "Big board" },
-//  { "name" : "Wildebeest", "description" : "Balanced sliders & leapers" },
+  { "name" : "Grand", "description" : "Big board" },
+  { "name" : "Wildebeest", "description" : "Balanced sliders & leapers" },
 //  { "name" : "Loser", "description" : "Lose all pieces" },
 //  { "name" : "Crazyhouse", "description" : "Captures reborn" },
 //  { "name" : "Switching", "description" : "Exchange pieces positions" },
-- 
2.44.0