From 375ecdd1387e729f85ed114e82253469e4849869 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 24 Dec 2018 13:50:44 +0100
Subject: [PATCH] Prepare some more variants (unfinished)

---
 LICENSE                                   |  5 ++
 public/javascripts/base_rules.js          | 93 ++++++++++-------------
 public/javascripts/components/game.js     | 48 +++++++-----
 public/javascripts/variant.js             |  3 +
 public/javascripts/variants/Berolina.js   | 93 +++++++++++++++++++++++
 public/javascripts/variants/Dark.js       | 67 ++++++++++++++++
 public/javascripts/variants/Grand.js      |  7 +-
 public/javascripts/variants/Magnetic.js   | 61 ---------------
 public/javascripts/variants/Wildebeest.js |  7 +-
 public/stylesheets/variant.sass           |  3 +
 views/variant.pug                         |  5 +-
 11 files changed, 249 insertions(+), 143 deletions(-)
 create mode 100644 LICENSE
 create mode 100644 public/javascripts/variants/Berolina.js
 create mode 100644 public/javascripts/variants/Dark.js

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..695f90a7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,5 @@
+Copyright 2018 Benjamin AUder
+
+Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js
index 7f27e855..d85ff869 100644
--- a/public/javascripts/base_rules.js
+++ b/public/javascripts/base_rules.js
@@ -599,74 +599,65 @@ class ChessRules
 		const color = this.turn;
 		let moves = [];
 		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const shift = (color == "w" ? -1 : 1);
+		const shiftX = (color == "w" ? -1 : 1);
 		const firstRank = (color == 'w' ? sizeX-1 : 0);
 		const startRank = (color == "w" ? sizeX-2 : 1);
 		const lastRank = (color == "w" ? 0 : sizeX-1);
+		const pawnColor = this.getColor(x,y); //can be different for checkered
 
-		if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank)
+		if (x+shiftX >= 0 && x+shiftX < sizeX) //TODO: always true
 		{
-			// Normal moves
-			if (this.board[x+shift][y] == V.EMPTY)
+			const finalPieces = x + shiftX == lastRank
+				? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+				: [V.PAWN]
+			// One square forward
+			if (this.board[x+shiftX][y] == V.EMPTY)
 			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y]));
-				// 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)
+				for (let piece of finalPieces)
+				{
+					moves.push(this.getBasicMove([x,y], [x+shiftX,y],
+						{c:pawnColor,p:piece}));
+				}
+				// Next condition because pawns on 1st rank can generally jump
+				if ([startRank,firstRank].includes(x)
+					&& this.board[x+2*shiftX][y] == V.EMPTY)
 				{
 					// Two squares jump
-					moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
+					moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
 				}
 			}
 			// Captures
-			if (y>0 && this.board[x+shift][y-1] != V.EMPTY
-				&& this.canTake([x,y], [x+shift,y-1]))
-			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y-1]));
-			}
-			if (y<sizeY-1 && this.board[x+shift][y+1] != V.EMPTY
-				&& this.canTake([x,y], [x+shift,y+1]))
+			for (let shiftY of [-1,1])
 			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y+1]));
-			}
-		}
-
-		if (x+shift == lastRank)
-		{
-			// Promotion
-			const pawnColor = this.getColor(x,y); //can be different for checkered
-			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:pawnColor,p:p}));
-				// Captures
-				if (y>0 && this.board[x+shift][y-1] != V.EMPTY
-					&& this.canTake([x,y], [x+shift,y-1]))
+				if (y + shiftY >= 0 && y + shiftY < sizeY
+					&& this.board[x+shiftX][y+shiftY] != V.EMPTY
+					&& this.canTake([x,y], [x+shiftX,y+shiftY]))
 				{
-					moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:pawnColor,p:p}));
-				}
-				if (y<sizeY-1 && this.board[x+shift][y+1] != V.EMPTY
-					&& this.canTake([x,y], [x+shift,y+1]))
-				{
-					moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:pawnColor,p:p}));
+					for (let piece of finalPieces)
+					{
+						moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+							{c:pawnColor,p:piece}));
+					}
 				}
-			});
+			}
 		}
 
