-#$# git-fat 25d1d22208da0bc58bf3076bfe684bbcb98894b2 16384
+#$# git-fat 039e3c0aadcb97c144dc2d12a35aeb83b4a3849e 16384
//////////////
// MISC UTILS
+ static get HasFlags() { return true; } //some variants don't have flags
+
+ static get HasEnpassant() { return true; } //some variants don't have ep.
+
// Path to pieces
static getPpath(b)
{
{
const fenParsed = V.ParseFen(fen);
// 1) Check position
- const position = fenParsed.position;
+ if (!V.IsGoodPosition(fenParsed.position))
+ return false;
+ // 2) Check turn
+ if (!fenParsed.turn || !["w","b"].includes(fenParsed.turn))
+ return false;
+ // 3) Check flags
+ if (V.HasFlags && (!fenParsed.flags || !V.IsGoodFlags(fenParsed.flags)))
+ return false;
+ // 4) Check enpassant
+ if (V.HasEnpassant)
+ {
+ if (!fenParsed.enpassant)
+ return false;
+ if (fenParsed.enpassant != "-")
+ {
+ const ep = V.SquareToCoords(fenParsed.enpassant);
+ if (ep.y < 0 || ep.y > V.size.y || isNaN(ep.x) || ep.x < 0 || ep.x > V.size.x)
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Is position part of the FEN a priori correct?
+ static IsGoodPosition(position)
+ {
+ if (position.length == 0)
+ return false;
const rows = position.split("/");
if (rows.length != V.size.x)
return false;
if (sumElts != V.size.y)
return false;
}
- // 2) Check flags (if present)
- if (!!fenParsed.flags && !V.IsGoodFlags(fenParsed.flags))
- return false;
- // 3) Check turn (if present)
- if (!!fenParsed.turn && !["w","b"].includes(fenParsed.turn))
- return false;
- // 4) Check enpassant (if present)
- if (!!fenParsed.enpassant)
- {
- const ep = V.SquareToCoords(fenParsed.enpassant);
- if (ep.y < 0 || ep.y > V.size.y || isNaN(ep.x) || ep.x < 0 || ep.x > V.size.x)
- return false;
- }
return true;
}
return !!flags.match(/^[01]{4,4}$/);
}
+ // 3 --> d (column letter from number)
+ static GetColumn(colnum)
+ {
+ return String.fromCharCode(97 + colnum);
+ }
+
// a4 --> {x:3,y:0}
static SquareToCoords(sq)
{
// {x:0,y:4} --> e8
static CoordsToSquare(coords)
{
- return String.fromCharCode(97 + coords.y) + (V.size.x - coords.x);
+ return V.GetColumn(coords.y) + (V.size.x - coords.x);
}
// Aggregates flags into one object
const square = moveOrSquare;
if (square == "-")
return undefined;
- return {
- x: square[0].charCodeAt()-97,
- y: V.size.x-parseInt(square[1])
- };
+ return V.SquareToCoords(square);
}
// Argument is a move:
const move = moveOrSquare;
static ParseFen(fen)
{
const fenParts = fen.split(" ");
- return {
+ let res =
+ {
position: fenParts[0],
turn: fenParts[1],
- flags: fenParts[2],
- enpassant: fenParts[3],
};
+ let nextIdx = 2;
+ if (V.hasFlags)
+ Object.assign(res, {flags: fenParts[nextIdx++]});
+ if (V.HasEnpassant)
+ Object.assign(res, {enpassant: fenParts[nextIdx]});
+ return res;
}
// Return current fen (game state)
getFen()
{
- return this.getBaseFen() + " " + this.turn + " " +
- this.getFlagsFen() + " " + this.getEnpassantFen();
+ return this.getBaseFen() + " " + this.turn +
+ (V.HasFlags ? (" " + this.getFlagsFen()) : "") +
+ (V.HasEnpassant ? (" " + this.getEnpassantFen()) : "");
}
// Position part of the FEN string
getEnpassantFen()
{
const L = this.epSquares.length;
- if (L == 0)
+ if (!this.epSquares[L-1])
return "-"; //no en-passant
return V.CoordsToSquare(this.epSquares[L-1]);
}
this.setOtherVariables(fen);
}
- // Some additional variables from FEN (variant dependant)
- setOtherVariables(fen)
+ // Scan board for kings and rooks positions
+ scanKingsRooks(fen)
{
- // Set flags and enpassant:
- const parsedFen = V.ParseFen(fen);
- this.setFlags(fenParsed.flags);
- this.epSquares = [ V.SquareToCoords(parsedFen.enpassant) ];
- // Search for king and rooks positions:
this.INIT_COL_KING = {'w':-1, 'b':-1};
this.INIT_COL_ROOK = {'w':[-1,-1], 'b':[-1,-1]};
this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; //squares of white and black king
- const fenRows = parsedFen.position.split("/");
+ const fenRows = V.ParseFen(fen).position.split("/");
for (let i=0; i<fenRows.length; i++)
{
let k = 0; //column index on board
}
}
+ // Some additional variables from FEN (variant dependant)
+ setOtherVariables(fen)
+ {
+ // Set flags and enpassant:
+ const parsedFen = V.ParseFen(fen);
+ if (V.HasFlags)
+ this.setFlags(fenParsed.flags);
+ if (V.HasEnpassant)
+ {
+ const epSq = parsedFen.enpassant != "-"
+ ? V.SquareToCoords(parsedFen.enpassant)
+ : undefined;
+ this.epSquares = [ epSq ];
+ }
+ // Search for king and rooks positions:
+ this.scanKingsRooks(fen);
+ }
+
/////////////////////
// GETTERS & SETTERS
// En passant
const Lep = this.epSquares.length;
- const epSquare = (Lep>0 ? this.epSquares[Lep-1] : undefined);
+ const epSquare = this.epSquares[Lep-1]; //always at least one element
if (!!epSquare && epSquare.x == x+shift && Math.abs(epSquare.y - y) == 1)
{
const epStep = epSquare.y - y;
if (!!ingame)
move.notation = [this.getNotation(move), this.getLongNotation(move)];
- move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
+ if (V.HasFlags)
+ move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
this.updateVariables(move);
this.moves.push(move);
- this.epSquares.push( this.getEpSquare(move) );
+ if (V.HasEnpassant)
+ this.epSquares.push( this.getEpSquare(move) );
this.turn = this.getOppCol(this.turn);
V.PlayOnBoard(this.board, move);
{
V.UndoOnBoard(this.board, move);
this.turn = this.getOppCol(this.turn);
- this.epSquares.pop();
+ if (V.HasEnpassant)
+ this.epSquares.pop();
this.moves.pop();
this.unupdateVariables(move);
- this.disaggregateFlags(JSON.parse(move.flags));
+ if (V.HasFlags)
+ this.disaggregateFlags(JSON.parse(move.flags));
// DEBUG:
// if (this.getFen() != this.states[this.states.length-1])
return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
}
- static get PIECES() {
+ static get PIECES()
+ {
return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES));
}
- initVariables(fen)
+ setOtherVariables(fen)
{
- super.initVariables(fen);
- const fenParts = fen.split(" ");
- const position = fenParts[0].split("/");
+ super.setOtherVariables(fen);
+ const rows = V.ParseFen(fen).position.split("/");
if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0)
{
- // INIT_COL_XXX won't be used, so no need to set them for Alice kings
- for (let i=0; i<position.length; i++)
+ // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
+ for (let i=0; i<rows.length; i++)
{
let k = 0; //column index on board
- for (let j=0; j<position[i].length; j++)
+ for (let j=0; j<rows[i].length; j++)
{
- switch (position[i].charAt(j))
+ switch (rows[i].charAt(j))
{
case 'l':
this.kingPos['b'] = [i,k];
this.kingPos['w'] = [i,k];
break;
default:
- let num = parseInt(position[i].charAt(j));
+ const num = parseInt(rows[i].charAt(j));
if (!isNaN(num))
k += (num-1);
}
return res;
}
- static get VALUES() {
+ static get VALUES()
+ {
return Object.assign(
ChessRules.VALUES,
{
return "0-0";
}
- const finalSquare = String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x);
+ const finalSquare = V.CoordsToSquare(move.end);
const piece = this.getPiece(move.start.x, move.start.y);
const captureMark = (move.vanish.length > move.appear.length ? "x" : "");
let pawnMark = "";
if (["p","s"].includes(piece) && captureMark.length == 1)
- pawnMark = String.fromCharCode(97 + move.start.y); //start column
+ pawnMark = V.GetColumn(move.start.y); //start column
// Piece or pawn movement
let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
static get ANTIKING() { return 'a'; }
- static get PIECES() {
+ static get PIECES()
+ {
return ChessRules.PIECES.concat([V.ANTIKING]);
}
- initVariables(fen)
+ setOtherVariables(fen)
{
- super.initVariables(fen);
+ super.setOtherVariables(fen);
this.antikingPos = {'w':[-1,-1], 'b':[-1,-1]};
- const position = fen.split(" ")[0].split("/");
- for (let i=0; i<position.length; i++)
+ const rows = V.ParseFen(fen).position.split("/");
+ for (let i=0; i<rows.length; i++)
{
let k = 0;
- for (let j=0; j<position[i].length; j++)
+ for (let j=0; j<rows[i].length; j++)
{
- switch (position[i].charAt(j))
+ switch (rows[i].charAt(j))
{
case 'a':
this.antikingPos['b'] = [i,k];
this.antikingPos['w'] = [i,k];
break;
default:
- let num = parseInt(position[i].charAt(j));
+ const num = parseInt(rows[i].charAt(j));
if (!isNaN(num))
k += (num-1);
}
return pieces["b"].join("") + "/" + ranks23_black +
"/8/8/" +
ranks23_white + "/" + pieces["w"].join("").toUpperCase() +
- " 1111 w";
+ " w 1111";
}
}
{
return b[0]=='c' ? "Checkered/"+b : b;
}
+
static board2fen(b)
{
const checkered_codes = {
return checkered_codes[b[1]];
return ChessRules.board2fen(b);
}
+
static fen2board(f)
{
// Tolerate upper-case versions of checkered pieces (why not?)
return ChessRules.fen2board(f);
}
- static get PIECES() {
+ static get PIECES()
+ {
return ChessRules.PIECES.concat(['s','t','u','c','o']);
}
}
}
- // Aggregates flags into one object
- get flags() {
+ aggregateFlags()
+ {
return [this.castleFlags, this.pawnFlags];
}
- // Reverse operation
- parseFlags(flags)
+ disaggregateFlags(flags)
{
this.castleFlags = flags[0];
this.pawnFlags = flags[1];
{
this.play(move);
const color = this.turn;
- this.moves.push(move); //artifically change turn, for checkered pawns (TODO)
+ // Artifically change turn, for checkered pawns
+ this.turn = this.getOppCol(color);
const kingAttacked = this.isAttacked(
this.kingPos[color], [this.getOppCol(color),'c']);
let res = kingAttacked
- ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ] //need to duplicate!
- : [ ];
- this.moves.pop();
+ ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
+ : [];
+ this.turn = color;
this.undo(move);
return res;
}
{
const randFen = ChessRules.GenRandInitFen();
// Add 16 pawns flags:
- return randFen.replace(" 1111 w", " 11111111111111111111 w");
+ return randFen.replace(" w 1111", " w 11111111111111111111");
}
getFlagsFen()
}
// Translate final square
- let finalSquare =
- String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x);
+ const finalSquare = V.CoordsToSquare(move.end);
- let piece = this.getPiece(move.start.x, move.start.y);
+ const piece = this.getPiece(move.start.x, move.start.y);
if (piece == V.PAWN)
{
// Pawn move
if (move.vanish.length > 1)
{
// Capture
- let startColumn = String.fromCharCode(97 + move.start.y);
+ const startColumn = V.GetColumn(move.start.y);
notation = startColumn + "x" + finalSquare +
"=" + move.appear[0].p.toUpperCase();
}
class CrazyhouseRules extends ChessRules
{
- initVariables(fen)
+ static IsGoodFen(fen)
{
- super.initVariables(fen);
- // Also init reserves (used by the interface to show landing pieces)
+ if (!ChessRules.IsGoodFen(fen))
+ return false;
+ const fenParsed = V.ParseFen(fen);
+ // 5) Check reserves
+ if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
+ return false;
+ // 6) Check promoted array
+ if (!fenParsed.promoted)
+ return false;
+ fenpromoted = fenParsed.promoted;
+ if (fenpromoted == "-")
+ return true; //no promoted piece on board
+ const squares = fenpromoted.split(",");
+ for (let square of squares)
+ {
+ const c = V.SquareToCoords(square);
+ if (c.y < 0 || c.y > V.size.y || isNaN(c.x) || c.x < 0 || c.x > V.size.x)
+ return false;
+ }
+ return true;
+ }
+
+ static GenRandInitFen()
+ {
+ const fen = ChessRules.GenRandInitFen();
+ return fen.replace(" w 1111", " w 1111 0000000000 -");
+ }
+
+ getFen()
+ {
+ return super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen();
+ }
+
+ getReserveFen()
+ {
+ let counts = _.map(_.range(10), 0);
+ for (let i=0; i<V.PIECES.length; i++)
+ {
+ counts[i] = this.reserve["w"][V.PIECES[i]];
+ counts[5+i] = this.reserve["b"][V.PIECES[i]];
+ }
+ return counts.join("");
+ }
+
+ getPromotedFen()
+ {
+ let res = "";
+ for (let i=0; i<V.size.x; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ {
+ if (this.promoted[i][j])
+ res += V.CoordsToSquare({x:i,y:j});
+ }
+ }
+ if (res.length > 0)
+ res = res.slice(0,-1); //remove last comma
+ return res;
+ }
+
+ setOtherVariables(fen)
+ {
+ super.setOtherVariables(fen);
+ const fenParsed = V.ParseFen(fen);
+ // Also init reserves (used by the interface to show landable pieces)
this.reserve =
{
"w":
{
- [V.PAWN]: 0,
- [V.ROOK]: 0,
- [V.KNIGHT]: 0,
- [V.BISHOP]: 0,
- [V.QUEEN]: 0,
+ [V.PAWN]: parseInt(fenParsed.reserve[0]),
+ [V.ROOK]: parseInt(fenParsed.reserve[1]),
+ [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
+ [V.BISHOP]: parseInt(fenParsed.reserve[3]),
+ [V.QUEEN]: parseInt(fenParsed.reserve[4]),
},
"b":
{
- [V.PAWN]: 0,
- [V.ROOK]: 0,
- [V.KNIGHT]: 0,
- [V.BISHOP]: 0,
- [V.QUEEN]: 0,
+ [V.PAWN]: parseInt(fenParsed.reserve[5]),
+ [V.ROOK]: parseInt(fenParsed.reserve[6]),
+ [V.KNIGHT]: parseInt(fenParsed.reserve[7]),
+ [V.BISHOP]: parseInt(fenParsed.reserve[8]),
+ [V.QUEEN]: parseInt(fenParsed.reserve[9]),
}
};
this.promoted = doubleArray(V.size.x, V.size.y, false);
- // May be a continuation: adjust numbers of pieces in reserve + promoted pieces
- this.moves.forEach(m => { this.updateVariables(m); });
+ for (let square of fenParsd.promoted.split(","))
+ {
+ const [x,y] = V.SquareToCoords(square);
+ promoted[x][y] = true;
+ }
}
getColor(i,j)
return (i==V.size.x ? "w" : "b");
return this.board[i][j].charAt(0);
}
+
getPiece(i,j)
{
if (i >= V.size.x)
}
// Ordering on reserve pieces
- static get RESERVE_PIECES() {
+ static get RESERVE_PIECES()
+ {
return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
}
// Rebirth:
const piece =
(move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "");
- const finalSquare =
- String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x);
- return piece + "@" + finalSquare;
+ return piece + "@" + V.CoordsToSquare(move.end);
}
getLongNotation(move)
{
if (move.vanish.length > 0)
return super.getLongNotation(move);
- const finalSquare =
- String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x);
- return "@" + finalSquare;
+ return "@" + V.CoordsToSquare(move.end);
}
}
class ExtinctionRules extends ChessRules
{
- initVariables(fen)
+ setOtherVariables(fen)
{
- super.initVariables(fen);
+ super.setOtherVariables(fen);
+ const pos = V.ParseFen(fen).position;
+ // NOTE: no need for safety "|| []", because each piece type must be present
+ // (otherwise game is already over!)
this.material =
{
"w":
{
- [V.KING]: 1,
- [V.QUEEN]: 1,
- [V.ROOK]: 2,
- [V.KNIGHT]: 2,
- [V.BISHOP]: 2,
- [V.PAWN]: 8
+ [V.KING]: pos.match(/K/g).length,
+ [V.QUEEN]: pos.match(/Q/g).length,
+ [V.ROOK]: pos.match(/R/g).length,
+ [V.KNIGHT]: pos.match(/N/g).length,
+ [V.BISHOP]: pos.match(/B/g).length,
+ [V.PAWN]: pos.match(/P/g).length
},
"b":
{
- [V.KING]: 1,
- [V.QUEEN]: 1,
- [V.ROOK]: 2,
- [V.KNIGHT]: 2,
- [V.BISHOP]: 2,
- [V.PAWN]: 8
+ [V.KING]: pos.match(/k/g).length,
+ [V.QUEEN]: pos.match(/q/g).length,
+ [V.ROOK]: pos.match(/r/g).length,
+ [V.KNIGHT]: pos.match(/n/g).length,
+ [V.BISHOP]: pos.match(/b/g).length,
+ [V.PAWN]: pos.match(/p/g).length
}
};
}
checkGameEnd()
{
- return this.turn == "w" ? "0-1" : "1-0";
+ return (this.turn == "w" ? "0-1" : "1-0");
}
evalPosition()
return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
}
- initVariables(fen)
+ static IsGoodFen(fen)
{
- super.initVariables(fen);
- this.captures = { "w": {}, "b": {} }; //for promotions
+ if (!ChessRules.IsGoodFen(fen))
+ return false;
+ const fenParsed = V.ParseFen(fen);
+ // 5) Check captures
+ if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{10,10}$/))
+ return false;
+ return true;
+ }
+
+ static GenRandInitFen()
+ {
+ const fen = ChessRules.GenRandInitFen();
+ return fen.replace(" w 1111", " w 1111 0000000000");
+ }
+
+ getFen()
+ {
+ return super.getFen() + " " + this.getCapturedFen();
+ }
+
+ getCapturedFen()
+ {
+ let counts = _.map(_.range(10), 0);
+ for (let i=0; i<V.PIECES.length; i++)
+ {
+ counts[i] = this.captured["w"][V.PIECES[i]];
+ counts[5+i] = this.captured["b"][V.PIECES[i]];
+ }
+ return counts.join("");
+ }
+
+ setOtherVariables(fen)
+ {
+ super.setOtherVariables(fen);
+ const fenParsed = V.ParseFen(fen);
+ // Initialize captured pieces' counts from FEN
+ this.captured =
+ {
+ "w":
+ {
+ [V.PAWN]: parseInt(fenParsed.captured[0]),
+ [V.ROOK]: parseInt(fenParsed.captured[1]),
+ [V.KNIGHT]: parseInt(fenParsed.captured[2]),
+ [V.BISHOP]: parseInt(fenParsed.captured[3]),
+ [V.QUEEN]: parseInt(fenParsed.captured[4]),
+ },
+ "b":
+ {
+ [V.PAWN]: parseInt(fenParsed.captured[5]),
+ [V.ROOK]: parseInt(fenParsed.captured[6]),
+ [V.KNIGHT]: parseInt(fenParsed.captured[7]),
+ [V.BISHOP]: parseInt(fenParsed.captured[8]),
+ [V.QUEEN]: parseInt(fenParsed.captured[9]),
+ }
+ };
}
static get size() { return {x:10,y:10}; }
static get MARSHALL() { return 'm'; } //rook+knight
static get CARDINAL() { return 'c'; } //bishop+knight
- static get PIECES() {
+ static get PIECES()
+ {
return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]);
}
+ // There may be 2 enPassant squares (if pawn jump 3 squares)
+ getEnpassantFen()
+ {
+ const L = this.epSquares.length;
+ if (!this.epSquares[L-1])
+ return "-"; //no en-passant
+ let res = "";
+ this.epSquares[L-1].forEach(sq => {
+ res += V.CoordsToSquare(sq) + ",";
+ });
+ return res.slice(0,-1); //remove last comma
+ }
+
// En-passant after 2-sq or 3-sq jumps
- getEpSquare(move)
+ getEpSquare(moveOrSquare)
{
+ if (!moveOrSquare)
+ return undefined;
+ if (typeof moveOrSquare === "string")
+ {
+ const square = moveOrSquare;
+ if (square == "-")
+ return undefined;
+ let res = [];
+ square.split(",").forEach(sq => {
+ res.push(V.SquareToCoords(sq));
+ });
+ return res;
+ }
+ // Argument is a move:
+ const move = moveOrSquare;
const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
{
// Promotion
let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
promotionPieces.forEach(p => {
- if (!this.captures[color][p] || this.captures[color][p]==0)
+ if (this.captured[color][p]==0)
return;
// Normal move
if (this.board[x+shift][y] == V.EMPTY)
super.updateVariables(move);
if (move.vanish.length==2 && move.appear.length==1 && move.vanish[1].p != V.PAWN)
{
- // Capture: update this.captures
- if (!this.captures[move.vanish[1].c][move.vanish[1].p])
- this.captures[move.vanish[1].c][move.vanish[1].p] = 1;
- else
- this.captures[move.vanish[1].c][move.vanish[1].p]++;
+ // Capture: update this.captured
+ this.captured[move.vanish[1].c][move.vanish[1].p]++;
}
}
{
super.unupdateVariables(move);
if (move.vanish.length==2 && move.appear.length==1 && move.vanish[1].p != V.PAWN)
- {
- this.captures[move.vanish[1].c][move.vanish[1].p] =
- Math.max(0, this.captures[move.vanish[1].c][move.vanish[1].p]-1);
- }
+ this.captured[move.vanish[1].c][move.vanish[1].p]--;
}
- static get VALUES() {
+ static get VALUES()
+ {
return Object.assign(
ChessRules.VALUES,
{'c': 5, 'm': 7} //experimental
static get SEARCH_DEPTH() { return 2; }
- // TODO: this function could be generalized and shared better
+ // TODO: this function could be generalized and shared better (how ?!...)
static GenRandInitFen()
{
let pieces = { "w": new Array(10), "b": new Array(10) };
return pieces["b"].join("") +
"/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- " 1111 w";
+ " w 1111 -";
}
}
class LoserRules extends ChessRules
{
- initVariables(fen)
- {
- const epSq = this.moves.length > 0 ? this.getEpSquare(this.lastMove) : undefined;
- this.epSquares = [ epSq ];
- }
-
- static IsGoodFlags(flags)
- {
- return true; //anything is good: no flags
- }
+ static get HasFlags() { return false; }
- setFlags(fenflags)
+ setOtherVariables(fen)
{
- // No castling, hence no flags; but flags defined for compatibility
- this.castleFlags = { "w":[false,false], "b":[false,false] };
+ const parsedFen = V.ParseFen(fen);
+ const epSq = parsedFen.enpassant != "-"
+ ? V.SquareToCoords(parsedFen.enpassant)
+ : undefined;
+ this.epSquares = [ epSq ];
+ this.scanKingsRooks(fen);
}
getPotentialPawnMoves([x,y])
getPotentialKingMoves(sq)
{
+ // No castle:
return this.getSlideNJumpMoves(sq,
V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
}
return [];
}
- // Unused:
+ // No variables update because no castling
updateVariables(move) { }
unupdateVariables(move) { }
- getFlagsFen()
- {
- return "-";
- }
-
checkGameEnd()
{
// No valid move: you win!
return this.turn == "w" ? "1-0" : "0-1";
}
- static get VALUES() { //experimental...
+ static get VALUES()
+ {
+ // Experimental...
return {
'p': 1,
'r': 7,
return pieces["b"].join("") +
"/pppppppp/8/8/8/8/PPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- " 0000 w"; //add flags (TODO?!)
+ " w -"; //no en-passant
}
}
class MagneticRules extends ChessRules
{
- getEpSquare(move)
+ static get HasEnpassant { return false; }
+
+ setOtherVariables(fen)
{
- return undefined; //no en-passant
+ // No en-passant:
+ const parsedFen = V.ParseFen(fen);
+ this.setFlags(fenParsed.flags);
+ this.scanKingsRooks(fen);
}
getPotentialMovesFrom([x,y])
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)
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)) ]
- : [ ];
+ ? [JSON.parse(JSON.stringify(saveKingPos))]
+ : [];
this.undo(move);
return res;
}
return this.turn == "w" ? "0-1" : "1-0";
}
- static get THRESHOLD_MATE() {
+ static get THRESHOLD_MATE()
+ {
return 500; //checkmates evals may be slightly below 1000
}
}
class UltimaRules extends ChessRules
{
+ static get HasFlags { return false; }
+
+ static get HasEnpassant { return false; }
+
static getPpath(b)
{
if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1)
return b; //usual piece
}
- static get PIECES() {
- return ChessRules.PIECES.concat([V.IMMOBILIZER]);
- }
-
- static IsGoodFlags(flags)
+ static get PIECES()
{
- return true; //anything is good: no flags
+ return ChessRules.PIECES.concat([V.IMMOBILIZER]);
}
- initVariables(fen)
+ // No castling, but checks, so keep track of kings
+ setOtherVariables(fen)
{
this.kingPos = {'w':[-1,-1], 'b':[-1,-1]};
const fenParts = fen.split(" ");
k++;
}
}
- this.epSquares = []; //no en-passant here
- }
-
- setFlags(fenflags)
- {
- // TODO: for compatibility?
- this.castleFlags = {"w":[false,false], "b":[false,false]};
}
static get IMMOBILIZER() { return 'm'; }
}
}
- static get VALUES() { //TODO: totally experimental!
+ static get VALUES()
+ {
+ // TODO: totally experimental!
return {
'p': 1,
'r': 2,
return pieces["b"].join("") +
"/pppppppp/8/8/8/8/PPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- " 0000 w"; //TODO: flags?!
- }
-
- getFlagsFen()
- {
- return "0000"; //TODO: or "-" ?
+ " w";
}
getNotation(move)
{
- const initialSquare =
- String.fromCharCode(97 + move.start.y) + (V.size.x-move.start.x);
- const finalSquare = String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x);
+ const initialSquare = V.CoordsToSquare(move.start);
+ const finalSquare = V.CoordsToSquare(move.end);
let notation = undefined;
if (move.appear[0].p == V.PAWN)
{
static get CAMEL() { return 'c'; }
static get WILDEBEEST() { return 'w'; }
- static get PIECES() {
+ static get PIECES()
+ {
return ChessRules.PIECES.concat([V.CAMEL,V.WILDEBEEST]);
}
- static get steps() {
+ static get steps()
+ {
return Object.assign(
ChessRules.steps, //add camel moves:
{'c': [ [-3,-1],[-3,1],[-1,-3],[-1,3],[1,-3],[1,3],[3,-1],[3,1] ]}
);
}
+ // There may be 2 enPassant squares (if pawn jump 3 squares)
+ getEnpassantFen()
+ {
+ const L = this.epSquares.length;
+ if (!this.epSquares[L-1])
+ return "-"; //no en-passant
+ let res = "";
+ this.epSquares[L-1].forEach(sq => {
+ res += V.CoordsToSquare(sq) + ",";
+ });
+ return res.slice(0,-1); //remove last comma
+ }
+
// En-passant after 2-sq or 3-sq jumps
- getEpSquare(move)
+ getEpSquare(moveOrSquare)
{
+ if (!moveOrSquare)
+ return undefined;
+ if (typeof moveOrSquare === "string")
+ {
+ const square = moveOrSquare;
+ if (square == "-")
+ return undefined;
+ let res = [];
+ square.split(",").forEach(sq => {
+ res.push(V.SquareToCoords(sq));
+ });
+ return res;
+ }
+ // Argument is a move:
+ const move = moveOrSquare;
const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
{
return pieces["b"].join("") +
"/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- " 1111 w";
+ " w 1111 -";
}
}
class ZenRules extends ChessRules
{
// NOTE: enPassant, if enabled, would need to redefine carefully getEpSquare
- getEpSquare(move)
- {
- return undefined;
- }
+ static get HasEnpassant { return false; }
// TODO(?): some duplicated code in 2 next functions
getSlideNJumpMoves([x,y], steps, oneStep)
}
// Translate initial square (because pieces may fly unusually in this variant!)
- const initialSquare =
- String.fromCharCode(97 + move.start.y) + (V.size.x-move.start.x);
+ const initialSquare = V.CoordsToSquare(move.start);
// Translate final square
- const finalSquare =
- String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x);
+ const finalSquare = V.CoordsToSquare(move.end);
let notation = "";
const piece = this.getPiece(move.start.x, move.start.y);
return notation;
}
- static get VALUES() { //TODO: experimental
+ static get VALUES()
+ {
+ // TODO: experimental
return {
'p': 1,
'r': 3,