1 2from .bitboard import bitPosArray, notBitPosArray, lastBit, firstBit, clearBit, lsb 3from .ldata import moveArray, rays, directions, fromToRay, PIECE_VALUES, PAWN_VALUE 4from pychess.Utils.const import ASEAN_VARIANTS, ASEAN_BBISHOP, ASEAN_WBISHOP, ASEAN_QUEEN, \ 5 BLACK, WHITE, PAWN, BPAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, ENPASSANT, ATOMICCHESS 6 7# 8# Caveat: Many functions in this module has very similar code. If you fix a 9# bug, or write a performance enhance, please update all functions. Apologies 10# for the inconvenience 11# 12 13 14def isAttacked(board, cord, color, ischecked=False): 15 """ To determine if cord is attacked by any pieces from color. """ 16 17 _moveArray = moveArray 18 pboards = board.boards[color] 19 20 # Knights 21 if pboards[KNIGHT] & _moveArray[KNIGHT][cord]: 22 return True 23 24 rayto = fromToRay[cord] 25 blocker = board.blocker 26 27 # Bishops & Queens 28 if board.variant in ASEAN_VARIANTS: 29 bishopMoves = _moveArray[ASEAN_BBISHOP if color == WHITE else 30 ASEAN_WBISHOP] 31 if pboards[BISHOP] & bishopMoves[cord]: 32 return True 33 if pboards[QUEEN] & _moveArray[ASEAN_QUEEN][cord]: 34 return True 35 else: 36 bitboard = (pboards[BISHOP] | 37 pboards[QUEEN]) & _moveArray[BISHOP][cord] 38 if bitboard: 39 others = ~bitboard & blocker 40 # inlined iterBits() 41 while bitboard: 42 bit = bitboard & -bitboard 43 ray = rayto[lsb[bit]] 44 # If there is a path and no other piece stand in our way 45 if ray and not ray & others: 46 return True 47 bitboard -= bit 48 49 # Rooks & Queens 50 if board.variant in ASEAN_VARIANTS: 51 bitboard = pboards[ROOK] & _moveArray[ROOK][cord] 52 else: 53 bitboard = (pboards[ROOK] | pboards[QUEEN]) & _moveArray[ROOK][cord] 54 if bitboard: 55 others = ~bitboard & blocker 56 # inlined iterBits() 57 while bitboard: 58 bit = bitboard & -bitboard 59 ray = rayto[lsb[bit]] 60 # If there is a path and no other piece stand in our way 61 if ray and not ray & others: 62 return True 63 bitboard -= bit 64 65 # Pawns 66 # Would a pawn of the opposite color, standing at out kings cord, be able 67 # to attack any of our pawns? 68 ptype = color == WHITE and BPAWN or PAWN 69 if pboards[PAWN] & _moveArray[ptype][cord]: 70 return True 71 72 # King 73 if pboards[KING] & _moveArray[KING][cord]: 74 if board.variant == ATOMICCHESS and ischecked: 75 return False 76 else: 77 return True 78 79 return False 80 81 82# TODO tests 83def propagateRayFollowingMovement(board, cord, bitboard): 84 "tests if there are pieces blocking the way on the movement line" 85 rayto = fromToRay[cord] 86 blocker = board.blocker 87 88 bits = 0 89 90 while bitboard: 91 bit = bitboard & -bitboard 92 c = lsb[bit] 93 ray = rayto[c] 94 if ray and not clearBit(ray & blocker, c): 95 bits |= bitPosArray[c] 96 bitboard -= bit 97 98 return bits 99 100 101def piecesAttackingCord(board, cord, color): 102 """ return the type of piece attacking Cord 103 does not support variant yet 104 The type of args are LBoard, Cord as flat number (cord argument of class Cord, BLACK or WHITE from const file """ 105 106 _moveArray = moveArray 107 pieces = board.boards[color] 108 109 pieces_attacking = [] 110 111 # Knights 112 bits_knight = pieces[KNIGHT] & _moveArray[KNIGHT][cord] 113 if bits_knight: 114 pieces_attacking.append(KNIGHT) 115 116 # Kings 117 bits_king = pieces[KING] & _moveArray[KING][cord] 118 if bits_king: 119 pieces_attacking.append(KING) 120 121 # Pawns, to test , bug possible with BPAWN 122 bits_pawn = pieces[PAWN] & _moveArray[color == WHITE and BPAWN or PAWN][cord] 123 if bits_pawn: 124 pieces_attacking.append(PAWN) 125 126 # Bishops 127 bitboard_bishop = (pieces[BISHOP] | pieces[QUEEN]) & _moveArray[BISHOP][cord] 128 129 if propagateRayFollowingMovement(board, cord, bitboard_bishop): 130 pieces_attacking.append(BISHOP) 131 132 bitboard_rook = (pieces[ROOK] | pieces[QUEEN]) & _moveArray[ROOK][cord] 133 # inlined iterBits() 134 135 if propagateRayFollowingMovement(board, cord, bitboard_rook): 136 pieces_attacking.append(ROOK) 137 138 bitboard_queen_diago = pieces[QUEEN] & _moveArray[BISHOP][cord] 139 if propagateRayFollowingMovement(board, cord, bitboard_queen_diago): 140 pieces_attacking.append(QUEEN) 141 142 bitboard_queen_straight = pieces[QUEEN] & _moveArray[ROOK][cord] 143 if propagateRayFollowingMovement(board, cord, bitboard_queen_straight): 144 pieces_attacking.append(QUEEN) 145 146 return pieces_attacking 147 148 149def getAttacks(board, cord, color): 150 """ To create a bitboard of pieces of color, which attacks cord 151 The type of args are LBoard, ,BLACK or WHITE from const file 152 """ 153 154 _moveArray = moveArray 155 pieces = board.boards[color] 156 157 # Knights 158 bits = pieces[KNIGHT] & _moveArray[KNIGHT][cord] 159 160 # Kings 161 bits |= pieces[KING] & _moveArray[KING][cord] 162 163 # Pawns, to test , bug possible with BPAWN 164 bits |= pieces[PAWN] & _moveArray[color == WHITE and BPAWN or PAWN][cord] 165 166 rayto = fromToRay[cord] 167 blocker = board.blocker 168 169 # Bishops and Queens 170 if board.variant in ASEAN_VARIANTS: 171 bishopMoves = _moveArray[ASEAN_BBISHOP if color == WHITE else 172 ASEAN_WBISHOP] 173 bits |= pieces[BISHOP] & bishopMoves[cord] 174 175 bits |= pieces[QUEEN] & _moveArray[ASEAN_QUEEN][cord] 176 else: 177 bitboard = (pieces[BISHOP] | pieces[QUEEN]) & _moveArray[BISHOP][cord] 178 # inlined iterBits() 179 # check whether or not there is a piece blocking the path in diagonal 180 while bitboard: 181 bit = bitboard & -bitboard 182 c = lsb[bit] 183 ray = rayto[c] 184 if ray and not clearBit(ray & blocker, c): 185 bits |= bitPosArray[c] 186 bitboard -= bit 187 188 # Rooks and queens 189 if board.variant in ASEAN_VARIANTS: 190 bitboard = pieces[ROOK] & _moveArray[ROOK][cord] 191 else: 192 bitboard = (pieces[ROOK] | pieces[QUEEN]) & _moveArray[ROOK][cord] 193 # inlined iterBits() 194 195 # check whether or not there is a piece blocking the path in straight line 196 while bitboard: 197 bit = bitboard & -bitboard 198 c = lsb[bit] 199 ray = rayto[c] 200 if ray and not clearBit(ray & blocker, c): 201 bits |= bitPosArray[c] 202 bitboard -= bit 203 204 return bits 205 206 207def pinnedOnKing(board, cord, color): 208 # Determine if the piece on cord is pinned against its colors king. 209 # In chess, a pin is a situation in which a piece is forced to stay put 210 # because moving it would expose a more valuable piece behind it to 211 # capture. 212 # Caveat: pinnedOnKing should only be called by genCheckEvasions(). 213 214 kingCord = board.kings[color] 215 216 dir = directions[kingCord][cord] 217 if dir == -1: 218 return False 219 220 opcolor = 1 - color 221 blocker = board.blocker 222 223 # Path from piece to king is blocked, so no pin 224 if clearBit(fromToRay[kingCord][cord], cord) & blocker: 225 return False 226 227 b = (rays[kingCord][dir] ^ fromToRay[kingCord][cord]) & blocker 228 if not b: 229 return False 230 231 cord1 = cord > kingCord and firstBit(b) or lastBit(b) 232 233 # If diagonal 234 if board.variant in ASEAN_VARIANTS: 235 pass 236 else: 237 if dir <= 3 and bitPosArray[cord1] & \ 238 (board.boards[opcolor][QUEEN] | board.boards[opcolor][BISHOP]): 239 return True 240 241# Rank / file 242 if board.variant in ASEAN_VARIANTS: 243 if dir >= 4 and bitPosArray[cord1] & \ 244 board.boards[opcolor][ROOK]: 245 return True 246 else: 247 if dir >= 4 and bitPosArray[cord1] & \ 248 (board.boards[opcolor][QUEEN] | board.boards[opcolor][ROOK]): 249 return True 250 251 return False 252 253 254def staticExchangeEvaluate(board, moveOrTcord, color=None): 255 """ The GnuChess Static Exchange Evaluator (or SEE for short). 256 First determine the target square. Create a bitboard of all squares 257 attacking the target square for both sides. Using these 2 bitboards, 258 we take turn making captures from smallest piece to largest piece. 259 When a sliding piece makes a capture, we check behind it to see if 260 another attacker piece has been exposed. If so, add this to the bitboard 261 as well. When performing the "captures", we stop if one side is ahead 262 and doesn't need to capture, a form of pseudo-minimaxing. """ 263 264 # 265 # Notice: If you use the tcord version, the color is the color attacked, and 266 # the color to witch the score is relative. 267 # 268 269 swaplist = [0] 270 271 if color is None: 272 move = moveOrTcord 273 flag = move >> 12 274 fcord = (move >> 6) & 63 275 tcord = move & 63 276 277 color = board.friends[BLACK] & bitPosArray[fcord] and BLACK or WHITE 278 opcolor = 1 - color 279 boards = board.boards[color] 280 opboards = board.boards[opcolor] 281 282 ours = getAttacks(board, tcord, color) 283 ours = clearBit(ours, fcord) 284 theirs = getAttacks(board, tcord, opcolor) 285 286 if xray[board.arBoard[fcord]]: 287 ours, theirs = addXrayPiece(board, tcord, fcord, color, ours, 288 theirs) 289 290 from pychess.Variants import variants 291 PROMOTIONS = variants[board.variant].PROMOTIONS 292 if flag in PROMOTIONS: 293 swaplist = [PIECE_VALUES[flag - 3] - PAWN_VALUE] 294 lastval = -PIECE_VALUES[flag - 3] 295 else: 296 if flag == ENPASSANT: 297 swaplist = [PAWN_VALUE] 298 else: 299 swaplist = [PIECE_VALUES[board.arBoard[tcord]]] 300 lastval = -PIECE_VALUES[board.arBoard[fcord]] 301 302 else: 303 tcord = moveOrTcord 304 opcolor = 1 - color 305 boards = board.boards[color] 306 opboards = board.boards[opcolor] 307 308 ours = getAttacks(board, tcord, color) 309 theirs = getAttacks(board, tcord, opcolor) 310 311 lastval = -PIECE_VALUES[board.arBoard[tcord]] 312 313 while theirs: 314 for piece in range(PAWN, KING + 1): 315 r = theirs & opboards[piece] 316 if r: 317 cord = firstBit(r) 318 theirs = clearBit(theirs, cord) 319 if xray[piece]: 320 ours, theirs = addXrayPiece(board, tcord, cord, color, 321 ours, theirs) 322 swaplist.append(swaplist[-1] + lastval) 323 lastval = PIECE_VALUES[piece] 324 break 325 326 if not ours: 327 break 328 329 for piece in range(PAWN, KING + 1): 330 r = ours & boards[piece] 331 if r: 332 cord = firstBit(r) 333 ours = clearBit(ours, cord) 334 if xray[piece]: 335 ours, theirs = addXrayPiece(board, tcord, cord, color, 336 ours, theirs) 337 swaplist.append(swaplist[-1] + lastval) 338 lastval = -PIECE_VALUES[piece] 339 break 340 341 # At this stage, we have the swap scores in a list. We just need to 342 # mini-max the scores from the bottom up to the top of the list. 343 344 for n in range(len(swaplist) - 1, 0, -1): 345 if n & 1: 346 if swaplist[n] <= swaplist[n - 1]: 347 swaplist[n - 1] = swaplist[n] 348 else: 349 if swaplist[n] >= swaplist[n - 1]: 350 swaplist[n - 1] = swaplist[n] 351 352 return swaplist[0] 353 354 355xray = (False, True, False, True, True, True, False) 356 357 358def addXrayPiece(board, tcord, fcord, color, ours, theirs): 359 """ This is used by swapOff. 360 The purpose of this routine is to find a piece which attack through 361 another piece (e.g. two rooks, Q+B, B+P, etc.) Color is the side attacking 362 the square where the swapping is to be done. """ 363 364 dir = directions[tcord][fcord] 365 a = rays[fcord][dir] & board.blocker 366 if not a: 367 return ours, theirs 368 369 if tcord < fcord: 370 ncord = firstBit(a) 371 else: 372 ncord = lastBit(a) 373 374 piece = board.arBoard[ncord] 375 if board.variant in ASEAN_VARIANTS: 376 cond = piece == ROOK and dir > 3 377 else: 378 cond = piece == QUEEN or ( 379 piece == ROOK and dir > 3) or ( 380 piece == BISHOP and dir < 4) 381 if cond: 382 bit = bitPosArray[ncord] 383 if bit & board.friends[color]: 384 ours |= bit 385 else: 386 theirs |= bit 387 388 return ours, theirs 389 390 391def defends(board, fcord, tcord): 392 """ Could fcord attack tcord if the piece on tcord wasn't on the team of 393 fcord? 394 Doesn't test check. """ 395 396 # Work on a board copy, as we are going to change some stuff 397 board = board.clone() 398 399 if board.friends[WHITE] & bitPosArray[fcord]: 400 color = WHITE 401 else: 402 color = BLACK 403 opcolor = 1 - color 404 405 boards = board.boards[color] 406 opboards = board.boards[opcolor] 407 408 # To see if we now defend the piece, we have to "give" it to the other team 409 piece = board.arBoard[tcord] 410 411 # backup = boards[piece] 412 # opbackup = opboards[piece] 413 414 boards[piece] &= notBitPosArray[tcord] 415 opboards[piece] |= bitPosArray[tcord] 416 board.friends[color] &= notBitPosArray[tcord] 417 board.friends[opcolor] |= bitPosArray[tcord] 418 419 # Can we "attack" the piece now? 420 backupColor = board.color 421 board.setColor(color) 422 from .lmovegen import newMove 423 from .validator import validateMove 424 islegal = validateMove(board, newMove(fcord, tcord)) 425 board.setColor(backupColor) 426 427 # We don't need to set the board back, as we work on a copy 428 # boards[piece] = backup 429 # opboards[piece] = opbackup 430 # board.friends[color] |= bitPosArray[tcord] 431 # board.friends[opcolor] &= notBitPosArray[tcord] 432 433 return islegal 434