From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 27 Nov 2018 17:28:36 +0000 (+0100)
Subject: Some fixes. Loser implemented. Draft Extinction,Crazyhouse,Switching
X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/images/assets/current/img/DESCRIPTION?a=commitdiff_plain;h=a6abf094c35a26019e47fea21302c4be32ff030b;p=vchess.git

Some fixes. Loser implemented. Draft Extinction,Crazyhouse,Switching
---

diff --git a/TODO b/TODO
index 23b3412c..bcda2e84 100644
--- a/TODO
+++ b/TODO
@@ -1,2 +1,4 @@
 For animation, moves should contains "moving" and "fading" maybe...
 (But it's really just for Magnetic chess)
+setInterval "CRON" task in sockets.js to check connected clients
+(every 1hour maybe, or more)
diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js
index 6cab3496..f1188ab5 100644
--- a/public/javascripts/base_rules.js
+++ b/public/javascripts/base_rules.js
@@ -492,9 +492,9 @@ class ChessRules
 		const oppCol = this.getOppCol(color);
 		let potentialMoves = [];
 		const [sizeX,sizeY] = VariantRules.size;
-		for (var i=0; i<sizeX; i++)
+		for (let i=0; i<sizeX; i++)
 		{
-			for (var j=0; j<sizeY; j++)
+			for (let j=0; j<sizeY; j++)
 			{
 				// Next condition ... != oppCol is a little HACK to work with checkered variant
 				if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) != oppCol)
@@ -808,13 +808,23 @@ class ChessRules
 	}
 
 	// Assumption: at least one legal move
