From 1221ac47836806efb287b0323b92957d9129c653 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Wed, 28 Nov 2018 03:07:29 +0100
Subject: [PATCH] Switching chess almost OK, Extinction seems OK, Crazyhouse
 advanced draft but to be debugged

---
 TODO                                      |   2 +
 public/javascripts/base_rules.js          |  30 ++++--
 public/javascripts/components/game.js     |  50 ++++++----
 public/javascripts/variants/Crazyhouse.js | 106 ++++++++++++++++------
 public/javascripts/variants/Extinction.js |  59 +++++++++++-
 public/javascripts/variants/Magnetic.js   |   3 +-
 public/javascripts/variants/Switching.js  |  73 ++++++++++++++-
 public/javascripts/variants/Zen.js        |  40 ++++----
 views/rules/Crazyhouse.pug                |  33 +++++++
 views/rules/Extinction.pug                |  34 +++++++
 views/rules/Switching.pug                 |  32 +++++++
 11 files changed, 380 insertions(+), 82 deletions(-)
 create mode 100644 views/rules/Crazyhouse.pug
 create mode 100644 views/rules/Extinction.pug
 create mode 100644 views/rules/Switching.pug

diff --git a/TODO b/TODO
index bcda2e84..fe7d8473 100644
--- a/TODO
+++ b/TODO
@@ -2,3 +2,5 @@ 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)
+Systematically show init+dest squares in PGN, maybe after short notation
+(2 moves list, second for de-ambiguification)
diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js
index f1188ab5..360f60c5 100644
--- a/public/javascripts/base_rules.js
+++ b/public/javascripts/base_rules.js
@@ -276,7 +276,8 @@ class ChessRules
 		{
 			let i = x + step[0];
 			let j = y + step[1];
-			while (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j] == VariantRules.EMPTY)
+			while (i>=0 && i<sizeX && j>=0 && j<sizeY
+				&& this.board[i][j] == VariantRules.EMPTY)
 			{
 				moves.push(this.getBasicMove([x,y], [i,j]));
 				if (oneStep !== undefined)
@@ -296,7 +297,7 @@ class ChessRules
 		const color = this.turn;
 		let moves = [];
 		const V = VariantRules;
-		const [sizeX,sizeY] = VariantRules.size;
+		const [sizeX,sizeY] = V.size;
 		const shift = (color == "w" ? -1 : 1);
 		const firstRank = (color == 'w' ? sizeX-1 : 0);
 		const startRank = (color == "w" ? sizeX-2 : 1);
@@ -308,7 +309,7 @@ class ChessRules
 			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
+				// Next condition because variants with pawns on 1st rank allow them to jump
 				if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
 				{
 					// Two squares jump
@@ -316,10 +317,16 @@ class ChessRules
 				}
 			}
 			// Captures
-			if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
+			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)
+			}
+			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)
@@ -331,10 +338,16 @@ class ChessRules
 				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)
+				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)
+				}
+				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}));
+				}
 			});
 		}
 
@@ -603,7 +616,8 @@ class ChessRules
 			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 ?
