X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fbase_rules.js;h=f07aaff263422efde7c1b243b3872859304183a6;hb=2526c041baf44968b0aa7b98af56730e88f6a595;hp=9a4ef9120ca8463a9e4e36d4197aead688c94299;hpb=e64a4effa6b357addc3253504870cf1a6fc29977;p=vchess.git diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 9a4ef912..f07aaff2 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -46,14 +46,14 @@ class ChessRules ///////////////// // INITIALIZATION - // fen = "position flags epSquare movesCount" + // fen == "position flags" constructor(fen, moves) { this.moves = moves; // Use fen string to initialize variables, flags and board - this.initVariables(fen); - this.flags = VariantRules.GetFlags(fen); this.board = VariantRules.GetBoard(fen); + this.setFlags(fen); + this.initVariables(fen); } initVariables(fen) @@ -65,54 +65,48 @@ class ChessRules const position = fenParts[0].split("/"); for (let i=0; i 0 ? this.getEpSquare(this.lastMove) : undefined; this.epSquares = [ epSq ]; - this.movesCount = Number.parseInt(fenParts[3]); } // Turn diagram fen into double array ["wb","wp","bk",...] static GetBoard(fen) { let rows = fen.split(" ")[0].split("/"); - let [sizeX,sizeY] = VariantRules.size; + const [sizeX,sizeY] = VariantRules.size; let board = doubleArray(sizeX, sizeY, ""); for (let i=0; i0 ? this.moves[L-1] : null; } get turn() { - return this.movesCount%2==0 ? 'w' : 'b'; + return this.moves.length%2==0 ? 'w' : 'b'; } // Pieces codes @@ -182,6 +175,17 @@ class ChessRules }; } + // Aggregates flags into one object + get flags() { + return this.castleFlags; + } + + // Reverse operation + parseFlags(flags) + { + this.castleFlags = flags; + } + // En-passant square, if any getEpSquare(move) { @@ -196,10 +200,10 @@ class ChessRules return undefined; //default } - // can color1 take color2? - canTake(color1, color2) + // can thing on square1 take thing on square2 + canTake([x1,y1], [x2,y2]) { - return color1 != color2; + return this.getColor(x1,y1) != this.getColor(x2,y2); } /////////////////// @@ -208,35 +212,33 @@ class ChessRules // All possible moves from selected square (assumption: color is OK) getPotentialMovesFrom([x,y]) { - let c = this.getColor(x,y); - // Fill possible moves according to piece type switch (this.getPiece(x,y)) { case VariantRules.PAWN: - return this.getPotentialPawnMoves(x,y,c); + return this.getPotentialPawnMoves([x,y]); case VariantRules.ROOK: - return this.getPotentialRookMoves(x,y,c); + return this.getPotentialRookMoves([x,y]); case VariantRules.KNIGHT: - return this.getPotentialKnightMoves(x,y,c); + return this.getPotentialKnightMoves([x,y]); case VariantRules.BISHOP: - return this.getPotentialBishopMoves(x,y,c); + return this.getPotentialBishopMoves([x,y]); case VariantRules.QUEEN: - return this.getPotentialQueenMoves(x,y,c); + return this.getPotentialQueenMoves([x,y]); case VariantRules.KING: - return this.getPotentialKingMoves(x,y,c); + return this.getPotentialKingMoves([x,y]); } } // Build a regular move from its initial and destination squares; tr: transformation - getBasicMove(sx, sy, ex, ey, tr) + getBasicMove([sx,sy], [ex,ey], tr) { - var mv = new Move({ + let mv = new Move({ appear: [ new PiPo({ x: ex, y: ey, - c: this.getColor(sx,sy), - p: !!tr ? tr : this.getPiece(sx,sy) + c: !!tr ? tr.c : this.getColor(sx,sy), + p: !!tr ? tr.p : this.getPiece(sx,sy) }) ], vanish: [ @@ -265,63 +267,60 @@ class ChessRules } // Generic method to find possible moves of non-pawn pieces ("sliding or jumping") - getSlideNJumpMoves(x, y, color, steps, oneStep) + getSlideNJumpMoves([x,y], steps, oneStep) { - var moves = []; - let [sizeX,sizeY] = VariantRules.size; + const color = this.getColor(x,y); + let moves = []; + const [sizeX,sizeY] = VariantRules.size; outerLoop: for (let step of steps) { - var i = x + step[0]; - var j = y + step[1]; - while (i>=0 && i=0 && j=0 && i=0 && j=0 && i<8 && j>=0 && j<8 && this.canTake(color, this.getColor(i,j))) - moves.push(this.getBasicMove(x, y, i, j)); + if (i>=0 && i<8 && j>=0 && j<8 && this.canTake([x,y], [i,j])) + moves.push(this.getBasicMove([x,y], [i,j])); } return moves; } // What are the pawn moves from square x,y considering color "color" ? - getPotentialPawnMoves(x, y, color) + getPotentialPawnMoves([x,y]) { - var moves = []; - var V = VariantRules; - let [sizeX,sizeY] = VariantRules.size; - let shift = (color == "w" ? -1 : 1); - let startRank = (color == "w" ? sizeY-2 : 1); - let lastRank = (color == "w" ? 0 : sizeY-1); + const color = this.turn; + let moves = []; + const V = VariantRules; + const [sizeX,sizeY] = VariantRules.size; + const shift = (color == "w" ? -1 : 1); + const firstRank = (color == 'w' ? sizeY-1 : 0); + const startRank = (color == "w" ? sizeY-2 : 1); + const lastRank = (color == "w" ? 0 : sizeY-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)); - if (x==startRank && this.board[x+2*shift][y] == V.EMPTY) + moves.push(this.getBasicMove([x,y], [x+shift,y])); + // Next condition because variants with pawns on 1st rank generally 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)); + moves.push(this.getBasicMove([x,y], [x+2*shift,y])); } } // Captures - if (y>0 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y-1)) - && this.board[x+shift][y-1] != V.EMPTY) - { - moves.push(this.getBasicMove(x, y, x+shift, y-1)); - } - if (y0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY) + 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, p)); + moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p})); // Captures - if (y>0 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y-1)) - && this.board[x+shift][y-1] != V.EMPTY) - { - moves.push(this.getBasicMove(x, y, x+shift, y-1, p)); - } - if (y0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY) + moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:p})); + if (y { - return !this.underCheck(m, color); - }); + let color = this.turn; + return moves.filter(m => { return !this.underCheck(m, color); }); } // Search for all valid moves considering current turn (for engine and game end) - getAllValidMoves(color) + getAllValidMoves() { + const color = this.turn; const oppCol = this.getOppCol(color); var potentialMoves = []; let [sizeX,sizeY] = VariantRules.size; @@ -519,8 +505,9 @@ class ChessRules } // Stop at the first move found - atLeastOneMove(color) + atLeastOneMove() { + const color = this.turn; const oppCol = this.getOppCol(color); let [sizeX,sizeY] = VariantRules.size; for (var i=0; i=0 && x+pawnShift<8) + for (let c of colors) { - for (let i of [-1,1]) + let pawnShift = (c=="w" ? 1 : -1); + if (x+pawnShift>=0 && x+pawnShift<8) { - if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==VariantRules.PAWN - && this.getColor(x+pawnShift,y+i)==c) + for (let i of [-1,1]) { - return true; + if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==VariantRules.PAWN + && this.getColor(x+pawnShift,y+i)==c) + { + return true; + } } } } @@ -574,42 +564,42 @@ class ChessRules } // Is square x,y attacked by rooks of color c ? - isAttackedByRook(sq, color) + isAttackedByRook(sq, colors) { - return this.isAttackedBySlideNJump(sq, color, + return this.isAttackedBySlideNJump(sq, colors, VariantRules.ROOK, VariantRules.steps[VariantRules.ROOK]); } // Is square x,y attacked by knights of color c ? - isAttackedByKnight(sq, color) + isAttackedByKnight(sq, colors) { - return this.isAttackedBySlideNJump(sq, color, + return this.isAttackedBySlideNJump(sq, colors, VariantRules.KNIGHT, VariantRules.steps[VariantRules.KNIGHT], "oneStep"); } // Is square x,y attacked by bishops of color c ? - isAttackedByBishop(sq, color) + isAttackedByBishop(sq, colors) { - return this.isAttackedBySlideNJump(sq, color, + return this.isAttackedBySlideNJump(sq, colors, VariantRules.BISHOP, VariantRules.steps[VariantRules.BISHOP]); } // Is square x,y attacked by queens of color c ? - isAttackedByQueen(sq, color) + isAttackedByQueen(sq, colors) { - return this.isAttackedBySlideNJump(sq, color, + return this.isAttackedBySlideNJump(sq, colors, VariantRules.QUEEN, VariantRules.steps[VariantRules.QUEEN]); } // Is square x,y attacked by king of color c ? - isAttackedByKing(sq, color) + isAttackedByKing(sq, colors) { - return this.isAttackedBySlideNJump(sq, color, + return this.isAttackedBySlideNJump(sq, colors, VariantRules.KING, VariantRules.steps[VariantRules.QUEEN], "oneStep"); } // Generic method for non-pawn pieces ("sliding or jumping"): is x,y attacked by piece != color ? - isAttackedBySlideNJump([x,y], c,piece,steps,oneStep) + isAttackedBySlideNJump([x,y], colors, piece, steps, oneStep) { for (let step of steps) { @@ -621,7 +611,7 @@ class ChessRules ry += step[1]; } if (rx>=0 && rx<8 && ry>=0 && ry<8 && this.board[rx][ry] != VariantRules.EMPTY - && this.getPiece(rx,ry) == piece && this.getColor(rx,ry) == c) + && this.getPiece(rx,ry) == piece && colors.includes(this.getColor(rx,ry))) { return true; } @@ -629,10 +619,24 @@ class ChessRules return false; } - underCheck(move, c) + // Is color c under check after move ? + underCheck(move) + { + const color = this.turn; + this.play(move); + let res = this.isAttacked(this.kingPos[color], this.getOppCol(color)); + this.undo(move); + return res; + } + + // On which squares is color c under check (after move) ? + getCheckSquares(move) { this.play(move); - let res = this.isAttacked(this.kingPos[c], this.getOppCol(c)); + const color = this.turn; //opponent + let res = this.isAttacked(this.kingPos[color], this.getOppCol(color)) + ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ] //need to duplicate! + : [ ]; this.undo(move); return res; } @@ -654,7 +658,7 @@ class ChessRules board[psq.x][psq.y] = psq.c + psq.p; } - // Before move is played: + // Before move is played, update variables + flags updateVariables(move) { const piece = this.getPiece(move.start.x,move.start.y); @@ -666,7 +670,7 @@ class ChessRules { this.kingPos[c][0] = move.appear[0].x; this.kingPos[c][1] = move.appear[0].y; - this.flags[c] = [false,false]; + this.castleFlags[c] = [false,false]; return; } const oppCol = this.getOppCol(c); @@ -674,55 +678,52 @@ class ChessRules 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.flags[c][flagIdx] = false; + 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[c].includes(move.end.y)) { - const flagIdx = move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1; - this.flags[oppCol][flagIdx] = false; + const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1); + this.castleFlags[oppCol][flagIdx] = false; } } - play(move, ingame) + unupdateVariables(move) { - // Save flags (for undo) - move.flags = JSON.stringify(this.flags); //TODO: less costly - this.updateVariables(move); + // (Potentially) Reset king position + const c = this.getColor(move.start.x,move.start.y); + if (this.getPiece(move.start.x,move.start.y) == VariantRules.KING) + this.kingPos[c] = [move.start.x, move.start.y]; + } + play(move, ingame) + { + console.log("AVANT " + this.getNotation(move) + " " + this.board[1][5]); if (!!ingame) - { move.notation = this.getNotation(move); - this.moves.push(move); - } + move.flags = JSON.stringify(this.flags); //save flags (for undo) + this.updateVariables(move); + this.moves.push(move); this.epSquares.push( this.getEpSquare(move) ); VariantRules.PlayOnBoard(this.board, move); - this.movesCount++; } - undo(move, ingame) + undo(move) { VariantRules.UndoOnBoard(this.board, move); this.epSquares.pop(); - this.movesCount--; - - if (!!ingame) - this.moves.pop(); - - // Update king position, and reset stored/computed flags - const c = this.getColor(move.start.x,move.start.y); - if (this.getPiece(move.start.x,move.start.y) == VariantRules.KING) - this.kingPos[c] = [move.start.x, move.start.y]; - - this.flags = JSON.parse(move.flags); + this.moves.pop(); + this.unupdateVariables(move); + this.parseFlags(JSON.parse(move.flags)); + console.log("APRES " + this.getNotation(move) + " " + this.board[1][5]); } ////////////// // END OF GAME - checkGameOver(color) + checkRepetition() { // Check for 3 repetitions if (this.moves.length >= 8) @@ -734,23 +735,28 @@ class ChessRules _.isEqual(this.moves[L-3], this.moves[L-7]) && _.isEqual(this.moves[L-4], this.moves[L-8])) { - return "1/2 (repetition)"; + return true; } } + return false; + } - if (this.atLeastOneMove(color)) - { - // game not over + checkGameOver() + { + if (this.checkRepetition()) + return "1/2"; + + if (this.atLeastOneMove()) // game not over return "*"; - } // Game over - return this.checkGameEnd(color); + return this.checkGameEnd(); } - // Useful stand-alone for engine - checkGameEnd(color) + // No moves are possible: compute score + checkGameEnd() { + const color = this.turn; // No valid move: stalemate or checkmate? if (!this.isAttacked(this.kingPos[color], this.getOppCol(color))) return "1/2"; @@ -774,12 +780,12 @@ class ChessRules } // Assumption: at least one legal move - getComputerMove(color) + getComputerMove() { - const oppCol = this.getOppCol(color); + const color = this.turn; // Rank moves using a min-max at depth 2 - let moves1 = this.getAllValidMoves(color); + let moves1 = this.getAllValidMoves(); for (let i=0; i { return (color=="w" ? 1 : -1) * (b.eval - a.eval); }); @@ -824,12 +830,12 @@ class ChessRules return moves1[_.sample(candidates, 1)]; } - alphabeta(color, oppCol, depth, alpha, beta) + alphabeta(depth, alpha, beta) { - const moves = this.getAllValidMoves(color); - if (moves.length == 0) + const color = this.turn; + if (!this.atLeastOneMove()) { - switch (this.checkGameEnd(color)) + switch (this.checkGameEnd()) { case "1/2": return 0; default: return color=="w" ? -1000 : 1000; @@ -837,13 +843,14 @@ class ChessRules } if (depth == 0) return this.evalPosition(); + const moves = this.getAllValidMoves(); let v = color=="w" ? -1000 : 1000; if (color == "w") { for (let i=0; i= beta) @@ -855,7 +862,7 @@ class ChessRules for (let i=0; i= beta) @@ -937,19 +944,14 @@ class ChessRules let fen = pieces[0].join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + pieces[1].join("").toUpperCase() + - " 1111 - 0"; //flags + enPassant + movesCount + " 1111"; //add flags return fen; } // Return current fen according to pieces+colors state getFen() { - const L = this.epSquares.length; - const epSq = this.epSquares[L-1]===undefined - ? "-" - : this.epSquares[L-1].x+","+this.epSquares[L-1].y; - return this.getBaseFen() + " " + this.getFlagsFen() - + " " + epSq + " " + this.movesCount; + return this.getBaseFen() + " " + this.getFlagsFen(); } getBaseFen() @@ -993,7 +995,7 @@ class ChessRules for (let i of ['w','b']) { for (let j=0; j<2; j++) - fen += this.flags[i][j] ? '1' : '0'; + fen += this.castleFlags[i][j] ? '1' : '0'; } return fen; } @@ -1040,14 +1042,15 @@ class ChessRules } // The score is already computed when calling this function - getPGN(mycolor, score, fenStart) + getPGN(mycolor, score, fenStart, mode) { let pgn = ""; pgn += '[Site "vchess.club"]
'; const d = new Date(); - pgn += '[Date "' + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + '"]
'; - pgn += '[White "' + (mycolor=='w'?'Myself':'Anonymous') + '"]
'; - pgn += '[Black "' + (mycolor=='b'?'Myself':'Anonymous') + '"]
'; + const opponent = mode=="human" ? "Anonymous" : "Computer"; + pgn += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate() + '"]
'; + pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]
'; + pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]
'; pgn += '[Fen "' + fenStart + '"]
'; pgn += '[Result "' + score + '"]

';