Some code cleaning + clarifying (TODO: work on variables names)
[vchess.git] / public / javascripts / variants / Magnetic.js
index e941532..7d9d511 100644 (file)
-class MagneticRules
+class MagneticRules extends ChessRules
 {
        getEpSquare(move)
        {
                return undefined; //no en-passant
        }
 
-       // Complete a move with magnetic actions
-       applyMagneticLaws([x,y], move)
-       {
-               // TODO
-       }
-
-       getBasicMove([sx,sy], [ex,ey], tr)
+       getPotentialMovesFrom([x,y])
        {
-               var mv = new Move({
-                       appear: [
-                               new PiPo({
-                                       x: ex,
-                                       y: ey,
-                                       c: !!tr ? tr.c : this.getColor(sx,sy),
-                                       p: !!tr ? tr.p : this.getPiece(sx,sy)
-                               })
-                       ],
-                       vanish: [
-                               new PiPo({
-                                       x: sx,
-                                       y: sy,
-                                       c: this.getColor(sx,sy),
-                                       p: this.getPiece(sx,sy)
-                               })
-                       ]
+               let standardMoves = super.getPotentialMovesFrom([x,y]);
+               let moves = [];
+               standardMoves.forEach(m => {
+                       let newMove_s = this.applyMagneticLaws(m);
+                       if (newMove_s.length == 1)
+                               moves.push(newMove_s[0]);
+                       else //promotion
+                               moves = moves.concat(newMove_s);
                });
-
-               if (this.board[ex][ey] != VariantRules.EMPTY)
-               {
-                       mv.vanish.push(
-                               new PiPo({
-                                       x: ex,
-                                       y: ey,
-                                       c: this.getColor(ex,ey),
-                                       p: this.getPiece(ex,ey)
-                               })
-                       );
-               }
-               this.applyMagneticLaws([ex,ey], mv);
-               return mv;
+               return moves;
        }
 
-       getCastleMoves([x,y])
+       // Complete a move with magnetic actions
+       // TODO: job is done multiple times for (normal) promotions.
+       applyMagneticLaws(move)
        {
-               const c = this.getColor(x,y);
-               if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c])
-                       return []; //x isn't first rank, or king has moved (shortcut)
-
                const V = VariantRules;
-
-               // Castling ?
-               const oppCol = this.getOppCol(c);
-               let moves = [];
-               let i = 0;
-               const finalSquares = [ [2,3], [6,5] ]; //king, then rook
-               castlingCheck:
-               for (let castleSide=0; castleSide < 2; castleSide++) //large, then small
+               if (move.appear[0].p == V.KING && move.appear.length==1)
+                       return [move]; //kings are not charged
+               const aIdx = (move.appear[0].p != V.KING ? 0 : 1); //if castling, rook is charged
+               const [x,y] = [move.appear[aIdx].x, move.appear[aIdx].y];
+               const color = this.turn;
+               const lastRank = (color=="w" ? 0 : 7);
+               const standardMove = JSON.parse(JSON.stringify(move));
+               this.play(standardMove);
+               const [sizeX,sizeY] = V.size;
+               for (let step of [[-1,0],[1,0],[0,-1],[0,1]])
                {
-                       if (!this.flags[c][castleSide])
-                               continue;
-                       // If this code is reached, rooks and king are on initial position
-
-                       // Nothing on the path of the king (and no checks; OK also if y==finalSquare)?
-                       let step = finalSquares[castleSide][0] < y ? -1 : 1;
-                       for (i=y; i!=finalSquares[castleSide][0]; i+=step)
+                       let [i,j] = [x+step[0],y+step[1]];
+                       while (i>=0 && i<sizeX && j>=0 && j<sizeY)
                        {
-                               if (this.isAttacked([x,i], oppCol) || (this.board[x][i] != V.EMPTY &&
-                                       // NOTE: next check is enough, because of chessboard constraints
-                                       (this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i)))))
+                               if (this.board[i][j] != V.EMPTY)
                                {
-                                       continue castlingCheck;
+                                       // Found something. Same color or not?
+                                       if (this.getColor(i,j) != color)
+                                       {
+                                               // Attraction
+                                               if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2) && this.getPiece(i,j) != V.KING)
+                                               {
+                                                       move.vanish.push(
+                                                               new PiPo({
+                                                                       p:this.getPiece(i,j),
+                                                                       c:this.getColor(i,j),
+                                                                       x:i,
+                                                                       y:j
+                                                               })
+                                                       );
+                                                       move.appear.push(
+                                                               new PiPo({
+                                                                       p:this.getPiece(i,j),
+                                                                       c:this.getColor(i,j),
+                                                                       x:x+step[0],
+                                                                       y:y+step[1]
+                                                               })
+                                                       );
+                                               }
+                                       }
+                                       else
+                                       {
+                                               // Repulsion
+                                               if (this.getPiece(i,j) != V.KING)
+                                               {
+                                                       // Push it until we meet an obstacle or edge of the board
+                                                       let [ii,jj] = [i+step[0],j+step[1]];
+                                                       while (ii>=0 && ii<sizeX && jj>=0 && jj<sizeY)
+                                                       {
+                                                               if (this.board[ii][jj] != V.EMPTY)
+                                                                       break;
+                                                               ii += step[0];
+                                                               jj += step[1];
+                                                       }
+                                                       ii -= step[0];
+                                                       jj -= step[1];
+                                                       if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1)
+                                                       {
+                                                               move.vanish.push(
+                                                                       new PiPo({
+                                                                               p:this.getPiece(i,j),
+                                                                               c:this.getColor(i,j),
+                                                                               x:i,
+                                                                               y:j
+                                                                       })
+                                                               );
+                                                               move.appear.push(
+                                                                       new PiPo({
+                                                                               p:this.getPiece(i,j),
+                                                                               c:this.getColor(i,j),
+                                                                               x:ii,
+                                                                               y:jj
+                                                                       })
+                                                               );
+                                                       }
+                                               }
+                                       }
+                                       break;
                                }
