| 1 | class CheckeredRules extends ChessRules |
| 2 | { |
| 3 | // Path to pieces |
| 4 | static getPpath(b) |
| 5 | { |
| 6 | return b[0]=='c' ? "Checkered/"+b : b; |
| 7 | } |
| 8 | static board2fen(b) |
| 9 | { |
| 10 | const checkered_codes = { |
| 11 | 'p': 's', |
| 12 | 'q': 't', |
| 13 | 'r': 'u', |
| 14 | 'b': 'c', |
| 15 | 'n': 'o', |
| 16 | }; |
| 17 | if (b[0]=="c") |
| 18 | return checkered_codes[b[1]]; |
| 19 | return ChessRules.board2fen(b); |
| 20 | } |
| 21 | static fen2board(f) |
| 22 | { |
| 23 | const checkered_pieces = { |
| 24 | 's': 'p', |
| 25 | 't': 'q', |
| 26 | 'u': 'r', |
| 27 | 'c': 'b', |
| 28 | 'o': 'n', |
| 29 | }; |
| 30 | if (Object.keys(checkered_pieces).includes(f)) |
| 31 | return 'c'+checkered_pieces[f]; |
| 32 | return ChessRules.fen2board(f); |
| 33 | } |
| 34 | |
| 35 | static GetFlags(fen) |
| 36 | { |
| 37 | let flags = [ |
| 38 | ChessRules.GetFlags(fen), //castle |
| 39 | { |
| 40 | "w": new Array(8), //pawns can move 2 squares |
| 41 | "b": new Array(8) |
| 42 | } |
| 43 | ]; |
| 44 | const fenFlags = fen.split(" ")[1].substr(4); //skip first 4 digits, for castle |
| 45 | for (let c of ['w','b']) |
| 46 | { |
| 47 | for (let i=0; i<8; i++) |
| 48 | flags[1][c][i] = (fenFlags.charAt((c=='w'?0:8)+i) == '1'); |
| 49 | } |
| 50 | return flags; |
| 51 | } |
| 52 | |
| 53 | canTake([x1,y1], [x2,y2]) |
| 54 | { |
| 55 | const color1 = this.getColor(x1,y1); |
| 56 | const color2 = this.getColor(x2,y2); |
| 57 | // Checkered aren't captured |
| 58 | return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn); |
| 59 | } |
| 60 | |
| 61 | addCaptures([sx,sy], [ex,ey], moves) |
| 62 | { |
| 63 | const piece = this.getPiece(sx,sy); |
| 64 | if (piece != VariantRules.KING) |
| 65 | { |
| 66 | moves.push(this.getBasicMove([sx,sy], [ex,ey], {c:'c',p:piece})); |
| 67 | const takePiece = this.getPiece(ex,ey); |
| 68 | if (takePiece != piece) |
| 69 | moves.push(this.getBasicMove([sx,sy], [ex,ey], {c:'c',p:takePiece})); |
| 70 | } |
| 71 | else |
| 72 | moves.push(this.getBasicMove([sx,sy], [ex,ey])); |
| 73 | } |
| 74 | |
| 75 | // Generic method to find possible moves of non-pawn pieces ("sliding or jumping") |
| 76 | getSlideNJumpMoves([x,y], steps, oneStep) |
| 77 | { |
| 78 | const color = this.getColor(x,y); |
| 79 | let moves = []; |
| 80 | const [sizeX,sizeY] = VariantRules.size; |
| 81 | outerLoop: |
| 82 | for (var loop=0; loop<steps.length; loop++) |
| 83 | { |
| 84 | let step = steps[loop]; |
| 85 | let i = x + step[0]; |
| 86 | let j = y + step[1]; |
| 87 | while (i>=0 && i<sizeX && j>=0 && j<sizeY && this.board[i][j] == VariantRules.EMPTY) |
| 88 | { |
| 89 | moves.push(this.getBasicMove([x,y], [i,j])); //no capture |
| 90 | if (oneStep !== undefined) |
| 91 | continue outerLoop; |
| 92 | i += step[0]; |
| 93 | j += step[1]; |
| 94 | } |
| 95 | if (i>=0 && i<8 && j>=0 && j<8 && this.canTake([x,y], [i,j])) |
| 96 | this.addCaptures([x,y], [i,j], moves); |
| 97 | } |
| 98 | return moves; |
| 99 | } |
| 100 | |
| 101 | // What are the pawn moves from square x,y considering color "color" ? |
| 102 | getPotentialPawnMoves([x,y]) |
| 103 | { |
| 104 | const color = this.getColor(x,y); |
| 105 | var moves = []; |
| 106 | var V = VariantRules; |
| 107 | let [sizeX,sizeY] = VariantRules.size; |
| 108 | const c = (color == 'c' ? this.turn : color); |
| 109 | const shift = (c == "w" ? -1 : 1); |
| 110 | let startRank = (c == "w" ? sizeY-2 : 1); |
| 111 | let lastRank = (c == "w" ? 0 : sizeY-1); |
| 112 | |
| 113 | if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank) |
| 114 | { |
| 115 | // Normal moves |
| 116 | if (this.board[x+shift][y] == V.EMPTY) |
| 117 | { |
| 118 | moves.push(this.getBasicMove([x,y], [x+shift,y])); |
| 119 | if (x==startRank && this.board[x+2*shift][y] == V.EMPTY && this.flags[1][c][y]) |
| 120 | { |
| 121 | // Two squares jump |
| 122 | moves.push(this.getBasicMove([x,y], [x+2*shift,y])); |
| 123 | } |
| 124 | } |
| 125 | // Captures |
| 126 | if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY) |
| 127 | this.addCaptures([x,y], [x+shift,y-1], moves); |
| 128 | if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY) |
| 129 | this.addCaptures([x,y], [x+shift,y+1], moves); |
| 130 | } |
| 131 | |
| 132 | if (x+shift == lastRank) |
| 133 | { |
| 134 | // Promotion |
| 135 | let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]; |
| 136 | promotionPieces.forEach(p => { |
| 137 | // Normal move |
| 138 | if (this.board[x+shift][y] == V.EMPTY) |
| 139 | moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p})); |
| 140 | // Captures |
| 141 | if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY) |
| 142 | moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:'c',p:p})); |
| 143 | if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY) |
| 144 | moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:'c',p:p})); |
| 145 | }); |
| 146 | } |
| 147 | |
| 148 | // En passant |
| 149 | const Lep = this.epSquares.length; |
| 150 | const epSquare = Lep>0 ? this.epSquares[Lep-1] : undefined; |
| 151 | if (!!epSquare && epSquare.x == x+shift && Math.abs(epSquare.y - y) == 1) |
| 152 | { |
| 153 | let epStep = epSquare.y - y; |
| 154 | var enpassantMove = this.getBasicMove([x,y], [x+shift,y+epStep]); |
| 155 | enpassantMove.vanish.push({ |
| 156 | x: x, |
| 157 | y: y+epStep, |
| 158 | p: 'p', |
| 159 | c: this.getColor(x,y+epStep) |
| 160 | }); |
| 161 | enpassantMove.appear[0].c = 'c'; |
| 162 | moves.push(enpassantMove); |
| 163 | } |
| 164 | |
| 165 | return moves; |
| 166 | } |
| 167 | |
| 168 | getCastleMoves([x,y]) |
| 169 | { |
| 170 | const c = this.getColor(x,y); |
| 171 | if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c]) |
| 172 | return []; //x isn't first rank, or king has moved (shortcut) |
| 173 | |
| 174 | const V = VariantRules; |
| 175 | |
| 176 | // Castling ? |
| 177 | const oppCol = this.getOppCol(c); |
| 178 | let moves = []; |
| 179 | let i = 0; |
| 180 | const finalSquares = [ [2,3], [6,5] ]; //king, then rook |
| 181 | castlingCheck: |
| 182 | for (let castleSide=0; castleSide < 2; castleSide++) //large, then small |
| 183 | { |
| 184 | if (!this.flags[0][c][castleSide]) |
| 185 | continue; |
| 186 | // If this code is reached, rooks and king are on initial position |
| 187 | |
| 188 | // Nothing on the path of the king (and no checks; OK also if y==finalSquare)? |
| 189 | let step = finalSquares[castleSide][0] < y ? -1 : 1; |
| 190 | for (i=y; i!=finalSquares[castleSide][0]; i+=step) |
| 191 | { |
| 192 | if (this.isAttacked([x,i], oppCol) || (this.board[x][i] != V.EMPTY && |
| 193 | // NOTE: next check is enough, because of chessboard constraints |
| 194 | (this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i))))) |
| 195 | { |
| 196 | continue castlingCheck; |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | // Nothing on the path to the rook? |
| 201 | step = castleSide == 0 ? -1 : 1; |
| 202 | for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) |
| 203 | { |
| 204 | if (this.board[x][i] != V.EMPTY) |
| 205 | continue castlingCheck; |
| 206 | } |
| 207 | const rookPos = this.INIT_COL_ROOK[c][castleSide]; |
| 208 | |
| 209 | // Nothing on final squares, except maybe king and castling rook? |
| 210 | for (i=0; i<2; i++) |
| 211 | { |
| 212 | if (this.board[x][finalSquares[castleSide][i]] != V.EMPTY && |
| 213 | this.getPiece(x,finalSquares[castleSide][i]) != V.KING && |
| 214 | finalSquares[castleSide][i] != rookPos) |
| 215 | { |
| 216 | continue castlingCheck; |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | // If this code is reached, castle is valid |
| 221 | moves.push( new Move({ |
| 222 | appear: [ |
| 223 | new PiPo({x:x,y:finalSquares[castleSide][0],p:V.KING,c:c}), |
| 224 | new PiPo({x:x,y:finalSquares[castleSide][1],p:V.ROOK,c:c})], |
| 225 | vanish: [ |
| 226 | new PiPo({x:x,y:y,p:V.KING,c:c}), |
| 227 | new PiPo({x:x,y:rookPos,p:V.ROOK,c:c})], |
| 228 | end: Math.abs(y - rookPos) <= 2 |
| 229 | ? {x:x, y:rookPos} |
| 230 | : {x:x, y:y + 2 * (castleSide==0 ? -1 : 1)} |
| 231 | }) ); |
| 232 | } |
| 233 | |
| 234 | return moves; |
| 235 | } |
| 236 | |
| 237 | canIplay(side, [x,y]) |
| 238 | { |
| 239 | return ((side=='w' && this.moves.length%2==0) || (side=='b' && this.moves.length%2==1)) |
| 240 | && [side,'c'].includes(this.getColor(x,y)); |
| 241 | } |
| 242 | |
| 243 | // Does m2 un-do m1 ? (to disallow undoing checkered moves) |
| 244 | oppositeMoves(m1, m2) |
| 245 | { |
| 246 | return m1.appear.length == 1 && m2.appear.length == 1 |
| 247 | && m1.vanish.length == 1 && m2.vanish.length == 1 |
| 248 | && m1.start.x == m2.end.x && m1.end.x == m2.start.x |
| 249 | && m1.start.y == m2.end.y && m1.end.y == m2.start.y |
| 250 | && m1.appear[0].c == m2.vanish[0].c && m1.appear[0].p == m2.vanish[0].p |
| 251 | && m1.vanish[0].c == m2.appear[0].c && m1.vanish[0].p == m2.appear[0].p; |
| 252 | } |
| 253 | |
| 254 | filterValid(moves) |
| 255 | { |
| 256 | if (moves.length == 0) |
| 257 | return []; |
| 258 | const color = this.turn; |
| 259 | return moves.filter(m => { |
| 260 | const L = this.moves.length; |
| 261 | if (L > 0 && this.oppositeMoves(this.moves[L-1], m)) |
| 262 | return false; |
| 263 | return !this.underCheck(m); |
| 264 | }); |
| 265 | } |
| 266 | |
| 267 | isAttackedByPawn([x,y], colors) |
| 268 | { |
| 269 | for (let c of colors) |
| 270 | { |
| 271 | const color = (c=="c" ? this.turn : c); |
| 272 | let pawnShift = (color=="w" ? 1 : -1); |
| 273 | if (x+pawnShift>=0 && x+pawnShift<8) |
| 274 | { |
| 275 | for (let i of [-1,1]) |
| 276 | { |
| 277 | if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==VariantRules.PAWN |
| 278 | && this.getColor(x+pawnShift,y+i)==c) |
| 279 | { |
| 280 | return true; |
| 281 | } |
| 282 | } |
| 283 | } |
| 284 | } |
| 285 | return false; |
| 286 | } |
| 287 | |
| 288 | underCheck(move) |
| 289 | { |
| 290 | const color = this.turn; |
| 291 | this.play(move); |
| 292 | let res = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c']); |
| 293 | this.undo(move); |
| 294 | return res; |
| 295 | } |
| 296 | |
| 297 | getCheckSquares(move) |
| 298 | { |
| 299 | this.play(move); |
| 300 | const color = this.turn; |
| 301 | this.moves.push(move); //artifically change turn, for checkered pawns (TODO) |
| 302 | const kingAttacked = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c']); |
| 303 | let res = kingAttacked |
| 304 | ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ] //need to duplicate! |
| 305 | : [ ]; |
| 306 | this.moves.pop(); |
| 307 | this.undo(move); |
| 308 | return res; |
| 309 | } |
| 310 | |
| 311 | updateVariables(move) |
| 312 | { |
| 313 | const piece = this.getPiece(move.start.x,move.start.y); |
| 314 | const c = this.getColor(move.start.x,move.start.y); |
| 315 | |
| 316 | if (c != 'c') //checkered not concerned by castle flags |
| 317 | { |
| 318 | const firstRank = (c == "w" ? 7 : 0); |
| 319 | // Update king position + flags |
| 320 | if (piece == VariantRules.KING && move.appear.length > 0) |
| 321 | { |
| 322 | this.kingPos[c][0] = move.appear[0].x; |
| 323 | this.kingPos[c][1] = move.appear[0].y; |
| 324 | this.flags[0][c] = [false,false]; |
| 325 | return; |
| 326 | } |
| 327 | const oppCol = this.getOppCol(c); |
| 328 | const oppFirstRank = 7 - firstRank; |
| 329 | if (move.start.x == firstRank //our rook moves? |
| 330 | && this.INIT_COL_ROOK[c].includes(move.start.y)) |
| 331 | { |
| 332 | const flagIdx = move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1; |
| 333 | this.flags[0][c][flagIdx] = false; |
| 334 | } |
| 335 | else if (move.end.x == oppFirstRank //we took opponent rook? |
| 336 | && this.INIT_COL_ROOK[c].includes(move.end.y)) |
| 337 | { |
| 338 | const flagIdx = move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1; |
| 339 | this.flags[0][oppCol][flagIdx] = false; |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | // Does it turn off a 2-squares pawn flag? |
| 344 | const secondRank = [1,6]; |
| 345 | if (secondRank.includes(move.start.x) && move.vanish[0].p == VariantRules.PAWN) |
| 346 | this.flags[1][move.start.x==6 ? "w" : "b"][move.start.y] = false; |
| 347 | } |
| 348 | |
| 349 | checkGameEnd() |
| 350 | { |
| 351 | const color = this.turn; |
| 352 | if (!this.isAttacked(this.kingPos[color], this.getOppCol(color)) |
| 353 | && !this.isAttacked(this.kingPos[color], 'c')) |
| 354 | { |
| 355 | return "1/2"; |
| 356 | } |
| 357 | // OK, checkmate |
| 358 | return color == "w" ? "0-1" : "1-0"; |
| 359 | } |
| 360 | |
| 361 | evalPosition() |
| 362 | { |
| 363 | const [sizeX,sizeY] = VariantRules.size; |
| 364 | let evaluation = 0; |
| 365 | //Just count material for now, considering checkered neutral (...) |
| 366 | for (let i=0; i<sizeX; i++) |
| 367 | { |
| 368 | for (let j=0; j<sizeY; j++) |
| 369 | { |
| 370 | if (this.board[i][j] != VariantRules.EMPTY) |
| 371 | { |
| 372 | const sqColor = this.getColor(i,j); |
| 373 | const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0); |
| 374 | evaluation += sign * VariantRules.VALUES[this.getPiece(i,j)]; |
| 375 | } |
| 376 | } |
| 377 | } |
| 378 | return evaluation; |
| 379 | } |
| 380 | |
| 381 | static GenRandInitFen() |
| 382 | { |
| 383 | return ChessRules.GenRandInitFen() + "1111111111111111"; //add 16 pawns flags |
| 384 | } |
| 385 | |
| 386 | getFlagsFen() |
| 387 | { |
| 388 | let fen = ""; |
| 389 | // Add castling flags |
| 390 | for (let c of ['w','b']) |
| 391 | { |
| 392 | for (let i=0; i<2; i++) |
| 393 | fen += this.flags[0][c][i] ? '1' : '0'; |
| 394 | } |
| 395 | // Add pawns flags |
| 396 | for (let c of ['w','b']) |
| 397 | { |
| 398 | for (let i=0; i<8; i++) |
| 399 | fen += this.flags[1][c][i] ? '1' : '0'; |
| 400 | } |
| 401 | return fen; |
| 402 | } |
| 403 | |
| 404 | getNotation(move) |
| 405 | { |
| 406 | if (move.appear.length == 2) |
| 407 | { |
| 408 | // Castle |
| 409 | if (move.end.y < move.start.y) |
| 410 | return "0-0-0"; |
| 411 | else |
| 412 | return "0-0"; |
| 413 | } |
| 414 | |
| 415 | // Translate final square |
| 416 | let finalSquare = |
| 417 | String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x); |
| 418 | |
| 419 | let piece = this.getPiece(move.start.x, move.start.y); |
| 420 | if (piece == VariantRules.PAWN) |
| 421 | { |
| 422 | // Pawn move |
| 423 | let notation = ""; |
| 424 | if (move.vanish.length > 1) |
| 425 | { |
| 426 | // Capture |
| 427 | let startColumn = String.fromCharCode(97 + move.start.y); |
| 428 | notation = startColumn + "x" + finalSquare + "=" + move.appear[0].p.toUpperCase(); |
| 429 | } |
| 430 | else //no capture |
| 431 | notation = finalSquare; |
| 432 | if (move.appear.length > 0 && piece != move.appear[0].p) //promotion |
| 433 | notation += "=" + move.appear[0].p.toUpperCase(); |
| 434 | return notation; |
| 435 | } |
| 436 | |
| 437 | else |
| 438 | { |
| 439 | // Piece movement |
| 440 | return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare |
| 441 | + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : ""); |
| 442 | } |
| 443 | } |
| 444 | } |