+	// Generic method for non-pawn pieces ("sliding or jumping"):
+	// is x,y attacked by piece !of color in colors?
 	isAttackedBySlideNJump([x,y], colors, piece, steps, oneStep)
 	{
 		const [sizeX,sizeY] = VariantRules.size;
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 86f25210..2897b1cd 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -240,23 +240,39 @@ Vue.component('my-game', {
 				);
 			}
 			elementArray.push(gameDiv);
-	//			if (!!vr.reserve)
-	//			{
-	//				let reserve = h('div',
-	//					{'class':{'game':true}}, [
-	//						h('div',
-	//							{ 'class': { 'row': true }},
-	//							[
-	//								h('div',
-	//									{'class':{'board':true}},
-	//									[h('img',{'class':{"piece":true},attrs:{"src":"/images/pieces/wb.svg"}})]
-	//								)
-	//							]
-	//						)
-	//					],
-	//				);
-	//				elementArray.push(reserve);
-	//			}
+			if (!!this.vr.reserve) //TODO: table, show counts for reserve pieces
+				//<tr style="padding:0">
+				//    <td style="padding:0;font-size:10px">3</td>
+			{
+				let reservePiecesArray = [];
+				for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++)
+				{
+					reservePiecesArray.push(h('img',
+						{
+							'class': {"piece":true},
+							attrs: {
+								"src": "/images/pieces/" +
+									this.vr.getReservePpath(this.mycolor,i) + ".svg",
+								id: this.getSquareId({x:sizeX,y:i}),
+							}
+						})
+					);
+				}
+				let reserve = h('div',
+					{'class':{'game':true}}, [
+						h('div',
+							{ 'class': { 'row': true }},
+							[
+								h('div',
+									{'class':{'board':true, ['board'+sizeY]:true}},
+									reservePiecesArray
+								)
+							]
+						)
+					],
+				);
+				elementArray.push(reserve);
+			}
 			const eogMessage = this.getEndgameMessage(this.score);
 			const modalEog = [
 				h('input',
diff --git a/public/javascripts/variants/Crazyhouse.js b/public/javascripts/variants/Crazyhouse.js
index 0ee4bd37..9cb07685 100644
--- a/public/javascripts/variants/Crazyhouse.js
+++ b/public/javascripts/variants/Crazyhouse.js
@@ -2,7 +2,7 @@ class CrazyhouseRules extends ChessRules
 {
 	initVariables(fen)
 	{
-		super.initVariables();
+		super.initVariables(fen);
 		// Also init reserves (used by the interface to show landing pieces)
 		const V = VariantRules;
 		this.reserve =
@@ -24,59 +24,111 @@ class CrazyhouseRules extends ChessRules
 				[V.QUEEN]: 0,
 			}
 		};
-		// It may be a continuation: adjust numbers of pieces according to captures + rebirths
-		// TODO
+		// May be a continuation: adjust numbers of pieces according to captures + rebirths
+		this.moves.forEach(m => {
+			if (m.vanish.length == 2)
+				this.reserve[m.appear[0].c][m.vanish[1].p]++;
+			else if (m.vanish.length == 0)
+				this.reserve[m.appear[0].c][m.appear[0].p]--;
+		});
 	}
 
 	// Used by the interface:
-	getReservePieces(color)
+	getReservePpath(color, index)
 	{
-		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],
-		};
+		return color + VariantRules.RESERVE_PIECES[index];
 	}
 
-	getPotentialMovesFrom([x,y])
+	// Put an ordering on reserve pieces
+	static get RESERVE_PIECES() {
+		const V = VariantRules;
+		return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+	}
+
+	getReserveMoves([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
-		});
+		const p = VariantRules.RESERVE_PIECES[y];
+		if (this.reserve[color][p] == 0)
+			return [];
+		let moves = [];
+		for (let i=0; i<sizeX; i++)
+		{
+			for (let j=0; j<sizeY; j++)
+			{
+				if (this.board[i][j] != VariantRules.EMPTY)
+				{
+					let mv = new Move({
+						appear: [
+							new PiPo({
+								x: i,
+								y: j,
+								c: color,
+								p: p
+							})
+						]
+					});
+					moves.push(mv);
+				}
+			}
+		}
 		return moves;
 	}
 
-	// TODO: condition "if this is reserve" --> special square !!! coordinates ??
-	getPossibleMovesFrom(sq)
+	getPotentialMovesFrom([x,y])
 	{
-		// Assuming color is right (already checked)
-		return this.filterValid( this.getPotentialMovesFrom(sq) );
+		const sizeX = VariantRules.size[0];
+		if (x < sizeX)
+			return super.getPotentialMovesFrom([x,y]);
+		// Reserves, outside of board: x == sizeX
+		return this.getReserveMoves([x,y]);
 	}
 
