| 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 | // can color1 take color2? |
| 54 | canTake(color1, color2) |
| 55 | { |
| 56 | // Checkered aren't captured |
| 57 | return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn); |
| 58 | } |
| 59 | |
| 60 | // Build regular move(s) from its initial and destination squares; tr: transformation |
| 61 | getBasicMove(sx, sy, ex, ey, tr) |
| 62 | { |
| 63 | if (this.board[ex][ey] == VariantRules.EMPTY) |
| 64 | { |
| 65 | // No capture, standard move construction |
| 66 | return [super.getBasicMove(sx,sy,ex,ey,tr)]; |
| 67 | } |
| 68 | let moves = []; //captures: generally 2 choices, unless 'tr' is specified or piece==king |
| 69 | const startPiece = this.getPiece(sx,sy); |
| 70 | const endPiece = this.getPiece(ex,ey); |
| 71 | const startColor = this.getColor(sx,sy); |
| 72 | const endColor = this.getColor(ex,ey); |
| 73 | for (let piece of !!tr ? [tr] : |
| 74 | (startPiece==VariantRules.KING ? VariantRules.KING : _.uniq([startPiece,endPiece]))) |
| 75 | { |
| 76 | var mv = new Move({ |
| 77 | appear: [ |
| 78 | new PiPo({ |
| 79 | x: ex, |
| 80 | y: ey, |
| 81 | c: startPiece==VariantRules.KING ? startColor : 'c', |
| 82 | p: piece |
| 83 | }) |
| 84 | ], |
| 85 | vanish: [ |
| 86 | new PiPo({ |
| 87 | x: sx, |
| 88 | y: sy, |
| 89 | c: startColor, |
| 90 | p: startPiece |
| 91 | }), |
| 92 | new PiPo({ |
| 93 | x: ex, |
| 94 | y: ey, |
| 95 | c: endColor, |
| 96 | p: endPiece |
| 97 | }) |
| 98 | ] |
| 99 | }); |
| 100 | moves.push(mv); |
| 101 | } |
| 102 | return moves; |
| 103 | } |
| 104 | |
| 105 | // Generic method to find possible moves of non-pawn pieces ("sliding or jumping") |
| 106 | getSlideNJumpMoves(x, y, color, steps, oneStep) |
| 107 | { |
| 108 | var moves = []; |
| 109 | let [sizeX,sizeY] = VariantRules.size; |
| 110 | outerLoop: |
| 111 | for (var loop=0; loop<steps.length; loop++) |
| 112 | { |
| 113 | var step = steps[loop]; |
| 114 | var i = x + step[0]; |
| 115 | var j = y + step[1]; |
| 116 | while (i>=0 && i<sizeX && j>=0 && j<sizeY |
| 117 | && this.board[i][j] == VariantRules.EMPTY) |
| 118 | { |
| 119 | moves.push(this.getBasicMove(x, y, i, j)[0]); //no capture |
| 120 | if (oneStep !== undefined) |
| 121 | continue outerLoop; |
| 122 | i += step[0]; |
| 123 | j += step[1]; |
| 124 | } |
| 125 | if (i>=0 && i<8 && j>=0 && j<8 && this.canTake(color, this.getColor(i,j))) |
| 126 | moves = moves.concat(this.getBasicMove(x, y, i, j)); |
| 127 | } |
| 128 | return moves; |
| 129 | } |
| 130 | |
| 131 | // What are the pawn moves from square x,y considering color "color" ? |
| 132 | getPotentialPawnMoves(x, y, color) |
| 133 | { |
| 134 | var moves = []; |
| 135 | var V = VariantRules; |
| 136 | let [sizeX,sizeY] = VariantRules.size; |
| 137 | const c = (color == 'c' ? this.turn : color); |
| 138 | const shift = (c == "w" ? -1 : 1); |
| 139 | let startRank = (c == "w" ? sizeY-2 : 1); |
| 140 | let lastRank = (c == "w" ? 0 : sizeY-1); |
| 141 | |
| 142 | if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank) |
| 143 | { |
| 144 | // Normal moves |
| 145 | if (this.board[x+shift][y] == V.EMPTY) |
| 146 | { |
| 147 | moves.push(this.getBasicMove(x, y, x+shift, y)[0]); |
| 148 | if (x==startRank && this.board[x+2*shift][y] == V.EMPTY && this.flags[1][c][y]) |
| 149 | { |
| 150 | // Two squares jump |
| 151 | moves.push(this.getBasicMove(x, y, x+2*shift, y)[0]); |
| 152 | } |
| 153 | } |
| 154 | // Captures |
| 155 | if (y>0 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y-1)) |
| 156 | && this.board[x+shift][y-1] != V.EMPTY) |
| 157 | { |
| 158 | moves = moves.concat(this.getBasicMove(x, y, x+shift, y-1)); |
| 159 | } |
| 160 | if (y<sizeY-1 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y+1)) |
| 161 | && this.board[x+shift][y+1] != V.EMPTY) |
| 162 | { |
| 163 | moves = moves.concat(this.getBasicMove(x, y, x+shift, y+1)); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | if (x+shift == lastRank) |
| 168 | { |
| 169 | // Promotion |
| 170 | let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]; |
| 171 | promotionPieces.forEach(p => { |
| 172 | // Normal move |
| 173 | if (this.board[x+shift][y] == V.EMPTY) |
| 174 | moves.push(this.getBasicMove(x, y, x+shift, y, p)[0]); |
| 175 | // Captures |
| 176 | if (y>0 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y-1)) |
| 177 | && this.board[x+shift][y-1] != V.EMPTY) |
| 178 | { |
| 179 | moves = moves.concat(this.getBasicMove(x, y, x+shift, y-1, p)); |
| 180 | } |
| 181 | if (y<sizeY-1 && this.canTake(this.getColor(x,y), this.getColor(x+shift,y+1)) |
| 182 | && this.board[x+shift][y+1] != V.EMPTY) |
| 183 | { |
| 184 | moves = moves.concat(this.getBasicMove(x, y, x+shift, y+1, p)); |
| 185 | } |
| 186 | }); |
| 187 | } |
| 188 | |
| 189 | // En passant |
| 190 | const Lep = this.epSquares.length; |
| 191 | const epSquare = Lep>0 ? this.epSquares[Lep-1] : undefined; |
| 192 | if (!!epSquare && epSquare.x == x+shift && Math.abs(epSquare.y - y) == 1) |
| 193 | { |
| 194 | let epStep = epSquare.y - y; |
| 195 | var enpassantMove = this.getBasicMove(x, y, x+shift, y+epStep)[0]; |
| 196 | enpassantMove.vanish.push({ |
| 197 | x: x, |
| 198 | y: y+epStep, |
| 199 | p: 'p', |
| 200 | c: this.getColor(x,y+epStep) |
| 201 | }); |
| 202 | enpassantMove.appear[0].c = 'c'; |
| 203 | moves.push(enpassantMove); |
| 204 | } |
| 205 | |
| 206 | return moves; |
| 207 | } |
| 208 | |
| 209 | getCastleMoves(x,y,c) |
| 210 | { |
| 211 | if (x != (c=="w" ? 7 : 0) || y != this.INIT_COL_KING[c]) |
| 212 | return []; //x isn't first rank, or king has moved (shortcut) |
| 213 | |
| 214 | const V = VariantRules; |
| 215 | |
| 216 | // Castling ? |
| 217 | const oppCol = this.getOppCol(c); |
| 218 | let moves = []; |
| 219 | let i = 0; |
| 220 | const finalSquares = [ [2,3], [6,5] ]; //king, then rook |
| 221 | castlingCheck: |
| 222 | for (let castleSide=0; castleSide < 2; castleSide++) //large, then small |
| 223 | { |
| 224 | if (!this.flags[0][c][castleSide]) |
| 225 | continue; |
| 226 | // If this code is reached, rooks and king are on initial position |
| 227 | |
| 228 | // Nothing on the path of the king (and no checks; OK also if y==finalSquare)? |
| 229 | let step = finalSquares[castleSide][0] < y ? -1 : 1; |
| 230 | for (i=y; i!=finalSquares[castleSide][0]; i+=step) |
| 231 | { |
| 232 | if (this.isAttacked([x,i], oppCol) || (this.board[x][i] != V.EMPTY && |
| 233 | // NOTE: next check is enough, because of chessboard constraints |
| 234 | (this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i))))) |
| 235 | { |
| 236 | continue castlingCheck; |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | // Nothing on the path to the rook? |
| 241 | step = castleSide == 0 ? -1 : 1; |
| 242 | for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) |
| 243 | { |
| 244 | if (this.board[x][i] != V.EMPTY) |
| 245 | continue castlingCheck; |
| 246 | } |
| 247 | const rookPos = this.INIT_COL_ROOK[c][castleSide]; |
| 248 | |
| 249 | // Nothing on final squares, except maybe king and castling rook? |
| 250 | for (i=0; i<2; i++) |
| 251 | { |
| 252 | if (this.board[x][finalSquares[castleSide][i]] != V.EMPTY && |
| 253 | this.getPiece(x,finalSquares[castleSide][i]) != V.KING && |
| 254 | finalSquares[castleSide][i] != rookPos) |
| 255 | { |
| 256 | continue castlingCheck; |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | // If this code is reached, castle is valid |
| 261 | moves.push( new Move({ |
| 262 | appear: [ |
| 263 | new PiPo({x:x,y:finalSquares[castleSide][0],p:V.KING,c:c}), |
| 264 | new PiPo({x:x,y:finalSquares[castleSide][1],p:V.ROOK,c:c})], |
| 265 | vanish: [ |
| 266 | new PiPo({x:x,y:y,p:V.KING,c:c}), |
| 267 | new PiPo({x:x,y:rookPos,p:V.ROOK,c:c})], |
| 268 | end: Math.abs(y - rookPos) <= 2 |
| 269 | ? {x:x, y:rookPos} |
| 270 | : {x:x, y:y + 2 * (castleSide==0 ? -1 : 1)} |
| 271 | }) ); |
| 272 | } |
| 273 | |
| 274 | return moves; |
| 275 | } |
| 276 | |
| 277 | canIplay(color, sq) |
| 278 | { |
| 279 | return ((color=='w' && this.movesCount%2==0) || color=='c' |
| 280 | || (color=='b' && this.movesCount%2==1)) |
| 281 | && [color,'c'].includes(this.getColor(sq[0], sq[1])); |
| 282 | } |
| 283 | |
| 284 | // Does m2 un-do m1 ? (to disallow undoing checkered moves) |
| 285 | oppositeMoves(m1, m2) |
| 286 | { |
| 287 | return m1.appear.length == 1 && m2.appear.length == 1 |
| 288 | && m1.vanish.length == 1 && m2.vanish.length == 1 |
| 289 | // && _.isEqual(m1.appear[0], m2.vanish[0]) //fails in HH case |
| 290 | // && _.isEqual(m1.vanish[0], m2.appear[0]); |
| 291 | && m1.start.x == m2.end.x && m1.end.x == m2.start.x |
| 292 | && m1.start.y == m2.end.y && m1.end.y == m2.start.y |
| 293 | && m1.appear[0].c == m2.vanish[0].c && m1.appear[0].p == m2.vanish[0].p |
| 294 | && m1.vanish[0].c == m2.appear[0].c && m1.vanish[0].p == m2.appear[0].p; |
| 295 | } |
| 296 | |
| 297 | filterValid(moves) |
| 298 | { |
| 299 | if (moves.length == 0) |
| 300 | return []; |
| 301 | let color = this.getColor( moves[0].start.x, moves[0].start.y ); |
| 302 | return moves.filter(m => { |
| 303 | const L = this.moves.length; |
| 304 | if (L > 0 && this.oppositeMoves(this.moves[L-1], m)) |
| 305 | return false; |
| 306 | return !this.underCheck(m, color); |
| 307 | }); |
| 308 | } |
| 309 | |
| 310 | isAttackedByPawn([x,y], c) |
| 311 | { |
| 312 | const color = (c=="c" ? this.turn : c); |
| 313 | let pawnShift = (color=="w" ? 1 : -1); |
| 314 | if (x+pawnShift>=0 && x+pawnShift<8) |
| 315 | { |
| 316 | for (let i of [-1,1]) |
| 317 | { |
| 318 | if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==VariantRules.PAWN |
| 319 | && this.getColor(x+pawnShift,y+i)==c) |
| 320 | { |
| 321 | return true; |
| 322 | } |
| 323 | } |
| 324 | } |
| 325 | return false; |
| 326 | } |
| 327 | |
| 328 | underCheck(move, c) |
| 329 | { |
| 330 | const color = c == 'c' ? this.turn : c; |
| 331 | this.play(move); |
| 332 | let res = this.isAttacked(this.kingPos[color], this.getOppCol(color)) |
| 333 | || this.isAttacked(this.kingPos[color], 'c'); //TODO: quite inefficient... |
| 334 | this.undo(move); |
| 335 | return res; |
| 336 | } |
| 337 | |
| 338 | getCheckSquares(move, c) |
| 339 | { |
| 340 | this.play(move); |
| 341 | const kingAttacked = this.isAttacked(this.kingPos[c], this.getOppCol(c)) |
| 342 | || this.isAttacked(this.kingPos[c], 'c'); |
| 343 | let res = kingAttacked |
| 344 | ? [ JSON.parse(JSON.stringify(this.kingPos[c])) ] //need to duplicate! |
| 345 | : [ ]; |
| 346 | this.undo(move); |
| 347 | return res; |
| 348 | } |
| 349 | |
| 350 | updateVariables(move) |
| 351 | { |
| 352 | const piece = this.getPiece(move.start.x,move.start.y); |
| 353 | const c = this.getColor(move.start.x,move.start.y); |
| 354 | |
| 355 | if (c != 'c') //checkered not concerned by castle flags |
| 356 | { |
| 357 | const firstRank = (c == "w" ? 7 : 0); |
| 358 | // Update king position + flags |
| 359 | if (piece == VariantRules.KING && move.appear.length > 0) |
| 360 | { |
| 361 | this.kingPos[c][0] = move.appear[0].x; |
| 362 | this.kingPos[c][1] = move.appear[0].y; |
| 363 | this.flags[0][c] = [false,false]; |
| 364 | return; |
| 365 | } |
| 366 | const oppCol = this.getOppCol(c); |
| 367 | const oppFirstRank = 7 - firstRank; |
| 368 | if (move.start.x == firstRank //our rook moves? |
| 369 | && this.INIT_COL_ROOK[c].includes(move.start.y)) |
| 370 | { |
| 371 | const flagIdx = move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1; |
| 372 | this.flags[0][c][flagIdx] = false; |
| 373 | } |
| 374 | else if (move.end.x == oppFirstRank //we took opponent rook? |
| 375 | && this.INIT_COL_ROOK[c].includes(move.end.y)) |
| 376 | { |
| 377 | const flagIdx = move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1; |
| 378 | this.flags[0][oppCol][flagIdx] = false; |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | // Does it turn off a 2-squares pawn flag? |
| 383 | const secondRank = [1,6]; |
| 384 | if (secondRank.includes(move.start.x) && move.vanish[0].p == VariantRules.PAWN) |
| 385 | this.flags[1][move.start.x==6 ? "w" : "b"][move.start.y] = false; |
| 386 | } |
| 387 | |
| 388 | play(move, ingame) |
| 389 | { |
| 390 | super.play(move, ingame); |
| 391 | if (!ingame) |
| 392 | this.moves.push(move); //needed for turn indication for checkered pieces |
| 393 | } |
| 394 | |
| 395 | undo(move) |
| 396 | { |
| 397 | super.undo(move); |
| 398 | this.moves.pop(); |
| 399 | } |
| 400 | |
| 401 | checkGameEnd(color) |
| 402 | { |
| 403 | if (!this.isAttacked(this.kingPos[color], this.getOppCol(color)) |
| 404 | && !this.isAttacked(this.kingPos[color], 'c')) |
| 405 | { |
| 406 | return "1/2"; |
| 407 | } |
| 408 | // OK, checkmate |
| 409 | return color == "w" ? "0-1" : "1-0"; |
| 410 | } |
| 411 | |
| 412 | evalPosition() |
| 413 | { |
| 414 | const [sizeX,sizeY] = VariantRules.size; |
| 415 | let evaluation = 0; |
| 416 | //Just count material for now, considering checkered neutral (...) |
| 417 | for (let i=0; i<sizeX; i++) |
| 418 | { |
| 419 | for (let j=0; j<sizeY; j++) |
| 420 | { |
| 421 | if (this.board[i][j] != VariantRules.EMPTY) |
| 422 | { |
| 423 | const sqColor = this.getColor(i,j); |
| 424 | const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0); |
| 425 | evaluation += sign * VariantRules.VALUES[this.getPiece(i,j)]; |
| 426 | } |
| 427 | } |
| 428 | } |
| 429 | return evaluation; |
| 430 | } |
| 431 | |
| 432 | static GenRandInitFen() |
| 433 | { |
| 434 | return ChessRules.GenRandInitFen() + "1111111111111111"; //add 16 pawns flags |
| 435 | } |
| 436 | |
| 437 | getFlagsFen() |
| 438 | { |
| 439 | let fen = ""; |
| 440 | // Add castling flags |
| 441 | for (let c of ['w','b']) |
| 442 | { |
| 443 | for (let i=0; i<2; i++) |
| 444 | fen += this.flags[0][c][i] ? '1' : '0'; |
| 445 | } |
| 446 | // Add pawns flags |
| 447 | for (let c of ['w','b']) |
| 448 | { |
| 449 | for (let i=0; i<8; i++) |
| 450 | fen += this.flags[1][c][i] ? '1' : '0'; |
| 451 | } |
| 452 | return fen; |
| 453 | } |
| 454 | |
| 455 | getNotation(move) |
| 456 | { |
| 457 | if (move.appear.length == 2) |
| 458 | { |
| 459 | // Castle |
| 460 | if (move.end.y < move.start.y) |
| 461 | return "0-0-0"; |
| 462 | else |
| 463 | return "0-0"; |
| 464 | } |
| 465 | |
| 466 | // Translate final square |
| 467 | let finalSquare = |
| 468 | String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x); |
| 469 | |
| 470 | let piece = this.getPiece(move.start.x, move.start.y); |
| 471 | if (piece == VariantRules.PAWN) |
| 472 | { |
| 473 | // Pawn move |
| 474 | let notation = ""; |
| 475 | if (move.vanish.length > 1) |
| 476 | { |
| 477 | // Capture |
| 478 | let startColumn = String.fromCharCode(97 + move.start.y); |
| 479 | notation = startColumn + "x" + finalSquare + "=" + move.appear[0].p.toUpperCase(); |
| 480 | } |
| 481 | else //no capture |
| 482 | notation = finalSquare; |
| 483 | if (move.appear.length > 0 && piece != move.appear[0].p) //promotion |
| 484 | notation += "=" + move.appear[0].p.toUpperCase(); |
| 485 | return notation; |
| 486 | } |
| 487 | |
| 488 | else |
| 489 | { |
| 490 | // Piece movement |
| 491 | return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare |
| 492 | + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : ""); |
| 493 | } |
| 494 | } |
| 495 | } |