From 2d7194bd9c976f444e43e5dc0a725823b6472eb9 Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Tue, 18 Dec 2018 02:59:43 +0100 Subject: [PATCH] Some refactoring in variants logic: more robust FEN handling (untested) --- db/vchess.sqlite | 2 +- public/javascripts/base_rules.js | 120 +++++++++++++++------- public/javascripts/variants/Alice.js | 27 ++--- public/javascripts/variants/Antiking.js | 19 ++-- public/javascripts/variants/Checkered.js | 30 +++--- public/javascripts/variants/Crazyhouse.js | 108 +++++++++++++++---- public/javascripts/variants/Extinction.js | 33 +++--- public/javascripts/variants/Grand.js | 113 ++++++++++++++++---- public/javascripts/variants/Loser.js | 35 +++---- public/javascripts/variants/Magnetic.js | 77 +++++++++++++- public/javascripts/variants/Ultima.js | 38 +++---- public/javascripts/variants/Wildebeest.js | 38 ++++++- public/javascripts/variants/Zen.js | 15 ++- 13 files changed, 462 insertions(+), 193 deletions(-) diff --git a/db/vchess.sqlite b/db/vchess.sqlite index 9d7e4a2a..f1d4f13e 100644 --- a/db/vchess.sqlite +++ b/db/vchess.sqlite @@ -1 +1 @@ -#$# git-fat 25d1d22208da0bc58bf3076bfe684bbcb98894b2 16384 +#$# git-fat 039e3c0aadcb97c144dc2d12a35aeb83b4a3849e 16384 diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 620b86f8..ae0800cd 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -34,6 +34,10 @@ class ChessRules ////////////// // 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) { @@ -57,7 +61,34 @@ class ChessRules { 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; @@ -79,19 +110,6 @@ class ChessRules 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; } @@ -101,6 +119,12 @@ class ChessRules 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) { @@ -113,7 +137,7 @@ class ChessRules // {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 @@ -138,10 +162,7 @@ class ChessRules 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; @@ -246,19 +267,25 @@ class ChessRules 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 @@ -311,7 +338,7 @@ class ChessRules 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]); } @@ -361,18 +388,13 @@ class ChessRules 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 @@ -410,6 +432,24 @@ class ChessRules } } + // 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 @@ -615,7 +655,7 @@ class ChessRules // 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; @@ -961,10 +1001,12 @@ class ChessRules 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); @@ -979,10 +1021,12 @@ class ChessRules { 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]) diff --git a/public/javascripts/variants/Alice.js b/public/javascripts/variants/Alice.js index ece59cdc..00103a7b 100644 --- a/public/javascripts/variants/Alice.js +++ b/public/javascripts/variants/Alice.js @@ -29,24 +29,24 @@ class AliceRules extends ChessRules 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]; @@ -55,7 +55,7 @@ class AliceRules extends ChessRules 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); } @@ -293,7 +293,8 @@ class AliceRules extends ChessRules return res; } - static get VALUES() { + static get VALUES() + { return Object.assign( ChessRules.VALUES, { @@ -317,13 +318,13 @@ class AliceRules extends ChessRules 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; diff --git a/public/javascripts/variants/Antiking.js b/public/javascripts/variants/Antiking.js index 2b07cfbe..38cadf22 100644 --- a/public/javascripts/variants/Antiking.js +++ b/public/javascripts/variants/Antiking.js @@ -7,21 +7,22 @@ class AntikingRules extends ChessRules 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]; @@ -30,7 +31,7 @@ class AntikingRules extends ChessRules 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); } @@ -200,6 +201,6 @@ class AntikingRules extends ChessRules return pieces["b"].join("") + "/" + ranks23_black + "/8/8/" + ranks23_white + "/" + pieces["w"].join("").toUpperCase() + - " 1111 w"; + " w 1111"; } } diff --git a/public/javascripts/variants/Checkered.js b/public/javascripts/variants/Checkered.js index 370c6896..314cd8e4 100644 --- a/public/javascripts/variants/Checkered.js +++ b/public/javascripts/variants/Checkered.js @@ -4,6 +4,7 @@ class CheckeredRules extends ChessRules { return b[0]=='c' ? "Checkered/"+b : b; } + static board2fen(b) { const checkered_codes = { @@ -17,6 +18,7 @@ class CheckeredRules extends ChessRules return checkered_codes[b[1]]; return ChessRules.board2fen(b); } + static fen2board(f) { // Tolerate upper-case versions of checkered pieces (why not?) @@ -37,7 +39,8 @@ class CheckeredRules extends ChessRules return ChessRules.fen2board(f); } - static get PIECES() { + static get PIECES() + { return ChessRules.PIECES.concat(['s','t','u','c','o']); } @@ -65,13 +68,12 @@ class CheckeredRules extends ChessRules } } - // 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]; @@ -187,13 +189,14 @@ class CheckeredRules extends ChessRules { 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; } @@ -242,7 +245,7 @@ class CheckeredRules extends ChessRules { const randFen = ChessRules.GenRandInitFen(); // Add 16 pawns flags: - return randFen.replace(" 1111 w", " 11111111111111111111 w"); + return randFen.replace(" w 1111", " w 11111111111111111111"); } getFlagsFen() @@ -269,10 +272,9 @@ class CheckeredRules extends ChessRules } // 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 @@ -280,7 +282,7 @@ class CheckeredRules extends ChessRules 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(); } diff --git a/public/javascripts/variants/Crazyhouse.js b/public/javascripts/variants/Crazyhouse.js index bf47197d..00452cbd 100644 --- a/public/javascripts/variants/Crazyhouse.js +++ b/public/javascripts/variants/Crazyhouse.js @@ -1,31 +1,97 @@ 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) @@ -34,6 +100,7 @@ class CrazyhouseRules extends ChessRules return (i==V.size.x ? "w" : "b"); return this.board[i][j].charAt(0); } + getPiece(i,j) { if (i >= V.size.x) @@ -48,7 +115,8 @@ class CrazyhouseRules extends ChessRules } // Ordering on reserve pieces - static get RESERVE_PIECES() { + static get RESERVE_PIECES() + { return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]; } @@ -188,17 +256,13 @@ class CrazyhouseRules extends ChessRules // 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); } } diff --git a/public/javascripts/variants/Extinction.js b/public/javascripts/variants/Extinction.js index 2b0aecaa..49d61b2d 100644 --- a/public/javascripts/variants/Extinction.js +++ b/public/javascripts/variants/Extinction.js @@ -1,27 +1,30 @@ 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 } }; } @@ -117,7 +120,7 @@ class ExtinctionRules extends ChessRules checkGameEnd() { - return this.turn == "w" ? "0-1" : "1-0"; + return (this.turn == "w" ? "0-1" : "1-0"); } evalPosition() diff --git a/public/javascripts/variants/Grand.js b/public/javascripts/variants/Grand.js index 276acbc3..b598f2d2 100644 --- a/public/javascripts/variants/Grand.js +++ b/public/javascripts/variants/Grand.js @@ -7,10 +7,63 @@ class GrandRules extends ChessRules 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}; } @@ -18,13 +71,42 @@ class GrandRules extends ChessRules 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) { @@ -104,7 +186,7 @@ class GrandRules extends ChessRules // 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) @@ -189,11 +271,8 @@ class GrandRules extends ChessRules 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]++; } } @@ -201,13 +280,11 @@ class GrandRules extends ChessRules { 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 @@ -216,7 +293,7 @@ class GrandRules extends ChessRules 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) }; @@ -278,6 +355,6 @@ class GrandRules extends ChessRules return pieces["b"].join("") + "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" + pieces["w"].join("").toUpperCase() + - " 1111 w"; + " w 1111 -"; } } diff --git a/public/javascripts/variants/Loser.js b/public/javascripts/variants/Loser.js index 2d60d6e8..5f156129 100644 --- a/public/javascripts/variants/Loser.js +++ b/public/javascripts/variants/Loser.js @@ -1,20 +1,15 @@ 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]) @@ -48,6 +43,7 @@ class LoserRules extends ChessRules getPotentialKingMoves(sq) { + // No castle: return this.getSlideNJumpMoves(sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); } @@ -111,22 +107,19 @@ class LoserRules extends ChessRules 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, @@ -197,6 +190,6 @@ class LoserRules extends ChessRules return pieces["b"].join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + pieces["w"].join("").toUpperCase() + - " 0000 w"; //add flags (TODO?!) + " w -"; //no en-passant } } diff --git a/public/javascripts/variants/Magnetic.js b/public/javascripts/variants/Magnetic.js index 2e90e6c0..225489ec 100644 --- a/public/javascripts/variants/Magnetic.js +++ b/public/javascripts/variants/Magnetic.js @@ -1,8 +1,13 @@ 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]) @@ -19,6 +24,67 @@ 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) @@ -154,8 +220,8 @@ class MagneticRules extends ChessRules 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; } @@ -210,7 +276,8 @@ class MagneticRules extends ChessRules 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 } } diff --git a/public/javascripts/variants/Ultima.js b/public/javascripts/variants/Ultima.js index 05fba2f5..7f4a7ebe 100644 --- a/public/javascripts/variants/Ultima.js +++ b/public/javascripts/variants/Ultima.js @@ -1,5 +1,9 @@ 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) @@ -7,16 +11,13 @@ class UltimaRules extends ChessRules 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(" "); @@ -42,13 +43,6 @@ class UltimaRules extends ChessRules 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'; } @@ -544,7 +538,9 @@ class UltimaRules extends ChessRules } } - static get VALUES() { //TODO: totally experimental! + static get VALUES() + { + // TODO: totally experimental! return { 'p': 1, 'r': 2, @@ -608,19 +604,13 @@ class UltimaRules extends ChessRules 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) { diff --git a/public/javascripts/variants/Wildebeest.js b/public/javascripts/variants/Wildebeest.js index 9b074108..4194521e 100644 --- a/public/javascripts/variants/Wildebeest.js +++ b/public/javascripts/variants/Wildebeest.js @@ -10,20 +10,50 @@ class WildebeestRules extends ChessRules 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) { @@ -248,6 +278,6 @@ class WildebeestRules extends ChessRules return pieces["b"].join("") + "/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" + pieces["w"].join("").toUpperCase() + - " 1111 w"; + " w 1111 -"; } } diff --git a/public/javascripts/variants/Zen.js b/public/javascripts/variants/Zen.js index 64587d20..da4dd7af 100644 --- a/public/javascripts/variants/Zen.js +++ b/public/javascripts/variants/Zen.js @@ -1,10 +1,7 @@ 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) @@ -184,12 +181,10 @@ class ZenRules extends ChessRules } // 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); @@ -218,7 +213,9 @@ class ZenRules extends ChessRules return notation; } - static get VALUES() { //TODO: experimental + static get VALUES() + { + // TODO: experimental return { 'p': 1, 'r': 3, -- 2.44.0