niklasf / python-chess

A chess library for Python, with move generation and validation, PGN parsing and writing, Polyglot opening book reading, Gaviota tablebase probing, Syzygy tablebase probing, and UCI/XBoard engine communication
https://python-chess.readthedocs.io/en/latest/
GNU General Public License v3.0
2.38k stars 520 forks source link

Not seeing x-ray defenders by calling board.attackers() #1090

Closed SylviaStout closed 1 month ago

SylviaStout commented 2 months ago

I’m working with checkmate patterns, so one thing is to enumerate the defenders of the adjacent-winning-color-pieces in the pattern.

In the test case below, the losing king would escape checkmate by fleeing (capturing white’s f5-pawn while fleeing). But the f5-pawn is x-ray-defended by white’s f8-rook.

I’m not seeing x-ray defenders by calling board.attackers()

If there is a workaround, please let me know. I totally apologize if I messed up on anything.

The 1st five asserts just verify test-case validity. It’s the last and 6th assert showing the issue (likely too obvious to mention, sorry).

import chess

board = chess.Board()
board.set_epd('5R2/1p4p1/2pK1kPp/3p1P2/3r2nP/8/1P6/8 b - -')

assert board.is_checkmate() is True
winning_color = board.outcome().winner
assert chess.WHITE == winning_color

rook_defending_pawn = board.piece_at(chess.F8)
losing_king = board.piece_at(chess.F6)
pawn_defended_by_rook = board.piece_at(chess.F5)

assert 'R' == board.piece_at(chess.F8).symbol()  # This rook
assert 'k' == board.piece_at(chess.F6).symbol()  # x-rays through this king
assert 'P' == board.piece_at(chess.F5).symbol()  # thereby defending this pawn

f5_pawn_defenders = list(board.attackers(winning_color, chess.F5))
num_f5_pawn_defenders = len(f5_pawn_defenders)  # Debugger shows this value is 0
assert 1 == num_f5_pawn_defenders
SylviaStout commented 2 months ago

Looking into this a little more, using the excellent lichess puzzle database, I found that of about 997,000 puzzles that end in mate, about 283,000 (28%) hit this issue, but when the x-rayed square is empty. Like a back-rank mate where the king is on g8, and the mating R is on e8, and e8R x-rays right through g8K to prevent K from fleeing onto empty h8. 28% isn't any kind of majority. Non-trivial minority. It's about 6,400 puzzles that hit this issue when the square being x-rayed is occupied by a win-side piece. Less than 1%, not a lot.

niklasf commented 1 month ago

The behavior of .attackers() is intended as is, but detecting X-ray attacks seems very useful indeed. With 63aac2ec65860a64256a190220b793e80b132b50 it is now possible via:

board.attackers(winning_color, chess.F5, board.occupied ^ board.pieces_mask(chess.KING, board.turn))

With your second comment, do you mean there's a bug in the Lichess puzzle generator missing or misclassifying some positions due to this, or just that this is a common pattern?

SylviaStout commented 1 month ago

...do you mean there's a bug in the Lichess puzzle generator missing or misclassifying some positions... No, I didn't mean to suggest anything about any lichess bug. I was just surprised to see a data point where these x-rays happened 28 times more frequently with empty x-rayed squares than with occupied x-rayed squares. Thanks very much for the fix :-)