Prepare some more variants (unfinished)
[vchess.git] / public / javascripts / base_rules.js
index 620b86f..d85ff86 100644 (file)
@@ -34,6 +34,10 @@ class ChessRules
        //////////////
        // MISC UTILS
 
+       static get HasFlags() { return true; } //some variants don't have flags
+
+       static get HasEnpassant() { return true; } //some variants don't have ep.
+
        // Path to pieces
        static getPpath(b)
        {
@@ -57,7 +61,34 @@ class ChessRules
        {
                const fenParsed = V.ParseFen(fen);
                // 1) Check position
-               const position = fenParsed.position;
+               if (!V.IsGoodPosition(fenParsed.position))
+                       return false;
+               // 2) Check turn
+               if (!fenParsed.turn || !["w","b"].includes(fenParsed.turn))
+                       return false;
+               // 3) Check flags
+               if (V.HasFlags && (!fenParsed.flags || !V.IsGoodFlags(fenParsed.flags)))
+                       return false;
+               // 4) Check enpassant
+               if (V.HasEnpassant)
+               {
+                       if (!fenParsed.enpassant)
+                               return false;
+                       if (fenParsed.enpassant != "-")
+                       {
+                               const ep = V.SquareToCoords(fenParsed.enpassant);
+                               if (ep.y < 0 || ep.y > V.size.y || isNaN(ep.x) || ep.x < 0 || ep.x > V.size.x)
+                                       return false;
+                       }
+               }
+               return true;
+       }
+
+       // Is position part of the FEN a priori correct?
+       static IsGoodPosition(position)
+       {
+               if (position.length == 0)
+                       return false;
                const rows = position.split("/");
                if (rows.length != V.size.x)
                        return false;
@@ -79,19 +110,6 @@ class ChessRules
                        if (sumElts != V.size.y)
                                return false;
                }
-               // 2) Check flags (if present)
-               if (!!fenParsed.flags && !V.IsGoodFlags(fenParsed.flags))
-                       return false;
-               // 3) Check turn (if present)
-               if (!!fenParsed.turn && !["w","b"].includes(fenParsed.turn))
-                       return false;
-               // 4) Check enpassant (if present)
-               if (!!fenParsed.enpassant)
-               {
-                       const ep = V.SquareToCoords(fenParsed.enpassant);
-                       if (ep.y < 0 || ep.y > V.size.y || isNaN(ep.x) || ep.x < 0 || ep.x > V.size.x)
-                               return false;
-               }
                return true;
        }
 
@@ -101,6 +119,12 @@ class ChessRules
                return !!flags.match(/^[01]{4,4}$/);
        }
 
+       // 3 --> d (column letter from number)
+       static GetColumn(colnum)
+       {
+               return String.fromCharCode(97 + colnum);
+       }
+
        // a4 --> {x:3,y:0}
        static SquareToCoords(sq)
        {
@@ -113,7 +137,7 @@ class ChessRules
        // {x:0,y:4} --> e8
        static CoordsToSquare(coords)
        {
-               return String.fromCharCode(97 + coords.y) + (V.size.x - coords.x);
+               return V.GetColumn(coords.y) + (V.size.x - coords.x);
        }
 
        // Aggregates flags into one object
@@ -138,10 +162,7 @@ class ChessRules
                        const square = moveOrSquare;
                        if (square == "-")
                                return undefined;
-                       return {
-                               x: square[0].charCodeAt()-97,
-                               y: V.size.x-parseInt(square[1])
-                       };
+                       return V.SquareToCoords(square);
                }
                // Argument is a move:
                const move = moveOrSquare;
@@ -246,19 +267,25 @@ class ChessRules
        static ParseFen(fen)
        {
                const fenParts = fen.split(" ");
-               return {
+               let res =
+               {
                        position: fenParts[0],
                        turn: fenParts[1],
-                       flags: fenParts[2],
-                       enpassant: fenParts[3],
                };
+               let nextIdx = 2;
+               if (V.HasFlags)
+                       Object.assign(res, {flags: fenParts[nextIdx++]});
+               if (V.HasEnpassant)
+                       Object.assign(res, {enpassant: fenParts[nextIdx]});
+               return res;
        }
 
        // Return current fen (game state)
        getFen()
        {
-               return this.getBaseFen() + " " + this.turn + " " +
-                       this.getFlagsFen() + " " + this.getEnpassantFen();
+               return this.getBaseFen() + " " + this.turn +
+                       (V.HasFlags ? (" " + this.getFlagsFen()) : "") +
+                       (V.HasEnpassant ? (" " + this.getEnpassantFen()) : "");
        }
 
        // Position part of the FEN string
@@ -280,7 +307,7 @@ class ChessRules
                                                position += emptyCount;
                                                emptyCount = 0;
                                        }