-		// En passant
-		const Lep = this.epSquares.length;
-		const epSquare = this.epSquares[Lep-1]; //always at least one element
-		if (!!epSquare && epSquare.x == x+shift && Math.abs(epSquare.y - y) == 1)
+		if (V.HasEnpassant)
 		{
-			const epStep = epSquare.y - y;
-			let 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);
+			// En passant
+			const Lep = this.epSquares.length;
+			const epSquare = this.epSquares[Lep-1]; //always at least one element
+			if (!!epSquare && epSquare.x == x+shiftX && Math.abs(epSquare.y - y) == 1)
+			{
+				let enpassantMove = this.getBasicMove([x,y], [epSquare.x,epSquare.y]);
+				enpassantMove.vanish.push({
+					x: x,
+					y: epSquare.y,
+					p: 'p',
+					c: this.getColor(x,epSquare.y)
+				});
+				moves.push(enpassantMove);
+			}
 		}
 
 		return moves;
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 88b193ff..b308aa89 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -63,7 +63,7 @@ Vue.component('my-game', {
 			},
 			[h('i', { 'class': { "material-icons": true } }, "accessibility")])
 		);
-		if (["idle","chat","computer"].includes(this.mode))
+		if (variant!="Dark" && ["idle","chat","computer"].includes(this.mode))
 		{
 			actionArray.push(
 				h('button',
@@ -80,7 +80,7 @@ Vue.component('my-game', {
 				[h('i', { 'class': { "material-icons": true } }, "computer")])
 			);
 		}
-		if (["idle","chat","friend"].includes(this.mode))
+		if (variant!="Dark" && ["idle","chat","friend"].includes(this.mode))
 		{
 			actionArray.push(
 				h('button',
@@ -298,7 +298,8 @@ Vue.component('my-game', {
 						_.range(sizeY).map(j => {
 							let cj = (this.mycolor=='w' ? j : sizeY-j-1);
 							let elems = [];
-							if (this.vr.board[ci][cj] != VariantRules.EMPTY)
+							if (this.vr.board[ci][cj] != VariantRules.EMPTY && (variant!="Dark"
+								|| this.score!="*" || this.vr.isEnlightened(ci,cj,this.mycolor)))
 							{
 								elems.push(
 									h(
@@ -342,6 +343,8 @@ Vue.component('my-game', {
 										'light-square': (i+j)%2==0,
 										'dark-square': (i+j)%2==1,
 										[this.color]: true,
+										'in-shadow': variant=="Dark" && this.score=="*"
+											&& !this.vr.isEnlightened(ci,cj,this.mycolor),
 										'highlight': showLight && !!lm && _.isMatch(lm.end, {x:ci,y:cj}),
 										'incheck': showLight && incheckSq[ci][cj],
 									},
@@ -919,24 +922,27 @@ Vue.component('my-game', {
 					)
 				);
 			}
-			// Show current FEN
-			elementArray.push(
-				h('div',
-					{
-						attrs: { id: "fen-div" },
-						"class": { "section-content": true },
-					},
-					[
-						h('p',
-							{
-								attrs: { id: "fen-string" },
-								domProps: { innerHTML: this.vr.getBaseFen() },
-								"class": { "text-center": true },
-							}
-						)
-					]
-				)
-			);
+			if (variant != "Dark" || this.score!="*")
+			{
+				// Show current FEN
+				elementArray.push(
+					h('div',
+						{
+							attrs: { id: "fen-div" },
+							"class": { "section-content": true },
+						},
+						[
+							h('p',
+								{
+									attrs: { id: "fen-string" },
+									domProps: { innerHTML: this.vr.getBaseFen() },
+									"class": { "text-center": true },
+								}
+							)
+						]
+					)
+				);
+			}
 		}
 		return h(
 			'div',
diff --git a/public/javascripts/variant.js b/public/javascripts/variant.js
index a7d18200..819bf90f 100644
--- a/public/javascripts/variant.js
+++ b/public/javascripts/variant.js
@@ -21,5 +21,8 @@ new Vue({
 			if (!!menuToggle)
 				menuToggle.checked = false;
 		},
+		notDark: function() {
+			return variant != "Dark";
+		},
 	},
 });
diff --git a/public/javascripts/variants/Berolina.js b/public/javascripts/variants/Berolina.js
new file mode 100644
index 00000000..c556f517
--- /dev/null
+++ b/public/javascripts/variants/Berolina.js
@@ -0,0 +1,93 @@
+class BerolinaRules extends ChessRules
+{
+	// En-passant after 2-sq jump
+	getEpSquare(moveOrSquare)
+	{
+		if (!moveOrSquare)
+			return undefined;
+		if (typeof moveOrSquare === "string")
+		{
+			const square = moveOrSquare;
+			if (square == "-")
+				return undefined;
+			return V.SquareToCoords(square);
+		}
+		// Argument is a move:
+		const move = moveOrSquare;
+		const [sx,ex,sy] = [move.start.x,move.end.x,move.start.y];
+		if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
+		{
+			return {
+				x: ex,
+				y: (move.end.y + sy)/2
+			};
+		}
+		return undefined; //default
+	}
+
+	// Special pawn rules: promotions to captured friendly pieces,
+	// optional on ranks 8-9 and mandatory on rank 10.
+	getPotentialPawnMoves([x,y])
+	{
+		const color = this.turn;
+		let moves = [];
+		const [sizeX,sizeY] = [V.size.x,V.size.y];
+		const shiftX = (color == "w" ? -1 : 1);
+		const firstRank = (color == 'w' ? sizeX-1 : 0);
+		const startRank = (color == "w" ? sizeX-2 : 1);
+		const lastRank = (color == "w" ? 0 : sizeX-1);
+
+		if (x+shiftX >= 0 && x+shiftX < sizeX) //TODO: always true
+		{
+			const finalPieces = x + shiftX == lastRank
+				? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+				: [V.PAWN]
+			// One square diagonally
+			for (let shiftY of [-1,1])
+			{
+				if (this.board[x+shiftX][y+shiftY] == V.EMPTY)
+				{
+					for (let piece of finalPieces)
+					{
+						moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+							{c:pawnColor,p:piece}));
+					}
+					if (x == startRank && this.board[x+2*shiftX][y] == V.EMPTY)
+					{
+						// Two squares jump
+						moves.push(this.getBasicMove([x,y], [x+2*shiftX,y+2*shiftY]);
+					}
+				}
+			}
+			// Capture
+			if (this.board[x+shiftX][y] != V.EMPTY
+				&& this.canTake([x,y], [x+shiftX,y]))
+			{
+				for (let piece of finalPieces)
+				{
+					moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+						{c:pawnColor,p:piece}));
+				}
+			}
+		}
+
+		// En passant
+		const Lep = this.epSquares.length;
+		const epSquare = this.epSquares[Lep-1]; //always at least one element
+		if (!!epSquare && epSquare.x == x+shiftX && epSquare.y == y)
+		{
+			let enpassantMove = this.getBasicMove([x,y], [x+shift,y]);
+			enpassantMove.vanish.push({
+				x: epSquare.x,
+				y: y,
+				p: 'p',
+				c: this.getColor(epSquare.x,y)
+			});
+			moves.push(enpassantMove);
+		}
+
+		return moves;
+	}
+}
+
+const VariantRules = BerolinaRules;
diff --git a/public/javascripts/variants/Dark.js b/public/javascripts/variants/Dark.js
new file mode 100644
index 00000000..9cf50513
--- /dev/null
+++ b/public/javascripts/variants/Dark.js
@@ -0,0 +1,67 @@
+class Chess960Rules extends ChessRules
+{
+	// Standard rules, in the shadow
+	setOtherVariables(fen)
+	{
+		super.setOtherVariables(fen);
+		const [sizeX,sizeY] = {V.size.x,V.size.y};
+		this.enlightened = {
+			"w": doubleArray(sizeX,sizeY,false),
+			"b": doubleArray(sizeX,sizeY,false)
+		};
+		setup enlightened: squares reachable by each side (TODO: one side would be enough)
+	}
+
+	isEnlightened(x, y, color)
+	{
+		//TODO: artificlaly change turn
+	}
+
+	getAllPotentialMoves()
+	{
+		let moves = []; //TODO
+	}
+
+	atLeastOneMove()
+	{
+		if (this.kingPos[this.turn][0] < 0)
+			return false;
+		return true; //TODO: is it right?
+	}
+
+	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;
+	}
+
+	// NOTE: no (un)updateVariables() because no computer mode
+	// --> but isEnlightened() should have its variable updated
+	// --> in fact an array is enough (no need for a function)
+	// recomputed after every play/undo (although there are no undo here for now)
+
+	checkGameEnd()
+	{
+		// No valid move: our king disappeared
+		return this.turn == "w" ? "0-1" : "1-0";
+	}
+
+	static get THRESHOLD_MATE()
+	{
+		return 500; //checkmates evals may be slightly below 1000
+	}
+}
+
+const VariantRules = DarkRules;
diff --git a/public/javascripts/variants/Grand.js b/public/javascripts/variants/Grand.js
index 4271a693..7248f578 100644
--- a/public/javascripts/variants/Grand.js
+++ b/public/javascripts/variants/Grand.js
@@ -218,13 +218,12 @@ class GrandRules extends ChessRules
 				// TODO: some redundant checks
 				if (epsq.x == x+shift && Math.abs(epsq.y - y) == 1)
 				{
-					let epStep = epsq.y - y;
-					var enpassantMove = this.getBasicMove([x,y], [x+shift,y+epStep]);
+					var enpassantMove = this.getBasicMove([x,y], [x+shift,epsq.y]);
 					enpassantMove.vanish.push({
 						x: x,
-						y: y+epStep,
+						y: epsq.y,
 						p: 'p',
-						c: this.getColor(x,y+epStep)
+						c: this.getColor(x,epsq.y)
 					});
 					moves.push(enpassantMove);
 				}
diff --git a/public/javascripts/variants/Magnetic.js b/public/javascripts/variants/Magnetic.js
index c4995dd3..8480d799 100644
--- a/public/javascripts/variants/Magnetic.js
+++ b/public/javascripts/variants/Magnetic.js
@@ -16,67 +16,6 @@ class MagneticRules extends ChessRules
 		return moves;
 	}
 
-	getPotentialPawnMoves([x,y])
-	{
-		const color = this.turn;
-		let moves = [];
-		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const shift = (color == "w" ? -1 : 1);
-		const firstRank = (color == 'w' ? sizeX-1 : 0);
-		const startRank = (color == "w" ? sizeX-2 : 1);
-		const lastRank = (color == "w" ? 0 : sizeX-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 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.board[x+shift][y-1] != V.EMPTY
-				&& this.canTake([x,y], [x+shift,y-1]))
-			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y-1]));
-			}
-			if (y<sizeY-1 && this.board[x+shift][y+1] != V.EMPTY
-				&& this.canTake([x,y], [x+shift,y+1]))
-			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y+1]));
-			}
-		}
-
-		if (x+shift == lastRank)
-		{
-			// Promotion
-			const pawnColor = this.getColor(x,y); //can be different for checkered
-			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:pawnColor,p:p}));
-				// Captures
-				if (y>0 && this.board[x+shift][y-1] != V.EMPTY
-					&& this.canTake([x,y], [x+shift,y-1]))
-				{
-					moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:pawnColor,p:p}));
-				}
-				if (y<sizeY-1 && this.board[x+shift][y+1] != V.EMPTY
-					&& this.canTake([x,y], [x+shift,y+1]))
-				{
-					moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:pawnColor,p:p}));
-				}
-			});
-		}
-		return moves; //no en-passant
-	}
-
 	// Complete a move with magnetic actions
 	// TODO: job is done multiple times for (normal) promotions.
 	applyMagneticLaws(move)