-	// TODO: add reserve moves
 	getAllValidMoves()
 	{
-
+		let moves = super.getAllValidMoves();
+		const color = this.turn;
+		const sizeX = VariantRules.size[0];
+		for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++)
+			moves = moves.concat(this.getReserveMoves([sizeX,i]));
+		return this.filterValid(moves);
 	}
 
-	// TODO: also
 	atLeastOneMove()
 	{
-
+		if (!super.atLeastOneMove())
+		{
+			const sizeX = VariantRules.size[0];
+			// Scan for reserve moves
+			for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++)
+			{
+				let moves = this.filterValid(this.getReserveMoves([sizeX,i]));
+				if (moves.length > 0)
+					return true;
+			}
+			return false;
+		}
+		return true;
 	}
 
-	// TODO: update reserve
 	updateVariables(move)
 	{
+		super.updateVariables(move);
+		const color = this.turn;
+		if (move.vanish.length==2)
+			this.reserve[color][move.appear[0].p]++;
+		if (move.vanish.length==0)
+			this.reserve[color][move.appear[0].p]--;
 	}
+
 	unupdateVariables(move)
 	{
+		super.unupdateVariables(move);
+		const color = this.turn;
+		if (move.vanish.length==2)
+			this.reserve[color][move.appear[0].p]--;
+		if (move.vanish.length==0)
+			this.reserve[color][move.appear[0].p]++;
 	}
 
 	static get SEARCH_DEPTH() { return 2; } //high branching factor
diff --git a/public/javascripts/variants/Extinction.js b/public/javascripts/variants/Extinction.js
index db330d9e..9be4b0d7 100644
--- a/public/javascripts/variants/Extinction.js
+++ b/public/javascripts/variants/Extinction.js
@@ -27,6 +27,37 @@ class ExtinctionRules extends ChessRules
 		};
 	}
 
+	getPotentialPawnMoves([x,y])
+	{
+		let moves = super.getPotentialPawnMoves([x,y]);
+		// Add potential promotions into king
+		const color = this.turn;
+		const V = VariantRules;
+		const [sizeX,sizeY] = V.size;
+		const shift = (color == "w" ? -1 : 1);
+		const lastRank = (color == "w" ? 0 : sizeX-1);
+
+		if (x+shift == lastRank)
+		{
+			// Normal move
+			if (this.board[x+shift][y] == V.EMPTY)
+				moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
+			// 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:V.KING}));
+			}
+			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:V.KING}));
+			}
+		}
+
+		return moves;
+	}
+
 	// TODO: verify this assertion
 	atLeastOneMove()
 	{
@@ -46,13 +77,24 @@ class ExtinctionRules extends ChessRules
 	updateVariables(move)
 	{
 		super.updateVariables(move);
-		if (move.vanish.length==2 && move.appear.length==1)
+		// Treat the promotion case: (not the capture part)
+		if (move.appear[0].p != move.vanish[0].p)
+		{
+			this.material[move.appear[0].c][move.appear[0].p]++;
+			this.material[move.appear[0].c][VariantRules.PAWN]--;
+		}
+		if (move.vanish.length==2 && move.appear.length==1) //capture
 			this.material[move.vanish[1].c][move.vanish[1].p]--;
 	}
 
 	unupdateVariables(move)
 	{
 		super.unupdateVariables(move);
+		if (move.appear[0].p != move.vanish[0].p)
+		{
+			this.material[move.appear[0].c][move.appear[0].p]--;
+			this.material[move.appear[0].c][VariantRules.PAWN]++;
+		}
 		if (move.vanish.length==2 && move.appear.length==1)
 			this.material[move.vanish[1].c][move.vanish[1].p]++;
 	}
@@ -73,14 +115,23 @@ class ExtinctionRules extends ChessRules
 			return "*";
 		}
 
