X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fbase_rules.js;h=79b6a7a3ce468e6f23e64dd798317ff29eb2aaad;hb=dda21a71b6245832a78ca987b14c77176bd15dd6;hp=ec5f9372f826e15ea1f0b4848e001f67101c03fb;hpb=0f51ef985c094530a63d242dddf514e66d23959b;p=vchess.git diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index ec5f9372..79b6a7a3 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -124,7 +124,7 @@ class ChessRules return board; } - // Overridable: flags can change a lot + // Extract (relevant) flags from fen setFlags(fen) { // white a-castle, h-castle, black a-castle, h-castle @@ -137,7 +137,6 @@ class ChessRules /////////////////// // GETTERS, SETTERS - // Simple useful getters static get size() { return [8,8]; } // Two next functions return 'undefined' if called on empty square getColor(i,j) { return this.board[i][j].charAt(0); } @@ -171,7 +170,6 @@ 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] ] }; } @@ -200,7 +198,7 @@ class ChessRules return undefined; //default } - // can thing on square1 take thing on square2 + // Can thing on square1 take thing on square2 canTake([x1,y1], [x2,y2]) { return this.getColor(x1,y1) != this.getColor(x2,y2); @@ -277,7 +275,8 @@ class ChessRules { let i = x + step[0]; let j = y + step[1]; - while (i>=0 && i=0 && j=0 && i=0 && j=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) { @@ -309,7 +308,7 @@ class ChessRules 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 generally allow them to jump + // 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 @@ -317,10 +316,16 @@ class ChessRules } } // Captures - if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY) + if (y>0 && 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 (y0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY) + if (y>0 && 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); }); + return moves.filter(m => { return !this.underCheck(m); }); } // Search for all valid moves considering current turn (for engine and game end) @@ -488,11 +502,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 && 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) @@ -674,7 +695,7 @@ class ChessRules 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)) { @@ -689,6 +710,8 @@ class ChessRules } } + // After move is undo-ed, un-update variables (flags are reset) + // TODO: more symmetry, by storing flags increment in move... unupdateVariables(move) { // (Potentially) Reset king position @@ -700,7 +723,7 @@ class ChessRules play(move, ingame) { if (!!ingame) - move.notation = this.getNotation(move); + move.notation = [this.getNotation(move), this.getLongNotation(move)]; move.flags = JSON.stringify(this.flags); //save flags (for undo) this.updateVariables(move); @@ -721,12 +744,11 @@ class ChessRules ////////////// // END OF GAME + // Basic check for 3 repetitions (in the last moves only) checkRepetition() { - // Check for 3 repetitions if (this.moves.length >= 8) { - // NOTE: crude detection, only moves repetition const L = this.moves.length; if (_.isEqual(this.moves[L-1], this.moves[L-5]) && _.isEqual(this.moves[L-2], this.moves[L-6]) && @@ -739,6 +761,7 @@ class ChessRules return false; } + // Is game over ? And if yes, what is the score ? checkGameOver() { if (this.checkRepetition()) @@ -756,7 +779,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"; @@ -777,18 +800,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]; })); return moves1[_.sample(candidates, 1)]; } + // TODO: some optimisations, understand why CH get mated in 2 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'; pgn += '[Result "' + score + '"]

'; + // Standard PGN for (let i=0; i
"; + // "Complete moves" PGN (helping in ambiguous cases) + for (let i=0; i