diff --git a/public/javascripts/variants/Wildebeest.js b/public/javascripts/variants/Wildebeest.js
index 783eb440..ef3779ad 100644
--- a/public/javascripts/variants/Wildebeest.js
+++ b/public/javascripts/variants/Wildebeest.js
@@ -159,13 +159,12 @@ class WildebeestRules extends ChessRules
 				// TODO: some redundant checks
 				if (epsq.x == x+shift && Math.abs(epsq.y - y) == 1)
 				{
-					let epStep = epsq.y - y;
-					var enpassantMove = this.getBasicMove([x,y], [x+shift,y+epStep]);
+					var enpassantMove = this.getBasicMove([x,y], [x+shift,epsq.y]);
 					enpassantMove.vanish.push({
 						x: x,
-						y: y+epStep,
+						y: epsq.y,
 						p: 'p',
-						c: this.getColor(x,y+epStep)
+						c: this.getColor(x,epsq.y)
 					});
 					moves.push(enpassantMove);
 				}
diff --git a/public/stylesheets/variant.sass b/public/stylesheets/variant.sass
index ecb1bc9c..0ebad8c3 100644
--- a/public/stylesheets/variant.sass
+++ b/public/stylesheets/variant.sass
@@ -221,6 +221,9 @@ img.ghost
 .highlight
   background-color: #00cc66 !important
 
+.in-shadow
+  opacity: 0.5
+
 .incheck
   background-color: #cc3300 !important
 
diff --git a/views/variant.pug b/views/variant.pug
index 23e70766..7f01b910 100644
--- a/views/variant.pug
+++ b/views/variant.pug
@@ -39,7 +39,7 @@ block content
 							=translations["Rules"]
 						a(href="#play" @click="setDisplay('play')")
 							=translations["Play"]
-						a(href="#problems" @click="setDisplay('problems')") 
+						a(href="#problems" v-if="notDark()" @click="setDisplay('problems')") 
 							=translations["Problems"]
 					#flagMenu.clickable(
 							onClick="document.getElementById('modalLang').checked=true")
@@ -51,7 +51,8 @@ block content
 		.row
 			my-rules(v-show="display=='rules'")
 			my-game(v-show="display=='play'" v-bind:problem="problem")
-			my-problems(v-show="display=='problems'" v-on:show-problem="showProblem($event)")
+			my-problems(v-if="notDark()" v-show="display=='problems'"
+				v-on:show-problem="showProblem($event)")
 
 block javascripts
 	script(src="/javascripts/utils/misc.js")
-- 
2.44.0