X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fbase_rules.js;h=f1188ab5b1c544ca14b6d17c64d9e1a3ded05f8a;hb=a6abf094c35a26019e47fea21302c4be32ff030b;hp=7c52fc1c47e917438cd6b20e195786da2257169b;hpb=1af36beb39aa5d59735483e915fd1040f93eecca;p=vchess.git diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 7c52fc1c..f1188ab5 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -52,7 +52,7 @@ class ChessRules this.moves = moves; // Use fen string to initialize variables, flags and board this.board = VariantRules.GetBoard(fen); - this.flags = VariantRules.GetFlags(fen); + this.setFlags(fen); this.initVariables(fen); } @@ -125,14 +125,13 @@ class ChessRules } // Overridable: flags can change a lot - static GetFlags(fen) + setFlags(fen) { // white a-castle, h-castle, black a-castle, h-castle - let flags = {'w': new Array(2), 'b': new Array(2)}; - let fenFlags = fen.split(" ")[1]; //flags right after position + this.castleFlags = {'w': new Array(2), 'b': new Array(2)}; + let flags = fen.split(" ")[1]; //flags right after position for (let i=0; i<4; i++) - flags[i < 2 ? 'w' : 'b'][i%2] = (fenFlags.charAt(i) == '1'); - return flags; + this.castleFlags[i < 2 ? 'w' : 'b'][i%2] = (flags.charAt(i) == '1'); } /////////////////// @@ -172,10 +171,20 @@ class ChessRules 'r': [ [-1,0],[1,0],[0,-1],[0,1] ], 'n': [ [-1,-2],[-1,2],[1,-2],[1,2],[-2,-1],[-2,1],[2,-1],[2,1] ], 'b': [ [-1,-1],[-1,1],[1,-1],[1,1] ], - 'q': [ [-1,0],[1,0],[0,-1],[0,1],[-1,-1],[-1,1],[1,-1],[1,1] ] }; } + // Aggregates flags into one object + get flags() { + return this.castleFlags; + } + + // Reverse operation + parseFlags(flags) + { + this.castleFlags = flags; + } + // En-passant square, if any getEpSquare(move) { @@ -222,7 +231,7 @@ class ChessRules // Build a regular move from its initial and destination squares; tr: transformation getBasicMove([sx,sy], [ex,ey], tr) { - var mv = new Move({ + let mv = new Move({ appear: [ new PiPo({ x: ex, @@ -260,7 +269,7 @@ class ChessRules getSlideNJumpMoves([x,y], steps, oneStep) { const color = this.getColor(x,y); - var moves = []; + let moves = []; const [sizeX,sizeY] = VariantRules.size; outerLoop: for (let step of steps) @@ -275,7 +284,7 @@ class ChessRules i += step[0]; j += step[1]; } - if (i>=0 && i<8 && j>=0 && j<8 && this.canTake([x,y], [i,j])) + if (i>=0 && i=0 && j= 0 && x+shift < sizeX && x+shift != lastRank) { @@ -298,7 +308,8 @@ class ChessRules 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) + // 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])); @@ -367,21 +378,25 @@ class ChessRules // What are the queen moves from square x,y ? getPotentialQueenMoves(sq) { - return this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.QUEEN]); + const V = VariantRules; + return this.getSlideNJumpMoves(sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP])); } // What are the king moves from square x,y ? getPotentialKingMoves(sq) { + const V = VariantRules; // Initialize with normal moves - let moves = this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.QUEEN], "oneStep"); + let moves = this.getSlideNJumpMoves(sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"); return moves.concat(this.getCastleMoves(sq)); } getCastleMoves([x,y]) { const c = this.getColor(x,y); - if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c]) + const [sizeX,sizeY] = VariantRules.size; + if (x != (c=="w" ? sizeX-1 : 0) || y != this.INIT_COL_KING[c]) return []; //x isn't first rank, or king has moved (shortcut) const V = VariantRules; @@ -390,11 +405,11 @@ class ChessRules const oppCol = this.getOppCol(c); let moves = []; let i = 0; - const finalSquares = [ [2,3], [6,5] ]; //king, then rook + const finalSquares = [ [2,3], [sizeY-2,sizeY-3] ]; //king, then rook castlingCheck: for (let castleSide=0; castleSide < 2; castleSide++) //large, then small { - if (!this.flags[c][castleSide]) + if (!this.castleFlags[c][castleSide]) continue; // If this code is reached, rooks and king are on initial position @@ -402,7 +417,7 @@ class ChessRules 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 && + 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))))) { @@ -467,8 +482,7 @@ class ChessRules { if (moves.length == 0) return []; - let color = this.turn; - return moves.filter(m => { return !this.underCheck(m, color); }); + return moves.filter(m => { return !this.underCheck(m); }); } // Search for all valid moves considering current turn (for engine and game end) @@ -476,11 +490,11 @@ class ChessRules { const color = this.turn; const oppCol = this.getOppCol(color); - var potentialMoves = []; - let [sizeX,sizeY] = VariantRules.size; - for (var i=0; i 0) { - for (let i=0; i 0) + if (this.filterValid([moves[k]]).length > 0) return true; } } @@ -533,14 +547,15 @@ class ChessRules // Is square x,y attacked by pawns of color c ? isAttackedByPawn([x,y], colors) { + const [sizeX,sizeY] = VariantRules.size; for (let c of colors) { let pawnShift = (c=="w" ? 1 : -1); - if (x+pawnShift>=0 && x+pawnShift<8) + if (x+pawnShift>=0 && x+pawnShift=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==VariantRules.PAWN + if (y+i>=0 && y+i=0 && rx<8 && ry>=0 && ry<8 && this.board[rx][ry] == VariantRules.EMPTY - && !oneStep) + while (rx>=0 && rx=0 && ry=0 && rx<8 && ry>=0 && ry<8 && this.board[rx][ry] != VariantRules.EMPTY + if (rx>=0 && rx=0 && ry 0) { 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); - const oppFirstRank = 7 - firstRank; + const oppFirstRank = (sizeX-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.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)) + && this.INIT_COL_ROOK[oppCol].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; } } @@ -687,11 +707,14 @@ class ChessRules play(move, ingame) { + // DEBUG: +// if (!this.states) this.states = []; +// if (!ingame) this.states.push(JSON.stringify(this.board)); + if (!!ingame) move.notation = this.getNotation(move); - // Save flags (for undo) - move.flags = JSON.stringify(this.flags); //TODO: less costly? + move.flags = JSON.stringify(this.flags); //save flags (for undo) this.updateVariables(move); this.moves.push(move); this.epSquares.push( this.getEpSquare(move) ); @@ -704,7 +727,12 @@ class ChessRules this.epSquares.pop(); this.moves.pop(); this.unupdateVariables(move); - this.flags = JSON.parse(move.flags); + this.parseFlags(JSON.parse(move.flags)); + + // DEBUG: +// let state = this.states.pop(); +// if (JSON.stringify(this.board) != state) +// debugger; } ////////////// @@ -745,7 +773,7 @@ class ChessRules { const color = this.turn; // No valid move: stalemate or checkmate? - if (!this.isAttacked(this.kingPos[color], this.getOppCol(color))) + if (!this.isAttacked(this.kingPos[color], [this.getOppCol(color)])) return "1/2"; // OK, checkmate return color == "w" ? "0-1" : "1-0"; @@ -766,18 +794,43 @@ class ChessRules }; } + static get INFINITY() { + return 9999; //"checkmate" (unreachable eval) + } + + static get THRESHOLD_MATE() { + // At this value or above, the game is over + return VariantRules.INFINITY; + } + + static get SEARCH_DEPTH() { + return 3; //2 for high branching factor, 4 for small (Loser chess) + } + // Assumption: at least one legal move + // NOTE: works also for extinction chess because depth is 3... getComputerMove() { + this.shouldReturn = false; + const maxeval = VariantRules.INFINITY; const color = this.turn; - - // Rank moves using a min-max at depth 2 let moves1 = this.getAllValidMoves(); + // Can I mate in 1 ? (for Magnetic & Extinction) + for (let i of _.shuffle(_.range(moves1.length))) + { + this.play(moves1[i]); + const finish = (Math.abs(this.evalPosition()) >= VariantRules.THRESHOLD_MATE); + this.undo(moves1[i]); + if (finish) + return moves1[i]; + } + + // Rank moves using a min-max at depth 2 for (let i=0; i { return (color=="w" ? 1 : -1) * (b.eval - a.eval); }); - // TODO: show current analyzed move for depth 3, allow stopping eval (return moves1[0]) - for (let i=0; i= 3 + && Math.abs(moves1[0].eval) < VariantRules.THRESHOLD_MATE) { - this.play(moves1[i]); - // 0.1 * oldEval : heuristic to avoid some bad moves (not all...) - moves1[i].eval = 0.1*moves1[i].eval + this.alphabeta(2, -1000, 1000); - this.undo(moves1[i]); + for (let i=0; i { 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; - let candidates = [0]; //indices of candidates moves + candidates = [0]; for (let j=1; j { return [this.getNotation(m), m.eval]; })); +// console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); return moves1[_.sample(candidates, 1)]; } alphabeta(depth, alpha, beta) { + const maxeval = VariantRules.INFINITY; const color = this.turn; if (!this.atLeastOneMove()) { switch (this.checkGameEnd()) { case "1/2": return 0; - default: return color=="w" ? -1000 : 1000; + default: return color=="w" ? -maxeval : maxeval; } } if (depth == 0) return this.evalPosition(); const moves = this.getAllValidMoves(); - let v = color=="w" ? -1000 : 1000; + let v = color=="w" ? -maxeval : maxeval; if (color == "w") { for (let i=0; i 1) + if (move.vanish.length > move.appear.length) { // Capture - let startColumn = String.fromCharCode(97 + move.start.y); + const startColumn = String.fromCharCode(97 + move.start.y); notation = startColumn + "x" + finalSquare; } else //no capture @@ -1024,7 +1091,8 @@ class ChessRules else { // Piece movement - return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare; + return piece.toUpperCase() + + (move.vanish.length > move.appear.length ? "x" : "") + finalSquare; } } @@ -1035,6 +1103,7 @@ class ChessRules pgn += '[Site "vchess.club"]
'; const d = new Date(); const opponent = mode=="human" ? "Anonymous" : "Computer"; + pgn += '[Variant "' + variant + '"]
'; pgn += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate() + '"]
'; pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]
'; pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]
';