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