bhlangonijr / chesslib

chess library for legal move generation, FEN/PGN parsing and more
Apache License 2.0
229 stars 80 forks source link

Some methods for game end still missing #41

Closed dlbbld closed 3 years ago

dlbbld commented 4 years ago

There are a few game end relevant methods not yet implemented. I suggest adding them to improve the API further.

The methods are mentioned below, taken from the python chess API:

has_insufficient_material(color: chess.Color)

is_seventyfive_moves() is_fivefold_repetition()

can_claim_draw() can_claim_fifty_moves() can_claim_threefold_repetition()

bhlangonijr commented 4 years ago

Aren't these 3 methods:

can_claim_draw()
can_claim_fifty_moves()
can_claim_threefold_repetition()

equivalent to:

Board#isDraw()
Board#getHalfMoveCounter() >= 100 // yes, we can give it a more comprehensive API name
Board#isInsufficientMaterial()

Aren't these equivalent?

PS: chesslib also has isStaleMate()

dlbbld commented 4 years ago

No, can_claim_fifty_moves() for the player having the move means the following: Either with the last halfmove of the opponent 100 or more consecutive half moves without capture or pawn advance have already happened. Or there are 99 such halfmoves, and the player itself has a legal move, which will result in 100 such halfmoves. Such a continuation is of course not always possible, e.g. the player may be forced to make a capture to get out of check or to make a pawn move for Zugzwang.

In the second case, in OTB games, when continuation from 99 to 100 is possible, the player can claim the draw by announcing the intention to perform such a move, without performing this move. The need to have the method comes from this rule.

can_claim_threefold_repetition() is likewise for threefold repetition rule. When you have a move which leads to a threefold repetition, you can claim it without performing the move. Of course, if there are already three or more repetitions on the board, you can also claim the draw. When there are five or more repetitions on the board, the game should have already been drawn by the arbiter, by the fivefold repetition rule. Likewise for seventy-five halfmoves without capture or pawn move by the seventy-five-move rule.

can_claim_draw() is just having one of the cases above, can_claim_fifty_moves() or can_claim_threefold_repetition().

Of course, python chess also has the stalemate (is_stalemate()).

Board.isInsufficientMaterial() is fine, both sides have insufficient material. But a method like board.isInsufficientMaterial(Side) is also needed for checking if one side has insufficient material. When the time runs out for a player and the opponent has insufficient material, the result is automatically a draw unless it's a dead position otherwise, where it is also a draw. Otherwise, it's, of course, a win on time by for the opponent.

To find out dead position is a chapter on its own that nobody has mastered to today. So we can skip that happily.

bhlangonijr commented 4 years ago

Gotcha. I will have a crack at these for the next releases.

dlbbld commented 4 years ago

It looks a lot at first glance but is not. The required logic is already there, so is_seventyfive_moves() and is_fivefold_repetition() are one-liners. can_claim_fifty_moves() and can_claim_threefold_repetition() require a loop over the legal moves, using the existing halfmove clock and repetition, so do not require more than a few lines.

bhlangonijr commented 4 years ago

yes I understand the difference now. I am gonna add these.

dlbbld commented 3 years ago

What is the status of these methods? Are you still planning to add them?

I want to emphasize that determining the draw on timeout requires the "has_insufficient_material(color: chess.Color" so this method has practical relevance, as the others. If player A runs out of time, but player B has insufficient material according to this method, meaning having no theoretical chance to checkmate, the game is a draw.

That is if you play according to the FIDE rules. I except you are using the FIDE rules, or are you supporting any other rules like USCF? There are differences here.

I put the python code below:

   def is_insufficient_material(self) -> bool:
        """
        Checks if neither side has sufficient winning material
        (:func:`~chess.Board.has_insufficient_material()`).
        """
        return all(self.has_insufficient_material(color) for color in COLORS)

    def has_insufficient_material(self, color: Color) -> bool:
        """
        Checks if *color* has insufficient winning material.

        This is guaranteed to return ``False`` if *color* can still win the
        game.

        The converse does not necessarily hold:
        The implementation only looks at the material, including the colors
        of bishops, but not considering piece positions. So fortress
        positions or positions with forced lines may return ``False``, even
        though there is no possible winning line.
        """
        if self.occupied_co[color] & (self.pawns | self.rooks | self.queens):
            return False

        # Knights are only insufficient material if:
        # (1) We do not have any other pieces, including more than one knight.
        # (2) The opponent does not have pawns, knights, bishops or rooks.
        #     These would allow selfmate.
        if self.occupied_co[color] & self.knights:
            return (popcount(self.occupied_co[color]) <= 2 and
                    not (self.occupied_co[not color] & ~self.kings & ~self.queens))

        # Bishops are only insufficient material if:
        # (1) We do not have any other pieces, including bishops of the
        #     opposite color.
        # (2) The opponent does not have bishops of the opposite color,
        #     pawns or knights. These would allow selfmate.
        if self.occupied_co[color] & self.bishops:
            same_color = (not self.bishops & BB_DARK_SQUARES) or (not self.bishops & BB_LIGHT_SQUARES)
            return same_color and not self.pawns and not self.knights

        return True
dlbbld commented 3 years ago

Closing the issue as it became stale.