-                                       fen += V.board2fen(this.board[i][j]);
+                                       position += V.board2fen(this.board[i][j]);
                                }
                        }
                        if (emptyCount > 0)
@@ -311,7 +338,7 @@ class ChessRules
        getEnpassantFen()
        {
                const L = this.epSquares.length;
-               if (L == 0)
+               if (!this.epSquares[L-1])
                        return "-"; //no en-passant
                return V.CoordsToSquare(this.epSquares[L-1]);
        }
@@ -361,18 +388,13 @@ class ChessRules
                this.setOtherVariables(fen);
        }
 
-       // Some additional variables from FEN (variant dependant)
-       setOtherVariables(fen)
+       // Scan board for kings and rooks positions
+       scanKingsRooks(fen)
        {
-               // Set flags and enpassant:
-               const parsedFen = V.ParseFen(fen);
-               this.setFlags(fenParsed.flags);
-               this.epSquares = [ V.SquareToCoords(parsedFen.enpassant) ];
-               // Search for king and rooks positions:
                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]}; //squares of white and black king
-               const fenRows = parsedFen.position.split("/");
+               const fenRows = V.ParseFen(fen).position.split("/");
                for (let i=0; i<fenRows.length; i++)
                {
                        let k = 0; //column index on board
@@ -410,6 +432,24 @@ class ChessRules
                }
        }
 
+       // Some additional variables from FEN (variant dependant)
+       setOtherVariables(fen)
+       {
+               // Set flags and enpassant:
+               const parsedFen = V.ParseFen(fen);
+               if (V.HasFlags)
+                       this.setFlags(parsedFen.flags);
+               if (V.HasEnpassant)
+               {
+                       const epSq = parsedFen.enpassant != "-"
+                               ? V.SquareToCoords(parsedFen.enpassant)
+                               : undefined;
+                       this.epSquares = [ epSq ];
+               }
+               // Search for king and rooks positions:
+               this.scanKingsRooks(fen);
+       }
+
        /////////////////////
        // GETTERS & SETTERS
 
@@ -559,74 +599,65 @@ class ChessRules
                const color = this.turn;
                let moves = [];
                const [sizeX,sizeY] = [V.size.x,V.size.y];
-               const shift = (color == "w" ? -1 : 1);
+               const shiftX = (color == "w" ? -1 : 1);
                const firstRank = (color == 'w' ? sizeX-1 : 0);
                const startRank = (color == "w" ? sizeX-2 : 1);
                const lastRank = (color == "w" ? 0 : sizeX-1);
+               const pawnColor = this.getColor(x,y); //can be different for checkered
 