+                               i += step[0];
+                               j += step[1];
                        }
-
-                       // Nothing on the path to the rook?
-                       step = castleSide == 0 ? -1 : 1;
-                       for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step)
-                       {
-                               if (this.board[x][i] != V.EMPTY)
-                                       continue castlingCheck;
-                       }
-                       const rookPos = this.INIT_COL_ROOK[c][castleSide];
-
-                       // Nothing on final squares, except maybe king and castling rook?
-                       for (i=0; i<2; i++)
+               }
+               this.undo(standardMove);
+               let moves = [];
+               // Scan move for pawn (max 1) on 8th rank
+               for (let i=1; i<move.appear.length; i++)
+               {
+                       if (move.appear[i].p==V.PAWN && move.appear[i].c==color
+                               && move.appear[i].x==lastRank)
                        {
-                               if (this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-                                       this.getPiece(x,finalSquares[castleSide][i]) != V.KING &&
-                                       finalSquares[castleSide][i] != rookPos)
+                               move.appear[i].p = V.ROOK;
+                               moves.push(move);
+                               for (let piece of [V.KNIGHT, V.BISHOP, V.QUEEN])
                                {
-                                       continue castlingCheck;
+                                       let cmove = JSON.parse(JSON.stringify(move));
+                                       cmove.appear[i].p = piece;
+                                       moves.push(cmove);
                                }
+                               // Swap appear[i] and appear[0] for moves presentation (TODO: this is awkward)
+                               moves.forEach(m => {
+                                       let tmp = m.appear[0];
+                                       m.appear[0] = m.appear[i];
+                                       m.appear[i] = tmp;
+                               });
+                               break;
                        }
-
-                       // If this code is reached, castle is valid
-                       let cmove = new Move({
-                               appear: [
-                                       new PiPo({x:x,y:finalSquares[castleSide][0],p:V.KING,c:c}),
-                                       new PiPo({x:x,y:finalSquares[castleSide][1],p:V.ROOK,c:c})],
-                               vanish: [
-                                       new PiPo({x:x,y:y,p:V.KING,c:c}),
-                                       new PiPo({x:x,y:rookPos,p:V.ROOK,c:c})],
-                               end: Math.abs(y - rookPos) <= 2
-                                       ? {x:x, y:rookPos}
-                                       : {x:x, y:y + 2 * (castleSide==0 ? -1 : 1)}
-                       });
-                       this.applyMagneticLaws([x,finalSquares[castleSide][1]], cmove);
-                       moves.push(cmove);
                }
-
+               if (moves.length == 0) //no pawn on 8th rank
+                       moves.push(move);
                return moves;
        }
 
        // TODO: verify this assertion
-//     atLeastOneMove()
-//     {
-//             return true; //always at least one possible move
-//     }
+       atLeastOneMove()
+       {
+               return true; //always at least one possible move
+       }
 
        underCheck(move)
        {
@@ -135,7 +154,7 @@ class MagneticRules
                const saveKingPos = this.kingPos[c]; //king might be taken
                this.play(move);
                // The only way to be "under check" is to have lost the king (thus game over)
-               let res = this.kingPos[c][0] < 0;
+               let res = this.kingPos[c][0] < 0
                        ? [ JSON.parse(JSON.stringify(saveKingPos)) ]
                        : [ ];
                this.undo(move);
@@ -153,8 +172,18 @@ class MagneticRules
                        // We took opponent king !
                        const oppCol = this.getOppCol(c);
                        this.kingPos[oppCol] = [-1,-1];
-                       this.flags[oppCol] = [false,false];
+                       this.castleFlags[oppCol] = [false,false];
                }
+               // Did we magnetically move our (init) rooks or opponents' ones ?
+               const firstRank = (c == "w" ? 7 : 0);
+               const oppFirstRank = 7 - firstRank;
+               const oppCol = this.getOppCol(c);
+               move.vanish.forEach(psq => {
+                       if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
+                               this.castleFlags[c][psq.y==this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
+                       else if (psq.x == oppFirstRank && this.INIT_COL_ROOK[oppCol].includes(psq.y))
+                               this.castleFlags[oppCol][psq.y==this.INIT_COL_ROOK[oppCol][0] ? 0 : 1] = false;
+               });
        }
 
        unupdateVariables(move)
@@ -194,4 +223,8 @@ class MagneticRules
                // No valid move: our king disappeared
                return this.turn == "w" ? "0-1" : "1-0";
        }
+
+       static get THRESHOLD_MATE() {
+               return 500; //checkmates evals may be slightly below 1000
+       }
 }