Early draft of Wildebeest + Grand
[vchess.git] / public / javascripts / base_rules.js
index 346eff0..f466624 100644 (file)
@@ -171,7 +171,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] ]
                };
        }
 
@@ -379,14 +378,17 @@ 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));
        }
 
@@ -479,8 +481,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)
@@ -503,25 +504,25 @@ class ChessRules
                // No: if happen on last 1/2 move, could lead to forbidden moves, wrong evals
                return this.filterValid(potentialMoves);
        }
-       
+
        // Stop at the first move found
        atLeastOneMove()
        {
                const color = this.turn;
                const oppCol = this.getOppCol(color);
                let [sizeX,sizeY] = VariantRules.size;
-               for (var i=0; i<sizeX; i++)
+               for (let i=0; i<sizeX; i++)
                {
-                       for (var j=0; j<sizeY; j++)
+                       for (let j=0; j<sizeY; j++)
                        {
                                if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) != oppCol)
                                {
                                        const moves = this.getPotentialMovesFrom([i,j]);
                                        if (moves.length > 0)
                                        {
-                                               for (let i=0; i<moves.length; i++)
+                                               for (let k=0; k<moves.length; k++)
                                                {
-                                                       if (this.filterValid([moves[i]]).length > 0)
+                                                       if (this.filterValid([moves[k]]).length > 0)
                                                                return true;
                                                }
                                        }
@@ -587,15 +588,17 @@ class ChessRules
        // Is square x,y attacked by queens of color c ?
        isAttackedByQueen(sq, colors)
        {
-               return this.isAttackedBySlideNJump(sq, colors,
-                       VariantRules.QUEEN, VariantRules.steps[VariantRules.QUEEN]);
+               const V = VariantRules;
+               return this.isAttackedBySlideNJump(sq, colors, V.QUEEN,
+                       V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
        }
 
        // Is square x,y attacked by king of color c ?
        isAttackedByKing(sq, colors)
        {
-               return this.isAttackedBySlideNJump(sq, colors,
-                       VariantRules.KING, VariantRules.steps[VariantRules.QUEEN], "oneStep");
+               const V = VariantRules;
+               return this.isAttackedBySlideNJump(sq, colors, V.KING,
+                       V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
        }
 
        // Generic method for non-pawn pieces ("sliding or jumping"): is x,y attacked by piece != color ?
@@ -699,6 +702,10 @@ 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);
 
@@ -716,6 +723,11 @@ class ChessRules
                this.moves.pop();
                this.unupdateVariables(move);
                this.parseFlags(JSON.parse(move.flags));
+
+               // DEBUG:
+//             let state = this.states.pop();
+//             if (JSON.stringify(this.board) != state)
+//                     debugger;
        }
 
        //////////////
@@ -777,18 +789,28 @@ 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;
+       }
+
        // Assumption: at least one legal move
-       getComputerMove()
+       getComputerMove(moves1) //moves1 might be precomputed (Magnetic chess)
        {
+               const maxeval = VariantRules.INFINITY;
                const color = this.turn;
+               if (!moves1)
+                       moves1 = this.getAllValidMoves();
 
                // Rank moves using a min-max at depth 2
-               let moves1 = this.getAllValidMoves();
-
                for (let i=0; i<moves1.length; i++)
                {
-                       moves1[i].eval = (color=="w" ? -1 : 1) * 1000; //very low, I'm checkmated
-                       let eval2 = (color=="w" ? 1 : -1) * 1000; //initialized with very high (checkmate) value
+                       moves1[i].eval = (color=="w" ? -1 : 1) * maxeval; //very low, I'm checkmated
+                       let eval2 = (color=="w" ? 1 : -1) * maxeval; //initialized with checkmate value
                        this.play(moves1[i]);
                        // Second half-move:
                        let moves2 = this.getAllValidMoves();
@@ -810,39 +832,43 @@ class ChessRules
                }
                moves1.sort( (a,b) => { 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<moves1.length; i++)
+               // Skip depth 3 if we found a checkmate (or if we are checkmated in 1...)
+               if (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]);
+                       // TODO: show current analyzed move for depth 3, allow stopping eval (return moves1[0])
+                       for (let i=0; i<moves1.length; i++)
+                       {
+                               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, -maxeval, maxeval);
+                               this.undo(moves1[i]);
+                       }
+                       moves1.sort( (a,b) => { return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
                }
-               moves1.sort( (a,b) => { return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
 
                let candidates = [0]; //indices of candidates moves
                for (let j=1; j<moves1.length && moves1[j].eval == moves1[0].eval; j++)
                        candidates.push(j);
-
-               //console.log(moves1.map(m => { 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<moves.length; i++)
@@ -1047,6 +1073,7 @@ class ChessRules
                pgn += '[Site "vchess.club"]<br>';
                const d = new Date();
                const opponent = mode=="human" ? "Anonymous" : "Computer";
+               pgn += '[Variant "' + variant + '"]<br>';
                pgn += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate() + '"]<br>';
                pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]<br>';
                pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]<br>';