X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fbase_rules.js;h=8f875e4f918d727d99a2e5e404c30703bc5f5f3b;hb=edcd679ab1fe609641451586ef1e9484925c4f83;hp=7f27e8559cae2a8b64af91b8297384d244d5cd1f;hpb=45109880413a50dec3a07298b987fb07d60630b2;p=vchess.git diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 7f27e855..8f875e4f 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -64,22 +64,16 @@ class ChessRules if (!V.IsGoodPosition(fenParsed.position)) return false; // 2) Check turn - if (!fenParsed.turn || !["w","b"].includes(fenParsed.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) + if (V.HasEnpassant && + (!fenParsed.enpassant || !V.IsGoodEnpassant(fenParsed.enpassant))) { - 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 false; } return true; } @@ -113,22 +107,47 @@ class ChessRules 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}$/); } - // 3 --> d (column letter from number) - static GetColumn(colnum) + static IsGoodEnpassant(enpassant) + { + if (enpassant != "-") + { + const ep = V.SquareToCoords(fenParsed.enpassant); + if (isNaN(ep.x) || !V.OnBoard(ep)) + return false; + } + return true; + } + + // 3 --> d (column number to letter) + static CoordToColumn(colnum) { 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 }; @@ -137,7 +156,7 @@ class ChessRules // {x:0,y:4} --> e8 static CoordsToSquare(coords) { - return V.GetColumn(coords.y) + (V.size.x - coords.x); + return V.CoordToColumn(coords.y) + (V.size.x - coords.x); } // Aggregates flags into one object @@ -167,7 +186,8 @@ class ChessRules // 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) + // TODO: next conditions are first for Atomic, and third for Checkered + if (move.appear.length > 0 && move.appear[0].p == V.PAWN && ["w","b"].includes(move.appear[0].c) && Math.abs(sx - ex) == 2) { return { x: (sx + ex)/2, @@ -195,16 +215,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; } ///////////// @@ -221,31 +237,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'; @@ -283,7 +300,7 @@ class ChessRules // Return current fen (game state) getFen() { - return this.getBaseFen() + " " + this.turn + + return this.getBaseFen() + " " + this.getTurnFen() + (V.HasFlags ? (" " + this.getFlagsFen()) : "") + (V.HasEnpassant ? (" " + this.getEnpassantFen()) : ""); } @@ -321,6 +338,11 @@ class ChessRules return position; } + getTurnFen() + { + return this.turn; + } + // Flags part of the FEN string getFlagsFen() { @@ -384,7 +406,7 @@ 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); } @@ -532,7 +554,8 @@ class ChessRules } } - // Build a regular move from its initial and destination squares; tr: transformation + // Build a regular move from its initial and destination squares. + // tr: transformation getBasicMove([sx,sy], [ex,ey], tr) { let mv = new Move({ @@ -569,7 +592,8 @@ class ChessRules return mv; } - // Generic method to find possible moves of non-pawn pieces ("sliding or jumping") + // Generic method to find possible moves of non-pawn pieces: + // "sliding or jumping" getSlideNJumpMoves([x,y], steps, oneStep) { const color = this.getColor(x,y); @@ -599,74 +623,66 @@ 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) + // 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])) + for (let shiftY of [-1,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 = 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; @@ -693,7 +709,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 ? @@ -723,13 +740,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; } @@ -775,9 +794,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) ); } @@ -786,10 +805,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; @@ -799,13 +825,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 @@ -963,22 +1003,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; + } } } @@ -1003,12 +1048,12 @@ class ChessRules if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo) - this.updateVariables(move); - this.moves.push(move); if (V.HasEnpassant) this.epSquares.push( this.getEpSquare(move) ); - this.turn = this.getOppCol(this.turn); V.PlayOnBoard(this.board, move); + this.turn = this.getOppCol(this.turn); + this.moves.push(move); + this.updateVariables(move); if (!!ingame) { @@ -1019,14 +1064,14 @@ class ChessRules undo(move) { - V.UndoOnBoard(this.board, move); - this.turn = this.getOppCol(this.turn); if (V.HasEnpassant) this.epSquares.pop(); - this.moves.pop(); - this.unupdateVariables(move); if (V.HasFlags) this.disaggregateFlags(JSON.parse(move.flags)); + V.UndoOnBoard(this.board, move); + this.turn = this.getOppCol(this.turn); + this.moves.pop(); + this.unupdateVariables(move); // DEBUG: // if (this.getFen() != this.states[this.states.length-1]) @@ -1078,7 +1123,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"); } /////////////// @@ -1136,12 +1181,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]); } } @@ -1196,11 +1246,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; } @@ -1322,15 +1373,22 @@ class ChessRules getPGN(mycolor, score, fenStart, mode) { let pgn = ""; - pgn += '[Site "vchess.club"]
'; + pgn += '[Site "vchess.club"]\n'; const opponent = mode=="human" ? "Anonymous" : "Computer"; - pgn += '[Variant "' + variant + '"]
'; - pgn += '[Date "' + getDate(new Date()) + '"]
'; - pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]
'; - pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]
'; - pgn += '[FenStart "' + fenStart + '"]
'; - pgn += '[Fen "' + this.getFen() + '"]
'; - pgn += '[Result "' + score + '"]

'; + pgn += '[Variant "' + variant + '"]\n'; + pgn += '[Date "' + getDate(new Date()) + '"]\n'; + // TODO: later when users are a bit less anonymous, use better names + const whiteName = ["human","computer"].includes(mode) + ? (mycolor=='w'?'Myself':opponent) + : "analyze"; + const blackName = ["human","computer"].includes(mode) + ? (mycolor=='b'?'Myself':opponent) + : "analyze"; + pgn += '[White "' + whiteName + '"]\n'; + pgn += '[Black "' + blackName + '"]\n'; + pgn += '[FenStart "' + fenStart + '"]\n'; + pgn += '[Fen "' + this.getFen() + '"]\n'; + pgn += '[Result "' + score + '"]\n\n'; // Standard PGN for (let i=0; i