1from pychess.Utils.const import EMPTY, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, \ 2 ATOMICCHESS, BUGHOUSECHESS, CRAZYHOUSECHESS, CAMBODIANCHESS, MAKRUKCHESS, \ 3 FISCHERRANDOMCHESS, SITTUYINCHESS, WILDCASTLECHESS, WILDCASTLESHUFFLECHESS, \ 4 SUICIDECHESS, GIVEAWAYCHESS, DROP_VARIANTS, BLACK, WHITE, FAN_PIECES, NULL_MOVE, CAS_FLAGS, \ 5 NORMALCHESS, PLACEMENTCHESS, THREECHECKCHESS, SETUPCHESS, FEN_START, \ 6 chrU2Sign, cordDic, reprCord, reprFile, reprSign, reprSignMakruk, reprSignSittuyin, \ 7 A1, A8, B1, B8, \ 8 C1, C8, D1, D8, \ 9 E1, E8, F1, F8, \ 10 G1, G8, H1, H8, \ 11 KING_CASTLE, QUEEN_CASTLE, DROP, PROMOTIONS, ENPASSANT, B_OO, B_OOO, W_OO, W_OOO 12from pychess.Utils.repr import reprColor 13from .ldata import FILE, fileBits 14from .attack import isAttacked 15from .bitboard import clearBit, setBit, bitPosArray 16from .PolyglotHash import pieceHashes, epHashes, \ 17 W_OOHash, W_OOOHash, B_OOHash, B_OOOHash, colorHash, holdingHash 18 19################################################################################ 20# FEN # 21################################################################################ 22 23# This will cause applyFen to raise an exception, if halfmove clock and fullmove 24# number is not specified 25STRICT_FEN = False 26 27################################################################################ 28# LBoard # 29################################################################################ 30 31 32class LBoard: 33 __hash__ = None 34 35 ini_kings = (E1, E8) 36 ini_rooks = ((A1, H1), (A8, H8)) 37 38 # Final positions of castled kings and rooks 39 fin_kings = ((C1, G1), (C8, G8)) 40 fin_rooks = ((D1, F1), (D8, F8)) 41 42 holding = ({PAWN: 0, 43 KNIGHT: 0, 44 BISHOP: 0, 45 ROOK: 0, 46 QUEEN: 0, 47 KING: 0}, 48 {PAWN: 0, 49 KNIGHT: 0, 50 BISHOP: 0, 51 ROOK: 0, 52 QUEEN: 0, 53 KING: 0}) 54 55 def __init__(self, variant=NORMALCHESS): 56 self.variant = variant 57 58 self.nags = [] 59 # children can contain comments and variations 60 # variations are lists of lboard objects 61 self.children = [] 62 63 # the next and prev lboard objects in the variation list 64 self.next = None 65 self.prev = None 66 67 # The high level owner Board (with Piece objects) in gamemodel 68 self.pieceBoard = None 69 70 # This will True except in so called null_board 71 # null_board act as parent of the variation 72 # when we add a variation to last played board from hint panel 73 self.fen_was_applied = False 74 self.plyCount = 0 75 76 @property 77 def lastMove(self): 78 return self.hist_move[-1] if self.fen_was_applied and len( 79 self.hist_move) > 0 else None 80 81 def repetitionCount(self, draw_threshold=3): 82 rc = 1 83 for ply in range(4, 1 + min(len(self.hist_hash), self.fifty), 2): 84 if self.hist_hash[-ply] == self.hash: 85 rc += 1 86 if rc >= draw_threshold: 87 break 88 return rc 89 90 def iniAtomic(self): 91 self.hist_exploding_around = [] 92 93 def iniHouse(self): 94 self.promoted = [0] * 64 95 self.capture_promoting = False 96 self.hist_capture_promoting = [] 97 self.holding = ({PAWN: 0, 98 KNIGHT: 0, 99 BISHOP: 0, 100 ROOK: 0, 101 QUEEN: 0, 102 KING: 0}, 103 {PAWN: 0, 104 KNIGHT: 0, 105 BISHOP: 0, 106 ROOK: 0, 107 QUEEN: 0, 108 KING: 0}) 109 110 def iniCambodian(self): 111 self.ini_kings = (D1, E8) 112 self.ini_queens = (E1, D8) 113 self.is_first_move = {KING: [True, True], QUEEN: [True, True]} 114 self.hist_is_first_move = [] 115 116 def applyFen(self, fenstr): 117 """ Applies the fenstring to the board. 118 If the string is not properly 119 written a SyntaxError will be raised, having its message ending in 120 Pos(%d) specifying the string index of the problem. 121 if an error is found, no changes will be made to the board. """ 122 123 assert not self.fen_was_applied, "The applyFen() method can be used on new LBoard objects only!" 124 125 # Set board to empty on Black's turn (which Polyglot-hashes to 0) 126 self.blocker = 0 127 128 self.friends = [0, 0] 129 self.kings = [-1, -1] 130 131 # this variable is a 2-dimmensionnal array, each case containing a bitboard 132 # self.boards[color] contains an array of bitboards, each representing the position of the pieces 133 # use example : self.boards[color][KNIGHT] 134 self.boards = ([0] * 7, [0] * 7) 135 136 self.enpassant = None # cord which can be captured by enpassant or None 137 self.color = BLACK 138 self.castling = 0 # The castling availability in the position 139 self.hasCastled = [False, False] 140 self.fifty = 0 # A ply counter for the fifty moves rule 141 self.plyCount = 0 142 143 self.checked = None 144 self.opchecked = None 145 146 self.arBoard = [0] * 64 147 148 self.hash = 0 149 self.pawnhash = 0 150 151 # Data from the position's history: 152 self.hist_move = [] # The move that was applied to get the position 153 self.hist_tpiece = [] 154 # The piece the move captured, == EMPTY for normal moves 155 self.hist_enpassant = [] 156 self.hist_castling = [] 157 self.hist_hash = [] 158 self.hist_fifty = [] 159 self.hist_checked = [] 160 self.hist_opchecked = [] 161 162 # piece counts 163 self.pieceCount = ([0] * 7, [0] * 7) 164 165 # initial cords of rooks and kings for castling in Chess960 166 if self.variant == FISCHERRANDOMCHESS: 167 self.ini_kings = [None, None] 168 self.ini_rooks = ([None, None], [None, None]) 169 170 elif self.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS): 171 self.ini_kings = [None, None] 172 self.fin_kings = ([None, None], [None, None]) 173 self.fin_rooks = ([None, None], [None, None]) 174 175 elif self.variant == ATOMICCHESS: 176 self.iniAtomic() 177 178 elif self.variant == THREECHECKCHESS: 179 self.remaining_checks = [3, 3] 180 181 elif self.variant == CAMBODIANCHESS: 182 self.iniCambodian() 183 184 if self.variant in DROP_VARIANTS: 185 self.iniHouse() 186 187 # Get information 188 parts = fenstr.split() 189 castChr = "-" 190 epChr = "-" 191 fiftyChr = "0" 192 moveNoChr = "1" 193 if STRICT_FEN and len(parts) != 6: 194 raise SyntaxError(_("FEN needs 6 data fields. \n\n%s") % fenstr) 195 elif len(parts) < 2: 196 raise SyntaxError( 197 _("FEN needs at least 2 data fields in fenstr. \n\n%s") % 198 fenstr) 199 elif len(parts) >= 7 and self.variant == THREECHECKCHESS: 200 pieceChrs, colChr, castChr, epChr, checksChr, fiftyChr, moveNoChr = parts[:7] 201 self.remaining_checks = list(map(int, checksChr.split("+"))) 202 elif len(parts) >= 6: 203 pieceChrs, colChr, castChr, epChr, fiftyChr, moveNoChr = parts[:6] 204 elif len(parts) == 5: 205 pieceChrs, colChr, castChr, epChr, fiftyChr = parts 206 elif len(parts) == 4: 207 if parts[2].isdigit() and parts[3].isdigit(): 208 # xboard FEN usage for asian variants 209 pieceChrs, colChr, fiftyChr, moveNoChr = parts 210 else: 211 pieceChrs, colChr, castChr, epChr = parts 212 elif len(parts) == 3: 213 pieceChrs, colChr, castChr = parts 214 else: 215 pieceChrs, colChr = parts 216 217 # Try to validate some information 218 # TODO: This should be expanded and perhaps moved 219 220 slashes = pieceChrs.count("/") 221 if slashes < 7: 222 raise SyntaxError( 223 _("Needs 7 slashes in piece placement field. \n\n%s") % fenstr) 224 225 if not colChr.lower() in ("w", "b"): 226 raise SyntaxError( 227 _("Active color field must be one of w or b. \n\n%s") % fenstr) 228 229 if castChr != "-": 230 for Chr in castChr: 231 valid_chars = "ABCDEFGHKQ" if self.variant == FISCHERRANDOMCHESS or self.variant == SETUPCHESS else "KQ" 232 if Chr.upper() not in valid_chars: 233 if self.variant == CAMBODIANCHESS: 234 pass 235 # sjaakii uses DEde in cambodian starting fen to indicate 236 # that queens and kings are virgins (not moved yet) 237 else: 238 raise SyntaxError(_("Castling availability field is not legal. \n\n%s") 239 % fenstr) 240 241 if epChr != "-" and epChr not in cordDic: 242 raise SyntaxError(_("En passant cord is not legal. \n\n%s") % 243 fenstr) 244 245 # Parse piece placement field 246 promoted = False 247 # if there is a holding within [] we change it to BFEN style first 248 if pieceChrs.endswith("]"): 249 pieceChrs = pieceChrs[:-1].replace("[", "/").replace("-", "") 250 for r, rank in enumerate(pieceChrs.split("/")): 251 cord = (7 - r) * 8 252 for char in rank: 253 if r > 7: 254 # After the 8.rank BFEN can contain holdings (captured pieces) 255 # "~" after a piece letter denotes promoted piece 256 if r == 8 and self.variant in DROP_VARIANTS: 257 color = char.islower() and BLACK or WHITE 258 piece = chrU2Sign[char.upper()] 259 self.holding[color][piece] += 1 260 self.hash ^= holdingHash[color][piece][self.holding[color][piece]] 261 continue 262 else: 263 break 264 265 if char.isdigit(): 266 cord += int(char) 267 elif char == "~": 268 promoted = True 269 else: 270 color = char.islower() and BLACK or WHITE 271 piece = chrU2Sign[char.upper()] 272 self._addPiece(cord, piece, color) 273 self.pieceCount[color][piece] += 1 274 275 if self.variant in DROP_VARIANTS and promoted: 276 self.promoted[cord] = 1 277 promoted = False 278 279 if self.variant == CAMBODIANCHESS: 280 if piece == KING and self.kings[ 281 color] != self.ini_kings[color]: 282 self.is_first_move[KING][color] = False 283 if piece == QUEEN and cord != self.ini_queens[color]: 284 self.is_first_move[QUEEN][color] = False 285 286 cord += 1 287 288 if self.variant == FISCHERRANDOMCHESS: 289 # Save ranks fo find outermost rooks 290 # if KkQq was used in castling rights 291 if r == 0: 292 rank8 = rank 293 elif r == 7: 294 rank1 = rank 295 296 # Parse active color field 297 298 if colChr.lower() == "w": 299 self.setColor(WHITE) 300 else: 301 self.setColor(BLACK) 302 303 # Parse castling availability 304 305 castling = 0 306 for char in castChr: 307 if self.variant == FISCHERRANDOMCHESS: 308 if char in reprFile: 309 if char < reprCord[self.kings[BLACK]][0]: 310 castling |= B_OOO 311 self.ini_rooks[1][0] = reprFile.index(char) + 56 312 else: 313 castling |= B_OO 314 self.ini_rooks[1][1] = reprFile.index(char) + 56 315 elif char in [c.upper() for c in reprFile]: 316 if char < reprCord[self.kings[WHITE]][0].upper(): 317 castling |= W_OOO 318 self.ini_rooks[0][0] = reprFile.index(char.lower()) 319 else: 320 castling |= W_OO 321 self.ini_rooks[0][1] = reprFile.index(char.lower()) 322 elif char == "K": 323 castling |= W_OO 324 self.ini_rooks[0][1] = rank1.rfind('R') 325 elif char == "Q": 326 castling |= W_OOO 327 self.ini_rooks[0][0] = rank1.find('R') 328 elif char == "k": 329 castling |= B_OO 330 self.ini_rooks[1][1] = rank8.rfind('r') + 56 331 elif char == "q": 332 castling |= B_OOO 333 self.ini_rooks[1][0] = rank8.find('r') + 56 334 else: 335 if char == "K": 336 castling |= W_OO 337 elif char == "Q": 338 castling |= W_OOO 339 elif char == "k": 340 castling |= B_OO 341 elif char == "q": 342 castling |= B_OOO 343 344 if self.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS, 345 FISCHERRANDOMCHESS): 346 self.ini_kings[WHITE] = self.kings[WHITE] 347 self.ini_kings[BLACK] = self.kings[BLACK] 348 if self.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS): 349 if self.ini_kings[WHITE] == D1 and self.ini_kings[BLACK] == D8: 350 self.fin_kings = ([B1, F1], [B8, F8]) 351 self.fin_rooks = ([C1, E1], [C8, E8]) 352 elif self.ini_kings[WHITE] == D1: 353 self.fin_kings = ([B1, F1], [C8, G8]) 354 self.fin_rooks = ([C1, E1], [D8, F8]) 355 elif self.ini_kings[BLACK] == D8: 356 self.fin_kings = ([C1, G1], [B8, F8]) 357 self.fin_rooks = ([D1, F1], [C8, E8]) 358 else: 359 self.fin_kings = ([C1, G1], [C8, G8]) 360 self.fin_rooks = ([D1, F1], [D8, F8]) 361 362 self.setCastling(castling) 363 364 # Parse en passant target sqaure 365 366 if epChr == "-": 367 self.setEnpassant(None) 368 else: 369 self.setEnpassant(cordDic[epChr]) 370 371 # Parse halfmove clock field 372 373 if fiftyChr.isdigit(): 374 self.fifty = int(fiftyChr) 375 else: 376 self.fifty = 0 377 378 # Parse fullmove number 379 380 if moveNoChr.isdigit(): 381 movenumber = max(int(moveNoChr), 1) * 2 - 2 382 if self.color == BLACK: 383 movenumber += 1 384 self.plyCount = movenumber 385 else: 386 self.plyCount = 1 387 388 self.fen_was_applied = True 389 390 def isChecked(self): 391 if self.variant == SUICIDECHESS or self.variant == GIVEAWAYCHESS: 392 return False 393 elif self.variant == ATOMICCHESS: 394 if not self.boards[self.color][KING]: 395 return False 396 if -2 < (self.kings[0] >> 3) - (self.kings[1] >> 3) < 2 and -2 < (self.kings[0] & 7) - (self.kings[1] & 7) < 2: 397 return False 398 if self.checked is None: 399 kingcord = self.kings[self.color] 400 if kingcord == -1: 401 return False 402 self.checked = isAttacked(self, 403 kingcord, 404 1 - self.color, 405 ischecked=True) 406 return self.checked 407 408 def opIsChecked(self): 409 if self.variant == SUICIDECHESS or self.variant == GIVEAWAYCHESS: 410 return False 411 elif self.variant == ATOMICCHESS: 412 if not self.boards[1 - self.color][KING]: 413 return False 414 if -2 < (self.kings[0] >> 3) - (self.kings[1] >> 3) < 2 and -2 < (self.kings[0] & 7) - (self.kings[1] & 7) < 2: 415 return False 416 if self.opchecked is None: 417 kingcord = self.kings[1 - self.color] 418 if kingcord == -1: 419 return False 420 self.opchecked = isAttacked(self, 421 kingcord, 422 self.color, 423 ischecked=True) 424 return self.opchecked 425 426 def willLeaveInCheck(self, move): 427 if self.variant == SUICIDECHESS or self.variant == GIVEAWAYCHESS: 428 return False 429 board_clone = self.clone() 430 board_clone.applyMove(move) 431 return board_clone.opIsChecked() 432 433 def willGiveCheck(self, move): 434 board_clone = self.clone() 435 board_clone.applyMove(move) 436 return board_clone.isChecked() 437 438 def _addPiece(self, cord, piece, color): 439 _setBit = setBit 440 self.boards[color][piece] = _setBit(self.boards[color][piece], cord) 441 self.friends[color] = _setBit(self.friends[color], cord) 442 self.blocker = _setBit(self.blocker, cord) 443 444 if piece == PAWN: 445 self.pawnhash ^= pieceHashes[color][PAWN][cord] 446 elif piece == KING: 447 self.kings[color] = cord 448 self.hash ^= pieceHashes[color][piece][cord] 449 self.arBoard[cord] = piece 450 451 def _removePiece(self, cord, piece, color): 452 _clearBit = clearBit 453 self.boards[color][piece] = _clearBit(self.boards[color][piece], cord) 454 self.friends[color] = _clearBit(self.friends[color], cord) 455 self.blocker = _clearBit(self.blocker, cord) 456 457 if piece == PAWN: 458 self.pawnhash ^= pieceHashes[color][PAWN][cord] 459 460 self.hash ^= pieceHashes[color][piece][cord] 461 self.arBoard[cord] = EMPTY 462 463 def setColor(self, color): 464 if color == self.color: 465 return 466 self.color = color 467 self.hash ^= colorHash 468 469 def setCastling(self, castling): 470 if self.castling == castling: 471 return 472 473 if castling & W_OO != self.castling & W_OO: 474 self.hash ^= W_OOHash 475 if castling & W_OOO != self.castling & W_OOO: 476 self.hash ^= W_OOOHash 477 if castling & B_OO != self.castling & B_OO: 478 self.hash ^= B_OOHash 479 if castling & B_OOO != self.castling & B_OOO: 480 self.hash ^= B_OOOHash 481 482 self.castling = castling 483 484 def setEnpassant(self, epcord): 485 # Strip the square if there's no adjacent enemy pawn to make the capture 486 if epcord is not None: 487 sideToMove = (epcord >> 3 == 2 and BLACK or WHITE) 488 fwdPawns = self.boards[sideToMove][PAWN] 489 if sideToMove == WHITE: 490 fwdPawns >>= 8 491 else: 492 fwdPawns <<= 8 493 pawnTargets = (fwdPawns & ~fileBits[0]) << 1 494 pawnTargets |= (fwdPawns & ~fileBits[7]) >> 1 495 if not pawnTargets & bitPosArray[epcord]: 496 epcord = None 497 498 if self.enpassant == epcord: 499 return 500 if self.enpassant is not None: 501 self.hash ^= epHashes[self.enpassant & 7] 502 if epcord is not None: 503 self.hash ^= epHashes[epcord & 7] 504 self.enpassant = epcord 505 506 # @profile 507 def applyMove(self, move): 508 flag = move >> 12 509 510 fcord = (move >> 6) & 63 511 tcord = move & 63 512 513 fpiece = fcord if flag == DROP else self.arBoard[fcord] 514 tpiece = self.arBoard[tcord] 515 516 color = self.color 517 opcolor = 1 - self.color 518 castling = self.castling 519 520 self.hist_move.append(move) 521 self.hist_enpassant.append(self.enpassant) 522 self.hist_castling.append(self.castling) 523 self.hist_hash.append(self.hash) 524 self.hist_fifty.append(self.fifty) 525 self.hist_checked.append(self.checked) 526 self.hist_opchecked.append(self.opchecked) 527 if self.variant in DROP_VARIANTS: 528 self.hist_capture_promoting.append(self.capture_promoting) 529 if self.variant == CAMBODIANCHESS: 530 self.hist_is_first_move.append({KING: self.is_first_move[KING][:], 531 QUEEN: self.is_first_move[QUEEN][:]}) 532 533 self.opchecked = None 534 self.checked = None 535 536 if flag == NULL_MOVE: 537 self.setColor(opcolor) 538 self.plyCount += 1 539 return move 540 541 if self.variant == CAMBODIANCHESS: 542 if fpiece == KING and self.is_first_move[KING][color]: 543 self.is_first_move[KING][color] = False 544 elif fpiece == QUEEN and self.is_first_move[QUEEN][color]: 545 self.is_first_move[QUEEN][color] = False 546 547 # Castling moves can be represented strangely, so normalize them. 548 if flag in (KING_CASTLE, QUEEN_CASTLE): 549 side = flag - QUEEN_CASTLE 550 fpiece = KING 551 tpiece = EMPTY # In FRC, there may be a rook there, but the king doesn't capture it. 552 fcord = self.ini_kings[color] 553 if FILE(fcord) == 3 and self.variant in (WILDCASTLECHESS, 554 WILDCASTLESHUFFLECHESS): 555 side = 0 if side == 1 else 1 556 tcord = self.fin_kings[color][side] 557 rookf = self.ini_rooks[color][side] 558 rookt = self.fin_rooks[color][side] 559 560 # Capture (sittuyin in place promotion is not capture move!) 561 if tpiece != EMPTY and fcord != tcord: 562 self._removePiece(tcord, tpiece, opcolor) 563 self.pieceCount[opcolor][tpiece] -= 1 564 if self.variant in DROP_VARIANTS: 565 if self.promoted[tcord]: 566 if self.variant == CRAZYHOUSECHESS: 567 self.holding[color][PAWN] += 1 568 self.hash ^= holdingHash[color][PAWN][self.holding[color][PAWN]] 569 self.capture_promoting = True 570 else: 571 if self.variant == CRAZYHOUSECHESS: 572 self.holding[color][tpiece] += 1 573 self.hash ^= holdingHash[color][tpiece][self.holding[color][tpiece]] 574 self.capture_promoting = False 575 elif self.variant == ATOMICCHESS: 576 from pychess.Variants.atomic import piecesAround 577 apieces = [(fcord, fpiece, color), ] 578 for acord, apiece, acolor in piecesAround(self, tcord): 579 if apiece != PAWN and acord != fcord: 580 self._removePiece(acord, apiece, acolor) 581 self.pieceCount[acolor][apiece] -= 1 582 apieces.append((acord, apiece, acolor)) 583 if apiece == ROOK and acord != fcord: 584 if acord == self.ini_rooks[opcolor][0]: 585 castling &= ~CAS_FLAGS[opcolor][0] 586 elif acord == self.ini_rooks[opcolor][1]: 587 castling &= ~CAS_FLAGS[opcolor][1] 588 self.hist_exploding_around.append(apieces) 589 self.hist_tpiece.append(tpiece) 590 591 # Remove moving piece(s), then add them at their destination. 592 if flag == DROP: 593 if self.variant in DROP_VARIANTS: 594 assert self.holding[color][fpiece] > 0 595 self.holding[color][fpiece] -= 1 596 self.hash ^= holdingHash[color][fpiece][self.holding[color][fpiece]] 597 self.pieceCount[color][fpiece] += 1 598 else: 599 self._removePiece(fcord, fpiece, color) 600 601 if flag in (KING_CASTLE, QUEEN_CASTLE): 602 self._removePiece(rookf, ROOK, color) 603 self._addPiece(rookt, ROOK, color) 604 self.hasCastled[color] = True 605 606 if flag == ENPASSANT: 607 takenPawnC = tcord + (color == WHITE and -8 or 8) 608 self._removePiece(takenPawnC, PAWN, opcolor) 609 self.pieceCount[opcolor][PAWN] -= 1 610 if self.variant == CRAZYHOUSECHESS: 611 self.holding[color][PAWN] += 1 612 self.hash ^= holdingHash[color][PAWN][self.holding[color][PAWN]] 613 elif self.variant == ATOMICCHESS: 614 from pychess.Variants.atomic import piecesAround 615 apieces = [(fcord, fpiece, color), ] 616 for acord, apiece, acolor in piecesAround(self, tcord): 617 if apiece != PAWN and acord != fcord: 618 self._removePiece(acord, apiece, acolor) 619 self.pieceCount[acolor][apiece] -= 1 620 apieces.append((acord, apiece, acolor)) 621 self.hist_exploding_around.append(apieces) 622 elif flag in PROMOTIONS: 623 # Pretend the pawn changes into a piece before reaching its destination. 624 fpiece = flag - 2 625 self.pieceCount[color][fpiece] += 1 626 self.pieceCount[color][PAWN] -= 1 627 628 if self.variant in DROP_VARIANTS: 629 if tpiece == EMPTY: 630 self.capture_promoting = False 631 632 if flag in PROMOTIONS: 633 self.promoted[tcord] = 1 634 elif flag != DROP: 635 if self.promoted[fcord]: 636 self.promoted[fcord] = 0 637 self.promoted[tcord] = 1 638 elif tpiece != EMPTY: 639 self.promoted[tcord] = 0 640 641 if self.variant == ATOMICCHESS and (tpiece != EMPTY or 642 flag == ENPASSANT): 643 self.pieceCount[color][fpiece] -= 1 644 else: 645 self._addPiece(tcord, fpiece, color) 646 647 if fpiece == PAWN and abs(fcord - tcord) == 16: 648 self.setEnpassant((fcord + tcord) // 2) 649 else: 650 self.setEnpassant(None) 651 652 if tpiece == EMPTY and fpiece != PAWN: 653 self.fifty += 1 654 else: 655 self.fifty = 0 656 657 # Clear castle flags 658 king = self.ini_kings[color] 659 wildcastle = FILE(king) == 3 and self.variant in ( 660 WILDCASTLECHESS, WILDCASTLESHUFFLECHESS) 661 if fpiece == KING: 662 castling &= ~CAS_FLAGS[color][0] 663 castling &= ~CAS_FLAGS[color][1] 664 elif fpiece == ROOK: 665 if fcord == self.ini_rooks[color][0]: 666 side = 1 if wildcastle else 0 667 castling &= ~CAS_FLAGS[color][side] 668 elif fcord == self.ini_rooks[color][1]: 669 side = 0 if wildcastle else 1 670 castling &= ~CAS_FLAGS[color][side] 671 if tpiece == ROOK: 672 if tcord == self.ini_rooks[opcolor][0]: 673 side = 1 if wildcastle else 0 674 castling &= ~CAS_FLAGS[opcolor][side] 675 elif tcord == self.ini_rooks[opcolor][1]: 676 side = 0 if wildcastle else 1 677 castling &= ~CAS_FLAGS[opcolor][side] 678 679 if self.variant == PLACEMENTCHESS and self.plyCount == 15: 680 castling = 0 681 if self.arBoard[A1] == ROOK and self.arBoard[E1] == KING: 682 castling |= W_OOO 683 if self.arBoard[H1] == ROOK and self.arBoard[E1] == KING: 684 castling |= W_OO 685 if self.arBoard[A8] == ROOK and self.arBoard[E8] == KING: 686 castling |= B_OOO 687 if self.arBoard[H8] == ROOK and self.arBoard[E8] == KING: 688 castling |= B_OO 689 690 self.setCastling(castling) 691 692 self.setColor(opcolor) 693 self.plyCount += 1 694 695 def popMove(self): 696 # Note that we remove the last made move, which was not made by boards 697 # current color, but by its opponent 698 color = 1 - self.color 699 opcolor = self.color 700 701 move = self.hist_move.pop() 702 cpiece = self.hist_tpiece.pop() 703 704 flag = move >> 12 705 706 if flag == NULL_MOVE: 707 self.setColor(color) 708 return 709 710 fcord = (move >> 6) & 63 711 tcord = move & 63 712 tpiece = self.arBoard[tcord] 713 714 # Castling moves can be represented strangely, so normalize them. 715 if flag in (KING_CASTLE, QUEEN_CASTLE): 716 side = flag - QUEEN_CASTLE 717 tpiece = KING 718 fcord = self.ini_kings[color] 719 if FILE(fcord) == 3 and self.variant in (WILDCASTLECHESS, 720 WILDCASTLESHUFFLECHESS): 721 side = 0 if side == 1 else 1 722 tcord = self.fin_kings[color][side] 723 rookf = self.ini_rooks[color][side] 724 rookt = self.fin_rooks[color][side] 725 self._removePiece(tcord, tpiece, color) 726 self._removePiece(rookt, ROOK, color) 727 self._addPiece(rookf, ROOK, color) 728 self.hasCastled[color] = False 729 else: 730 self._removePiece(tcord, tpiece, color) 731 732 # Put back captured piece 733 if cpiece != EMPTY and fcord != tcord: 734 self._addPiece(tcord, cpiece, opcolor) 735 self.pieceCount[opcolor][cpiece] += 1 736 if self.variant == CRAZYHOUSECHESS: 737 if self.capture_promoting: 738 assert self.holding[color][PAWN] > 0 739 self.holding[color][PAWN] -= 1 740 self.hash ^= holdingHash[color][PAWN][self.holding[color][PAWN]] 741 else: 742 assert self.holding[color][cpiece] > 0 743 self.holding[color][cpiece] -= 1 744 self.hash ^= holdingHash[color][cpiece][self.holding[color][cpiece]] 745 elif self.variant == ATOMICCHESS: 746 apieces = self.hist_exploding_around.pop() 747 for acord, apiece, acolor in apieces: 748 self._addPiece(acord, apiece, acolor) 749 self.pieceCount[acolor][apiece] += 1 750 751 # Put back piece captured by enpassant 752 if flag == ENPASSANT: 753 epcord = color == WHITE and tcord - 8 or tcord + 8 754 self._addPiece(epcord, PAWN, opcolor) 755 self.pieceCount[opcolor][PAWN] += 1 756 if self.variant == CRAZYHOUSECHESS: 757 assert self.holding[color][PAWN] > 0 758 self.holding[color][PAWN] -= 1 759 self.hash ^= holdingHash[color][PAWN][self.holding[color][PAWN]] 760 elif self.variant == ATOMICCHESS: 761 apieces = self.hist_exploding_around.pop() 762 for acord, apiece, acolor in apieces: 763 self._addPiece(acord, apiece, acolor) 764 self.pieceCount[acolor][apiece] += 1 765 766 # Un-promote pawn 767 if flag in PROMOTIONS: 768 tpiece = PAWN 769 self.pieceCount[color][flag - 2] -= 1 770 self.pieceCount[color][PAWN] += 1 771 772 # Put back moved piece 773 if flag == DROP: 774 self.holding[color][tpiece] += 1 775 self.hash ^= holdingHash[color][tpiece][self.holding[color][tpiece]] 776 self.pieceCount[color][tpiece] -= 1 777 else: 778 if not (self.variant == ATOMICCHESS and 779 (cpiece != EMPTY or flag == ENPASSANT)): 780 self._addPiece(fcord, tpiece, color) 781 782 if self.variant in DROP_VARIANTS: 783 if flag != DROP: 784 if self.promoted[tcord] and (flag not in PROMOTIONS): 785 self.promoted[fcord] = 1 786 if self.capture_promoting: 787 self.promoted[tcord] = 1 788 else: 789 self.promoted[tcord] = 0 790 self.capture_promoting = self.hist_capture_promoting.pop() 791 792 if self.variant == CAMBODIANCHESS: 793 self.is_first_move = self.hist_is_first_move.pop() 794 795 self.setColor(color) 796 797 self.checked = self.hist_checked.pop() 798 self.opchecked = self.hist_opchecked.pop() 799 self.enpassant = self.hist_enpassant.pop() 800 self.castling = self.hist_castling.pop() 801 self.hash = self.hist_hash.pop() 802 self.fifty = self.hist_fifty.pop() 803 self.plyCount -= 1 804 805 def __eq__(self, other): 806 if not (other is not None and 807 self.fen_was_applied and other.fen_was_applied and 808 self.hash == other.hash and self.plyCount == other.plyCount): 809 return False 810 811 b0, b1 = self.prev, other.prev 812 ok = True 813 while ok and b0 is not None and b1 is not None: 814 if not (b0.fen_was_applied and b1.fen_was_applied and 815 b0.hash == b1.hash and b0.plyCount == b1.plyCount): 816 ok = False 817 else: 818 b0, b1 = b0.prev, b1.prev 819 return ok 820 821 def __ne__(self, other): 822 return not self.__eq__(other) 823 824 def reprCastling(self): 825 if not self.castling: 826 return "-" 827 else: 828 strs = [] 829 if self.variant == FISCHERRANDOMCHESS: 830 if self.castling & W_OO: 831 strs.append(reprCord[self.ini_rooks[0][1]][0].upper()) 832 if self.castling & W_OOO: 833 strs.append(reprCord[self.ini_rooks[0][0]][0].upper()) 834 if self.castling & B_OO: 835 strs.append(reprCord[self.ini_rooks[1][1]][0]) 836 if self.castling & B_OOO: 837 strs.append(reprCord[self.ini_rooks[1][0]][0]) 838 else: 839 if self.castling & W_OO: 840 strs.append("K") 841 if self.castling & W_OOO: 842 strs.append("Q") 843 if self.castling & B_OO: 844 strs.append("k") 845 if self.castling & B_OOO: 846 strs.append("q") 847 return "".join(strs) 848 849 def prepr(self, ascii=False): 850 if not self.fen_was_applied: 851 return ("LBoard without applied FEN") 852 b = "#" + reprColor[self.color] + " " 853 b += self.reprCastling() + " " 854 b += self.enpassant is not None and reprCord[self.enpassant] or "-" 855 b += "\n# " 856 rows = [self.arBoard[i:i + 8] for i in range(0, 64, 8)][::-1] 857 for r, row in enumerate(rows): 858 for i, piece in enumerate(row): 859 if piece != EMPTY: 860 if bitPosArray[(7 - r) * 8 + i] & self.friends[WHITE]: 861 assert self.boards[WHITE][ 862 piece], "self.boards doesn't match self.arBoard !!!" 863 sign = reprSign[piece] if ascii else FAN_PIECES[WHITE][ 864 piece] 865 else: 866 assert self.boards[BLACK][ 867 piece], "self.boards doesn't match self.arBoard !!!" 868 sign = reprSign[piece].lower( 869 ) if ascii else FAN_PIECES[BLACK][piece] 870 b += sign 871 else: 872 b += "." 873 b += " " 874 b += "\n# " 875 876 if self.variant in DROP_VARIANTS: 877 for color in (BLACK, WHITE): 878 holding = self.holding[color] 879 b += "\n# [%s]" % "".join([reprSign[ 880 piece] if ascii else FAN_PIECES[color][piece] * holding[ 881 piece] for piece in holding if holding[piece] > 0]) 882 return b 883 884 def __repr__(self): 885 return self.prepr() 886 887 def asFen(self, enable_bfen=True): 888 fenstr = [] 889 890 rows = [self.arBoard[i:i + 8] for i in range(0, 64, 8)][::-1] 891 for r, row in enumerate(rows): 892 empty = 0 893 for i, piece in enumerate(row): 894 if piece != EMPTY: 895 if empty > 0: 896 fenstr.append(str(empty)) 897 empty = 0 898 if self.variant in (CAMBODIANCHESS, MAKRUKCHESS): 899 sign = reprSignMakruk[piece] 900 elif self.variant == SITTUYINCHESS: 901 sign = reprSignSittuyin[piece] 902 else: 903 sign = reprSign[piece] 904 if bitPosArray[(7 - r) * 8 + i] & self.friends[WHITE]: 905 sign = sign.upper() 906 else: 907 sign = sign.lower() 908 fenstr.append(sign) 909 if self.variant in (BUGHOUSECHESS, CRAZYHOUSECHESS): 910 if self.promoted[r * 8 + i]: 911 fenstr.append("~") 912 else: 913 empty += 1 914 if empty > 0: 915 fenstr.append(str(empty)) 916 if r != 7: 917 fenstr.append("/") 918 919 if self.variant in DROP_VARIANTS: 920 holding_pieces = [] 921 for color in (BLACK, WHITE): 922 holding = self.holding[color] 923 for piece in holding: 924 if holding[piece] > 0: 925 if self.variant == SITTUYINCHESS: 926 sign = reprSignSittuyin[piece] 927 else: 928 sign = reprSign[piece] 929 sign = sign.upper() if color == WHITE else sign.lower() 930 holding_pieces.append(sign * holding[piece]) 931 if holding_pieces: 932 if enable_bfen: 933 fenstr.append("/") 934 fenstr += holding_pieces 935 else: 936 fenstr.append("[") 937 fenstr += holding_pieces 938 fenstr.append("]") 939 940 fenstr.append(" ") 941 942 fenstr.append(self.color == WHITE and "w" or "b") 943 fenstr.append(" ") 944 945 if self.variant == CAMBODIANCHESS: 946 cast = "" 947 if self.is_first_move[KING][WHITE]: 948 cast += "D" 949 if self.is_first_move[QUEEN][WHITE]: 950 cast += "E" 951 if self.is_first_move[KING][BLACK]: 952 cast += "d" 953 if self.is_first_move[QUEEN][BLACK]: 954 cast += "e" 955 if not cast: 956 cast = "-" 957 fenstr.append(cast) 958 else: 959 fenstr.append(self.reprCastling()) 960 fenstr.append(" ") 961 962 if not self.enpassant: 963 fenstr.append("-") 964 else: 965 fenstr.append(reprCord[self.enpassant]) 966 fenstr.append(" ") 967 968 fenstr.append(str(self.fifty)) 969 fenstr.append(" ") 970 971 fullmove = (self.plyCount) // 2 + 1 972 fenstr.append(str(fullmove)) 973 974 return "".join(fenstr) 975 976 def clone(self): 977 copy = LBoard(self.variant) 978 copy.blocker = self.blocker 979 980 copy.friends = self.friends[:] 981 copy.kings = self.kings[:] 982 copy.boards = (self.boards[WHITE][:], self.boards[BLACK][:]) 983 copy.arBoard = self.arBoard[:] 984 copy.pieceCount = (self.pieceCount[WHITE][:], self.pieceCount[BLACK][:]) 985 986 copy.color = self.color 987 copy.plyCount = self.plyCount 988 copy.hasCastled = self.hasCastled[:] 989 990 copy.enpassant = self.enpassant 991 copy.castling = self.castling 992 copy.hash = self.hash 993 copy.pawnhash = self.pawnhash 994 copy.fifty = self.fifty 995 copy.checked = self.checked 996 copy.opchecked = self.opchecked 997 998 copy.hist_move = self.hist_move[:] 999 copy.hist_tpiece = self.hist_tpiece[:] 1000 copy.hist_enpassant = self.hist_enpassant[:] 1001 copy.hist_castling = self.hist_castling[:] 1002 copy.hist_hash = self.hist_hash[:] 1003 copy.hist_fifty = self.hist_fifty[:] 1004 copy.hist_checked = self.hist_checked[:] 1005 copy.hist_opchecked = self.hist_opchecked[:] 1006 1007 if self.variant == FISCHERRANDOMCHESS: 1008 copy.ini_kings = self.ini_kings[:] 1009 copy.ini_rooks = (self.ini_rooks[0][:], self.ini_rooks[1][:]) 1010 elif self.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS): 1011 copy.ini_kings = self.ini_kings[:] 1012 copy.fin_kings = (self.fin_kings[0][:], self.fin_kings[1][:]) 1013 copy.fin_rooks = (self.fin_rooks[0][:], self.fin_rooks[1][:]) 1014 elif self.variant in DROP_VARIANTS: 1015 copy.promoted = self.promoted[:] 1016 copy.holding = (self.holding[0].copy(), self.holding[1].copy()) 1017 copy.capture_promoting = self.capture_promoting 1018 copy.hist_capture_promoting = self.hist_capture_promoting[:] 1019 elif self.variant == ATOMICCHESS: 1020 copy.hist_exploding_around = [a[:] for a in self.hist_exploding_around] 1021 elif self.variant == THREECHECKCHESS: 1022 copy.remaining_checks = self.remaining_checks[:] 1023 elif self.variant == CAMBODIANCHESS: 1024 copy.ini_kings = self.ini_kings 1025 copy.ini_queens = self.ini_queens 1026 copy.is_first_move = {KING: self.is_first_move[KING][:], 1027 QUEEN: self.is_first_move[QUEEN][:]} 1028 copy.hist_is_first_move = self.hist_is_first_move[:] 1029 1030 copy.fen_was_applied = self.fen_was_applied 1031 return copy 1032 1033 1034START_BOARD = LBoard() 1035START_BOARD.applyFen(FEN_START) 1036