X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fbase_rules.js;h=5c1ef97c08563f0ac7e0bc3ffee9730a37b75150;hb=9234226104764b91df9d677fb360ad538b98510c;hp=d4315c73724b5572437e3e7b3203b12bc7fefda4;hpb=c605216162dfc09cbbe61b0969f8890f28625372;p=vchess.git diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index d4315c73..5c1ef97c 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -1,3 +1,6 @@ +// (Orthodox) Chess rules are defined in ChessRules class. +// Variants generally inherit from it, and modify some parts. + class PiPo //Piece+Position { // o: {piece[p], color[c], posX[x], posY[y]} @@ -10,6 +13,7 @@ class PiPo //Piece+Position } } +// TODO: for animation, moves should contains "moving" and "fading" maybe... class Move { // o: {appear, vanish, [start,] [end,]} @@ -60,7 +64,7 @@ class ChessRules { 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]}; //respective squares of white and black king + this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; //squares of white and black king const fenParts = fen.split(" "); const position = fenParts[0].split("/"); for (let i=0; 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) { @@ -308,7 +312,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 @@ -316,25 +320,38 @@ 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 (y { // Normal move if (this.board[x+shift][y] == V.EMPTY) - moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p})); + moves.push(this.getBasicMove([x,y], [x+shift,y], {c:pawnColor,p:p})); // Captures - 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 (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:pawnColor,p:p})); + } + if (y=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= 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]) && @@ -753,6 +768,7 @@ class ChessRules return false; } + // Is game over ? And if yes, what is the score ? checkGameOver() { if (this.checkRepetition()) @@ -770,7 +786,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"; @@ -800,58 +816,102 @@ class ChessRules 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 - getComputerMove(moves1) //moves1 might be precomputed (Magnetic chess) + // NOTE: works also for extinction chess because depth is 3... + getComputerMove() { + this.shouldReturn = false; const maxeval = VariantRules.INFINITY; const color = this.turn; - if (!moves1) - moves1 = this.getAllValidMoves(); + // Some variants may show a bigger moves list to the human (Switching), + // thus the argument "computer" below (which is generally ignored) + let moves1 = this.getAllValidMoves("computer"); + + // 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 eval2)) + eval2 = evalPos; + this.undo(moves2[j]); + } + } + else { - this.play(moves2[j]); - let evalPos = this.evalPosition(); - if ((color == "w" && evalPos < eval2) || (color=="b" && evalPos > eval2)) - eval2 = evalPos; - this.undo(moves2[j]); + const score = this.checkGameEnd(); + eval2 = (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval); } - if ((color=="w" && eval2 > moves1[i].eval) || (color=="b" && eval2 < moves1[i].eval)) + if ((color=="w" && eval2 > moves1[i].eval) + || (color=="b" && eval2 < moves1[i].eval)) + { moves1[i].eval = eval2; + } this.undo(moves1[i]); } moves1.sort( (a,b) => { return (color=="w" ? 1 : -1) * (b.eval - a.eval); }); + //console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); - // Skip depth 3 if we found a checkmate (or if we are checkmated in 1...) - if (Math.abs(moves1[0].eval) < VariantRules.THRESHOLD_MATE) + let candidates = [0]; //indices of candidates moves + for (let j=1; j= 3 + && Math.abs(moves1[0].eval) < VariantRules.THRESHOLD_MATE) { - // TODO: show current analyzed move for depth 3, allow stopping eval (return moves1[0]) for (let i=0; i { return (color=="w" ? 1 : -1) * (b.eval - a.eval); }); } + else + return currentBest; + //console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); - 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)]; } @@ -863,13 +923,16 @@ class ChessRules { switch (this.checkGameEnd()) { - case "1/2": return 0; - default: return color=="w" ? -maxeval : maxeval; + case "1/2": + return 0; + default: + const score = this.checkGameEnd(); + return (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval); } } if (depth == 0) return this.evalPosition(); - const moves = this.getAllValidMoves(); + const moves = this.getAllValidMoves("computer"); let v = color=="w" ? -maxeval : maxeval; if (color == "w") { @@ -902,7 +965,7 @@ class ChessRules { const [sizeX,sizeY] = VariantRules.size; let evaluation = 0; - //Just count material for now + // Just count material for now for (let i=0; i { return (x<10 ? "0" : "") + x; }; let pgn = ""; 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 += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) + + '-' + zeroPad(d.getDate()) + '"]
'; pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]
'; pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]
'; - pgn += '[Fen "' + fenStart + '"]
'; + pgn += '[FenStart "' + fenStart + '"]
'; + pgn += '[Fen "' + this.getFen() + '"]
'; pgn += '[Result "' + score + '"]

'; + // Standard PGN + for (let i=0; i