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.44k stars 531 forks source link

"has_*side_castling_rights" behaves strange (in my eyes) #55

Closed LocutusOfPenguin closed 9 years ago

LocutusOfPenguin commented 9 years ago

Hello. Maybe i understand this 2 functions wrong. But for me they should tell me future possibilities to castle.

If i use the NON-chess960 mode, i get wrong answers. Seems the function only checks for the rook&king spaces in correct direction (that only helps me on chess960 mode).

Here is some code: fen_game = '1r2k3/8/8/8/8/8/8/R3KR2 w KQkq - 0 1' board = chess.Board(fen_game, False) print(board.has_queenside_castling_rights(chess.WHITE)) print(board.has_queenside_castling_rights(chess.BLACK)) print(board.has_kingside_castling_rights(chess.WHITE)) print(board.has_kingside_castling_rights(chess.BLACK)) print('') board = chess.Board(fen_game, True) print(board.has_queenside_castling_rights(chess.WHITE)) print(board.has_queenside_castling_rights(chess.BLACK)) print(board.has_kingside_castling_rights(chess.WHITE)) print(board.has_kingside_castling_rights(chess.BLACK))

The first part should display: T,F,F,F but i get T,T,T,F - for example, as black i can never play 0-0-0 nor as white 0-0. Similar problems with a wK on d1 for example.

niklasf commented 9 years ago

Hi. Your understanding of the functions is correct. They indicate future possibilities of castling. The real problem here is that the FEN is invalid in the first place.

>>> board = chess.Board("1r2k3/8/8/8/8/8/8/R3KR2 w KQkq - 0 1")
>>> board.is_valid()
False
>>> board.status() == chess.STATUS_BAD_CASTLING_RIGHTS
True

The FEN is also not a strictly valid Chess960 position, as black no longer has a kingside rook and the queenside rook files do not match. However validation is not so strict, because the move generator can handle it.

>>> board = chess.Board("1r2k3/8/8/8/8/8/8/R3KR2 w KQkq - 0 1", chess960=True)
>>> board.is_valid()
True

Suggestions to make this easier to work with?

LocutusOfPenguin commented 9 years ago

I'm fully aware the fen is invalid. I just wanted to limit the code-snippet :-)

Easy way: if board in normal mode, (also) check the K=e-file, R=a-file, R=h-file stuff. This way, i can just call this functions no matter if chess960 or not. Right now, its an own function i'm not happy with (not from me) ;-)

Seems you not take into account the "KQkq" part..which is GOOD (for us). All this is needed for SETUP-position, to startup with a valid castling string (assume none piece moved before). see dgtdisplay.py > complete_dgt_fen() function.

LocutusOfPenguin commented 9 years ago

update: on MY branch, i updated the complete_dgt_fen function. Its working now (but with an ugly "if") It would be alot better if this is done in your lib (esp. since i think its a bug).

niklasf commented 9 years ago

Fixed in v0.12.1. The methods now behave as expected.

Additionally everything is more robust when handling invalid castling rights. This means you don't even have to check for invalid castling rights, if you don't want to (but you can).

>>> board = chess.Board("1r2k3/8/8/8/8/8/8/R3KR2 w KQkq - 0 1")
>>> board.is_valid()
False
>>> board.status() == chess.STATUS_BAD_CASTLING_RIGHTS
True
>>> board.fen()
'1r2k3/8/8/8/8/8/8/R3KR2 w Q - 0 1'
                           ^
                           |
                  Automatically fixed.
LocutusOfPenguin commented 9 years ago

great...thanks!

LocutusOfPenguin commented 9 years ago

OK, something else. There is still a difference in behaviour. I understand as soon the program needs to manipulate castling fen part, it sets 256 error but continue. That doesn't happen on chess960 mode (see code below).

>>> import chess
>>> chess.__version__
'0.12.1'
>>> board = chess.Board("1r2k3/8/1p6/8/8/5P2/8/1R2KR2 w KQkq - 0 1", chess960=False)
>>> board.status()
256
>>> board.fen()
'1r2k3/8/1p6/8/8/5P2/8/1R2KR2 w - - 0 1'
>>> board = chess.Board("1r2k3/8/1p6/8/8/5P2/8/1R2KR2 w KQkq - 0 1", chess960=True)
>>> board.status()
0
>>> board.fen()
'1r2k3/8/1p6/8/8/5P2/8/1R2KR2 w KQq - 0 1'
niklasf commented 9 years ago

Almost, but not quite. That's why it was possible to lose information before status() was called. Fixed in another bugfix release v0.12.3.