-               if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank)
+               if (x+shiftX >= 0 && x+shiftX < sizeX) //TODO: always true
                {
-                       // Normal moves
-                       if (this.board[x+shift][y] == V.EMPTY)
+                       const finalPieces = x + shiftX == lastRank
+                               ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+                               : [V.PAWN]
+                       // One square forward
+                       if (this.board[x+shiftX][y] == V.EMPTY)
                        {
-                               moves.push(this.getBasicMove([x,y], [x+shift,y]));
-                               // 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)
+                               for (let piece of finalPieces)
+                               {
+                                       moves.push(this.getBasicMove([x,y], [x+shiftX,y],
+                                               {c:pawnColor,p:piece}));
+                               }
+                               // Next condition because pawns on 1st rank can generally jump
+                               if ([startRank,firstRank].includes(x)
+                                       && this.board[x+2*shiftX][y] == V.EMPTY)
                                {
                                        // Two squares jump
-                                       moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
+                                       moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
                                }
                        }
                        // Captures
-                       if (y>0 && this.board[x+shift][y-1] != V.EMPTY
-                               && this.canTake([x,y], [x+shift,y-1]))
-                       {
-                               moves.push(this.getBasicMove([x,y], [x+shift,y-1]));
-                       }
-                       if (y<sizeY-1 && this.board[x+shift][y+1] != V.EMPTY
-                               && this.canTake([x,y], [x+shift,y+1]))
+                       for (let shiftY of [-1,1])
                        {
-                               moves.push(this.getBasicMove([x,y], [x+shift,y+1]));
-                       }
-               }
-
-               if (x+shift == lastRank)
-               {
-                       // Promotion
-                       const pawnColor = this.getColor(x,y); //can be different for checkered
-                       let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
-                       promotionPieces.forEach(p => {
-                               // Normal move
-                               if (this.board[x+shift][y] == V.EMPTY)
-                                       moves.push(this.getBasicMove([x,y], [x+shift,y], {c:pawnColor,p:p}));
-                               // Captures
-                               if (y>0 && this.board[x+shift][y-1] != V.EMPTY
-                                       && this.canTake([x,y], [x+shift,y-1]))
+                               if (y + shiftY >= 0 && y + shiftY < sizeY
+                                       && this.board[x+shiftX][y+shiftY] != V.EMPTY
+                                       && this.canTake([x,y], [x+shiftX,y+shiftY]))
                                {
-                                       moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:pawnColor,p:p}));
-                               }
-                               if (y<sizeY-1 && this.board[x+shift][y+1] != V.EMPTY
-                                       && this.canTake([x,y], [x+shift,y+1]))
-                               {
-                                       moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:pawnColor,p:p}));
+                                       for (let piece of finalPieces)
+                                       {
+                                               moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+                                                       {c:pawnColor,p:piece}));
+                                       }
                                }
-                       });
+                       }
                }
 
-               // En passant
-               const Lep = this.epSquares.length;
-               const epSquare = (Lep>0 ? this.epSquares[Lep-1] : undefined);
-               if (!!epSquare && epSquare.x == x+shift && Math.abs(epSquare.y - y) == 1)
+               if (V.HasEnpassant)
                {
-                       const epStep = epSquare.y - y;
-                       let enpassantMove = this.getBasicMove([x,y], [x+shift,y+epStep]);
-                       enpassantMove.vanish.push({
-                               x: x,
-                               y: y+epStep,
-                               p: 'p',
-                               c: this.getColor(x,y+epStep)
-                       });
-                       moves.push(enpassantMove);
+                       // En passant
+                       const Lep = this.epSquares.length;
+                       const epSquare = this.epSquares[Lep-1]; //always at least one element
+                       if (!!epSquare && epSquare.x == x+shiftX && Math.abs(epSquare.y - y) == 1)
+                       {
+                               let enpassantMove = this.getBasicMove([x,y], [epSquare.x,epSquare.y]);
+                               enpassantMove.vanish.push({
+                                       x: x,
+                                       y: epSquare.y,
+                                       p: 'p',
+                                       c: this.getColor(x,epSquare.y)
+                               });
+                               moves.push(enpassantMove);
+                       }
                }
 
                return moves;
@@ -961,17 +992,19 @@ class ChessRules
                if (!!ingame)
                        move.notation = [this.getNotation(move), this.getLongNotation(move)];
 
-               move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
+               if (V.HasFlags)
+                       move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
                this.updateVariables(move);
                this.moves.push(move);
-               this.epSquares.push( this.getEpSquare(move) );
+               if (V.HasEnpassant)
+                       this.epSquares.push( this.getEpSquare(move) );
                this.turn = this.getOppCol(this.turn);
                V.PlayOnBoard(this.board, move);
 
                if (!!ingame)
                {
                        // Hash of current game state *after move*, to detect repetitions
-                       move.hash = hex_md5(this.getFen();
+                       move.hash = hex_md5(this.getFen());
                }
        }
 
@@ -979,10 +1012,12 @@ class ChessRules
        {
                V.UndoOnBoard(this.board, move);
                this.turn = this.getOppCol(this.turn);
-               this.epSquares.pop();
+               if (V.HasEnpassant)
+                       this.epSquares.pop();
                this.moves.pop();
                this.unupdateVariables(move);
-               this.disaggregateFlags(JSON.parse(move.flags));
+               if (V.HasFlags)
+                       this.disaggregateFlags(JSON.parse(move.flags));
 
                // DEBUG:
 //             if (this.getFen() != this.states[this.states.length-1])