From 1af36beb39aa5d59735483e915fd1040f93eecca Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 20 Nov 2018 17:01:02 +0100
Subject: [PATCH] First draft of Magnetic chess

---
 public/javascripts/base_rules.js        |  16 +-
 public/javascripts/variants/Atomic.js   |   5 +-
 public/javascripts/variants/Magnetic.js | 197 ++++++++++++++++++++++++
 variants.js                             |   2 +-
 4 files changed, 211 insertions(+), 9 deletions(-)
 create mode 100644 public/javascripts/variants/Magnetic.js

diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js
index cdd75727..7c52fc1c 100644
--- a/public/javascripts/base_rules.js
+++ b/public/javascripts/base_rules.js
@@ -710,7 +710,7 @@ class ChessRules
 	//////////////
 	// END OF GAME
 
-	checkGameOver()
+	checkRepetition()
 	{
 		// Check for 3 repetitions
 		if (this.moves.length >= 8)
@@ -722,15 +722,19 @@ class ChessRules
 				_.isEqual(this.moves[L-3], this.moves[L-7]) &&
 				_.isEqual(this.moves[L-4], this.moves[L-8]))
 			{
-				return "1/2 (repetition)";
+				return true;
 			}
 		}
+		return false;
+	}
 
-		if (this.atLeastOneMove())
-		{
-			// game not over
+	checkGameOver()
+	{
+		if (this.checkRepetition())
+			return "1/2";
+
+		if (this.atLeastOneMove()) // game not over
 			return "*";
-		}
 
 		// Game over
 		return this.checkGameEnd();
diff --git a/public/javascripts/variants/Atomic.js b/public/javascripts/variants/Atomic.js
index d86ed0e1..f32e21d1 100644
--- a/public/javascripts/variants/Atomic.js
+++ b/public/javascripts/variants/Atomic.js
@@ -134,13 +134,14 @@ class AtomicRules extends ChessRules
 	getCheckSquares(move)
 	{
 		const c = this.getOppCol(this.turn);
-		const saveKingPos = this.kingPos[c]; //king might explode
+		// King might explode:
+		const saveKingPos = JSON.parse(JSON.stringify(this.kingPos[c]));
 		this.play(move);
 		let res = [ ];
 		if (this.kingPos[c][0] < 0)
 			res = [saveKingPos];
 		else if (this.isAttacked(this.kingPos[c], this.getOppCol(c)))
-			res = [ this.kingPos[c] ]
+			res = [ JSON.parse(JSON.stringify(this.kingPos[c])) ]
 		this.undo(move);
 		return res;
 	}
diff --git a/public/javascripts/variants/Magnetic.js b/public/javascripts/variants/Magnetic.js
new file mode 100644
index 00000000..e9415322
--- /dev/null
+++ b/public/javascripts/variants/Magnetic.js
@@ -0,0 +1,197 @@
+class MagneticRules
+{
+	getEpSquare(move)
+	{
+		return undefined; //no en-passant
+	}
+
+	// Complete a move with magnetic actions
+	applyMagneticLaws([x,y], move)
+	{
+		// TODO
+	}
+
+	getBasicMove([sx,sy], [ex,ey], tr)
+	{
+		var mv = new Move({
+			appear: [
+				new PiPo({
+					x: ex,
+					y: ey,
+					c: !!tr ? tr.c : this.getColor(sx,sy),
+					p: !!tr ? tr.p : this.getPiece(sx,sy)
+				})
+			],
+			vanish: [
+				new PiPo({
+					x: sx,
+					y: sy,
+					c: this.getColor(sx,sy),
+					p: this.getPiece(sx,sy)
+				})
+			]
+		});
+
+		if (this.board[ex][ey] != VariantRules.EMPTY)
+		{
+			mv.vanish.push(
+				new PiPo({
+					x: ex,
+					y: ey,
+					c: this.getColor(ex,ey),
+					p: this.getPiece(ex,ey)
+				})
+			);
+		}
+		this.applyMagneticLaws([ex,ey], mv);
+		return mv;
+	}
+
+	getCastleMoves([x,y])
+	{
+		const c = this.getColor(x,y);
+		if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c])
+			return []; //x isn't first rank, or king has moved (shortcut)
+
+		const V = VariantRules;
+
+		// Castling ?
+		const oppCol = this.getOppCol(c);
+		let moves = [];
+		let i = 0;
+		const finalSquares = [ [2,3], [6,5] ]; //king, then rook
+		castlingCheck:
+		for (let castleSide=0; castleSide < 2; castleSide++) //large, then small
+		{
+			if (!this.flags[c][castleSide])
+				continue;
+			// If this code is reached, rooks and king are on initial position
+
+			// Nothing on the path of the king (and no checks; OK also if y==finalSquare)?
+			let step = finalSquares[castleSide][0] < y ? -1 : 1;
+			for (i=y; i!=finalSquares[castleSide][0]; i+=step)
+			{
+				if (this.isAttacked([x,i], oppCol) || (this.board[x][i] != V.EMPTY &&
+					// NOTE: next check is enough, because of chessboard constraints
+					(this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i)))))
+				{
+					continue castlingCheck;
+				}
+			}
+
+			// Nothing on the path to the rook?
+			step = castleSide == 0 ? -1 : 1;
+			for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step)
+			{
+				if (this.board[x][i] != V.EMPTY)
+					continue castlingCheck;
+			}
+			const rookPos = this.INIT_COL_ROOK[c][castleSide];
+
+			// Nothing on final squares, except maybe king and castling rook?
+			for (i=0; i<2; i++)
+			{
+				if (this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
+					this.getPiece(x,finalSquares[castleSide][i]) != V.KING &&
+					finalSquares[castleSide][i] != rookPos)
+				{
+					continue castlingCheck;
+				}
+			}
+
+			// If this code is reached, castle is valid
+			let cmove = new Move({
+				appear: [
+					new PiPo({x:x,y:finalSquares[castleSide][0],p:V.KING,c:c}),
+					new PiPo({x:x,y:finalSquares[castleSide][1],p:V.ROOK,c:c})],
+				vanish: [
+					new PiPo({x:x,y:y,p:V.KING,c:c}),
+					new PiPo({x:x,y:rookPos,p:V.ROOK,c:c})],
+				end: Math.abs(y - rookPos) <= 2
+					? {x:x, y:rookPos}
+					: {x:x, y:y + 2 * (castleSide==0 ? -1 : 1)}
+			});
+			this.applyMagneticLaws([x,finalSquares[castleSide][1]], cmove);
+			moves.push(cmove);
+		}
+
+		return moves;
+	}
+
+	// TODO: verify this assertion
+//	atLeastOneMove()
+//	{
+//		return true; //always at least one possible move
+//	}
+
+	underCheck(move)
+	{
+		return false; //there is no check
+	}
+
+	getCheckSquares(move)
+	{
+		const c = this.getOppCol(this.turn); //opponent
+		const saveKingPos = this.kingPos[c]; //king might be taken
+		this.play(move);
+		// The only way to be "under check" is to have lost the king (thus game over)
+		let res = this.kingPos[c][0] < 0;
+			? [ JSON.parse(JSON.stringify(saveKingPos)) ]
+			: [ ];
+		this.undo(move);
+		return res;
+	}
+
+	updateVariables(move)
+	{
+		super.updateVariables(move);
+		const c = this.getColor(move.start.x,move.start.y);
+		if (c != this.getColor(move.end.x,move.end.y)
+			&& this.board[move.end.x][move.end.y] != VariantRules.EMPTY
+			&& this.getPiece(move.end.x,move.end.y) == VariantRules.KING)
+		{
+			// We took opponent king !
+			const oppCol = this.getOppCol(c);
+			this.kingPos[oppCol] = [-1,-1];
+			this.flags[oppCol] = [false,false];
+		}
+	}
+
+	unupdateVariables(move)
+	{
+		super.unupdateVariables(move);
+		const c = this.getColor(move.start.x,move.start.y);
+		const oppCol = this.getOppCol(c);
+		if (this.kingPos[oppCol][0] < 0)
+		{
+			// Last move took opponent's king
+			for (let psq of move.vanish)
+			{
+				if (psq.p == 'k')
+				{
+					this.kingPos[oppCol] = [psq.x, psq.y];
+					break;
+				}
+			}
+		}
+	}
+
+	checkGameOver()
+	{
+		if (this.checkRepetition())
+			return "1/2";
+
+		const color = this.turn;
+		// TODO: do we need "atLeastOneMove()"?
+		if (this.atLeastOneMove() && this.kingPos[color][0] >= 0)
+			return "*";
+
+		return this.checkGameEnd();
+	}
+
+	checkGameEnd()
+	{
+		// No valid move: our king disappeared
+		return this.turn == "w" ? "0-1" : "1-0";
+	}
+}
diff --git a/variants.js b/variants.js
index 6c2f62cf..10367645 100644
--- a/variants.js
+++ b/variants.js
@@ -4,7 +4,7 @@ module.exports = [
 	{ "name" : "Atomic", "description" : "Explosive captures" },
 	{ "name" : "Chess960", "description" : "Standard rules" },
   { "name" : "Antiking", "description" : "Keep antiking in check" },
-//  { "name" : "Magnetic", "description" : "Laws of attraction" },
+  { "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" },
-- 
2.44.0