Draft of Ultima chess rules; almost OK HalfChess
[vchess.git] / public / javascripts / base_rules.js
index 860495a..750cd2d 100644 (file)
@@ -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]}
@@ -61,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<position.length; i++)
@@ -380,7 +383,8 @@ class ChessRules
        // What are the knight moves from square x,y ?
        getPotentialKnightMoves(sq)
        {
-               return this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.KNIGHT], "oneStep");
+               return this.getSlideNJumpMoves(
+                       sq, VariantRules.steps[VariantRules.KNIGHT], "oneStep");
        }
 
        // What are the bishop moves from square x,y ?
@@ -481,7 +485,8 @@ class ChessRules
 
        canIplay(side, [x,y])
        {
-               return ((side=='w' && this.moves.length%2==0) || (side=='b' && this.moves.length%2==1))
+               return ((side=='w' && this.moves.length%2==0)
+                               || (side=='b' && this.moves.length%2==1))
                        && this.getColor(x,y) == side;
        }
 
@@ -491,7 +496,7 @@ class ChessRules
                return this.filterValid( this.getPotentialMovesFrom(sq) );
        }
 
-       // TODO: once a promotion is filtered, the others results are same: useless computations
+       // TODO: promotions (into R,B,N,Q) should be filtered only once
        filterValid(moves)
        {
                if (moves.length == 0)
@@ -510,7 +515,7 @@ class ChessRules
                {
                        for (let j=0; j<sizeY; j++)
                        {
-                               // Next condition ... != oppCol is a little HACK to work with checkered variant
+                               // Next condition "!= oppCol" = harmless hack to work with checkered variant
                                if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) != oppCol)
                                        Array.prototype.push.apply(potentialMoves, this.getPotentialMovesFrom([i,j]));
                        }
@@ -747,6 +752,7 @@ class ChessRules
        // END OF GAME
 
        // Basic check for 3 repetitions (in the last moves only)
+       // TODO: extend to usual 3-repetition recognition (storing FEN with move?)
        checkRepetition()
        {
                if (this.moves.length >= 8)
@@ -819,7 +825,6 @@ class ChessRules
        // NOTE: works also for extinction chess because depth is 3...
        getComputerMove()
        {
-               this.shouldReturn = false;
                const maxeval = VariantRules.INFINITY;
                const color = this.turn;
                // Some variants may show a bigger moves list to the human (Switching),
@@ -869,8 +874,11 @@ class ChessRules
                                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); });
@@ -881,14 +889,17 @@ class ChessRules
                        candidates.push(j);
                let currentBest = moves1[_.sample(candidates, 1)];
 
+               // From here, depth >= 3: may take a while, so we control time
+               const timeStart = Date.now();
+
                // Skip depth 3+ if we found a checkmate (or if we are checkmated in 1...)
                if (VariantRules.SEARCH_DEPTH >= 3
                        && Math.abs(moves1[0].eval) < VariantRules.THRESHOLD_MATE)
                {
                        for (let i=0; i<moves1.length; i++)
                        {
-                               if (this.shouldReturn)
-                                       return currentBest; //depth-2, minimum
+                               if (Date.now()-timeStart >= 5000) //more than 5 seconds
+                                       return currentBest; //depth 2 at least
                                this.play(moves1[i]);
                                // 0.1 * oldEval : heuristic to avoid some bad moves (not all...)
                                moves1[i].eval = 0.1*moves1[i].eval +
@@ -978,9 +989,9 @@ class ChessRules
        // Setup the initial random (assymetric) position
        static GenRandInitFen()
        {
-               let pieces = [new Array(8), new Array(8)];
+               let pieces = { "w": new Array(8), "b": new Array(8) };
                // Shuffle pieces on first and last rank
-               for (let c = 0; c <= 1; c++)
+               for (let c of ["w","b"])
                {
                        let positions = _.range(8);
 
@@ -1022,9 +1033,9 @@ class ChessRules
                        pieces[c][knight2Pos] = 'n';
                        pieces[c][rook2Pos] = 'r';
                }
-               let fen = pieces[0].join("") +
+               let fen = pieces["b"].join("") +
                        "/pppppppp/8/8/8/8/PPPPPPPP/" +
-                       pieces[1].join("").toUpperCase() +
+                       pieces["w"].join("").toUpperCase() +
                        " 1111"; //add flags
                return fen;
        }
@@ -1137,7 +1148,8 @@ class ChessRules
                const d = new Date();
                const opponent = mode=="human" ? "Anonymous" : "Computer";
                pgn += '[Variant "' + variant + '"]<br>';
-               pgn += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) + '-' + zeroPad(d.getDate()) + '"]<br>';
+               pgn += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) +
+                       '-' + zeroPad(d.getDate()) + '"]<br>';
                pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]<br>';
                pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]<br>';
                pgn += '[FenStart "' + fenStart + '"]<br>';