-		return this.checkGameEnd();
+		return this.checkGameEnd(); //NOTE: currently unreachable...
+	}
+
+	checkGameEnd()
+	{
+		return this.turn == "w" ? "0-1" : "1-0";
 	}
 
 	// Very negative (resp. positive) if white (reps. black) pieces set is incomplete
 	evalPosition()
 	{
-		if (this.missAkind())
-			return (this.turn=="w"?-1:1) * VariantRules.INFINITY;
+		const color = this.turn;
+		if (Object.keys(this.material[color]).some(
+			p => { return this.material[color][p] == 0; }))
+		{
+			return (color=="w"?-1:1) * VariantRules.INFINITY;
+		}
 		return super.evalPosition();
 	}
 }
diff --git a/public/javascripts/variants/Magnetic.js b/public/javascripts/variants/Magnetic.js
index 844cc689..03912e7e 100644
--- a/public/javascripts/variants/Magnetic.js
+++ b/public/javascripts/variants/Magnetic.js
@@ -112,7 +112,8 @@ class MagneticRules extends ChessRules
 		// Scan move for pawn (max 1) on 8th rank
 		for (let i=1; i<move.appear.length; i++)
 		{
-			if (move.appear[i].p==V.PAWN && move.appear[i].c==color && move.appear[i].x==lastRank)
+			if (move.appear[i].p==V.PAWN && move.appear[i].c==color
+				&& move.appear[i].x==lastRank)
 			{
 				move.appear[i].p = V.ROOK;
 				moves.push(move);
diff --git a/public/javascripts/variants/Switching.js b/public/javascripts/variants/Switching.js
index a7d67879..cc2febd1 100644
--- a/public/javascripts/variants/Switching.js
+++ b/public/javascripts/variants/Switching.js
@@ -1,10 +1,73 @@
-//https://www.chessvariants.com/diffmove.dir/switching.html
 class SwitchingRules extends ChessRules
 {
-	//TODO:
-	// Move completion: promote switched pawns (as in Magnetic)
+	// Build switch move between squares x1,y1 and x2,y2
+	getSwitchMove_s([x1,y1],[x2,y2])
+	{
 
-	// To every piece potential moves: add switchings
+		const c = this.getColor(x1,y1); //same as color at square 2
+		const p1 = this.getPiece(x1,y1);
+		const p2 = this.getPiece(x2,y2);
+		let move = new Move({
+			appear: [
+				new PiPo({x:x2,y:y2,c:c,p:p1}),
+				new PiPo({x:x1,y:y1,c:c,p:p2})
+			],
+			vanish: [
+				new PiPo({x:x1,y:y1,c:c,p:p1}),
+				new PiPo({x:x2,y:y2,c:c,p:p2})
+			],
+			start: {x:x1,y:y1},
+			end: {x:x2,y:y2}
+		});
+		// Move completion: promote switched pawns (as in Magnetic)
+		const sizeX = VariantRules.size[0];
+		const lastRank = (c == "w" ? 0 : sizeX-1);
+		const V = VariantRules;
+		let moves = [];
+		if (p1==V.PAWN && x2==lastRank) //TODO: also the case p2==V.PAWN and x1==lastRank! see Magnetic chess
+		{
+			move.appear[0].p = V.ROOK;
+			moves.push(move);
+			for (let piece of [V.KNIGHT, V.BISHOP, V.QUEEN])
+			{
+				let cmove = JSON.parse(JSON.stringify(move));
+				cmove.appear[0].p = piece;
+				moves.push(cmove);
+			}
+		}
+		else //other cases
+			moves.push(move);
+		return moves;
+	}
 
-	// Prevent king switching if under check
+	getPotentialMovesFrom([x,y])
+	{
+		let moves = super.getPotentialMovesFrom([x,y]);
+		// Add switches:
+		const V = VariantRules;
+		const color = this.turn;
+		const piece = this.getPiece(x,y);
+		const [sizeX,sizeY] = V.size;
+		const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+		const kp = this.kingPos[color];
+		const oppCol = this.getOppCol(color);
+		for (let step of steps)
+		{
+			let [i,j] = [x+step[0],y+step[1]];
+			if (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j]!=V.EMPTY
+				&& this.getColor(i,j)==color && this.getPiece(i,j)!=piece
+				// No switching under check (theoretically non-king pieces could, but not)
+				&& !this.isAttacked(kp, [oppCol]))
+			{
+				let switchMove_s = this.getSwitchMove_s([x,y],[i,j]);
+				if (switchMove_s.length == 1)
+					moves.push(switchMove_s[0]);
+				else //promotion
+					moves = moves.concat(switchMove_s);
+			}
+		}
+		return moves;
+	}
+
+	static get SEARCH_DEPTH() { return 2; } //branching factor is quite high
 }
diff --git a/public/javascripts/variants/Zen.js b/public/javascripts/variants/Zen.js
index 17c922d2..31dfccd1 100644
--- a/public/javascripts/variants/Zen.js
+++ b/public/javascripts/variants/Zen.js
@@ -10,14 +10,14 @@ class ZenRules extends ChessRules
 	getSlideNJumpMoves([x,y], steps, oneStep)
 	{
 		const color = this.getColor(x,y);
-		var moves = [];
-		let [sizeX,sizeY] = VariantRules.size;
+		let moves = [];
+		const [sizeX,sizeY] = VariantRules.size;
 		outerLoop:
-		for (var loop=0; loop<steps.length; loop++)
+		for (let loop=0; loop<steps.length; loop++)
 		{
-			var step = steps[loop];
-			var i = x + step[0];
-			var j = y + step[1];
+			const step = steps[loop];
+			let i = x + step[0];
+			let j = y + step[1];
 			while (i>=0 && i<sizeX && j>=0 && j<sizeY
 				&& this.board[i][j] == VariantRules.EMPTY)
 			{
@@ -38,22 +38,21 @@ class ZenRules extends ChessRules
 	{
 		const color = this.getColor(x,y);
 		var moves = [];
-		var V = VariantRules;
-		var steps = asA != V.PAWN
-			? V.steps[asA]
+		const V = VariantRules;
+		const steps = asA != V.PAWN
+			? (asA==V.QUEEN ? V.steps[V.ROOK].concat(V.steps[V.BISHOP]) : V.steps[asA])
 			: color=='w' ? [[-1,-1],[-1,1]] : [[1,-1],[1,1]];
-		var oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king
-		let [sizeX,sizeY] = V.size;
-		let lastRank = (color == 'w' ? 0 : sizeY-1);
-		let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+		const oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king
+		const [sizeX,sizeY] = V.size;
+		const lastRank = (color == 'w' ? 0 : sizeY-1);
+		const promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
 		outerLoop:
-		for (var loop=0; loop<steps.length; loop++)
+		for (let loop=0; loop<steps.length; loop++)
 		{
-			var step = steps[loop];
-			var i = x + step[0];
-			var j = y + step[1];
-			while (i>=0 && i<sizeX && j>=0 && j<sizeY
-				&& this.board[i][j] == V.EMPTY)
+			const step = steps[loop];
+			let i = x + step[0];
+			let j = y + step[1];
+			while (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j] == V.EMPTY)
 			{
 				if (oneStep)
 					continue outerLoop;
@@ -173,7 +172,8 @@ class ZenRules extends ChessRules
 	getPotentialQueenMoves(sq)
 	{
 		const V = VariantRules;
-		let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK.concat(V.steps[V.BISHOP])]);
+		let noCaptures =
+			this.getSlideNJumpMoves(sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
 		let captures = this.findCaptures(sq);
 		return noCaptures.concat(captures);
 	}
diff --git a/views/rules/Crazyhouse.pug b/views/rules/Crazyhouse.pug
new file mode 100644
index 00000000..3928c309
--- /dev/null
+++ b/views/rules/Crazyhouse.pug
@@ -0,0 +1,33 @@
+p.boxed
+	| Every captured piece can be re-used by the capturer, landing it anywhere instead of moving a piece.
+
+h3 Specifications
+
+ul
+	li Chessboard: standard.
+	li Material: standard.
+	li Non-capturing moves: standard.
+	li Special moves: standard + rebirth.
+	li Captures: standard.
+	li End of game: standard.
+
+h3 Basics
+
+p
+	| Orthodox rules apply, with only one change:
+	| every time you capture a piece, your "reserve" of waiting pieces is augmented
+	| with this figure. At every move, you may choose to land one of your reserve
+	| pieces anywhere on the board (except last rank for pawns),
+	| instead of playing a regular move.
+
+p.
+	Note: when a promoted pawn is captured, capturing it put a pawn in the reserve,
+	not the promoted piece. This is to allow to gain material on last rank without
+	fear of giving a queen to the opponent.
+
+h3 Credits
+
+p
+	| This variant is very popular, a possible starting point is 
+	a(href="https://www.chessvariants.com/other.dir/crazyhouse.html") lichess.org
+	| .
diff --git a/views/rules/Extinction.pug b/views/rules/Extinction.pug
new file mode 100644
index 00000000..56725864
--- /dev/null
+++ b/views/rules/Extinction.pug
@@ -0,0 +1,34 @@
+p.boxed
+	| Win by eliminating all opponent pieces of the same type.
+
+h3 Specifications
+
+ul
+	li Chessboard: standard.
+	li Material: standard.
+	li Non-capturing moves: standard.
+	li Special moves: standard.
+	li Captures: standard.
+	li End of game: pieces extinction.
+
+h3 Basics
+
+p
+	| Standard rules apply, but the game ends when all pieces of a kind disappeared.
+	| Kings are treated as normal pieces (no royal power), but may castle -
+	| without any concern about checks.
+	| Pawns may promote into king.
+	| If all pieces of a kind disappear, the game is lost; except it's a pawns extinction
+	| which results in a non-pawn extinction by capturing and promoting on the last rank.
+
+h3 End of game
+
+p.
+	Win by eliminating all enemy pawns, or rooks, or knights, or bishops, or queen(s),
+	or king(s) (there may be several if promotions happened).
+
+h3 Credits
+
+p
+	a(href="https://www.chessvariants.com/winning.dir/extinction.html") Extinction chess 
+	| on chessvariants.com.
diff --git a/views/rules/Switching.pug b/views/rules/Switching.pug
new file mode 100644
index 00000000..3104734b
--- /dev/null
+++ b/views/rules/Switching.pug
@@ -0,0 +1,32 @@
+p.boxed
+	| In addition to standard moves, a piece can be exchanged with an adjacent friendly unit.
+
+h3 Specifications
+
+ul
+	li Chessboard: standard.
+	li Material: standard.
+	li Non-capturing moves: standard + switch.
+	li Special moves: standard.
+	li Captures: standard.
+	li End of game: standard.
+
+h3 Basics
+
+p
+	| Instead of a normal move, a piece may be exchanged with an adjacent friendly one.
+	| Switching can move pawns until rank 1 or final rank. In the first case a 2-squares,
+	| jump is possible, and in the second a promotion occurs.
+	| Switching must involves two different units.
+	| Switching while the king is under check is not allowed.
+
+p.
+	Note: if the king and rook are on two adjacent squares, castling and switching
+	from the king are triggered in the same way. Castling takes priority:
+	if you wanna switch, use the rook.
+
+h3 Credits
+
+p
+	a(href="https://www.chessvariants.com/diffmove.dir/switching.html") Switching chess 
+	| on chessvariants.com.
-- 
2.44.0