From 204e289bbcddc69e2d81aef492dbea6db9e31188 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 20 Nov 2018 14:59:19 +0100
Subject: [PATCH] Fist draft of Antiking variant (still being debugged)

---
 public/javascripts/base_rules.js        |   2 +-
 public/javascripts/components/game.js   |  26 +++---
 public/javascripts/variants/Antiking.js | 102 ++++++++++++++++++------
 variants.js                             |   2 +-
 views/rules/Antiking.pug                |  43 ++++++++++
 5 files changed, 136 insertions(+), 39 deletions(-)
 create mode 100644 views/rules/Antiking.pug

diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js
index a4001225..8626b888 100644
--- a/public/javascripts/base_rules.js
+++ b/public/javascripts/base_rules.js
@@ -621,7 +621,7 @@ class ChessRules
 	getCheckSquares(move)
 	{
 		this.play(move);
-		const color = this.turn;
+		const color = this.turn; //opponent
 		let res = this.isAttacked(this.kingPos[color], this.getOppCol(color))
 			? [ JSON.parse(JSON.stringify(this.kingPos[color])) ] //need to duplicate!
 			: [ ];
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 9364c8d9..18632858 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -217,18 +217,21 @@ Vue.component('my-game', {
 					);
 				}), choices]
 			);
-			actionArray.push(
-				h('button',
-					{
-						on: { click: this.resign },
-						attrs: { "aria-label": 'Resign' },
-						'class': {
-							"tooltip":true,
-							"bottom": true,
+			if (this.mode != "idle")
+			{
+				actionArray.push(
+					h('button',
+						{
+							on: { click: this.resign },
+							attrs: { "aria-label": 'Resign' },
+							'class': {
+								"tooltip":true,
+								"bottom": true,
+							},
 						},
-					},
-					[h('i', { 'class': { "material-icons": true } }, "flag")])
-			);
+						[h('i', { 'class': { "material-icons": true } }, "flag")])
+				);
+			}
 			elementArray.push(gameDiv);
 	//			if (!!vr.reserve)
 	//			{
@@ -494,6 +497,7 @@ Vue.component('my-game', {
 			this.score = score;
 			let modalBox = document.getElementById("modal-eog");
 			modalBox.checked = true;
+			// Variants may have special PGN structure (so next function isn't defined here)
 			this.pgnTxt = this.vr.getPGN(this.mycolor, this.score, this.fenStart, this.mode);
 			setTimeout(() => { modalBox.checked = false; }, 2000);
 			if (this.mode == "human")
diff --git a/public/javascripts/variants/Antiking.js b/public/javascripts/variants/Antiking.js
index 360deafa..2770261a 100644
--- a/public/javascripts/variants/Antiking.js
+++ b/public/javascripts/variants/Antiking.js
@@ -1,4 +1,4 @@
-class AntikingRules
+class AntikingRules extends ChessRules
 {
 	// Path to pieces
 	static getPpath(b)
@@ -11,30 +11,55 @@ class AntikingRules
 	initVariables(fen)
 	{
 		super.initVariables(fen);
-		// TODO: initialize this.antikingPos[...]
+		this.antikingPos = {'w':[-1,-1], 'b':[-1,-1]};
+		const position = fen.split(" ")[0].split("/");
+		for (let i=0; i<position.length; i++)
+		{
+			let j = 0;
+			while (j < position[i].length)
+			{
+				switch (position[i].charAt(j))
+				{
+					case 'a':
+						this.antikingPos['b'] = [i,j];
+						break;
+					case 'A':
+						this.antikingPos['w'] = [i,j];
+						break;
+					default:
+						let num = parseInt(position[i].charAt(j));
+						if (!isNaN(num))
+							j += (num-1);
+				}
+				j++;
+			}
+		}
 	}
 
-	canTake(color1, color2, [x,y])
+	canTake([x1,y1], [x2,y2])
 	{
-		const piece = this.getPiece(x,y);
-		return (piece != "a" && color1 != color2) || (piece == "a" && color1 == color2);
+		const piece1 = this.getPiece(x1,y1);
+		const piece2 = this.getPiece(x2,y2);
+		const color1 = this.getColor(x1,y1);
+		const color2 = this.getColor(x2,y2);
+		return !["a","A"].includes(piece2) &&
+			((piece1 != "a" && color1 != color2) || (piece1 == "a" && color1 == color2));
 	}
 
 	getPotentialMovesFrom([x,y])
 	{
-		let c = this.getColor(x,y);
 		switch (this.getPiece(x,y))
 		{
 			case VariantRules.ANTIKING:
-				return this.getPotentialAntikingMoves(x,y,c);
+				return this.getPotentialAntikingMoves([x,y]);
 			default:
-				return super.getPotentielMovesFrom([x,y]);
+				return super.getPotentialMovesFrom([x,y]);
 		}
 	}
 
-	getPotentialAntikingMoves([x,y])
+	getPotentialAntikingMoves(sq)
 	{
-		// TODO
+		return this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.QUEEN], "oneStep");
 	}
 
 	isAttacked(sq, colors)
@@ -42,49 +67,67 @@ class AntikingRules
 		return (super.isAttacked(sq, colors) || this.isAttackedByAntiking(sq, colors));
 	}
 
-	isAttackedByAntiking(sq, color)
+	isAttackedByAntiking([x,y], colors)
 	{
-		// TODO
+		console.log(x + " " + y); //TODO: debug -1, -1 (wrong undo ?!)
+		if (this.getPiece(x,y) == VariantRules.KING)
+			return false; //king is not attacked by antiking
+		return super.isAttackedBySlideNJump([x,y], colors,
+			VariantRules.ANTIKING, VariantRules.steps[VariantRules.QUEEN], "oneStep");
 	}
 
 	underCheck(move)
 	{
 		const c = this.turn;
-		this.play(move);
-		let res = this.isAttacked(this.kingPos[c], this.getOppCol(c));
-		// TODO: also check that antiking is still in check
+		const oppCol = this.getOppCol(c);
+		this.play(move)
+		let res = this.isAttacked(this.kingPos[c], oppCol)
+			|| !this.isAttacked(this.antikingPos[c], oppCol);
 		this.undo(move);
 		return res;
 	}
 
 	getCheckSquares(move)
 	{
+		let res = super.getCheckSquares(move);
 		this.play(move);
 		const c = this.turn;
-		// TODO
-		let res = this.isAttacked(this.kingPos[c], this.getOppCol(c))
-			? [ JSON.parse(JSON.stringify(this.kingPos[c])) ]
-			: [ ];
+		if (!this.isAttacked(this.antikingPos[c], this.getOppCol(c)))
+			res.push(JSON.parse(JSON.stringify(this.antikingPos[c])));
 		this.undo(move);
 		return res;
 	}
 
-	// TODO: need antikingPos as well
 	updateVariables(move)
 	{
-		// ...
+		super.updateVariables(move);
+		const piece = this.getPiece(move.start.x,move.start.y);
+		const c = this.getColor(move.start.x,move.start.y);
+		// Update antiking position
+		if (piece == VariantRules.ANTIKING)
+		{
+			this.antikingPos[c][0] = move.appear[0].x;
+			this.antikingPos[c][1] = move.appear[0].y;
+		}
 	}
 
 	unupdateVariables(move)
 	{
-		// TODO
+		super.unupdateVariables(move);
+		const c = this.getColor(move.start.x,move.start.y);
+		if (this.getPiece(move.start.x,move.start.y) == VariantRules.ANTIKING)
+			this.antikingPos[c] = [move.start.x, move.start.y];
 	}
 
-	checkGameEnd(color)
+	checkGameEnd()
 	{
-		// TODO
-		if (!this.isAttacked(this.kingPos[color], this.getOppCol(color)))
+		const color = this.turn;
+		const oppCol = this.getOppCol(color);
+		if (!this.isAttacked(this.kingPos[color], oppCol)
+			&& this.isAttacked(this.antikingPos[color], oppCol))
+		{
 			return "1/2";
+		}
 		return color == "w" ? "0-1" : "1-0";
 	}
 
@@ -104,7 +147,14 @@ class AntikingRules
 	static GenRandInitFen()
 	{
 		let randFen = ChessRules.GenRandInitFen();
-		// TODO: just add an antiking at random on 3rd ranks
+		// Black side
+		let antikingPos = _.random(7);
+		let ranks23 = "pppppppp/" + (antikingPos>0?antikingPos:"") + "A" + (antikingPos<7?7-antikingPos:"");
+		randFen = randFen.replace("pppppppp/8", ranks23);
+		// White side
+		antikingPos = _.random(7);
+		ranks23 = (antikingPos>0?antikingPos:"") + "a" + (antikingPos<7?7-antikingPos:"") + "/PPPPPPPP";
+		randFen = randFen.replace("8/PPPPPPPP", ranks23);
 		return randFen;
 	}
 }
