X-Git-Url: https://git.auder.net/?p=vchess.git;a=blobdiff_plain;f=public%2Fjavascripts%2Fbase_rules.js;h=9599fdbf4534b8dd5d748ef660d6874ed1daf9e4;hp=620b86f85b129d61078c877910bbac2660513271;hb=69f3d8014e594ef949792d04d97b8286e9c2c268;hpb=1a788978e3682ab54b77af3edfe38e0b371edbc4 diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 620b86f8..9599fdbf 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,28 @@ 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 || !V.IsGoodTurn(fenParsed.turn)) + return false; + // 3) Check flags + if (V.HasFlags && (!fenParsed.flags || !V.IsGoodFlags(fenParsed.flags))) + return false; + // 4) Check enpassant + if (V.HasEnpassant && + (!fenParsed.enpassant || !V.IsGoodEnpassant(fenParsed.enpassant))) + { + 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,32 +104,50 @@ 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) + return true; + } + + // For FEN checking + static IsGoodTurn(turn) + { + return ["w","b"].includes(turn); + } + + // For FEN checking + static IsGoodFlags(flags) + { + return !!flags.match(/^[01]{4,4}$/); + } + + static IsGoodEnpassant(enpassant) + { + if (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) + if (isNaN(ep.x) || !V.OnBoard(ep)) return false; } return true; } - // For FEN checking - static IsGoodFlags(flags) + // 3 --> d (column number to letter) + static CoordToColumn(colnum) { - return !!flags.match(/^[01]{4,4}$/); + return String.fromCharCode(97 + colnum); + } + + // d --> 3 (column letter to number) + static ColumnToCoord(column) + { + return column.charCodeAt(0) - 97; } // a4 --> {x:3,y:0} static SquareToCoords(sq) { return { + // NOTE: column is always one char => max 26 columns + // row is counted from black side => subtraction x: V.size.x - parseInt(sq.substr(1)), y: sq[0].charCodeAt() - 97 }; @@ -113,7 +156,7 @@ class ChessRules // {x:0,y:4} --> e8 static CoordsToSquare(coords) { - return String.fromCharCode(97 + coords.y) + (V.size.x - coords.x); + return V.CoordToColumn(coords.y) + (V.size.x - coords.x); } // Aggregates flags into one object @@ -138,10 +181,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; @@ -174,16 +214,12 @@ class ChessRules return (this.turn == side && this.getColor(x,y) == side); } - // On which squares is opponent under check after our move ? (for interface) - getCheckSquares(move) + // On which squares is color under check ? (for interface) + getCheckSquares(color) { - this.play(move); - const color = this.turn; //opponent - let res = this.isAttacked(this.kingPos[color], [this.getOppCol(color)]) + return this.isAttacked(this.kingPos[color], [this.getOppCol(color)]) ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate! : []; - this.undo(move); - return res; } ///////////// @@ -200,31 +236,32 @@ class ChessRules // Get random squares for bishops let randIndex = 2 * _.random(3); - let bishop1Pos = positions[randIndex]; + const bishop1Pos = positions[randIndex]; // The second bishop must be on a square of different color let randIndex_tmp = 2 * _.random(3) + 1; - let bishop2Pos = positions[randIndex_tmp]; + const bishop2Pos = positions[randIndex_tmp]; // Remove chosen squares positions.splice(Math.max(randIndex,randIndex_tmp), 1); positions.splice(Math.min(randIndex,randIndex_tmp), 1); // Get random squares for knights randIndex = _.random(5); - let knight1Pos = positions[randIndex]; + const knight1Pos = positions[randIndex]; positions.splice(randIndex, 1); randIndex = _.random(4); - let knight2Pos = positions[randIndex]; + const knight2Pos = positions[randIndex]; positions.splice(randIndex, 1); // Get random square for queen randIndex = _.random(3); - let queenPos = positions[randIndex]; + const queenPos = positions[randIndex]; positions.splice(randIndex, 1); - // Rooks and king positions are now fixed, because of the ordering rook-king-rook - let rook1Pos = positions[0]; - let kingPos = positions[1]; - let rook2Pos = positions[2]; + // Rooks and king positions are now fixed, + // because of the ordering rook-king-rook + const rook1Pos = positions[0]; + const kingPos = positions[1]; + const rook2Pos = positions[2]; // Finally put the shuffled pieces in the board array pieces[c][rook1Pos] = 'r'; @@ -246,19 +283,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.getTurnFen() + + (V.HasFlags ? (" " + this.getFlagsFen()) : "") + + (V.HasEnpassant ? (" " + this.getEnpassantFen()) : ""); } // Position part of the FEN string @@ -280,7 +323,7 @@ class ChessRules position += emptyCount; emptyCount = 0; } - fen += V.board2fen(this.board[i][j]); + position += V.board2fen(this.board[i][j]); } } if (emptyCount > 0) @@ -294,6 +337,11 @@ class ChessRules return position; } + getTurnFen() + { + return this.turn; + } + // Flags part of the FEN string getFlagsFen() { @@ -311,7 +359,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]); } @@ -357,22 +405,17 @@ class ChessRules this.moves = moves; const fenParsed = V.ParseFen(fen); this.board = V.GetBoard(fenParsed.position); - this.turn = (fenParsed.turn || "w"); + this.turn = fenParsed.turn[0]; //[0] to work with MarseilleRules 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= 0 && x+shift < sizeX && x+shift != lastRank) + // NOTE: next condition is generally true (no pawn on last rank) + if (x+shiftX >= 0 && x+shiftX < sizeX) { - // 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 { - // 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= 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})); + 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 = (Lep>0 ? this.epSquares[Lep-1] : undefined); - 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; @@ -653,7 +708,8 @@ class ChessRules // What are the queen moves from square x,y ? getPotentialQueenMoves(sq) { - return this.getSlideNJumpMoves(sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP])); + return this.getSlideNJumpMoves(sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP])); } // What are the king moves from square x,y ? @@ -683,13 +739,15 @@ class ChessRules continue; // If this code is reached, rooks and king are on initial position - // Nothing on the path of the king (and no checks; OK also if y==finalSquare)? + // Nothing on the path of the king ? + // (And no checks; OK also if y==finalSquare) let step = finalSquares[castleSide][0] < y ? -1 : 1; for (i=y; i!=finalSquares[castleSide][0]; i+=step) { if (this.isAttacked([x,i], [oppCol]) || (this.board[x][i] != V.EMPTY && // NOTE: next check is enough, because of chessboard constraints - (this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i))))) + (this.getColor(x,i) != c + || ![V.KING,V.ROOK].includes(this.getPiece(x,i))))) { continue castlingCheck; } @@ -735,9 +793,9 @@ class ChessRules //////////////////// // MOVES VALIDATION + // For the interface: possible moves for the current turn from square sq getPossibleMovesFrom(sq) { - // Assuming color is right (already checked) return this.filterValid( this.getPotentialMovesFrom(sq) ); } @@ -746,10 +804,17 @@ class ChessRules { if (moves.length == 0) return []; - return moves.filter(m => { return !this.underCheck(m); }); + const color = this.turn; + return moves.filter(m => { + this.play(m); + const res = !this.underCheck(color); + this.undo(m); + return res; + }); } - // Search for all valid moves considering current turn (for engine and game end) + // Search for all valid moves considering current turn + // (for engine and game end) getAllValidMoves() { const color = this.turn; @@ -759,13 +824,14 @@ class ChessRules { for (let j=0; j= 1) + { + // Usual case, something is moved + piece = move.vanish[0].p; + c = move.vanish[0].c; + } + else + { + // Crazyhouse-like variants + piece = move.appear[0].p; + c = move.appear[0].c; + } + if (c == "c") //if (!["w","b"].includes(c)) + { + // 'c = move.vanish[0].c' doesn't work for Checkered + c = this.getOppCol(this.turn); + } const firstRank = (c == "w" ? V.size.x-1 : 0); // Update king position + flags @@ -923,22 +1002,27 @@ class ChessRules { this.kingPos[c][0] = move.appear[0].x; this.kingPos[c][1] = move.appear[0].y; - this.castleFlags[c] = [false,false]; + if (V.HasFlags) + this.castleFlags[c] = [false,false]; return; } - const oppCol = this.getOppCol(c); - const oppFirstRank = (V.size.x-1) - firstRank; - if (move.start.x == firstRank //our rook moves? - && this.INIT_COL_ROOK[c].includes(move.start.y)) - { - const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1); - this.castleFlags[c][flagIdx] = false; - } - else if (move.end.x == oppFirstRank //we took opponent rook? - && this.INIT_COL_ROOK[oppCol].includes(move.end.y)) + if (V.HasFlags) { - const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1); - this.castleFlags[oppCol][flagIdx] = false; + // Update castling flags if rooks are moved + const oppCol = this.getOppCol(c); + const oppFirstRank = (V.size.x-1) - firstRank; + if (move.start.x == firstRank //our rook moves? + && this.INIT_COL_ROOK[c].includes(move.start.y)) + { + const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = false; + } + else if (move.end.x == oppFirstRank //we took opponent rook? + && this.INIT_COL_ROOK[oppCol].includes(move.end.y)) + { + const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1); + this.castleFlags[oppCol][flagIdx] = false; + } } } @@ -961,28 +1045,32 @@ class ChessRules if (!!ingame) move.notation = [this.getNotation(move), this.getLongNotation(move)]; - move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo) - this.updateVariables(move); - this.moves.push(move); - this.epSquares.push( this.getEpSquare(move) ); - this.turn = this.getOppCol(this.turn); + if (V.HasFlags) + move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo) + if (V.HasEnpassant) + this.epSquares.push( this.getEpSquare(move) ); V.PlayOnBoard(this.board, move); + this.turn = this.getOppCol(this.turn); + this.moves.push(move); + this.updateVariables(move); if (!!ingame) { // Hash of current game state *after move*, to detect repetitions - move.hash = hex_md5(this.getFen(); + move.hash = hex_md5(this.getFen()); } } undo(move) { + if (V.HasEnpassant) + this.epSquares.pop(); + if (V.HasFlags) + this.disaggregateFlags(JSON.parse(move.flags)); V.UndoOnBoard(this.board, move); this.turn = this.getOppCol(this.turn); - this.epSquares.pop(); this.moves.pop(); this.unupdateVariables(move); - this.disaggregateFlags(JSON.parse(move.flags)); // DEBUG: // if (this.getFen() != this.states[this.states.length-1]) @@ -1034,7 +1122,7 @@ class ChessRules if (!this.isAttacked(this.kingPos[color], [this.getOppCol(color)])) return "1/2"; // OK, checkmate - return color == "w" ? "0-1" : "1-0"; + return (color == "w" ? "0-1" : "1-0"); } /////////////// @@ -1092,12 +1180,14 @@ class ChessRules // Rank moves using a min-max at depth 2 for (let i=0; i eval2)) + if ((color == "w" && evalPos < eval2) + || (color=="b" && evalPos > eval2)) + { eval2 = evalPos; + } this.undo(moves2[j]); } } @@ -1152,11 +1245,12 @@ class ChessRules this.alphabeta(V.SEARCH_DEPTH-1, -maxeval, maxeval); this.undo(moves1[i]); } - moves1.sort( (a,b) => { return (color=="w" ? 1 : -1) * (b.eval - a.eval); }); + moves1.sort( (a,b) => { + return (color=="w" ? 1 : -1) * (b.eval - a.eval); }); } else return currentBest; - //console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); +// console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); candidates = [0]; for (let j=1; j move.appear.length) { // Capture - const startColumn = String.fromCharCode(97 + move.start.y); + const startColumn = V.CoordToColumn(move.start.y); notation = startColumn + "x" + finalSquare; } else //no capture notation = finalSquare; - if (move.appear.length > 0 && piece != move.appear[0].p) //promotion + if (move.appear.length > 0 && move.appear[0].p != V.PAWN) //promotion notation += "=" + move.appear[0].p.toUpperCase(); return notation; }