-	getComputerMove(moves1) //moves1 might be precomputed (Magnetic chess)
+	// NOTE: works also for extinction chess because depth is 3...
+	getComputerMove()
 	{
 		this.shouldReturn = false;
 		const maxeval = VariantRules.INFINITY;
 		const color = this.turn;
-		if (!moves1)
-			moves1 = this.getAllValidMoves();
+		let moves1 = this.getAllValidMoves();
+
+		// Can I mate in 1 ? (for Magnetic & Extinction)
+		for (let i of _.shuffle(_.range(moves1.length)))
+		{
+			this.play(moves1[i]);
+			const finish = (Math.abs(this.evalPosition()) >= VariantRules.THRESHOLD_MATE);
+			this.undo(moves1[i]);
+			if (finish)
+				return moves1[i];
+		}
 
 		// Rank moves using a min-max at depth 2
 		for (let i=0; i<moves1.length; i++)
@@ -847,11 +857,10 @@ class ChessRules
 			candidates.push(j);
 		let currentBest = moves1[_.sample(candidates, 1)];
 
-		// Skip depth 3 if we found a checkmate (or if we are checkmated in 1...)
+		// Skip depth 3+ if we found a checkmate (or if we are checkmated in 1...)
 		if (VariantRules.SEARCH_DEPTH >= 3
 			&& Math.abs(moves1[0].eval) < VariantRules.THRESHOLD_MATE)
 		{
-			// TODO: show current analyzed move for depth 3, allow stopping eval (return moves1[0])
 			for (let i=0; i<moves1.length; i++)
 			{
 				if (this.shouldReturn)
@@ -921,7 +930,7 @@ class ChessRules
 	{
 		const [sizeX,sizeY] = VariantRules.size;
 		let evaluation = 0;
-		//Just count material for now
+		// Just count material for now
 		for (let i=0; i<sizeX; i++)
 		{
 			for (let j=0; j<sizeY; j++)
diff --git a/public/javascripts/variants/Antiking.js b/public/javascripts/variants/Antiking.js
index 84f774a5..f9084951 100644
--- a/public/javascripts/variants/Antiking.js
+++ b/public/javascripts/variants/Antiking.js
@@ -42,7 +42,7 @@ class AntikingRules extends ChessRules
 		const piece2 = this.getPiece(x2,y2);
 		const color1 = this.getColor(x1,y1);
 		const color2 = this.getColor(x2,y2);
-		return !["a","A"].includes(piece2) &&
+		return piece2 != "a" &&
 			((piece1 != "a" && color1 != color2) || (piece1 == "a" && color1 == color2));
 	}
 
diff --git a/public/javascripts/variants/Crazyhouse.js b/public/javascripts/variants/Crazyhouse.js
new file mode 100644
index 00000000..0ee4bd37
--- /dev/null
+++ b/public/javascripts/variants/Crazyhouse.js
@@ -0,0 +1,95 @@
+class CrazyhouseRules extends ChessRules
+{
+	initVariables(fen)
+	{
+		super.initVariables();
+		// Also init reserves (used by the interface to show landing pieces)
+		const V = VariantRules;
+		this.reserve =
+		{
+			"w":
+			{
+				[V.PAWN]: 0,
+				[V.ROOK]: 0,
+				[V.KNIGHT]: 0,
+				[V.BISHOP]: 0,
+				[V.QUEEN]: 0,
+			},
+			"b":
+			{
+				[V.PAWN]: 0,
+				[V.ROOK]: 0,
+				[V.KNIGHT]: 0,
+				[V.BISHOP]: 0,
+				[V.QUEEN]: 0,
+			}
+		};
+		// It may be a continuation: adjust numbers of pieces according to captures + rebirths
+		// TODO
+	}
+
+	// Used by the interface:
+	getReservePieces(color)
+	{
+		return {
+			[color+V.PAWN]: this.reserve[color][V.PAWN],
+			[color+V.ROOK]: this.reserve[color][V.ROOK],
+			[color+V.KNIGHT]: this.reserve[color][V.KNIGHT],
+			[color+V.BISHOP]: this.reserve[color][V.BISHOP],
+			[color+V.QUEEN]: this.reserve[color][V.QUEEN],
+		};
+	}
+
+	getPotentialMovesFrom([x,y])
+	{
+		let moves = super.getPotentialMovesFrom([x,y]);
+		// Add landing moves:
+		const color = this.turn;
+		Object.keys(this.reserve[color]).forEach(p => {
+
+			moves.push(...); //concat... just appear
+		});
+		return moves;
+	}
+
+	// TODO: condition "if this is reserve" --> special square !!! coordinates ??
+	getPossibleMovesFrom(sq)
+	{
+		// Assuming color is right (already checked)
+		return this.filterValid( this.getPotentialMovesFrom(sq) );
+	}
+
+	// TODO: add reserve moves
+	getAllValidMoves()
+	{
+
+	}
+
+	// TODO: also
+	atLeastOneMove()
+	{
+
+	}
+
+	// TODO: update reserve
+	updateVariables(move)
+	{
+	}
+	unupdateVariables(move)
+	{
+	}
+
+	static get SEARCH_DEPTH() { return 2; } //high branching factor
+
+	getNotation(move)
+	{
+		if (move.vanish.length > 0)
+			return super.getNotation(move);
+		// Rebirth:
+		const piece =
+			(move.appear[0].p != VariantRules.PAWN ? move.appear.p.toUpperCase() : "");
+		const finalSquare =
+			String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x);
+		return piece + "@" + finalSquare;
+	}
+}
diff --git a/public/javascripts/variants/Extinction.js b/public/javascripts/variants/Extinction.js
new file mode 100644
index 00000000..db330d9e
--- /dev/null
+++ b/public/javascripts/variants/Extinction.js
@@ -0,0 +1,86 @@
+class ExtinctionRules extends ChessRules
+{
+	initVariables(fen)
+	{
+		super.initVariables(fen);
+		const V = VariantRules;
+		this.material =
+		{
+			"w":
+			{
+				[V.KING]: 1,
+				[V.QUEEN]: 1,
+				[V.ROOK]: 2,
+				[V.KNIGHT]: 2,
+				[V.BISHOP]: 2,
+				[V.PAWN]: 8
+			},
+			"b":
+			{
+				[V.KING]: 1,
+				[V.QUEEN]: 1,
+				[V.ROOK]: 2,
+				[V.KNIGHT]: 2,
+				[V.BISHOP]: 2,
+				[V.PAWN]: 8
+			}
+		};
+	}
+
+	// TODO: verify this assertion
+	atLeastOneMove()
+	{
+		return true; //always at least one possible move
+	}
+
+	underCheck(move)
+	{
+		return false; //there is no check
+	}
+
+	getCheckSquares(move)
+	{
+		return [];
+	}
+
+	updateVariables(move)
+	{
+		super.updateVariables(move);
+		if (move.vanish.length==2 && move.appear.length==1)
+			this.material[move.vanish[1].c][move.vanish[1].p]--;
+	}
+
+	unupdateVariables(move)
+	{
+		super.unupdateVariables(move);
+		if (move.vanish.length==2 && move.appear.length==1)
+			this.material[move.vanish[1].c][move.vanish[1].p]++;
+	}
+
+	checkGameOver()
+	{
+		if (this.checkRepetition())
+			return "1/2";
+
+		if (this.atLeastOneMove()) // game not over?
+		{
+			const color = this.turn;
+			if (Object.keys(this.material[color]).some(
+				p => { return this.material[color][p] == 0; }))
+			{
+				return this.checkGameEnd();
+			}
+			return "*";
+		}
+
+		return this.checkGameEnd();
+	}
+
+	// Very negative (resp. positive) if white (reps. black) pieces set is incomplete
+	evalPosition()
+	{
+		if (this.missAkind())
+			return (this.turn=="w"?-1:1) * VariantRules.INFINITY;
+		return super.evalPosition();
+	}
+}
diff --git a/public/javascripts/variants/Grand.js b/public/javascripts/variants/Grand.js
index 1409bcc5..844d62c7 100644
--- a/public/javascripts/variants/Grand.js
+++ b/public/javascripts/variants/Grand.js
@@ -171,9 +171,9 @@ class GrandRules extends ChessRules
 			|| this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
 	}
 
-	play(move, ingame)
+	updateVariables(move)
 	{
-		super.play(move, ingame);
+		super.updateVariables(move);
 		if (move.vanish.length==2 && move.appear.length==1
 			&& move.vanish[1].p != VariantRules.PAWN)
 		{
@@ -185,9 +185,9 @@ class GrandRules extends ChessRules
 		}
 	}
 
-	undo(move)
+	unupdateVariables(move)
 	{
-		super.undo(move);
+		super.unupdateVariables(move);
 		if (move.vanish.length==2 && move.appear.length==1
 			&& move.vanish[1].p != VariantRules.PAWN)
 		{
diff --git a/public/javascripts/variants/Loser.js b/public/javascripts/variants/Loser.js
new file mode 100644
index 00000000..0c484c12
--- /dev/null
+++ b/public/javascripts/variants/Loser.js
@@ -0,0 +1,144 @@
+class LoserRules extends ChessRules
+{
+	initVariables(fen)
+	{
+		// No castling, hence no flags
+		const epSq = this.moves.length > 0 ? this.getEpSquare(this.lastMove) : undefined;
+		this.epSquares = [ epSq ];
+	}
+
+	setFlags(fen) { }
+
+	getPotentialPawnMoves([x,y])
+	{
+		let moves = super.getPotentialPawnMoves([x,y]);
+
+		// Complete with promotion(s) into king, if possible
+		const color = this.turn;
+		const V = VariantRules;
+		const [sizeX,sizeY] = VariantRules.size;
+		const shift = (color == "w" ? -1 : 1);
+		const lastRank = (color == "w" ? 0 : sizeX-1);
+		if (x+shift == lastRank)
+		{
+			let p = V.KING;
+			// 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}));
+		}
+
+		return moves;
+	}
+
+	getPotentialKingMoves(sq)
+	{
+		const V = VariantRules;
+		return this.getSlideNJumpMoves(sq,
+			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+	}
+
+	// Stop at the first capture found (if any)
+	atLeastOneCapture()
+	{
+		const color = this.turn;
+		const oppCol = this.getOppCol(color);
+		const [sizeX,sizeY] = VariantRules.size;
+		for (let i=0; i<sizeX; i++)
+		{
+			for (let j=0; j<sizeY; j++)
+			{
+				if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) != oppCol)
+				{
+					const moves = this.getPotentialMovesFrom([i,j]);
+					if (moves.length > 0)
+					{
+						for (let k=0; k<moves.length; k++)
+						{
+							if (moves[k].vanish.length==2 && this.filterValid([moves[k]]).length > 0)
+								return true;
+						}
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	// Trim all non-capturing moves
+	static KeepCaptures(moves)
+	{
+		return moves.filter(m => { return m.vanish.length == 2; });
+	}
+
+	getPossibleMovesFrom(sq)
+	{
+		let moves = this.filterValid( this.getPotentialMovesFrom(sq) );
+		// This is called from interface: we need to know if a capture is possible
+		if (this.atLeastOneCapture())
+			moves = VariantRules.KeepCaptures(moves);
+		return moves;
+	}
+
+	getAllValidMoves()
+	{
+		let moves = super.getAllValidMoves();
+		if (moves.some(m => { return m.vanish.length == 2; }))
+			moves = VariantRules.KeepCaptures(moves);
+		return moves;
+	}
+
+	underCheck(move)
+	{
+		return false; //No notion of check
+	}
+
+	getCheckSquares(move)
+	{
+		return [];
+	}
+
+	play(move, ingame)
+	{
+		if (!!ingame)
+			move.notation = this.getNotation(move);
+		this.moves.push(move);
+		this.epSquares.push( this.getEpSquare(move) );
+		VariantRules.PlayOnBoard(this.board, move);
+	}
+
+	undo(move)
+	{
+		VariantRules.UndoOnBoard(this.board, move);
+		this.epSquares.pop();
+		this.moves.pop();
+	}
+
+	checkGameEnd()
+	{
+		// No valid move: you win!
+		return this.turn == "w" ? "1-0" : "0-1";
+	}
+
+	static get VALUES() { //experimental...
+		return {
+			'p': 1,
+			'r': 7,
+			'n': 3,
+			'b': 3,
+			'q': 5,
+			'k': 5
+		};
+	}
+
+	static get SEARCH_DEPTH() { return 4; }
+
+	evalPosition()
+	{
+		return - super.evalPosition(); //better with less material
+	}
+}
diff --git a/public/javascripts/variants/Magnetic.js b/public/javascripts/variants/Magnetic.js
index e126f1ce..844cc689 100644
--- a/public/javascripts/variants/Magnetic.js
+++ b/public/javascripts/variants/Magnetic.js
@@ -216,19 +216,4 @@ class MagneticRules extends ChessRules
 	static get THRESHOLD_MATE() {
 		return 500; //checkmates evals may be slightly below 1000
 	}
-
-	getComputerMove()
-	{
-		let moves1 = this.getAllValidMoves();
-		// Can I mate in 1 ?
-		for (let i of _.shuffle(_.range(moves1.length)))
-		{
-			this.play(moves1[i]);
-			const finish = (Math.abs(this.evalPosition()) >= VariantRules.THRESHOLD_MATE);
-			this.undo(moves1[i]);
-			if (finish)
-				return moves1[i];
-		}
-		return super.getComputerMove(moves1);
-	}
 }
diff --git a/public/javascripts/variants/Switching.js b/public/javascripts/variants/Switching.js
new file mode 100644
index 00000000..a7d67879
--- /dev/null
+++ b/public/javascripts/variants/Switching.js
@@ -0,0 +1,10 @@
+//https://www.chessvariants.com/diffmove.dir/switching.html
+class SwitchingRules extends ChessRules
+{
+	//TODO:
+	// Move completion: promote switched pawns (as in Magnetic)
+
+	// To every piece potential moves: add switchings
+
+	// Prevent king switching if under check
+}
diff --git a/variants.js b/variants.js
index 175c4269..8a00fe00 100644
--- a/variants.js
+++ b/variants.js
@@ -1,15 +1,15 @@
 module.exports = [
-	{ "name" : "Checkered", "description" : "Shared pieces" },
-	{ "name" : "Zen", "description" : "Reverse captures" },
-	{ "name" : "Atomic", "description" : "Explosive captures" },
-	{ "name" : "Chess960", "description" : "Standard rules" },
-  { "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" : "Loser", "description" : "Lose all pieces" },
-//  { "name" : "Crazyhouse", "description" : "Captures reborn" },
-//  { "name" : "Switching", "description" : "Exchange pieces positions" },
-//  { "name" : "Absorption", "description" : "Capture enhance movements" },
+	{ "name": "Checkered", "description": "Shared pieces" },
+	{ "name": "Zen", "description": "Reverse captures" },
+	{ "name": "Atomic", "description": "Explosive captures" },
+	{ "name": "Chess960", "description": "Standard rules" },
+	{ "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": "Loser", "description": "Lose all pieces" },
+	{ "name": "Crazyhouse", "description": "Captures reborn" },
+	{ "name": "Switching", "description": "Exchange pieces positions" },
+	{ "name": "Extinction", "description": "Capture all of a kind" },
 ];
diff --git a/views/rules/Grand.pug b/views/rules/Grand.pug
index d5ca2016..67b849fe 100644
--- a/views/rules/Grand.pug
+++ b/views/rules/Grand.pug
@@ -47,6 +47,6 @@ p.
 h3 Credits
 
 p
-	| Grand chess page on
+	| Grand chess page on 
 	a(href="https://www.chessvariants.com/large.dir/freeling.html") chessvariants.com
 	| .
diff --git a/views/rules/Loser.pug b/views/rules/Loser.pug
new file mode 100644
index 00000000..3b6b1b62
--- /dev/null
+++ b/views/rules/Loser.pug
@@ -0,0 +1,42 @@
+p.boxed
+	| Win by losing all your pieces. Capture is mandatory.
+
+h3 Specifications
+
+ul
+	li Chessboard: standard.
+	li Material: standard.
+	li Non-capturing moves: standard (when allowed).
+	li Special moves: no castle.
+	li Captures: standard.
+	li End of game: stalemate or lose all material.
+
+h3 Basics
+
+p.
+	The goal is to lose all pieces, or get stalemated like on the following diagram.
+	The king has no royal status: it can be taken as any other piece.
+	Thus, there is no castle rule, no checks.
+
+figure.diagram-container
+	.diagram
+		| fen:6nB/6P1/8/4p3/2p1P3/2P5/8/8:
+	figcaption White cannot move: 1-0.
+
+h3 Special moves
+
+p.
+	Castling is not possible, but en-passant captures are allowed.
+	Pawns may promote into king (so you can potentially have several kings on the board).
+
+h3 End of the game
+
+p You can win by losing all material or be stalemated.
+
+h3 Credits
+
+p
+	| This is a popular variant, played in many places on the web.
+	| A starting point can be the 
+	a(href="https://en.wikipedia.org/wiki/Losing_Chess") wikipedia page
+	| .
diff --git a/views/rules/Wildebeest.pug b/views/rules/Wildebeest.pug
index cb883f9d..482ab6b5 100644
--- a/views/rules/Wildebeest.pug
+++ b/views/rules/Wildebeest.pug
@@ -45,6 +45,6 @@ p.
 h3 Credits
 
 p
-	| Wildebeest page on
+	| Wildebeest page on 
 	a(href="https://www.chessvariants.com/large.dir/wildebeest.html") chessvariants.com
 	| .