diff --git a/variants.js b/variants.js
index ed2b6bad..6c2f62cf 100644
--- a/variants.js
+++ b/variants.js
@@ -3,7 +3,7 @@ module.exports = [
 	{ "name" : "Zen", "description" : "Reverse captures" },
 	{ "name" : "Atomic", "description" : "Explosive captures" },
 	{ "name" : "Chess960", "description" : "Standard rules" },
-//  { "name" : "AntiKing", "description" : "Keep anti-king in check" },
+  { "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" },
diff --git a/views/rules/Antiking.pug b/views/rules/Antiking.pug
new file mode 100644
index 00000000..eda7db44
--- /dev/null
+++ b/views/rules/Antiking.pug
@@ -0,0 +1,43 @@
+p.boxed
+	| You have a king and an antiking. King must stay away from checks, but antiking must always stay in check.
+	| Antiking captures his own kind.
+
+h3 Specifications
+
+ul
+	li Chessboard: standard.
+	li Material: additional antiking.
+	li Non-capturing moves: standard.
+	li Special moves: standard.
+	li Captures: special case of antiking (see below).
+	li End of game: Checkmate or anti-checkmate.
+
+h3 Basics
+
+p
+	| The additional piece is a royal figure, thus cannot be captured.
+	| It captures the pieces of his color (to help checkmate opponent antiking, but by doing so it also make standard checkmate more difficult...).
+	| It should always remains under check (if it cannot, game is over).
+
+figure.diagram-container
+	.diagram
+		| fen:rnbqkbnr/pppppppp/3A4/8/8/3a4/PPPPPPPP/RNBQKBNR:
+	figcaption Initial position (non-random). 1.Ae5 is forbidden.
+
+h3 End of the game
+
+p There are two ways to win:
+ol
+	li Checkmate opponent king
+	li Anti-checkmate opponent antiking
+p ...Or maybe do both at the same time?
+
+p Note 1: athough antiking captures his color, it doesn't check his king.
+
+p Note 2: since it would allow a basic tactic (keep antiking touching opponent's king), kings do not attack antikings.
+
+h3 Credits
+
+p
+	a(href="https://www.chessvariants.com/diffobjective.dir/anti-king-chess.html") Antiking chess 
+	| on chessvariants.com.
-- 
2.44.0