Advertisement
max2201111

__init__.py Z3

Jul 4th, 2024
996
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 148.55 KB | Science | 0 0
  1. # This file is part of the python-chess library.
  2. # Copyright (C) 2012-2021 Niklas Fiekas <niklas.fiekas@backscattering.de>
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16.  
  17. """
  18. A chess library with move generation and validation,
  19. Polyglot opening book probing, PGN reading and writing,
  20. Gaviota tablebase probing,
  21. Syzygy tablebase probing, and XBoard/UCI engine communication.
  22. """
  23.  
  24. from __future__ import annotations
  25.  
  26. __author__ = "Niklas Fiekas"
  27.  
  28. __email__ = "niklas.fiekas@backscattering.de"
  29.  
  30. __version__ = "1.10.0"
  31.  
  32. import collections
  33. import copy
  34. import dataclasses
  35. import enum
  36. import math
  37. import re
  38. import itertools
  39. import typing
  40.  
  41. from typing import ClassVar, Callable, Counter, Dict, Generic, Hashable, Iterable, Iterator, List, Mapping, Optional, SupportsInt, Tuple, Type, TypeVar, Union
  42.  
  43. try:
  44.     from typing import Literal
  45.     _EnPassantSpec = Literal["legal", "fen", "xfen"]
  46. except ImportError:
  47.     # Before Python 3.8.
  48.     _EnPassantSpec = str  # type: ignore
  49.  
  50.  
  51. Color = bool
  52. COLORS = [WHITE, BLACK] = [True, False]
  53. COLOR_NAMES = ["black", "white"]
  54.  
  55. PieceType = int
  56. PIECE_TYPES = [PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, AMAZON, CYRYL, EVE] = range(1, 10)
  57. PIECE_SYMBOLS = [None, "p", "n", "b", "r", "q", "k", "a", "c", "e"]
  58. PIECE_NAMES = [None, "pawn", "knight", "bishop", "rook", "queen", "king", "amazon", "cyryl", "eve"]
  59.  
  60. def piece_symbol(piece_type: PieceType) -> str:
  61.     return typing.cast(str, PIECE_SYMBOLS[piece_type])
  62.  
  63. def piece_name(piece_type: PieceType) -> str:
  64.     return typing.cast(str, PIECE_NAMES[piece_type])
  65.  
  66. UNICODE_PIECE_SYMBOLS = {
  67.     "R": "♖", "r": "♜",
  68.     "N": "♘", "n": "♞",
  69.     "B": "♗", "b": "♝",
  70.     "Q": "♕", "q": "♛",
  71.     "K": "♔", "k": "♚",
  72.     "P": "♙", "p": "♟",
  73. }
  74.  
  75. FILE_NAMES = ["a", "b", "c", "d", "e", "f", "g", "h"]
  76.  
  77. RANK_NAMES = ["1", "2", "3", "4", "5", "6", "7", "8"]
  78.  
  79. STARTING_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
  80. """The FEN for the standard chess starting position."""
  81.  
  82. STARTING_BOARD_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
  83. """The board part of the FEN for the standard chess starting position."""
  84.  
  85.  
  86. class Status(enum.IntFlag):
  87.     VALID = 0
  88.     NO_WHITE_KING = 1 << 0
  89.     NO_BLACK_KING = 1 << 1
  90.     TOO_MANY_KINGS = 1 << 2
  91.     TOO_MANY_WHITE_PAWNS = 1 << 3
  92.     TOO_MANY_BLACK_PAWNS = 1 << 4
  93.     PAWNS_ON_BACKRANK = 1 << 5
  94.     TOO_MANY_WHITE_PIECES = 1 << 6
  95.     TOO_MANY_BLACK_PIECES = 1 << 7
  96.     BAD_CASTLING_RIGHTS = 1 << 8
  97.     INVALID_EP_SQUARE = 1 << 9
  98.     OPPOSITE_CHECK = 1 << 10
  99.     EMPTY = 1 << 11
  100.     RACE_CHECK = 1 << 12
  101.     RACE_OVER = 1 << 13
  102.     RACE_MATERIAL = 1 << 14
  103.     TOO_MANY_CHECKERS = 1 << 15
  104.     IMPOSSIBLE_CHECK = 1 << 16
  105.  
  106. STATUS_VALID = Status.VALID
  107. STATUS_NO_WHITE_KING = Status.NO_WHITE_KING
  108. STATUS_NO_BLACK_KING = Status.NO_BLACK_KING
  109. STATUS_TOO_MANY_KINGS = Status.TOO_MANY_KINGS
  110. STATUS_TOO_MANY_WHITE_PAWNS = Status.TOO_MANY_WHITE_PAWNS
  111. STATUS_TOO_MANY_BLACK_PAWNS = Status.TOO_MANY_BLACK_PAWNS
  112. STATUS_PAWNS_ON_BACKRANK = Status.PAWNS_ON_BACKRANK
  113. STATUS_TOO_MANY_WHITE_PIECES = Status.TOO_MANY_WHITE_PIECES
  114. STATUS_TOO_MANY_BLACK_PIECES = Status.TOO_MANY_BLACK_PIECES
  115. STATUS_BAD_CASTLING_RIGHTS = Status.BAD_CASTLING_RIGHTS
  116. STATUS_INVALID_EP_SQUARE = Status.INVALID_EP_SQUARE
  117. STATUS_OPPOSITE_CHECK = Status.OPPOSITE_CHECK
  118. STATUS_EMPTY = Status.EMPTY
  119. STATUS_RACE_CHECK = Status.RACE_CHECK
  120. STATUS_RACE_OVER = Status.RACE_OVER
  121. STATUS_RACE_MATERIAL = Status.RACE_MATERIAL
  122. STATUS_TOO_MANY_CHECKERS = Status.TOO_MANY_CHECKERS
  123. STATUS_IMPOSSIBLE_CHECK = Status.IMPOSSIBLE_CHECK
  124.  
  125.  
  126. class Termination(enum.Enum):
  127.     """Enum with reasons for a game to be over."""
  128.  
  129.     CHECKMATE = enum.auto()
  130.     """See :func:`chess.Board.is_checkmate()`."""
  131.     STALEMATE = enum.auto()
  132.     """See :func:`chess.Board.is_stalemate()`."""
  133.     INSUFFICIENT_MATERIAL = enum.auto()
  134.     """See :func:`chess.Board.is_insufficient_material()`."""
  135.     SEVENTYFIVE_MOVES = enum.auto()
  136.     """See :func:`chess.Board.is_seventyfive_moves()`."""
  137.     FIVEFOLD_REPETITION = enum.auto()
  138.     """See :func:`chess.Board.is_fivefold_repetition()`."""
  139.     FIFTY_MOVES = enum.auto()
  140.     """See :func:`chess.Board.can_claim_fifty_moves()`."""
  141.     THREEFOLD_REPETITION = enum.auto()
  142.     """See :func:`chess.Board.can_claim_threefold_repetition()`."""
  143.     VARIANT_WIN = enum.auto()
  144.     """See :func:`chess.Board.is_variant_win()`."""
  145.     VARIANT_LOSS = enum.auto()
  146.     """See :func:`chess.Board.is_variant_loss()`."""
  147.     VARIANT_DRAW = enum.auto()
  148.     """See :func:`chess.Board.is_variant_draw()`."""
  149.  
  150. @dataclasses.dataclass
  151. class Outcome:
  152.     """
  153.    Information about the outcome of an ended game, usually obtained from
  154.    :func:`chess.Board.outcome()`.
  155.    """
  156.  
  157.     termination: Termination
  158.     """The reason for the game to have ended."""
  159.  
  160.     winner: Optional[Color]
  161.     """The winning color or ``None`` if drawn."""
  162.  
  163.     def result(self) -> str:
  164.         """Returns ``1-0``, ``0-1`` or ``1/2-1/2``."""
  165.         return "1/2-1/2" if self.winner is None else ("1-0" if self.winner else "0-1")
  166.  
  167.  
  168. class InvalidMoveError(ValueError):
  169.     """Raised when move notation is not syntactically valid"""
  170.  
  171.  
  172. class IllegalMoveError(ValueError):
  173.     """Raised when the attempted move is illegal in the current position"""
  174.  
  175.  
  176. class AmbiguousMoveError(ValueError):
  177.     """Raised when the attempted move is ambiguous in the current position"""
  178.  
  179.  
  180. Square = int
  181. SQUARES = [
  182.     A1, B1, C1, D1, E1, F1, G1, H1,
  183.     A2, B2, C2, D2, E2, F2, G2, H2,
  184.     A3, B3, C3, D3, E3, F3, G3, H3,
  185.     A4, B4, C4, D4, E4, F4, G4, H4,
  186.     A5, B5, C5, D5, E5, F5, G5, H5,
  187.     A6, B6, C6, D6, E6, F6, G6, H6,
  188.     A7, B7, C7, D7, E7, F7, G7, H7,
  189.     A8, B8, C8, D8, E8, F8, G8, H8,
  190. ] = range(64)
  191.  
  192. SQUARE_NAMES = [f + r for r in RANK_NAMES for f in FILE_NAMES]
  193.  
  194. def parse_square(name: str) -> Square:
  195.     """
  196.    Gets the square index for the given square *name*
  197.    (e.g., ``a1`` returns ``0``).
  198.  
  199.    :raises: :exc:`ValueError` if the square name is invalid.
  200.    """
  201.     return SQUARE_NAMES.index(name)
  202.  
  203. def square_name(square: Square) -> str:
  204.     """Gets the name of the square, like ``a3``."""
  205.     return SQUARE_NAMES[square]
  206.  
  207. def square(file_index: int, rank_index: int) -> Square:
  208.     """Gets a square number by file and rank index."""
  209.     return rank_index * 8 + file_index
  210.  
  211. def square_file(square: Square) -> int:
  212.     """Gets the file index of the square where ``0`` is the a-file."""
  213.     return square & 7
  214.  
  215. def square_rank(square: Square) -> int:
  216.     """Gets the rank index of the square where ``0`` is the first rank."""
  217.     return square >> 3
  218.  
  219. def square_distance(a: Square, b: Square) -> int:
  220.     """
  221.    Gets the Chebyshev distance (i.e., the number of king steps) from square *a* to *b*.
  222.    """
  223.     return max(abs(square_file(a) - square_file(b)), abs(square_rank(a) - square_rank(b)))
  224.  
  225. def square_manhattan_distance(a: Square, b: Square) -> int:
  226.     """
  227.    Gets the Manhattan/Taxicab distance (i.e., the number of orthogonal king steps) from square *a* to *b*.
  228.    """
  229.     return abs(square_file(a) - square_file(b)) + abs(square_rank(a) - square_rank(b))
  230.  
  231. def square_knight_distance(a: Square, b: Square) -> int:
  232.     """
  233.    Gets the Knight distance (i.e., the number of knight moves) from square *a* to *b*.
  234.    """
  235.     dx = abs(square_file(a) - square_file(b))
  236.     dy = abs(square_rank(a) - square_rank(b))
  237.  
  238.     if dx + dy == 1:
  239.         return 3
  240.     elif dx == dy == 2:
  241.         return 4
  242.     elif dx == dy == 1:
  243.         if BB_SQUARES[a] & BB_CORNERS or BB_SQUARES[b] & BB_CORNERS: # Special case only for corner squares
  244.             return 4
  245.  
  246.     m = math.ceil(max(dx / 2, dy / 2, (dx + dy) / 3))
  247.     return m + ((m + dx + dy) % 2)
  248.  
  249. def square_mirror(square: Square) -> Square:
  250.     """Mirrors the square vertically."""
  251.     return square ^ 0x38
  252.  
  253. SQUARES_180 = [square_mirror(sq) for sq in SQUARES]
  254.  
  255.  
  256. Bitboard = int
  257. BB_EMPTY = 0
  258. BB_ALL = 0xffff_ffff_ffff_ffff
  259.  
  260. BB_SQUARES = [
  261.     BB_A1, BB_B1, BB_C1, BB_D1, BB_E1, BB_F1, BB_G1, BB_H1,
  262.     BB_A2, BB_B2, BB_C2, BB_D2, BB_E2, BB_F2, BB_G2, BB_H2,
  263.     BB_A3, BB_B3, BB_C3, BB_D3, BB_E3, BB_F3, BB_G3, BB_H3,
  264.     BB_A4, BB_B4, BB_C4, BB_D4, BB_E4, BB_F4, BB_G4, BB_H4,
  265.     BB_A5, BB_B5, BB_C5, BB_D5, BB_E5, BB_F5, BB_G5, BB_H5,
  266.     BB_A6, BB_B6, BB_C6, BB_D6, BB_E6, BB_F6, BB_G6, BB_H6,
  267.     BB_A7, BB_B7, BB_C7, BB_D7, BB_E7, BB_F7, BB_G7, BB_H7,
  268.     BB_A8, BB_B8, BB_C8, BB_D8, BB_E8, BB_F8, BB_G8, BB_H8,
  269. ] = [1 << sq for sq in SQUARES]
  270.  
  271. BB_CORNERS = BB_A1 | BB_H1 | BB_A8 | BB_H8
  272. BB_CENTER = BB_D4 | BB_E4 | BB_D5 | BB_E5
  273.  
  274. BB_LIGHT_SQUARES = 0x55aa_55aa_55aa_55aa
  275. BB_DARK_SQUARES = 0xaa55_aa55_aa55_aa55
  276.  
  277. BB_FILES = [
  278.     BB_FILE_A,
  279.     BB_FILE_B,
  280.     BB_FILE_C,
  281.     BB_FILE_D,
  282.     BB_FILE_E,
  283.     BB_FILE_F,
  284.     BB_FILE_G,
  285.     BB_FILE_H,
  286. ] = [0x0101_0101_0101_0101 << i for i in range(8)]
  287.  
  288. BB_RANKS = [
  289.     BB_RANK_1,
  290.     BB_RANK_2,
  291.     BB_RANK_3,
  292.     BB_RANK_4,
  293.     BB_RANK_5,
  294.     BB_RANK_6,
  295.     BB_RANK_7,
  296.     BB_RANK_8,
  297. ] = [0xff << (8 * i) for i in range(8)]
  298.  
  299. BB_BACKRANKS = BB_RANK_1 | BB_RANK_8
  300.  
  301.  
  302. def lsb(bb: Bitboard) -> int:
  303.     return (bb & -bb).bit_length() - 1
  304.  
  305. def scan_forward(bb: Bitboard) -> Iterator[Square]:
  306.     while bb:
  307.         r = bb & -bb
  308.         yield r.bit_length() - 1
  309.         bb ^= r
  310.  
  311. def msb(bb: Bitboard) -> int:
  312.     return bb.bit_length() - 1
  313.  
  314. def scan_reversed(bb: Bitboard) -> Iterator[Square]:
  315.     while bb:
  316.         r = bb.bit_length() - 1
  317.         yield r
  318.         bb ^= BB_SQUARES[r]
  319.  
  320. # Python 3.10 or fallback.
  321. popcount: Callable[[Bitboard], int] = getattr(int, "bit_count", lambda bb: bin(bb).count("1"))
  322.  
  323. def flip_vertical(bb: Bitboard) -> Bitboard:
  324.     # https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating#FlipVertically
  325.     bb = ((bb >> 8) & 0x00ff_00ff_00ff_00ff) | ((bb & 0x00ff_00ff_00ff_00ff) << 8)
  326.     bb = ((bb >> 16) & 0x0000_ffff_0000_ffff) | ((bb & 0x0000_ffff_0000_ffff) << 16)
  327.     bb = (bb >> 32) | ((bb & 0x0000_0000_ffff_ffff) << 32)
  328.     return bb
  329.  
  330. def flip_horizontal(bb: Bitboard) -> Bitboard:
  331.     # https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating#MirrorHorizontally
  332.     bb = ((bb >> 1) & 0x5555_5555_5555_5555) | ((bb & 0x5555_5555_5555_5555) << 1)
  333.     bb = ((bb >> 2) & 0x3333_3333_3333_3333) | ((bb & 0x3333_3333_3333_3333) << 2)
  334.     bb = ((bb >> 4) & 0x0f0f_0f0f_0f0f_0f0f) | ((bb & 0x0f0f_0f0f_0f0f_0f0f) << 4)
  335.     return bb
  336.  
  337. def flip_diagonal(bb: Bitboard) -> Bitboard:
  338.     # https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating#FlipabouttheDiagonal
  339.     t = (bb ^ (bb << 28)) & 0x0f0f_0f0f_0000_0000
  340.     bb = bb ^ t ^ (t >> 28)
  341.     t = (bb ^ (bb << 14)) & 0x3333_0000_3333_0000
  342.     bb = bb ^ t ^ (t >> 14)
  343.     t = (bb ^ (bb << 7)) & 0x5500_5500_5500_5500
  344.     bb = bb ^ t ^ (t >> 7)
  345.     return bb
  346.  
  347. def flip_anti_diagonal(bb: Bitboard) -> Bitboard:
  348.     # https://www.chessprogramming.org/Flipping_Mirroring_and_Rotating#FlipabouttheAntidiagonal
  349.     t = bb ^ (bb << 36)
  350.     bb = bb ^ ((t ^ (bb >> 36)) & 0xf0f0_f0f0_0f0f_0f0f)
  351.     t = (bb ^ (bb << 18)) & 0xcccc_0000_cccc_0000
  352.     bb = bb ^ t ^ (t >> 18)
  353.     t = (bb ^ (bb << 9)) & 0xaa00_aa00_aa00_aa00
  354.     bb = bb ^ t ^ (t >> 9)
  355.     return bb
  356.  
  357.  
  358. def shift_down(b: Bitboard) -> Bitboard:
  359.     return b >> 8
  360.  
  361. def shift_2_down(b: Bitboard) -> Bitboard:
  362.     return b >> 16
  363.  
  364. def shift_up(b: Bitboard) -> Bitboard:
  365.     return (b << 8) & BB_ALL
  366.  
  367. def shift_2_up(b: Bitboard) -> Bitboard:
  368.     return (b << 16) & BB_ALL
  369.  
  370. def shift_right(b: Bitboard) -> Bitboard:
  371.     return (b << 1) & ~BB_FILE_A & BB_ALL
  372.  
  373. def shift_2_right(b: Bitboard) -> Bitboard:
  374.     return (b << 2) & ~BB_FILE_A & ~BB_FILE_B & BB_ALL
  375.  
  376. def shift_left(b: Bitboard) -> Bitboard:
  377.     return (b >> 1) & ~BB_FILE_H
  378.  
  379. def shift_2_left(b: Bitboard) -> Bitboard:
  380.     return (b >> 2) & ~BB_FILE_G & ~BB_FILE_H
  381.  
  382. def shift_up_left(b: Bitboard) -> Bitboard:
  383.     return (b << 7) & ~BB_FILE_H & BB_ALL
  384.  
  385. def shift_up_right(b: Bitboard) -> Bitboard:
  386.     return (b << 9) & ~BB_FILE_A & BB_ALL
  387.  
  388. def shift_down_left(b: Bitboard) -> Bitboard:
  389.     return (b >> 9) & ~BB_FILE_H
  390.  
  391. def shift_down_right(b: Bitboard) -> Bitboard:
  392.     return (b >> 7) & ~BB_FILE_A
  393.  
  394.  
  395. def _sliding_attacks(square: Square, occupied: Bitboard, deltas: Iterable[int]) -> Bitboard:
  396.     attacks = BB_EMPTY
  397.  
  398.     for delta in deltas:
  399.         sq = square
  400.  
  401.         while True:
  402.             sq += delta
  403.             if not (0 <= sq < 64) or square_distance(sq, sq - delta) > 2:
  404.                 break
  405.  
  406.             attacks |= BB_SQUARES[sq]
  407.  
  408.             if occupied & BB_SQUARES[sq]:
  409.                 break
  410.  
  411.     return attacks
  412.  
  413. def _step_attacks(square: Square, deltas: Iterable[int]) -> Bitboard:
  414.     return _sliding_attacks(square, BB_ALL, deltas)
  415.  
  416. BB_KNIGHT_ATTACKS = [_step_attacks(sq, [17, 15, 10, 6, -17, -15, -10, -6]) for sq in SQUARES]
  417. BB_KING_ATTACKS = [_step_attacks(sq, [9, 8, 7, 1, -9, -8, -7, -1]) for sq in SQUARES]
  418. BB_PAWN_ATTACKS = [[_step_attacks(sq, deltas) for sq in SQUARES] for deltas in [[-7, -9], [7, 9]]]
  419.  
  420.  
  421. def _edges(square: Square) -> Bitboard:
  422.     return (((BB_RANK_1 | BB_RANK_8) & ~BB_RANKS[square_rank(square)]) |
  423.             ((BB_FILE_A | BB_FILE_H) & ~BB_FILES[square_file(square)]))
  424.  
  425. def _carry_rippler(mask: Bitboard) -> Iterator[Bitboard]:
  426.     # Carry-Rippler trick to iterate subsets of mask.
  427.     subset = BB_EMPTY
  428.     while True:
  429.         yield subset
  430.         subset = (subset - mask) & mask
  431.         if not subset:
  432.             break
  433.  
  434. def _attack_table(deltas: List[int]) -> Tuple[List[Bitboard], List[Dict[Bitboard, Bitboard]]]:
  435.     mask_table = []
  436.     attack_table = []
  437.  
  438.     for square in SQUARES:
  439.         attacks = {}
  440.  
  441.         mask = _sliding_attacks(square, 0, deltas) & ~_edges(square)
  442.         for subset in _carry_rippler(mask):
  443.             attacks[subset] = _sliding_attacks(square, subset, deltas)
  444.  
  445.         attack_table.append(attacks)
  446.         mask_table.append(mask)
  447.  
  448.     return mask_table, attack_table
  449.  
  450. BB_DIAG_MASKS, BB_DIAG_ATTACKS = _attack_table([-9, -7, 7, 9])
  451. BB_FILE_MASKS, BB_FILE_ATTACKS = _attack_table([-8, 8])
  452. BB_RANK_MASKS, BB_RANK_ATTACKS = _attack_table([-1, 1])
  453.  
  454.  
  455. def _rays() -> List[List[Bitboard]]:
  456.     rays = []
  457.     for a, bb_a in enumerate(BB_SQUARES):
  458.         rays_row = []
  459.         for b, bb_b in enumerate(BB_SQUARES):
  460.             if BB_DIAG_ATTACKS[a][0] & bb_b:
  461.                 rays_row.append((BB_DIAG_ATTACKS[a][0] & BB_DIAG_ATTACKS[b][0]) | bb_a | bb_b)
  462.             elif BB_RANK_ATTACKS[a][0] & bb_b:
  463.                 rays_row.append(BB_RANK_ATTACKS[a][0] | bb_a)
  464.             elif BB_FILE_ATTACKS[a][0] & bb_b:
  465.                 rays_row.append(BB_FILE_ATTACKS[a][0] | bb_a)
  466.             else:
  467.                 rays_row.append(BB_EMPTY)
  468.         rays.append(rays_row)
  469.     return rays
  470.  
  471. BB_RAYS = _rays()
  472.  
  473. def ray(a: Square, b: Square) -> Bitboard:
  474.     return BB_RAYS[a][b]
  475.  
  476. def between(a: Square, b: Square) -> Bitboard:
  477.     bb = BB_RAYS[a][b] & ((BB_ALL << a) ^ (BB_ALL << b))
  478.     return bb & (bb - 1)
  479.  
  480.  
  481. SAN_REGEX = re.compile(r"^([NBKRQ])?([a-h])?([1-8])?[\-x]?([a-h][1-8])(=?[nbrqkNBRQK])?[\+#]?\Z")
  482.  
  483. FEN_CASTLING_REGEX = re.compile(r"^(?:-|[KQABCDEFGH]{0,2}[kqabcdefgh]{0,2})\Z")
  484.  
  485.  
  486. @dataclasses.dataclass
  487. class Piece:
  488.     """A piece with type and color."""
  489.  
  490.     piece_type: PieceType
  491.     """The piece type."""
  492.  
  493.     color: Color
  494.     """The piece color."""
  495.  
  496.     def symbol(self) -> str:
  497.         """
  498.        Gets the symbol ``P``, ``N``, ``B``, ``R``, ``Q`` or ``K`` for white
  499.        pieces or the lower-case variants for the black pieces.
  500.        """
  501.         symbol = piece_symbol(self.piece_type)
  502.         return symbol.upper() if self.color else symbol
  503.  
  504.     def unicode_symbol(self, *, invert_color: bool = False) -> str:
  505.         """
  506.        Gets the Unicode character for the piece.
  507.        """
  508.         symbol = self.symbol().swapcase() if invert_color else self.symbol()
  509.         return UNICODE_PIECE_SYMBOLS[symbol]
  510.  
  511.     def __hash__(self) -> int:
  512.         return self.piece_type + (-1 if self.color else 5)
  513.  
  514.     def __repr__(self) -> str:
  515.         return f"Piece.from_symbol({self.symbol()!r})"
  516.  
  517.     def __str__(self) -> str:
  518.         return self.symbol()
  519.  
  520.     def _repr_svg_(self) -> str:
  521.         import chess.svg
  522.         return chess.svg.piece(self, size=45)
  523.  
  524.     @classmethod
  525.     def from_symbol(cls, symbol: str) -> Piece:
  526.         """
  527.        Creates a :class:`~chess.Piece` instance from a piece symbol.
  528.  
  529.        :raises: :exc:`ValueError` if the symbol is invalid.
  530.        """
  531.         return cls(PIECE_SYMBOLS.index(symbol.lower()), symbol.isupper())
  532.  
  533.  
  534. @dataclasses.dataclass(unsafe_hash=True)
  535. class Move:
  536.     """
  537.    Represents a move from a square to a square and possibly the promotion
  538.    piece type.
  539.  
  540.    Drops and null moves are supported.
  541.    """
  542.  
  543.     from_square: Square
  544.     """The source square."""
  545.  
  546.     to_square: Square
  547.     """The target square."""
  548.  
  549.     promotion: Optional[PieceType] = None
  550.     """The promotion piece type or ``None``."""
  551.  
  552.     drop: Optional[PieceType] = None
  553.     """The drop piece type or ``None``."""
  554.  
  555.     def uci(self) -> str:
  556.         """
  557.        Gets a UCI string for the move.
  558.  
  559.        For example, a move from a7 to a8 would be ``a7a8`` or ``a7a8q``
  560.        (if the latter is a promotion to a queen).
  561.  
  562.        The UCI representation of a null move is ``0000``.
  563.        """
  564.         if self.drop:
  565.             return piece_symbol(self.drop).upper() + "@" + SQUARE_NAMES[self.to_square]
  566.         elif self.promotion:
  567.             return SQUARE_NAMES[self.from_square] + SQUARE_NAMES[self.to_square] + piece_symbol(self.promotion)
  568.         elif self:
  569.             return SQUARE_NAMES[self.from_square] + SQUARE_NAMES[self.to_square]
  570.         else:
  571.             return "0000"
  572.  
  573.     def xboard(self) -> str:
  574.         return self.uci() if self else "@@@@"
  575.  
  576.     def __bool__(self) -> bool:
  577.         return bool(self.from_square or self.to_square or self.promotion or self.drop)
  578.  
  579.     def __repr__(self) -> str:
  580.         return f"Move.from_uci({self.uci()!r})"
  581.  
  582.     def __str__(self) -> str:
  583.         return self.uci()
  584.  
  585.     @classmethod
  586.     def from_uci(cls, uci: str) -> Move:
  587.         """
  588.        Parses a UCI string.
  589.  
  590.        :raises: :exc:`InvalidMoveError` if the UCI string is invalid.
  591.        """
  592.         if uci == "0000":
  593.             return cls.null()
  594.         elif len(uci) == 4 and "@" == uci[1]:
  595.             try:
  596.                 drop = PIECE_SYMBOLS.index(uci[0].lower())
  597.                 square = SQUARE_NAMES.index(uci[2:])
  598.             except ValueError:
  599.                 raise InvalidMoveError(f"invalid uci: {uci!r}")
  600.             return cls(square, square, drop=drop)
  601.         elif 4 <= len(uci) <= 5:
  602.             try:
  603.                 from_square = SQUARE_NAMES.index(uci[0:2])
  604.                 to_square = SQUARE_NAMES.index(uci[2:4])
  605.                 promotion = PIECE_SYMBOLS.index(uci[4]) if len(uci) == 5 else None
  606.             except ValueError:
  607.                 raise InvalidMoveError(f"invalid uci: {uci!r}")
  608.             if from_square == to_square:
  609.                 raise InvalidMoveError(f"invalid uci (use 0000 for null moves): {uci!r}")
  610.             return cls(from_square, to_square, promotion=promotion)
  611.         else:
  612.             raise InvalidMoveError(f"expected uci string to be of length 4 or 5: {uci!r}")
  613.  
  614.     @classmethod
  615.     def null(cls) -> Move:
  616.         """
  617.        Gets a null move.
  618.  
  619.        A null move just passes the turn to the other side (and possibly
  620.        forfeits en passant capturing). Null moves evaluate to ``False`` in
  621.        boolean contexts.
  622.  
  623.        >>> import chess
  624.        >>>
  625.        >>> bool(chess.Move.null())
  626.        False
  627.        """
  628.         return cls(0, 0)
  629.  
  630.  
  631. BaseBoardT = TypeVar("BaseBoardT", bound="BaseBoard")
  632.  
  633. class BaseBoard:
  634.     """
  635.    A board representing the position of chess pieces. See
  636.    :class:`~chess.Board` for a full board with move generation.
  637.  
  638.    The board is initialized with the standard chess starting position, unless
  639.    otherwise specified in the optional *board_fen* argument. If *board_fen*
  640.    is ``None``, an empty board is created.
  641.    """
  642.  
  643.     def __init__(self, board_fen: Optional[str] = STARTING_BOARD_FEN) -> None:
  644.         self.occupied_co = [BB_EMPTY, BB_EMPTY]
  645.  
  646.         if board_fen is None:
  647.             self._clear_board()
  648.         elif board_fen == STARTING_BOARD_FEN:
  649.             self._reset_board()
  650.         else:
  651.             self._set_board_fen(board_fen)
  652.  
  653.     def _reset_board(self) -> None:
  654.         self.pawns = BB_RANK_2 | BB_RANK_7
  655.         self.knights = BB_B1 | BB_G1 | BB_B8 | BB_G8
  656.         self.bishops = BB_C1 | BB_F1 | BB_C8 | BB_F8
  657.         self.rooks = BB_CORNERS
  658.         self.queens = BB_D1 | BB_D8
  659.         self.kings = BB_E1 | BB_E8
  660.  
  661.         self.promoted = BB_EMPTY
  662.  
  663.         self.occupied_co[WHITE] = BB_RANK_1 | BB_RANK_2
  664.         self.occupied_co[BLACK] = BB_RANK_7 | BB_RANK_8
  665.         self.occupied = BB_RANK_1 | BB_RANK_2 | BB_RANK_7 | BB_RANK_8
  666.  
  667.     def reset_board(self) -> None:
  668.         """
  669.        Resets pieces to the starting position.
  670.  
  671.        :class:`~chess.Board` also resets the move stack, but not turn,
  672.        castling rights and move counters. Use :func:`chess.Board.reset()` to
  673.        fully restore the starting position.
  674.        """
  675.         self._reset_board()
  676.  
  677.     def _clear_board(self) -> None:
  678.         self.pawns = BB_EMPTY
  679.         self.knights = BB_EMPTY
  680.         self.bishops = BB_EMPTY
  681.         self.rooks = BB_EMPTY
  682.         self.queens = BB_EMPTY
  683.         self.kings = BB_EMPTY
  684.  
  685.         self.promoted = BB_EMPTY
  686.  
  687.         self.occupied_co[WHITE] = BB_EMPTY
  688.         self.occupied_co[BLACK] = BB_EMPTY
  689.         self.occupied = BB_EMPTY
  690.  
  691.     def clear_board(self) -> None:
  692.         """
  693.        Clears the board.
  694.  
  695.        :class:`~chess.Board` also clears the move stack.
  696.        """
  697.         self._clear_board()
  698.  
  699.     def pieces_mask(self, piece_type: PieceType, color: Color) -> Bitboard:
  700.         if piece_type == PAWN:
  701.             bb = self.pawns
  702.         elif piece_type == KNIGHT:
  703.             bb = self.knights
  704.         elif piece_type == BISHOP:
  705.             bb = self.bishops
  706.         elif piece_type == ROOK:
  707.             bb = self.rooks
  708.         elif piece_type == QUEEN:
  709.             bb = self.queens
  710.         elif piece_type == KING:
  711.             bb = self.kings
  712.         else:
  713.             assert False, f"expected PieceType, got {piece_type!r}"
  714.  
  715.         return bb & self.occupied_co[color]
  716.  
  717.     def pieces(self, piece_type: PieceType, color: Color) -> SquareSet:
  718.         """
  719.        Gets pieces of the given type and color.
  720.  
  721.        Returns a :class:`set of squares <chess.SquareSet>`.
  722.        """
  723.         return SquareSet(self.pieces_mask(piece_type, color))
  724.  
  725.     def piece_at(self, square: Square) -> Optional[Piece]:
  726.         """Gets the :class:`piece <chess.Piece>` at the given square."""
  727.         piece_type = self.piece_type_at(square)
  728.         if piece_type:
  729.             mask = BB_SQUARES[square]
  730.             color = bool(self.occupied_co[WHITE] & mask)
  731.             return Piece(piece_type, color)
  732.         else:
  733.             return None
  734.  
  735.     def piece_type_at(self, square: Square) -> Optional[PieceType]:
  736.         """Gets the piece type at the given square."""
  737.         mask = BB_SQUARES[square]
  738.  
  739.         if not self.occupied & mask:
  740.             return None  # Early return
  741.         elif self.pawns & mask:
  742.             return PAWN
  743.         elif self.knights & mask:
  744.             return KNIGHT
  745.         elif self.bishops & mask:
  746.             return BISHOP
  747.         elif self.rooks & mask:
  748.             return ROOK
  749.         elif self.queens & mask:
  750.             return QUEEN
  751.         else:
  752.             return KING
  753.  
  754.     def color_at(self, square: Square) -> Optional[Color]:
  755.         """Gets the color of the piece at the given square."""
  756.         mask = BB_SQUARES[square]
  757.         if self.occupied_co[WHITE] & mask:
  758.             return WHITE
  759.         elif self.occupied_co[BLACK] & mask:
  760.             return BLACK
  761.         else:
  762.             return None
  763.  
  764.     def king(self, color: Color) -> Optional[Square]:
  765.         """
  766.        Finds the king square of the given side. Returns ``None`` if there
  767.        is no king of that color.
  768.  
  769.        In variants with king promotions, only non-promoted kings are
  770.        considered.
  771.        """
  772.         king_mask = self.occupied_co[color] & self.kings & ~self.promoted
  773.         return msb(king_mask) if king_mask else None
  774.  
  775.     def attacks_mask(self, piece_type, square, occupied) -> Bitboard:
  776.         bb_square = BB_SQUARES[square]
  777.  
  778.         if bb_square & self.pawns:
  779.             color = bool(bb_square & self.occupied_co[WHITE])
  780.             return BB_PAWN_ATTACKS[color][square]
  781.         elif bb_square & self.knights:
  782.             return BB_KNIGHT_ATTACKS[square]
  783.         elif bb_square & self.kings:
  784.             return BB_KING_ATTACKS[square]
  785.         else:
  786.             attacks = 0
  787.             if bb_square & self.bishops or bb_square & self.queens:
  788.                 attacks = BB_DIAG_ATTACKS[square][BB_DIAG_MASKS[square] & self.occupied]
  789.             if bb_square & self.rooks or bb_square & self.queens:
  790.                 attacks |= (BB_RANK_ATTACKS[square][BB_RANK_MASKS[square] & self.occupied] |
  791.                             BB_FILE_ATTACKS[square][BB_FILE_MASKS[square] & self.occupied])
  792.             return attacks
  793.  
  794.     def attacks(self, square: Square) -> SquareSet:
  795.         """
  796.        Gets the set of attacked squares from the given square.
  797.  
  798.        There will be no attacks if the square is empty. Pinned pieces are
  799.        still attacking other squares.
  800.  
  801.        Returns a :class:`set of squares <chess.SquareSet>`.
  802.        """
  803.         return SquareSet(self.attacks_mask(square))
  804.  
  805.     def _attackers_mask(self, color: Color, square: Square, occupied: Bitboard) -> Bitboard:
  806.         rank_pieces = BB_RANK_MASKS[square] & occupied
  807.         file_pieces = BB_FILE_MASKS[square] & occupied
  808.         diag_pieces = BB_DIAG_MASKS[square] & occupied
  809.  
  810.         queens_and_rooks = self.queens | self.rooks
  811.         queens_and_bishops = self.queens | self.bishops
  812.  
  813.         attackers = (
  814.             (BB_KING_ATTACKS[square] & self.kings) |
  815.             (BB_KNIGHT_ATTACKS[square] & self.knights) |
  816.             (BB_RANK_ATTACKS[square][rank_pieces] & queens_and_rooks) |
  817.             (BB_FILE_ATTACKS[square][file_pieces] & queens_and_rooks) |
  818.             (BB_DIAG_ATTACKS[square][diag_pieces] & queens_and_bishops) |
  819.             (BB_PAWN_ATTACKS[not color][square] & self.pawns))
  820.  
  821.         return attackers & self.occupied_co[color]
  822.  
  823.     def attackers_mask(self, color: Color, square: Square) -> Bitboard:
  824.         return self._attackers_mask(color, square, self.occupied)
  825.  
  826.     def is_attacked_by(self, color: Color, square: Square) -> bool:
  827.         """
  828.        Checks if the given side attacks the given square.
  829.  
  830.        Pinned pieces still count as attackers. Pawns that can be captured
  831.        en passant are **not** considered attacked.
  832.        """
  833.         return bool(self.attackers_mask(color, square))
  834.  
  835.     def attackers(self, color: Color, square: Square) -> SquareSet:
  836.         """
  837.        Gets the set of attackers of the given color for the given square.
  838.  
  839.        Pinned pieces still count as attackers.
  840.  
  841.        Returns a :class:`set of squares <chess.SquareSet>`.
  842.        """
  843.         return SquareSet(self.attackers_mask(color, square))
  844.  
  845.     def pin_mask(self, color: Color, square: Square) -> Bitboard:
  846.         king = self.king(color)
  847.         if king is None:
  848.             return BB_ALL
  849.  
  850.         square_mask = BB_SQUARES[square]
  851.  
  852.         for attacks, sliders in [(BB_FILE_ATTACKS, self.rooks | self.queens),
  853.                                  (BB_RANK_ATTACKS, self.rooks | self.queens),
  854.                                  (BB_DIAG_ATTACKS, self.bishops | self.queens)]:
  855.             rays = attacks[king][0]
  856.             if rays & square_mask:
  857.                 snipers = rays & sliders & self.occupied_co[not color]
  858.                 for sniper in scan_reversed(snipers):
  859.                     if between(sniper, king) & (self.occupied | square_mask) == square_mask:
  860.                         return ray(king, sniper)
  861.  
  862.                 break
  863.  
  864.         return BB_ALL
  865.  
  866.     def pin(self, color: Color, square: Square) -> SquareSet:
  867.         """
  868.        Detects an absolute pin (and its direction) of the given square to
  869.        the king of the given color.
  870.  
  871.        >>> import chess
  872.        >>>
  873.        >>> board = chess.Board("rnb1k2r/ppp2ppp/5n2/3q4/1b1P4/2N5/PP3PPP/R1BQKBNR w KQkq - 3 7")
  874.        >>> board.is_pinned(chess.WHITE, chess.C3)
  875.        True
  876.        >>> direction = board.pin(chess.WHITE, chess.C3)
  877.        >>> direction
  878.        SquareSet(0x0000_0001_0204_0810)
  879.        >>> print(direction)
  880.        . . . . . . . .
  881.        . . . . . . . .
  882.        . . . . . . . .
  883.        1 . . . . . . .
  884.        . 1 . . . . . .
  885.        . . 1 . . . . .
  886.        . . . 1 . . . .
  887.        . . . . 1 . . .
  888.  
  889.        Returns a :class:`set of squares <chess.SquareSet>` that mask the rank,
  890.        file or diagonal of the pin. If there is no pin, then a mask of the
  891.        entire board is returned.
  892.        """
  893.         return SquareSet(self.pin_mask(color, square))
  894.  
  895.     def is_pinned(self, color: Color, square: Square) -> bool:
  896.         """
  897.        Detects if the given square is pinned to the king of the given color.
  898.        """
  899.         return self.pin_mask(color, square) != BB_ALL
  900.  
  901.     def _remove_piece_at(self, square: Square) -> Optional[PieceType]:
  902.         piece_type = self.piece_type_at(square)
  903.         mask = BB_SQUARES[square]
  904.  
  905.         if piece_type == PAWN:
  906.             self.pawns ^= mask
  907.         elif piece_type == KNIGHT:
  908.             self.knights ^= mask
  909.         elif piece_type == BISHOP:
  910.             self.bishops ^= mask
  911.         elif piece_type == ROOK:
  912.             self.rooks ^= mask
  913.         elif piece_type == QUEEN:
  914.             self.queens ^= mask
  915.         elif piece_type == KING:
  916.             self.kings ^= mask
  917.         else:
  918.             return None
  919.  
  920.         self.occupied ^= mask
  921.         self.occupied_co[WHITE] &= ~mask
  922.         self.occupied_co[BLACK] &= ~mask
  923.  
  924.         self.promoted &= ~mask
  925.  
  926.         return piece_type
  927.  
  928.     def remove_piece_at(self, square: Square) -> Optional[Piece]:
  929.         """
  930.        Removes the piece from the given square. Returns the
  931.        :class:`~chess.Piece` or ``None`` if the square was already empty.
  932.  
  933.        :class:`~chess.Board` also clears the move stack.
  934.        """
  935.         color = bool(self.occupied_co[WHITE] & BB_SQUARES[square])
  936.         piece_type = self._remove_piece_at(square)
  937.         return Piece(piece_type, color) if piece_type else None
  938.  
  939.     def _set_piece_at(self, square: Square, piece_type: PieceType, color: Color, promoted: bool = False) -> None:
  940.         self._remove_piece_at(square)
  941.  
  942.         mask = BB_SQUARES[square]
  943.  
  944.         if piece_type == PAWN:
  945.             self.pawns |= mask
  946.         elif piece_type == KNIGHT:
  947.             self.knights |= mask
  948.         elif piece_type == BISHOP:
  949.             self.bishops |= mask
  950.         elif piece_type == ROOK:
  951.             self.rooks |= mask
  952.         elif piece_type == QUEEN:
  953.             self.queens |= mask
  954.         elif piece_type == KING:
  955.             self.kings |= mask
  956.         else:
  957.             return
  958.  
  959.         self.occupied ^= mask
  960.         self.occupied_co[color] ^= mask
  961.  
  962.         if promoted:
  963.             self.promoted ^= mask
  964.  
  965.     def set_piece_at(self, square: Square, piece: Optional[Piece], promoted: bool = False) -> None:
  966.         """
  967.        Sets a piece at the given square.
  968.  
  969.        An existing piece is replaced. Setting *piece* to ``None`` is
  970.        equivalent to :func:`~chess.Board.remove_piece_at()`.
  971.  
  972.        :class:`~chess.Board` also clears the move stack.
  973.        """
  974.         if piece is None:
  975.             self._remove_piece_at(square)
  976.         else:
  977.             self._set_piece_at(square, piece.piece_type, piece.color, promoted)
  978.  
  979.     def board_fen(self, *, promoted: Optional[bool] = False) -> str:
  980.         """
  981.        Gets the board FEN (e.g.,
  982.        ``rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR``).
  983.        """
  984.         builder = []
  985.         empty = 0
  986.  
  987.         for square in SQUARES_180:
  988.             piece = self.piece_at(square)
  989.  
  990.             if not piece:
  991.                 empty += 1
  992.             else:
  993.                 if empty:
  994.                     builder.append(str(empty))
  995.                     empty = 0
  996.                 builder.append(piece.symbol())
  997.                 if promoted and BB_SQUARES[square] & self.promoted:
  998.                     builder.append("~")
  999.  
  1000.             if BB_SQUARES[square] & BB_FILE_H:
  1001.                 if empty:
  1002.                     builder.append(str(empty))
  1003.                     empty = 0
  1004.  
  1005.                 if square != H1:
  1006.                     builder.append("/")
  1007.  
  1008.         return "".join(builder)
  1009.  
  1010.     def _set_board_fen(self, fen: str) -> None:
  1011.         # Compatibility with set_fen().
  1012.         fen = fen.strip()
  1013.         if " " in fen:
  1014.             raise ValueError(f"expected position part of fen, got multiple parts: {fen!r}")
  1015.  
  1016.         # Ensure the FEN is valid.
  1017.         rows = fen.split("/")
  1018.         if len(rows) != 8:
  1019.             raise ValueError(f"expected 8 rows in position part of fen: {fen!r}")
  1020.  
  1021.         # Validate each row.
  1022.         for row in rows:
  1023.             field_sum = 0
  1024.             previous_was_digit = False
  1025.             previous_was_piece = False
  1026.  
  1027.             for c in row:
  1028.                 if c in ["1", "2", "3", "4", "5", "6", "7", "8"]:
  1029.                     if previous_was_digit:
  1030.                         pass
  1031.   #                      raise ValueError(f"two subsequent digits in position part of fen: {fen!r}")
  1032.                     field_sum += int(c)
  1033.                     previous_was_digit = True
  1034.                     previous_was_piece = False
  1035.                 elif c == "~":
  1036.                     if not previous_was_piece:
  1037.                         raise ValueError(f"'~' not after piece in position part of fen: {fen!r}")
  1038.                     previous_was_digit = False
  1039.                     previous_was_piece = False
  1040.                 elif c.lower() in PIECE_SYMBOLS:
  1041.                     field_sum += 1
  1042.                     previous_was_digit = False
  1043.                     previous_was_piece = True
  1044.                 else:
  1045.                     raise ValueError(f"invalid character in position part of fen: {fen!r}")
  1046.  
  1047.             if field_sum != 8:
  1048.                     raise ValueError(f"expected 8 columns per row in position part of fen: {fen!r}")
  1049.  
  1050.         # Clear the board.
  1051.         self._clear_board()
  1052.  
  1053.         # Put pieces on the board.
  1054.         square_index = 0
  1055.         for c in fen:
  1056.             if c in ["1", "2", "3", "4", "5", "6", "7", "8"]:
  1057.                 square_index += int(c)
  1058.             elif c.lower() in PIECE_SYMBOLS:
  1059.                 piece = Piece.from_symbol(c)
  1060.                 self._set_piece_at(SQUARES_180[square_index], piece.piece_type, piece.color)
  1061.                 square_index += 1
  1062.             elif c == "~":
  1063.                 self.promoted |= BB_SQUARES[SQUARES_180[square_index - 1]]
  1064.  
  1065.     def set_board_fen(self, fen: str) -> None:
  1066.         """
  1067.        Parses *fen* and sets up the board, where *fen* is the board part of
  1068.        a FEN.
  1069.  
  1070.        :class:`~chess.Board` also clears the move stack.
  1071.  
  1072.        :raises: :exc:`ValueError` if syntactically invalid.
  1073.        """
  1074.         self._set_board_fen(fen)
  1075.  
  1076.     def piece_map(self, *, mask: Bitboard = BB_ALL) -> Dict[Square, Piece]:
  1077.         """
  1078.        Gets a dictionary of :class:`pieces <chess.Piece>` by square index.
  1079.        """
  1080.         result = {}
  1081.         for square in scan_reversed(self.occupied & mask):
  1082.             result[square] = typing.cast(Piece, self.piece_at(square))
  1083.         return result
  1084.  
  1085.     def _set_piece_map(self, pieces: Mapping[Square, Piece]) -> None:
  1086.         self._clear_board()
  1087.         for square, piece in pieces.items():
  1088.             self._set_piece_at(square, piece.piece_type, piece.color)
  1089.  
  1090.     def set_piece_map(self, pieces: Mapping[Square, Piece]) -> None:
  1091.         """
  1092.        Sets up the board from a dictionary of :class:`pieces <chess.Piece>`
  1093.        by square index.
  1094.  
  1095.        :class:`~chess.Board` also clears the move stack.
  1096.        """
  1097.         self._set_piece_map(pieces)
  1098.  
  1099.     def _set_chess960_pos(self, scharnagl: int) -> None:
  1100.         if not 0 <= scharnagl <= 959:
  1101.             raise ValueError(f"chess960 position index not 0 <= {scharnagl!r} <= 959")
  1102.  
  1103.         # See http://www.russellcottrell.com/Chess/Chess960.htm for
  1104.         # a description of the algorithm.
  1105.         n, bw = divmod(scharnagl, 4)
  1106.         n, bb = divmod(n, 4)
  1107.         n, q = divmod(n, 6)
  1108.  
  1109.         for n1 in range(0, 4):
  1110.             n2 = n + (3 - n1) * (4 - n1) // 2 - 5
  1111.             if n1 < n2 and 1 <= n2 <= 4:
  1112.                 break
  1113.  
  1114.         # Bishops.
  1115.         bw_file = bw * 2 + 1
  1116.         bb_file = bb * 2
  1117.         self.bishops = (BB_FILES[bw_file] | BB_FILES[bb_file]) & BB_BACKRANKS
  1118.  
  1119.         # Queens.
  1120.         q_file = q
  1121.         q_file += int(min(bw_file, bb_file) <= q_file)
  1122.         q_file += int(max(bw_file, bb_file) <= q_file)
  1123.         self.queens = BB_FILES[q_file] & BB_BACKRANKS
  1124.  
  1125.         used = [bw_file, bb_file, q_file]
  1126.  
  1127.         # Knights.
  1128.         self.knights = BB_EMPTY
  1129.         for i in range(0, 8):
  1130.             if i not in used:
  1131.                 if n1 == 0 or n2 == 0:
  1132.                     self.knights |= BB_FILES[i] & BB_BACKRANKS
  1133.                     used.append(i)
  1134.                 n1 -= 1
  1135.                 n2 -= 1
  1136.  
  1137.         # RKR.
  1138.         for i in range(0, 8):
  1139.             if i not in used:
  1140.                 self.rooks = BB_FILES[i] & BB_BACKRANKS
  1141.                 used.append(i)
  1142.                 break
  1143.         for i in range(1, 8):
  1144.             if i not in used:
  1145.                 self.kings = BB_FILES[i] & BB_BACKRANKS
  1146.                 used.append(i)
  1147.                 break
  1148.         for i in range(2, 8):
  1149.             if i not in used:
  1150.                 self.rooks |= BB_FILES[i] & BB_BACKRANKS
  1151.                 break
  1152.  
  1153.         # Finalize.
  1154.         self.pawns = BB_RANK_2 | BB_RANK_7
  1155.         self.occupied_co[WHITE] = BB_RANK_1 | BB_RANK_2
  1156.         self.occupied_co[BLACK] = BB_RANK_7 | BB_RANK_8
  1157.         self.occupied = BB_RANK_1 | BB_RANK_2 | BB_RANK_7 | BB_RANK_8
  1158.         self.promoted = BB_EMPTY
  1159.  
  1160.     def set_chess960_pos(self, scharnagl: int) -> None:
  1161.         """
  1162.        Sets up a Chess960 starting position given its index between 0 and 959.
  1163.        Also see :func:`~chess.BaseBoard.from_chess960_pos()`.
  1164.        """
  1165.         self._set_chess960_pos(scharnagl)
  1166.  
  1167.     def chess960_pos(self) -> Optional[int]:
  1168.         """
  1169.        Gets the Chess960 starting position index between 0 and 959,
  1170.        or ``None``.
  1171.        """
  1172.         if self.occupied_co[WHITE] != BB_RANK_1 | BB_RANK_2:
  1173.             return None
  1174.         if self.occupied_co[BLACK] != BB_RANK_7 | BB_RANK_8:
  1175.             return None
  1176.         if self.pawns != BB_RANK_2 | BB_RANK_7:
  1177.             return None
  1178.         if self.promoted:
  1179.             return None
  1180.  
  1181.         # Piece counts.
  1182.         brnqk = [self.bishops, self.rooks, self.knights, self.queens, self.kings]
  1183.         if [popcount(pieces) for pieces in brnqk] != [4, 4, 4, 2, 2]:
  1184.             return None
  1185.  
  1186.         # Symmetry.
  1187.         if any((BB_RANK_1 & pieces) << 56 != BB_RANK_8 & pieces for pieces in brnqk):
  1188.             return None
  1189.  
  1190.         # Algorithm from ChessX, src/database/bitboard.cpp, r2254.
  1191.         x = self.bishops & (2 + 8 + 32 + 128)
  1192.         if not x:
  1193.             return None
  1194.         bs1 = (lsb(x) - 1) // 2
  1195.         cc_pos = bs1
  1196.         x = self.bishops & (1 + 4 + 16 + 64)
  1197.         if not x:
  1198.             return None
  1199.         bs2 = lsb(x) * 2
  1200.         cc_pos += bs2
  1201.  
  1202.         q = 0
  1203.         qf = False
  1204.         n0 = 0
  1205.         n1 = 0
  1206.         n0f = False
  1207.         n1f = False
  1208.         rf = 0
  1209.         n0s = [0, 4, 7, 9]
  1210.         for square in range(A1, H1 + 1):
  1211.             bb = BB_SQUARES[square]
  1212.             if bb & self.queens:
  1213.                 qf = True
  1214.             elif bb & self.rooks or bb & self.kings:
  1215.                 if bb & self.kings:
  1216.                     if rf != 1:
  1217.                         return None
  1218.                 else:
  1219.                     rf += 1
  1220.  
  1221.                 if not qf:
  1222.                     q += 1
  1223.  
  1224.                 if not n0f:
  1225.                     n0 += 1
  1226.                 elif not n1f:
  1227.                     n1 += 1
  1228.             elif bb & self.knights:
  1229.                 if not qf:
  1230.                     q += 1
  1231.  
  1232.                 if not n0f:
  1233.                     n0f = True
  1234.                 elif not n1f:
  1235.                     n1f = True
  1236.  
  1237.         if n0 < 4 and n1f and qf:
  1238.             cc_pos += q * 16
  1239.             krn = n0s[n0] + n1
  1240.             cc_pos += krn * 96
  1241.             return cc_pos
  1242.         else:
  1243.             return None
  1244.  
  1245.     def __repr__(self) -> str:
  1246.         return f"{type(self).__name__}({self.board_fen()!r})"
  1247.  
  1248.     def __str__(self) -> str:
  1249.         builder = []
  1250.  
  1251.         for square in SQUARES_180:
  1252.             piece = self.piece_at(square)
  1253.  
  1254.             if piece:
  1255.                 builder.append(piece.symbol())
  1256.             else:
  1257.                 builder.append(".")
  1258.  
  1259.             if BB_SQUARES[square] & BB_FILE_H:
  1260.                 if square != H1:
  1261.                     builder.append("\n")
  1262.             else:
  1263.                 builder.append(" ")
  1264.  
  1265.         return "".join(builder)
  1266.  
  1267.     def unicode(self, *, invert_color: bool = False, borders: bool = False, empty_square: str = "⭘", orientation: Color = WHITE) -> str:
  1268.         """
  1269.        Returns a string representation of the board with Unicode pieces.
  1270.        Useful for pretty-printing to a terminal.
  1271.  
  1272.        :param invert_color: Invert color of the Unicode pieces.
  1273.        :param borders: Show borders and a coordinate margin.
  1274.        """
  1275.         builder = []
  1276.         for rank_index in (range(7, -1, -1) if orientation else range(8)):
  1277.             if borders:
  1278.                 builder.append("  ")
  1279.                 builder.append("-" * 17)
  1280.                 builder.append("\n")
  1281.  
  1282.                 builder.append(RANK_NAMES[rank_index])
  1283.                 builder.append(" ")
  1284.  
  1285.             for i, file_index in enumerate(range(8) if orientation else range(7, -1, -1)):
  1286.                 square_index = square(file_index, rank_index)
  1287.  
  1288.                 if borders:
  1289.                     builder.append("|")
  1290.                 elif i > 0:
  1291.                     builder.append(" ")
  1292.  
  1293.                 piece = self.piece_at(square_index)
  1294.  
  1295.                 if piece:
  1296.                     builder.append(piece.unicode_symbol(invert_color=invert_color))
  1297.                 else:
  1298.                     builder.append(empty_square)
  1299.  
  1300.             if borders:
  1301.                 builder.append("|")
  1302.  
  1303.             if borders or (rank_index > 0 if orientation else rank_index < 7):
  1304.                 builder.append("\n")
  1305.  
  1306.         if borders:
  1307.             builder.append("  ")
  1308.             builder.append("-" * 17)
  1309.             builder.append("\n")
  1310.             letters = "a b c d e f g h" if orientation else "h g f e d c b a"
  1311.             builder.append("   " + letters)
  1312.  
  1313.         return "".join(builder)
  1314.  
  1315.     def _repr_svg_(self) -> str:
  1316.         import chess.svg
  1317.         return chess.svg.board(board=self, size=400)
  1318.  
  1319.     def __eq__(self, board: object) -> bool:
  1320.         if isinstance(board, BaseBoard):
  1321.             return (
  1322.                 self.occupied == board.occupied and
  1323.                 self.occupied_co[WHITE] == board.occupied_co[WHITE] and
  1324.                 self.pawns == board.pawns and
  1325.                 self.knights == board.knights and
  1326.                 self.bishops == board.bishops and
  1327.                 self.rooks == board.rooks and
  1328.                 self.queens == board.queens and
  1329.                 self.kings == board.kings)
  1330.         else:
  1331.             return NotImplemented
  1332.  
  1333.     def apply_transform(self, f: Callable[[Bitboard], Bitboard]) -> None:
  1334.         self.pawns = f(self.pawns)
  1335.         self.knights = f(self.knights)
  1336.         self.bishops = f(self.bishops)
  1337.         self.rooks = f(self.rooks)
  1338.         self.queens = f(self.queens)
  1339.         self.kings = f(self.kings)
  1340.  
  1341.         self.occupied_co[WHITE] = f(self.occupied_co[WHITE])
  1342.         self.occupied_co[BLACK] = f(self.occupied_co[BLACK])
  1343.         self.occupied = f(self.occupied)
  1344.         self.promoted = f(self.promoted)
  1345.  
  1346.     def transform(self: BaseBoardT, f: Callable[[Bitboard], Bitboard]) -> BaseBoardT:
  1347.         """
  1348.        Returns a transformed copy of the board (without move stack)
  1349.        by applying a bitboard transformation function.
  1350.  
  1351.        Available transformations include :func:`chess.flip_vertical()`,
  1352.        :func:`chess.flip_horizontal()`, :func:`chess.flip_diagonal()`,
  1353.        :func:`chess.flip_anti_diagonal()`, :func:`chess.shift_down()`,
  1354.        :func:`chess.shift_up()`, :func:`chess.shift_left()`, and
  1355.        :func:`chess.shift_right()`.
  1356.  
  1357.        Alternatively, :func:`~chess.BaseBoard.apply_transform()` can be used
  1358.        to apply the transformation on the board.
  1359.        """
  1360.         board = self.copy()
  1361.         board.apply_transform(f)
  1362.         return board
  1363.  
  1364.     def apply_mirror(self: BaseBoardT) -> None:
  1365.         self.apply_transform(flip_vertical)
  1366.         self.occupied_co[WHITE], self.occupied_co[BLACK] = self.occupied_co[BLACK], self.occupied_co[WHITE]
  1367.  
  1368.     def mirror(self: BaseBoardT) -> BaseBoardT:
  1369.         """
  1370.        Returns a mirrored copy of the board (without move stack).
  1371.  
  1372.        The board is mirrored vertically and piece colors are swapped, so that
  1373.        the position is equivalent modulo color.
  1374.  
  1375.        Alternatively, :func:`~chess.BaseBoard.apply_mirror()` can be used
  1376.        to mirror the board.
  1377.        """
  1378.         board = self.copy()
  1379.         board.apply_mirror()
  1380.         return board
  1381.  
  1382.     def copy(self: BaseBoardT) -> BaseBoardT:
  1383.         """Creates a copy of the board."""
  1384.         board = type(self)(None)
  1385.  
  1386.         board.pawns = self.pawns
  1387.         board.knights = self.knights
  1388.         board.bishops = self.bishops
  1389.         board.rooks = self.rooks
  1390.         board.queens = self.queens
  1391.         board.kings = self.kings
  1392.  
  1393.         board.occupied_co[WHITE] = self.occupied_co[WHITE]
  1394.         board.occupied_co[BLACK] = self.occupied_co[BLACK]
  1395.         board.occupied = self.occupied
  1396.         board.promoted = self.promoted
  1397.  
  1398.         return board
  1399.  
  1400.     def __copy__(self: BaseBoardT) -> BaseBoardT:
  1401.         return self.copy()
  1402.  
  1403.     def __deepcopy__(self: BaseBoardT, memo: Dict[int, object]) -> BaseBoardT:
  1404.         board = self.copy()
  1405.         memo[id(self)] = board
  1406.         return board
  1407.  
  1408.     @classmethod
  1409.     def empty(cls: Type[BaseBoardT]) -> BaseBoardT:
  1410.         """
  1411.        Creates a new empty board. Also see
  1412.        :func:`~chess.BaseBoard.clear_board()`.
  1413.        """
  1414.         return cls(None)
  1415.  
  1416.     @classmethod
  1417.     def from_chess960_pos(cls: Type[BaseBoardT], scharnagl: int) -> BaseBoardT:
  1418.         """
  1419.        Creates a new board, initialized with a Chess960 starting position.
  1420.  
  1421.        >>> import chess
  1422.        >>> import random
  1423.        >>>
  1424.        >>> board = chess.Board.from_chess960_pos(random.randint(0, 959))
  1425.        """
  1426.         board = cls.empty()
  1427.         board.set_chess960_pos(scharnagl)
  1428.         return board
  1429.  
  1430.  
  1431. BoardT = TypeVar("BoardT", bound="Board")
  1432.  
  1433. class _BoardState(Generic[BoardT]):
  1434.  
  1435.     def __init__(self, board: BoardT) -> None:
  1436.         self.pawns = board.pawns
  1437.         self.knights = board.knights
  1438.         self.bishops = board.bishops
  1439.         self.rooks = board.rooks
  1440.         self.queens = board.queens
  1441.         self.kings = board.kings
  1442.  
  1443.         self.occupied_w = board.occupied_co[WHITE]
  1444.         self.occupied_b = board.occupied_co[BLACK]
  1445.         self.occupied = board.occupied
  1446.  
  1447.         self.promoted = board.promoted
  1448.  
  1449.         self.turn = board.turn
  1450.         self.castling_rights = board.castling_rights
  1451.         self.ep_square = board.ep_square
  1452.         self.halfmove_clock = board.halfmove_clock
  1453.         self.fullmove_number = board.fullmove_number
  1454.  
  1455.     def restore(self, board: BoardT) -> None:
  1456.         board.pawns = self.pawns
  1457.         board.knights = self.knights
  1458.         board.bishops = self.bishops
  1459.         board.rooks = self.rooks
  1460.         board.queens = self.queens
  1461.         board.kings = self.kings
  1462.  
  1463.         board.occupied_co[WHITE] = self.occupied_w
  1464.         board.occupied_co[BLACK] = self.occupied_b
  1465.         board.occupied = self.occupied
  1466.  
  1467.         board.promoted = self.promoted
  1468.  
  1469.         board.turn = self.turn
  1470.         board.castling_rights = self.castling_rights
  1471.         board.ep_square = self.ep_square
  1472.         board.halfmove_clock = self.halfmove_clock
  1473.         board.fullmove_number = self.fullmove_number
  1474.  
  1475. class Board(BaseBoard):
  1476.     """
  1477.    A :class:`~chess.BaseBoard`, additional information representing
  1478.    a chess position, and a :data:`move stack <chess.Board.move_stack>`.
  1479.  
  1480.    Provides :data:`move generation <chess.Board.legal_moves>`, validation,
  1481.    :func:`parsing <chess.Board.parse_san()>`, attack generation,
  1482.    :func:`game end detection <chess.Board.is_game_over()>`,
  1483.    and the capability to :func:`make <chess.Board.push()>` and
  1484.    :func:`unmake <chess.Board.pop()>` moves.
  1485.  
  1486.    The board is initialized to the standard chess starting position,
  1487.    unless otherwise specified in the optional *fen* argument.
  1488.    If *fen* is ``None``, an empty board is created.
  1489.  
  1490.    Optionally supports *chess960*. In Chess960, castling moves are encoded
  1491.    by a king move to the corresponding rook square.
  1492.    Use :func:`chess.Board.from_chess960_pos()` to create a board with one
  1493.    of the Chess960 starting positions.
  1494.  
  1495.    It's safe to set :data:`~Board.turn`, :data:`~Board.castling_rights`,
  1496.    :data:`~Board.ep_square`, :data:`~Board.halfmove_clock` and
  1497.    :data:`~Board.fullmove_number` directly.
  1498.  
  1499.    .. warning::
  1500.        It is possible to set up and work with invalid positions. In this
  1501.        case, :class:`~chess.Board` implements a kind of "pseudo-chess"
  1502.        (useful to gracefully handle errors or to implement chess variants).
  1503.        Use :func:`~chess.Board.is_valid()` to detect invalid positions.
  1504.    """
  1505.  
  1506.     aliases: ClassVar[List[str]] = ["Standard", "Chess", "Classical", "Normal", "Illegal", "From Position"]
  1507.     uci_variant: ClassVar[Optional[str]] = "chess"
  1508.     xboard_variant: ClassVar[Optional[str]] = "normal"
  1509.     starting_fen: ClassVar[str] = STARTING_FEN
  1510.  
  1511.     tbw_suffix: ClassVar[Optional[str]] = ".rtbw"
  1512.     tbz_suffix: ClassVar[Optional[str]] = ".rtbz"
  1513.     tbw_magic: ClassVar[Optional[bytes]] = b"\x71\xe8\x23\x5d"
  1514.     tbz_magic: ClassVar[Optional[bytes]] = b"\xd7\x66\x0c\xa5"
  1515.     pawnless_tbw_suffix: ClassVar[Optional[str]] = None
  1516.     pawnless_tbz_suffix: ClassVar[Optional[str]] = None
  1517.     pawnless_tbw_magic: ClassVar[Optional[bytes]] = None
  1518.     pawnless_tbz_magic: ClassVar[Optional[bytes]] = None
  1519.     connected_kings: ClassVar[bool] = False
  1520.     one_king: ClassVar[bool] = True
  1521.     captures_compulsory: ClassVar[bool] = False
  1522.  
  1523.     turn: Color
  1524.     """The side to move (``chess.WHITE`` or ``chess.BLACK``)."""
  1525.  
  1526.     castling_rights: Bitboard
  1527.     """
  1528.    Bitmask of the rooks with castling rights.
  1529.  
  1530.    To test for specific squares:
  1531.  
  1532.    >>> import chess
  1533.    >>>
  1534.    >>> board = chess.Board()
  1535.    >>> bool(board.castling_rights & chess.BB_H1)  # White can castle with the h1 rook
  1536.    True
  1537.  
  1538.    To add a specific square:
  1539.  
  1540.    >>> board.castling_rights |= chess.BB_A1
  1541.  
  1542.    Use :func:`~chess.Board.set_castling_fen()` to set multiple castling
  1543.    rights. Also see :func:`~chess.Board.has_castling_rights()`,
  1544.    :func:`~chess.Board.has_kingside_castling_rights()`,
  1545.    :func:`~chess.Board.has_queenside_castling_rights()`,
  1546.    :func:`~chess.Board.has_chess960_castling_rights()`,
  1547.    :func:`~chess.Board.clean_castling_rights()`.
  1548.    """
  1549.  
  1550.     ep_square: Optional[Square]
  1551.     """
  1552.    The potential en passant square on the third or sixth rank or ``None``.
  1553.  
  1554.    Use :func:`~chess.Board.has_legal_en_passant()` to test if en passant
  1555.    capturing would actually be possible on the next move.
  1556.    """
  1557.  
  1558.     fullmove_number: int
  1559.     """
  1560.    Counts move pairs. Starts at `1` and is incremented after every move
  1561.    of the black side.
  1562.    """
  1563.  
  1564.     halfmove_clock: int
  1565.     """The number of half-moves since the last capture or pawn move."""
  1566.  
  1567.     promoted: Bitboard
  1568.     """A bitmask of pieces that have been promoted."""
  1569.  
  1570.     chess960: bool
  1571.     """
  1572.    Whether the board is in Chess960 mode. In Chess960 castling moves are
  1573.    represented as king moves to the corresponding rook square.
  1574.    """
  1575.  
  1576.     move_stack: List[Move]
  1577.     """
  1578.    The move stack. Use :func:`Board.push() <chess.Board.push()>`,
  1579.    :func:`Board.pop() <chess.Board.pop()>`,
  1580.    :func:`Board.peek() <chess.Board.peek()>` and
  1581.    :func:`Board.clear_stack() <chess.Board.clear_stack()>` for
  1582.    manipulation.
  1583.    """
  1584.  
  1585.     def __init__(self: BoardT, fen: Optional[str] = STARTING_FEN, *, chess960: bool = False) -> None:
  1586.         BaseBoard.__init__(self, None)
  1587.  
  1588.         self.chess960 = chess960
  1589.  
  1590.         self.ep_square = None
  1591.         self.move_stack = []
  1592.         self._stack: List[_BoardState[BoardT]] = []
  1593.  
  1594.         if fen is None:
  1595.             self.clear()
  1596.         elif fen == type(self).starting_fen:
  1597.             self.reset()
  1598.         else:
  1599.             self.set_fen(fen)
  1600.  
  1601.     @property
  1602.     def legal_moves(self) -> LegalMoveGenerator:
  1603.         """
  1604.        A dynamic list of legal moves.
  1605.  
  1606.        >>> import chess
  1607.        >>>
  1608.        >>> board = chess.Board()
  1609.        >>> board.legal_moves.count()
  1610.        20
  1611.        >>> bool(board.legal_moves)
  1612.        True
  1613.        >>> move = chess.Move.from_uci("g1f3")
  1614.        >>> move in board.legal_moves
  1615.        True
  1616.  
  1617.        Wraps :func:`~chess.Board.generate_legal_moves()` and
  1618.        :func:`~chess.Board.is_legal()`.
  1619.        """
  1620.         return LegalMoveGenerator(self)
  1621.  
  1622.     @property
  1623.     def pseudo_legal_moves(self) -> PseudoLegalMoveGenerator:
  1624.         """
  1625.        A dynamic list of pseudo-legal moves, much like the legal move list.
  1626.  
  1627.        Pseudo-legal moves might leave or put the king in check, but are
  1628.        otherwise valid. Null moves are not pseudo-legal. Castling moves are
  1629.        only included if they are completely legal.
  1630.  
  1631.        Wraps :func:`~chess.Board.generate_pseudo_legal_moves()` and
  1632.        :func:`~chess.Board.is_pseudo_legal()`.
  1633.        """
  1634.         return PseudoLegalMoveGenerator(self)
  1635.  
  1636.     def reset(self) -> None:
  1637.         """Restores the starting position."""
  1638.         self.turn = WHITE
  1639.         self.castling_rights = BB_CORNERS
  1640.         self.ep_square = None
  1641.         self.halfmove_clock = 0
  1642.         self.fullmove_number = 1
  1643.  
  1644.         self.reset_board()
  1645.  
  1646.     def reset_board(self) -> None:
  1647.         super().reset_board()
  1648.         self.clear_stack()
  1649.  
  1650.     def clear(self) -> None:
  1651.         """
  1652.        Clears the board.
  1653.  
  1654.        Resets move stack and move counters. The side to move is white. There
  1655.        are no rooks or kings, so castling rights are removed.
  1656.  
  1657.        In order to be in a valid :func:`~chess.Board.status()`, at least kings
  1658.        need to be put on the board.
  1659.        """
  1660.         self.turn = WHITE
  1661.         self.castling_rights = BB_EMPTY
  1662.         self.ep_square = None
  1663.         self.halfmove_clock = 0
  1664.         self.fullmove_number = 1
  1665.  
  1666.         self.clear_board()
  1667.  
  1668.     def clear_board(self) -> None:
  1669.         super().clear_board()
  1670.         self.clear_stack()
  1671.  
  1672.     def clear_stack(self) -> None:
  1673.         """Clears the move stack."""
  1674.         self.move_stack.clear()
  1675.         self._stack.clear()
  1676.  
  1677.     def root(self: BoardT) -> BoardT:
  1678.         """Returns a copy of the root position."""
  1679.         if self._stack:
  1680.             board = type(self)(None, chess960=self.chess960)
  1681.             self._stack[0].restore(board)
  1682.             return board
  1683.         else:
  1684.             return self.copy(stack=False)
  1685.  
  1686.     def ply(self) -> int:
  1687.         """
  1688.        Returns the number of half-moves since the start of the game, as
  1689.        indicated by :data:`~chess.Board.fullmove_number` and
  1690.        :data:`~chess.Board.turn`.
  1691.  
  1692.        If moves have been pushed from the beginning, this is usually equal to
  1693.        ``len(board.move_stack)``. But note that a board can be set up with
  1694.        arbitrary starting positions, and the stack can be cleared.
  1695.        """
  1696.         return 2 * (self.fullmove_number - 1) + (self.turn == BLACK)
  1697.  
  1698.     def remove_piece_at(self, square: Square) -> Optional[Piece]:
  1699.         piece = super().remove_piece_at(square)
  1700.         self.clear_stack()
  1701.         return piece
  1702.  
  1703.     def set_piece_at(self, square: Square, piece: Optional[Piece], promoted: bool = False) -> None:
  1704.         super().set_piece_at(square, piece, promoted=promoted)
  1705.         self.clear_stack()
  1706.  
  1707.     def generate_pseudo_legal_moves(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]:
  1708.         our_pieces = self.occupied_co[self.turn]
  1709.  
  1710.         # Generate piece moves.
  1711.         non_pawns = our_pieces & ~self.pawns & from_mask
  1712.         for from_square in scan_reversed(non_pawns):
  1713.             piece_type = self.piece_type_at(from_square)
  1714.             moves = self.attacks_mask(piece_type, from_square, self.occupied) & ~our_pieces & to_mask
  1715.             for to_square in scan_reversed(moves):
  1716.                 yield Move(from_square, to_square)
  1717.  
  1718.         # Generate castling moves.
  1719.         if from_mask & self.kings:
  1720.             yield from self.generate_castling_moves(from_mask, to_mask)
  1721.  
  1722.         # The remaining moves are all pawn moves.
  1723.         pawns = self.pawns & self.occupied_co[self.turn] & from_mask
  1724.         if not pawns:
  1725.             return
  1726.  
  1727.         # Generate pawn moves.
  1728.         yield from self.generate_pseudo_legal_pawn_moves(pawns, to_mask)
  1729.  
  1730.         # Generate pawn captures.
  1731.         capturers = pawns
  1732.         for from_square in scan_reversed(capturers):
  1733.             targets = (
  1734.                 chess.BB_PAWN_ATTACKS[self.turn][from_square] &
  1735.                 self.occupied_co[not self.turn] & to_mask)
  1736.  
  1737.             for to_square in scan_reversed(targets):
  1738.                 if chess.square_rank(to_square) in [0, 7]:
  1739.                     yield Move(from_square, to_square, chess.QUEEN)
  1740.                     yield Move(from_square, to_square, chess.ROOK)
  1741.                     yield Move(from_square, to_square, chess.BISHOP)
  1742.                     yield Move(from_square, to_square, chess.KNIGHT)
  1743.                 else:
  1744.                     yield Move(from_square, to_square)
  1745.  
  1746.         # Prepare pawn advance generation.
  1747.         if self.turn == WHITE:
  1748.             single_moves = pawns << 8 & ~self.occupied
  1749.             double_moves = single_moves << 8 & ~self.occupied & (BB_RANK_3 | BB_RANK_4)
  1750.         else:
  1751.             single_moves = pawns >> 8 & ~self.occupied
  1752.             double_moves = single_moves >> 8 & ~self.occupied & (BB_RANK_6 | BB_RANK_5)
  1753.  
  1754.         single_moves &= to_mask
  1755.         double_moves &= to_mask
  1756.  
  1757.         # Generate single pawn moves.
  1758.         for to_square in scan_reversed(single_moves):
  1759.             from_square = to_square + (8 if self.turn == BLACK else -8)
  1760.  
  1761.             if square_rank(to_square) in [0, 7]:
  1762.                 yield Move(from_square, to_square, QUEEN)
  1763.                 yield Move(from_square, to_square, ROOK)
  1764.                 yield Move(from_square, to_square, BISHOP)
  1765.                 yield Move(from_square, to_square, KNIGHT)
  1766.             else:
  1767.                 yield Move(from_square, to_square)
  1768.  
  1769.         # Generate double pawn moves.
  1770.         for to_square in scan_reversed(double_moves):
  1771.             from_square = to_square + (16 if self.turn == BLACK else -16)
  1772.             yield Move(from_square, to_square)
  1773.  
  1774.         # Generate en passant captures.
  1775.         if self.ep_square:
  1776.             yield from self.generate_pseudo_legal_ep(from_mask, to_mask)
  1777.  
  1778.     def generate_pseudo_legal_ep(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]:
  1779.         if not self.ep_square or not BB_SQUARES[self.ep_square] & to_mask:
  1780.             return
  1781.  
  1782.         if BB_SQUARES[self.ep_square] & self.occupied:
  1783.             return
  1784.  
  1785.         capturers = (
  1786.             self.pawns & self.occupied_co[self.turn] & from_mask &
  1787.             BB_PAWN_ATTACKS[not self.turn][self.ep_square] &
  1788.             BB_RANKS[4 if self.turn else 3])
  1789.  
  1790.         for capturer in scan_reversed(capturers):
  1791.             yield Move(capturer, self.ep_square)
  1792.  
  1793.     def generate_pseudo_legal_captures(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]:
  1794.         return itertools.chain(
  1795.             self.generate_pseudo_legal_moves(from_mask, to_mask & self.occupied_co[not self.turn]),
  1796.             self.generate_pseudo_legal_ep(from_mask, to_mask))
  1797.  
  1798.     def checkers_mask(self) -> Bitboard:
  1799.         king = self.king(self.turn)
  1800.         return BB_EMPTY if king is None else self.attackers_mask(not self.turn, king)
  1801.  
  1802.     def checkers(self) -> SquareSet:
  1803.         """
  1804.        Gets the pieces currently giving check.
  1805.  
  1806.        Returns a :class:`set of squares <chess.SquareSet>`.
  1807.        """
  1808.         return SquareSet(self.checkers_mask())
  1809.  
  1810.     def is_check(self) -> bool:
  1811.         """Tests if the current side to move is in check."""
  1812.         return bool(self.checkers_mask())
  1813.  
  1814.     def gives_check(self, move: Move) -> bool:
  1815.         """
  1816.        Probes if the given move would put the opponent in check. The move
  1817.        must be at least pseudo-legal.
  1818.        """
  1819.         self.push(move)
  1820.         try:
  1821.             return self.is_check()
  1822.         finally:
  1823.             self.pop()
  1824.  
  1825.     def is_into_check(self, move: Move) -> bool:
  1826.         king = self.king(self.turn)
  1827.         if king is None:
  1828.             return False
  1829.  
  1830.         # If already in check, look if it is an evasion.
  1831.         checkers = self.attackers_mask(not self.turn, king)
  1832.         if checkers and move not in self._generate_evasions(king, checkers, BB_SQUARES[move.from_square], BB_SQUARES[move.to_square]):
  1833.             return True
  1834.  
  1835.         return not self._is_safe(king, self._slider_blockers(king), move)
  1836.  
  1837.     def was_into_check(self) -> bool:
  1838.         king = self.king(not self.turn)
  1839.         return king is not None and self.is_attacked_by(self.turn, king)
  1840.  
  1841.     def is_pseudo_legal(self, move: Move) -> bool:
  1842.         # Null moves are not pseudo-legal.
  1843.         if not move:
  1844.             return False
  1845.  
  1846.         # Drops are not pseudo-legal.
  1847.         if move.drop:
  1848.             return False
  1849.  
  1850.         # Source square must not be vacant.
  1851.         piece = self.piece_type_at(move.from_square)
  1852.         if not piece:
  1853.             return False
  1854.  
  1855.         # Get square masks.
  1856.         from_mask = BB_SQUARES[move.from_square]
  1857.         to_mask = BB_SQUARES[move.to_square]
  1858.  
  1859.         # Check turn.
  1860.         if not self.occupied_co[self.turn] & from_mask:
  1861.             return False
  1862.  
  1863.         # Only pawns can promote and only on the backrank.
  1864.         if move.promotion:
  1865.             if piece != PAWN:
  1866.                 return False
  1867.  
  1868.             if self.turn == WHITE and square_rank(move.to_square) != 7:
  1869.                 return False
  1870.             elif self.turn == BLACK and square_rank(move.to_square) != 0:
  1871.                 return False
  1872.  
  1873.         # Handle castling.
  1874.         if piece == KING:
  1875.             move = self._from_chess960(self.chess960, move.from_square, move.to_square)
  1876.             if move in self.generate_castling_moves():
  1877.                 return True
  1878.  
  1879.         # Destination square can not be occupied.
  1880.         if self.occupied_co[self.turn] & to_mask:
  1881.             return False
  1882.  
  1883.         # Handle pawn moves.
  1884.         if piece == PAWN:
  1885.             return move in self.generate_pseudo_legal_moves(from_mask, to_mask)
  1886.  
  1887.         # Handle all other pieces.
  1888.         return bool(self.attacks_mask(self.piece_type_at, move.from_square, self.occupied) & to_mask)
  1889.  
  1890.     def is_legal(self, move: Move) -> bool:
  1891.         return not self.is_variant_end() and self.is_pseudo_legal(move) and not self.is_into_check(move)
  1892.  
  1893.     def is_variant_end(self) -> bool:
  1894.         """
  1895.        Checks if the game is over due to a special variant end condition.
  1896.  
  1897.        Note, for example, that stalemate is not considered a variant-specific
  1898.        end condition (this method will return ``False``), yet it can have a
  1899.        special **result** in suicide chess (any of
  1900.        :func:`~chess.Board.is_variant_loss()`,
  1901.        :func:`~chess.Board.is_variant_win()`,
  1902.        :func:`~chess.Board.is_variant_draw()` might return ``True``).
  1903.        """
  1904.         return False
  1905.  
  1906.     def is_variant_loss(self) -> bool:
  1907.         """
  1908.        Checks if the current side to move lost due to a variant-specific
  1909.        condition.
  1910.        """
  1911.         return False
  1912.  
  1913.     def is_variant_win(self) -> bool:
  1914.         """
  1915.        Checks if the current side to move won due to a variant-specific
  1916.        condition.
  1917.        """
  1918.         return False
  1919.  
  1920.     def is_variant_draw(self) -> bool:
  1921.         """
  1922.        Checks if a variant-specific drawing condition is fulfilled.
  1923.        """
  1924.         return False
  1925.  
  1926.     def is_game_over(self, *, claim_draw: bool = False) -> bool:
  1927.         return self.outcome(claim_draw=claim_draw) is not None
  1928.  
  1929.     def result(self, *, claim_draw: bool = False) -> str:
  1930.         outcome = self.outcome(claim_draw=claim_draw)
  1931.         return outcome.result() if outcome else "*"
  1932.  
  1933.     def outcome(self, *, claim_draw: bool = False) -> Optional[Outcome]:
  1934.         """
  1935.        Checks if the game is over due to
  1936.        :func:`checkmate <chess.Board.is_checkmate()>`,
  1937.        :func:`stalemate <chess.Board.is_stalemate()>`,
  1938.        :func:`insufficient material <chess.Board.is_insufficient_material()>`,
  1939.        the :func:`seventyfive-move rule <chess.Board.is_seventyfive_moves()>`,
  1940.        :func:`fivefold repetition <chess.Board.is_fivefold_repetition()>`,
  1941.        or a :func:`variant end condition <chess.Board.is_variant_end()>`.
  1942.        Returns the :class:`chess.Outcome` if the game has ended, otherwise
  1943.        ``None``.
  1944.  
  1945.        Alternatively, use :func:`~chess.Board.is_game_over()` if you are not
  1946.        interested in who won the game and why.
  1947.  
  1948.        The game is not considered to be over by the
  1949.        :func:`fifty-move rule <chess.Board.can_claim_fifty_moves()>` or
  1950.        :func:`threefold repetition <chess.Board.can_claim_threefold_repetition()>`,
  1951.        unless *claim_draw* is given. Note that checking the latter can be
  1952.        slow.
  1953.        """
  1954.         # Variant support.
  1955.         if self.is_variant_loss():
  1956.             return Outcome(Termination.VARIANT_LOSS, not self.turn)
  1957.         if self.is_variant_win():
  1958.             return Outcome(Termination.VARIANT_WIN, self.turn)
  1959.         if self.is_variant_draw():
  1960.             return Outcome(Termination.VARIANT_DRAW, None)
  1961.  
  1962.         # Normal game end.
  1963.         if self.is_checkmate():
  1964.             return Outcome(Termination.CHECKMATE, not self.turn)
  1965.         if self.is_insufficient_material():
  1966.             return Outcome(Termination.INSUFFICIENT_MATERIAL, None)
  1967.         if not any(self.generate_legal_moves()):
  1968.             return Outcome(Termination.STALEMATE, None)
  1969.  
  1970.         # Automatic draws.
  1971.         if self.is_seventyfive_moves():
  1972.             return Outcome(Termination.SEVENTYFIVE_MOVES, None)
  1973.         if self.is_fivefold_repetition():
  1974.             return Outcome(Termination.FIVEFOLD_REPETITION, None)
  1975.  
  1976.         # Claimable draws.
  1977.         if claim_draw:
  1978.             if self.can_claim_fifty_moves():
  1979.                 return Outcome(Termination.FIFTY_MOVES, None)
  1980.             if self.can_claim_threefold_repetition():
  1981.                 return Outcome(Termination.THREEFOLD_REPETITION, None)
  1982.  
  1983.         return None
  1984.  
  1985.     def is_checkmate(self) -> bool:
  1986.         """Checks if the current position is a checkmate."""
  1987.         if not self.is_check():
  1988.             return False
  1989.  
  1990.         return not any(self.generate_legal_moves())
  1991.  
  1992.     def is_stalemate(self) -> bool:
  1993.         """Checks if the current position is a stalemate."""
  1994.         if self.is_check():
  1995.             return False
  1996.  
  1997.         if self.is_variant_end():
  1998.             return False
  1999.  
  2000.         return not any(self.generate_legal_moves())
  2001.  
  2002.     def is_insufficient_material(self) -> bool:
  2003.         """
  2004.        Checks if neither side has sufficient winning material
  2005.        (:func:`~chess.Board.has_insufficient_material()`).
  2006.        """
  2007.         return all(self.has_insufficient_material(color) for color in COLORS)
  2008.  
  2009.     def has_insufficient_material(self, color: Color) -> bool:
  2010.         """
  2011.        Checks if *color* has insufficient winning material.
  2012.  
  2013.        This is guaranteed to return ``False`` if *color* can still win the
  2014.        game.
  2015.  
  2016.        The converse does not necessarily hold:
  2017.        The implementation only looks at the material, including the colors
  2018.        of bishops, but not considering piece positions. So fortress
  2019.        positions or positions with forced lines may return ``False``, even
  2020.        though there is no possible winning line.
  2021.        """
  2022.         if self.occupied_co[color] & (self.pawns | self.rooks | self.queens):
  2023.             return False
  2024.  
  2025.         # Knights are only insufficient material if:
  2026.         # (1) We do not have any other pieces, including more than one knight.
  2027.         # (2) The opponent does not have pawns, knights, bishops or rooks.
  2028.         #     These would allow selfmate.
  2029.         if self.occupied_co[color] & self.knights:
  2030.             return (popcount(self.occupied_co[color]) <= 2 and
  2031.                     not (self.occupied_co[not color] & ~self.kings & ~self.queens))
  2032.  
  2033.         # Bishops are only insufficient material if:
  2034.         # (1) We do not have any other pieces, including bishops of the
  2035.         #     opposite color.
  2036.         # (2) The opponent does not have bishops of the opposite color,
  2037.         #     pawns or knights. These would allow selfmate.
  2038.         if self.occupied_co[color] & self.bishops:
  2039.             same_color = (not self.bishops & BB_DARK_SQUARES) or (not self.bishops & BB_LIGHT_SQUARES)
  2040.             return same_color and not self.pawns and not self.knights
  2041.  
  2042.         return True
  2043.  
  2044.     def _is_halfmoves(self, n: int) -> bool:
  2045.         return self.halfmove_clock >= n and any(self.generate_legal_moves())
  2046.  
  2047.     def is_seventyfive_moves(self) -> bool:
  2048.         """
  2049.        Since the 1st of July 2014, a game is automatically drawn (without
  2050.        a claim by one of the players) if the half-move clock since a capture
  2051.        or pawn move is equal to or greater than 150. Other means to end a game
  2052.        take precedence.
  2053.        """
  2054.         return self._is_halfmoves(150)
  2055.  
  2056.     def is_fivefold_repetition(self) -> bool:
  2057.         """
  2058.        Since the 1st of July 2014 a game is automatically drawn (without
  2059.        a claim by one of the players) if a position occurs for the fifth time.
  2060.        Originally this had to occur on consecutive alternating moves, but
  2061.        this has since been revised.
  2062.        """
  2063.         return self.is_repetition(5)
  2064.  
  2065.     def can_claim_draw(self) -> bool:
  2066.         """
  2067.        Checks if the player to move can claim a draw by the fifty-move rule or
  2068.        by threefold repetition.
  2069.  
  2070.        Note that checking the latter can be slow.
  2071.        """
  2072.         return self.can_claim_fifty_moves() or self.can_claim_threefold_repetition()
  2073.  
  2074.     def is_fifty_moves(self) -> bool:
  2075.         """
  2076.        Checks that the clock of halfmoves since the last capture or pawn move
  2077.        is greater or equal to 100, and that no other means of ending the game
  2078.        (like checkmate) take precedence.
  2079.        """
  2080.         return self._is_halfmoves(100)
  2081.  
  2082.     def can_claim_fifty_moves(self) -> bool:
  2083.         """
  2084.        Checks if the player to move can claim a draw by the fifty-move rule.
  2085.  
  2086.        In addition to :func:`~chess.Board.is_fifty_moves()`, the fifty-move
  2087.        rule can also be claimed if there is a legal move that achieves this
  2088.        condition.
  2089.        """
  2090.         if self.is_fifty_moves():
  2091.             return True
  2092.  
  2093.         if self.halfmove_clock >= 99:
  2094.             for move in self.generate_legal_moves():
  2095.                 if not self.is_zeroing(move):
  2096.                     self.push(move)
  2097.                     try:
  2098.                         if self.is_fifty_moves():
  2099.                             return True
  2100.                     finally:
  2101.                         self.pop()
  2102.  
  2103.         return False
  2104.  
  2105.     def can_claim_threefold_repetition(self) -> bool:
  2106.         """
  2107.        Checks if the player to move can claim a draw by threefold repetition.
  2108.  
  2109.        Draw by threefold repetition can be claimed if the position on the
  2110.        board occurred for the third time or if such a repetition is reached
  2111.        with one of the possible legal moves.
  2112.  
  2113.        Note that checking this can be slow: In the worst case
  2114.        scenario, every legal move has to be tested and the entire game has to
  2115.        be replayed because there is no incremental transposition table.
  2116.        """
  2117.         transposition_key = self._transposition_key()
  2118.         transpositions: Counter[Hashable] = collections.Counter()
  2119.         transpositions.update((transposition_key, ))
  2120.  
  2121.         # Count positions.
  2122.         switchyard = []
  2123.         while self.move_stack:
  2124.             move = self.pop()
  2125.             switchyard.append(move)
  2126.  
  2127.             if self.is_irreversible(move):
  2128.                 break
  2129.  
  2130.             transpositions.update((self._transposition_key(), ))
  2131.  
  2132.         while switchyard:
  2133.             self.push(switchyard.pop())
  2134.  
  2135.         # Threefold repetition occurred.
  2136.         if transpositions[transposition_key] >= 3:
  2137.             return True
  2138.  
  2139.         # The next legal move is a threefold repetition.
  2140.         for move in self.generate_legal_moves():
  2141.             self.push(move)
  2142.             try:
  2143.                 if transpositions[self._transposition_key()] >= 2:
  2144.                     return True
  2145.             finally:
  2146.                 self.pop()
  2147.  
  2148.         return False
  2149.  
  2150.     def is_repetition(self, count: int = 3) -> bool:
  2151.         """
  2152.        Checks if the current position has repeated 3 (or a given number of)
  2153.        times.
  2154.  
  2155.        Unlike :func:`~chess.Board.can_claim_threefold_repetition()`,
  2156.        this does not consider a repetition that can be played on the next
  2157.        move.
  2158.  
  2159.        Note that checking this can be slow: In the worst case, the entire
  2160.        game has to be replayed because there is no incremental transposition
  2161.        table.
  2162.        """
  2163.         # Fast check, based on occupancy only.
  2164.         maybe_repetitions = 1
  2165.         for state in reversed(self._stack):
  2166.             if state.occupied == self.occupied:
  2167.                 maybe_repetitions += 1
  2168.                 if maybe_repetitions >= count:
  2169.                     break
  2170.         if maybe_repetitions < count:
  2171.             return False
  2172.  
  2173.         # Check full replay.
  2174.         transposition_key = self._transposition_key()
  2175.         switchyard = []
  2176.  
  2177.         try:
  2178.             while True:
  2179.                 if count <= 1:
  2180.                     return True
  2181.  
  2182.                 if len(self.move_stack) < count - 1:
  2183.                     break
  2184.  
  2185.                 move = self.pop()
  2186.                 switchyard.append(move)
  2187.  
  2188.                 if self.is_irreversible(move):
  2189.                     break
  2190.  
  2191.                 if self._transposition_key() == transposition_key:
  2192.                     count -= 1
  2193.         finally:
  2194.             while switchyard:
  2195.                 self.push(switchyard.pop())
  2196.  
  2197.         return False
  2198.  
  2199.     def _board_state(self: BoardT) -> _BoardState[BoardT]:
  2200.         return _BoardState(self)
  2201.  
  2202.     def _push_capture(self, move: Move, capture_square: Square, piece_type: PieceType, was_promoted: bool) -> None:
  2203.         pass
  2204.  
  2205.     def push(self: BoardT, move: Move) -> None:
  2206.         """
  2207.        Updates the position with the given *move* and puts it onto the
  2208.        move stack.
  2209.  
  2210.        >>> import chess
  2211.        >>>
  2212.        >>> board = chess.Board()
  2213.        >>>
  2214.        >>> Nf3 = chess.Move.from_uci("g1f3")
  2215.        >>> board.push(Nf3)  # Make the move
  2216.  
  2217.        >>> board.pop()  # Unmake the last move
  2218.        Move.from_uci('g1f3')
  2219.  
  2220.        Null moves just increment the move counters, switch turns and forfeit
  2221.        en passant capturing.
  2222.  
  2223.        .. warning::
  2224.            Moves are not checked for legality. It is the caller's
  2225.            responsibility to ensure that the move is at least pseudo-legal or
  2226.            a null move.
  2227.        """
  2228.         # Push move and remember board state.
  2229.         move = self._to_chess960(move)
  2230.         board_state = self._board_state()
  2231.         self.castling_rights = self.clean_castling_rights()  # Before pushing stack
  2232.         self.move_stack.append(self._from_chess960(self.chess960, move.from_square, move.to_square, move.promotion, move.drop))
  2233.         self._stack.append(board_state)
  2234.  
  2235.         # Reset en passant square.
  2236.         ep_square = self.ep_square
  2237.         self.ep_square = None
  2238.  
  2239.         # Increment move counters.
  2240.         self.halfmove_clock += 1
  2241.         if self.turn == BLACK:
  2242.             self.fullmove_number += 1
  2243.  
  2244.         # On a null move, simply swap turns and reset the en passant square.
  2245.         if not move:
  2246.             self.turn = not self.turn
  2247.             return
  2248.  
  2249.         # Drops.
  2250.         if move.drop:
  2251.             self._set_piece_at(move.to_square, move.drop, self.turn)
  2252.             self.turn = not self.turn
  2253.             return
  2254.  
  2255.         # Zero the half-move clock.
  2256.         if self.is_zeroing(move):
  2257.             self.halfmove_clock = 0
  2258.  
  2259.         from_bb = BB_SQUARES[move.from_square]
  2260.         to_bb = BB_SQUARES[move.to_square]
  2261.  
  2262.         promoted = bool(self.promoted & from_bb)
  2263.         piece_type = self._remove_piece_at(move.from_square)
  2264.         assert piece_type is not None, f"push() expects move to be pseudo-legal, but got {move} in {self.board_fen()}"
  2265.         capture_square = move.to_square
  2266.         captured_piece_type = self.piece_type_at(capture_square)
  2267.  
  2268.         # Update castling rights.
  2269.         self.castling_rights &= ~to_bb & ~from_bb
  2270.         if piece_type == KING and not promoted:
  2271.             if self.turn == WHITE:
  2272.                 self.castling_rights &= ~BB_RANK_1
  2273.             else:
  2274.                 self.castling_rights &= ~BB_RANK_8
  2275.         elif captured_piece_type == KING and not self.promoted & to_bb:
  2276.             if self.turn == WHITE and square_rank(move.to_square) == 7:
  2277.                 self.castling_rights &= ~BB_RANK_8
  2278.             elif self.turn == BLACK and square_rank(move.to_square) == 0:
  2279.                 self.castling_rights &= ~BB_RANK_1
  2280.  
  2281.         # Handle special pawn moves.
  2282.         if piece_type == PAWN:
  2283.             diff = move.to_square - move.from_square
  2284.  
  2285.             if diff == 16 and square_rank(move.from_square) == 1:
  2286.                 self.ep_square = move.from_square + 8
  2287.             elif diff == -16 and square_rank(move.from_square) == 6:
  2288.                 self.ep_square = move.from_square - 8
  2289.             elif move.to_square == ep_square and abs(diff) in [7, 9] and not captured_piece_type:
  2290.                 # Remove pawns captured en passant.
  2291.                 down = -8 if self.turn == WHITE else 8
  2292.                 capture_square = ep_square + down
  2293.                 captured_piece_type = self._remove_piece_at(capture_square)
  2294.  
  2295.         # Promotion.
  2296.         if move.promotion:
  2297.             promoted = True
  2298.             piece_type = move.promotion
  2299.  
  2300.         # Castling.
  2301.         castling = piece_type == KING and self.occupied_co[self.turn] & to_bb
  2302.         if castling:
  2303.             a_side = square_file(move.to_square) < square_file(move.from_square)
  2304.  
  2305.             self._remove_piece_at(move.from_square)
  2306.             self._remove_piece_at(move.to_square)
  2307.  
  2308.             if a_side:
  2309.                 self._set_piece_at(C1 if self.turn == WHITE else C8, KING, self.turn)
  2310.                 self._set_piece_at(D1 if self.turn == WHITE else D8, ROOK, self.turn)
  2311.             else:
  2312.                 self._set_piece_at(G1 if self.turn == WHITE else G8, KING, self.turn)
  2313.                 self._set_piece_at(F1 if self.turn == WHITE else F8, ROOK, self.turn)
  2314.  
  2315.         # Put the piece on the target square.
  2316.         if not castling:
  2317.             was_promoted = bool(self.promoted & to_bb)
  2318.             self._set_piece_at(move.to_square, piece_type, self.turn, promoted)
  2319.  
  2320.             if captured_piece_type:
  2321.                 self._push_capture(move, capture_square, captured_piece_type, was_promoted)
  2322.  
  2323.         # Swap turn.
  2324.         self.turn = not self.turn
  2325.  
  2326.     def pop(self: BoardT) -> Move:
  2327.         """
  2328.        Restores the previous position and returns the last move from the stack.
  2329.  
  2330.        :raises: :exc:`IndexError` if the move stack is empty.
  2331.        """
  2332.         move = self.move_stack.pop()
  2333.         self._stack.pop().restore(self)
  2334.         return move
  2335.  
  2336.     def peek(self) -> Move:
  2337.         """
  2338.        Gets the last move from the move stack.
  2339.  
  2340.        :raises: :exc:`IndexError` if the move stack is empty.
  2341.        """
  2342.         return self.move_stack[-1]
  2343.  
  2344.     def find_move(self, from_square: Square, to_square: Square, promotion: Optional[PieceType] = None) -> Move:
  2345.         """
  2346.        Finds a matching legal move for an origin square, a target square, and
  2347.        an optional promotion piece type.
  2348.  
  2349.        For pawn moves to the backrank, the promotion piece type defaults to
  2350.        :data:`chess.QUEEN`, unless otherwise specified.
  2351.  
  2352.        Castling moves are normalized to king moves by two steps, except in
  2353.        Chess960.
  2354.  
  2355.        :raises: :exc:`IllegalMoveError` if no matching legal move is found.
  2356.        """
  2357.         if promotion is None and self.pawns & BB_SQUARES[from_square] and BB_SQUARES[to_square] & BB_BACKRANKS:
  2358.             promotion = QUEEN
  2359.  
  2360.         move = self._from_chess960(self.chess960, from_square, to_square, promotion)
  2361.         if not self.is_legal(move):
  2362.             raise IllegalMoveError(f"no matching legal move for {move.uci()} ({SQUARE_NAMES[from_square]} -> {SQUARE_NAMES[to_square]}) in {self.fen()}")
  2363.  
  2364.         return move
  2365.  
  2366.     def castling_shredder_fen(self) -> str:
  2367.         castling_rights = self.clean_castling_rights()
  2368.         if not castling_rights:
  2369.             return "-"
  2370.  
  2371.         builder = []
  2372.  
  2373.         for square in scan_reversed(castling_rights & BB_RANK_1):
  2374.             builder.append(FILE_NAMES[square_file(square)].upper())
  2375.  
  2376.         for square in scan_reversed(castling_rights & BB_RANK_8):
  2377.             builder.append(FILE_NAMES[square_file(square)])
  2378.  
  2379.         return "".join(builder)
  2380.  
  2381.     def castling_xfen(self) -> str:
  2382.         builder = []
  2383.  
  2384.         for color in COLORS:
  2385.             king = self.king(color)
  2386.             if king is None:
  2387.                 continue
  2388.  
  2389.             king_file = square_file(king)
  2390.             backrank = BB_RANK_1 if color == WHITE else BB_RANK_8
  2391.  
  2392.             for rook_square in scan_reversed(self.clean_castling_rights() & backrank):
  2393.                 rook_file = square_file(rook_square)
  2394.                 a_side = rook_file < king_file
  2395.  
  2396.                 other_rooks = self.occupied_co[color] & self.rooks & backrank & ~BB_SQUARES[rook_square]
  2397.  
  2398.                 if any((square_file(other) < rook_file) == a_side for other in scan_reversed(other_rooks)):
  2399.                     ch = FILE_NAMES[rook_file]
  2400.                 else:
  2401.                     ch = "q" if a_side else "k"
  2402.  
  2403.                 builder.append(ch.upper() if color == WHITE else ch)
  2404.  
  2405.         if builder:
  2406.             return "".join(builder)
  2407.         else:
  2408.             return "-"
  2409.  
  2410.     def has_pseudo_legal_en_passant(self) -> bool:
  2411.         """Checks if there is a pseudo-legal en passant capture."""
  2412.         return self.ep_square is not None and any(self.generate_pseudo_legal_ep())
  2413.  
  2414.     def has_legal_en_passant(self) -> bool:
  2415.         """Checks if there is a legal en passant capture."""
  2416.         return self.ep_square is not None and any(self.generate_legal_ep())
  2417.  
  2418.     def fen(self, *, shredder: bool = False, en_passant: _EnPassantSpec = "legal", promoted: Optional[bool] = None) -> str:
  2419.         """
  2420.        Gets a FEN representation of the position.
  2421.  
  2422.        A FEN string (e.g.,
  2423.        ``rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1``) consists
  2424.        of the board part :func:`~chess.Board.board_fen()`, the
  2425.        :data:`~chess.Board.turn`, the castling part
  2426.        (:data:`~chess.Board.castling_rights`),
  2427.        the en passant square (:data:`~chess.Board.ep_square`),
  2428.        the :data:`~chess.Board.halfmove_clock`
  2429.        and the :data:`~chess.Board.fullmove_number`.
  2430.  
  2431.        :param shredder: Use :func:`~chess.Board.castling_shredder_fen()`
  2432.            and encode castling rights by the file of the rook
  2433.            (like ``HAha``) instead of the default
  2434.            :func:`~chess.Board.castling_xfen()` (like ``KQkq``).
  2435.        :param en_passant: By default, only fully legal en passant squares
  2436.            are included (:func:`~chess.Board.has_legal_en_passant()`).
  2437.            Pass ``fen`` to strictly follow the FEN specification
  2438.            (always include the en passant square after a two-step pawn move)
  2439.            or ``xfen`` to follow the X-FEN specification
  2440.            (:func:`~chess.Board.has_pseudo_legal_en_passant()`).
  2441.        :param promoted: Mark promoted pieces like ``Q~``. By default, this is
  2442.            only enabled in chess variants where this is relevant.
  2443.        """
  2444.         return " ".join([
  2445.             self.epd(shredder=shredder, en_passant=en_passant, promoted=promoted),
  2446.             str(self.halfmove_clock),
  2447.             str(self.fullmove_number)
  2448.         ])
  2449.  
  2450.     def shredder_fen(self, *, en_passant: _EnPassantSpec = "legal", promoted: Optional[bool] = None) -> str:
  2451.         return " ".join([
  2452.             self.epd(shredder=True, en_passant=en_passant, promoted=promoted),
  2453.             str(self.halfmove_clock),
  2454.             str(self.fullmove_number)
  2455.         ])
  2456.  
  2457.     def set_fen(self, fen: str) -> None:
  2458.         """
  2459.        Parses a FEN and sets the position from it.
  2460.  
  2461.        :raises: :exc:`ValueError` if syntactically invalid. Use
  2462.            :func:`~chess.Board.is_valid()` to detect invalid positions.
  2463.        """
  2464.         parts = fen.split()
  2465.  
  2466.         # Board part.
  2467.         try:
  2468.             board_part = parts.pop(0)
  2469.         except IndexError:
  2470.             raise ValueError("empty fen")
  2471.  
  2472.         # Turn.
  2473.         try:
  2474.             turn_part = parts.pop(0)
  2475.         except IndexError:
  2476.             turn = WHITE
  2477.         else:
  2478.             if turn_part == "w":
  2479.                 turn = WHITE
  2480.             elif turn_part == "b":
  2481.                 turn = BLACK
  2482.             else:
  2483.                 raise ValueError(f"expected 'w' or 'b' for turn part of fen: {fen!r}")
  2484.  
  2485.         # Validate castling part.
  2486.         try:
  2487.             castling_part = parts.pop(0)
  2488.         except IndexError:
  2489.             castling_part = "-"
  2490.         else:
  2491.             if not FEN_CASTLING_REGEX.match(castling_part):
  2492.                 raise ValueError(f"invalid castling part in fen: {fen!r}")
  2493.  
  2494.         # En passant square.
  2495.         try:
  2496.             ep_part = parts.pop(0)
  2497.         except IndexError:
  2498.             ep_square = None
  2499.         else:
  2500.             try:
  2501.                 ep_square = None if ep_part == "-" else SQUARE_NAMES.index(ep_part)
  2502.             except ValueError:
  2503.                 raise ValueError(f"invalid en passant part in fen: {fen!r}")
  2504.  
  2505.         # Check that the half-move part is valid.
  2506.         try:
  2507.             halfmove_part = parts.pop(0)
  2508.         except IndexError:
  2509.             halfmove_clock = 0
  2510.         else:
  2511.             try:
  2512.                 halfmove_clock = int(halfmove_part)
  2513.             except ValueError:
  2514.                 raise ValueError(f"invalid half-move clock in fen: {fen!r}")
  2515.  
  2516.             if halfmove_clock < 0:
  2517.                 raise ValueError(f"half-move clock cannot be negative: {fen!r}")
  2518.  
  2519.         # Check that the full-move number part is valid.
  2520.         # 0 is allowed for compatibility, but later replaced with 1.
  2521.         try:
  2522.             fullmove_part = parts.pop(0)
  2523.         except IndexError:
  2524.             fullmove_number = 1
  2525.         else:
  2526.             try:
  2527.                 fullmove_number = int(fullmove_part)
  2528.             except ValueError:
  2529.                 raise ValueError(f"invalid fullmove number in fen: {fen!r}")
  2530.  
  2531.             if fullmove_number < 0:
  2532.                 raise ValueError(f"fullmove number cannot be negative: {fen!r}")
  2533.  
  2534.             fullmove_number = max(fullmove_number, 1)
  2535.  
  2536.         # All parts should be consumed now.
  2537.         if parts:
  2538.             raise ValueError(f"fen string has more parts than expected: {fen!r}")
  2539.  
  2540.         # Validate the board part and set it.
  2541.         self._set_board_fen(board_part)
  2542.  
  2543.         # Apply.
  2544.         self.turn = turn
  2545.         self._set_castling_fen(castling_part)
  2546.         self.ep_square = ep_square
  2547.         self.halfmove_clock = halfmove_clock
  2548.         self.fullmove_number = fullmove_number
  2549.         self.clear_stack()
  2550.  
  2551.     def _set_castling_fen(self, castling_fen: str) -> None:
  2552.         if not castling_fen or castling_fen == "-":
  2553.             self.castling_rights = BB_EMPTY
  2554.             return
  2555.  
  2556.         if not FEN_CASTLING_REGEX.match(castling_fen):
  2557.             raise ValueError(f"invalid castling fen: {castling_fen!r}")
  2558.  
  2559.         self.castling_rights = BB_EMPTY
  2560.  
  2561.         for flag in castling_fen:
  2562.             color = WHITE if flag.isupper() else BLACK
  2563.             flag = flag.lower()
  2564.             backrank = BB_RANK_1 if color == WHITE else BB_RANK_8
  2565.             rooks = self.occupied_co[color] & self.rooks & backrank
  2566.             king = self.king(color)
  2567.  
  2568.             if flag == "q":
  2569.                 # Select the leftmost rook.
  2570.                 if king is not None and lsb(rooks) < king:
  2571.                     self.castling_rights |= rooks & -rooks
  2572.                 else:
  2573.                     self.castling_rights |= BB_FILE_A & backrank
  2574.             elif flag == "k":
  2575.                 # Select the rightmost rook.
  2576.                 rook = msb(rooks)
  2577.                 if king is not None and king < rook:
  2578.                     self.castling_rights |= BB_SQUARES[rook]
  2579.                 else:
  2580.                     self.castling_rights |= BB_FILE_H & backrank
  2581.             else:
  2582.                 self.castling_rights |= BB_FILES[FILE_NAMES.index(flag)] & backrank
  2583.  
  2584.     def set_castling_fen(self, castling_fen: str) -> None:
  2585.         """
  2586.        Sets castling rights from a string in FEN notation like ``Qqk``.
  2587.  
  2588.        Also clears the move stack.
  2589.  
  2590.        :raises: :exc:`ValueError` if the castling FEN is syntactically
  2591.            invalid.
  2592.        """
  2593.         self._set_castling_fen(castling_fen)
  2594.         self.clear_stack()
  2595.  
  2596.     def set_board_fen(self, fen: str) -> None:
  2597.         super().set_board_fen(fen)
  2598.         self.clear_stack()
  2599.  
  2600.     def set_piece_map(self, pieces: Mapping[Square, Piece]) -> None:
  2601.         super().set_piece_map(pieces)
  2602.         self.clear_stack()
  2603.  
  2604.     def set_chess960_pos(self, scharnagl: int) -> None:
  2605.         super().set_chess960_pos(scharnagl)
  2606.         self.chess960 = True
  2607.         self.turn = WHITE
  2608.         self.castling_rights = self.rooks
  2609.         self.ep_square = None
  2610.         self.halfmove_clock = 0
  2611.         self.fullmove_number = 1
  2612.  
  2613.         self.clear_stack()
  2614.  
  2615.     def chess960_pos(self, *, ignore_turn: bool = False, ignore_castling: bool = False, ignore_counters: bool = True) -> Optional[int]:
  2616.         """
  2617.        Gets the Chess960 starting position index between 0 and 956,
  2618.        or ``None`` if the current position is not a Chess960 starting
  2619.        position.
  2620.  
  2621.        By default, white to move (**ignore_turn**) and full castling rights
  2622.        (**ignore_castling**) are required, but move counters
  2623.        (**ignore_counters**) are ignored.
  2624.        """
  2625.         if self.ep_square:
  2626.             return None
  2627.  
  2628.         if not ignore_turn:
  2629.             if self.turn != WHITE:
  2630.                 return None
  2631.  
  2632.         if not ignore_castling:
  2633.             if self.clean_castling_rights() != self.rooks:
  2634.                 return None
  2635.  
  2636.         if not ignore_counters:
  2637.             if self.fullmove_number != 1 or self.halfmove_clock != 0:
  2638.                 return None
  2639.  
  2640.         return super().chess960_pos()
  2641.  
  2642.     def _epd_operations(self, operations: Mapping[str, Union[None, str, int, float, Move, Iterable[Move]]]) -> str:
  2643.         epd = []
  2644.         first_op = True
  2645.  
  2646.         for opcode, operand in operations.items():
  2647.             assert opcode != "-", "dash (-) is not a valid epd opcode"
  2648.             for blacklisted in [" ", "\n", "\t", "\r"]:
  2649.                 assert blacklisted not in opcode, f"invalid character {blacklisted!r} in epd opcode: {opcode!r}"
  2650.  
  2651.             if not first_op:
  2652.                 epd.append(" ")
  2653.             first_op = False
  2654.             epd.append(opcode)
  2655.  
  2656.             if operand is None:
  2657.                 epd.append(";")
  2658.             elif isinstance(operand, Move):
  2659.                 epd.append(" ")
  2660.                 epd.append(self.san(operand))
  2661.                 epd.append(";")
  2662.             elif isinstance(operand, int):
  2663.                 epd.append(f" {operand};")
  2664.             elif isinstance(operand, float):
  2665.                 assert math.isfinite(operand), f"expected numeric epd operand to be finite, got: {operand}"
  2666.                 epd.append(f" {operand};")
  2667.             elif opcode == "pv" and not isinstance(operand, str) and hasattr(operand, "__iter__"):
  2668.                 position = self.copy(stack=False)
  2669.                 for move in operand:
  2670.                     epd.append(" ")
  2671.                     epd.append(position.san_and_push(move))
  2672.                 epd.append(";")
  2673.             elif opcode in ["am", "bm"] and not isinstance(operand, str) and hasattr(operand, "__iter__"):
  2674.                 for san in sorted(self.san(move) for move in operand):
  2675.                     epd.append(" ")
  2676.                     epd.append(san)
  2677.                 epd.append(";")
  2678.             else:
  2679.                 # Append as escaped string.
  2680.                 epd.append(" \"")
  2681.                 epd.append(str(operand).replace("\\", "\\\\").replace("\t", "\\t").replace("\r", "\\r").replace("\n", "\\n").replace("\"", "\\\""))
  2682.                 epd.append("\";")
  2683.  
  2684.         return "".join(epd)
  2685.  
  2686.     def epd(self, *, shredder: bool = False, en_passant: _EnPassantSpec = "legal", promoted: Optional[bool] = None, **operations: Union[None, str, int, float, Move, Iterable[Move]]) -> str:
  2687.         """
  2688.        Gets an EPD representation of the current position.
  2689.  
  2690.        See :func:`~chess.Board.fen()` for FEN formatting options (*shredder*,
  2691.        *ep_square* and *promoted*).
  2692.  
  2693.        EPD operations can be given as keyword arguments. Supported operands
  2694.        are strings, integers, finite floats, legal moves and ``None``.
  2695.        Additionally, the operation ``pv`` accepts a legal variation as
  2696.        a list of moves. The operations ``am`` and ``bm`` accept a list of
  2697.        legal moves in the current position.
  2698.  
  2699.        The name of the field cannot be a lone dash and cannot contain spaces,
  2700.        newlines, carriage returns or tabs.
  2701.  
  2702.        *hmvc* and *fmvn* are not included by default. You can use:
  2703.  
  2704.        >>> import chess
  2705.        >>>
  2706.        >>> board = chess.Board()
  2707.        >>> board.epd(hmvc=board.halfmove_clock, fmvn=board.fullmove_number)
  2708.        'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - hmvc 0; fmvn 1;'
  2709.        """
  2710.         if en_passant == "fen":
  2711.             ep_square = self.ep_square
  2712.         elif en_passant == "xfen":
  2713.             ep_square = self.ep_square if self.has_pseudo_legal_en_passant() else None
  2714.         else:
  2715.             ep_square = self.ep_square if self.has_legal_en_passant() else None
  2716.  
  2717.         epd = [self.board_fen(promoted=promoted),
  2718.                "w" if self.turn == WHITE else "b",
  2719.                self.castling_shredder_fen() if shredder else self.castling_xfen(),
  2720.                SQUARE_NAMES[ep_square] if ep_square is not None else "-"]
  2721.  
  2722.         if operations:
  2723.             epd.append(self._epd_operations(operations))
  2724.  
  2725.         return " ".join(epd)
  2726.  
  2727.     def _parse_epd_ops(self: BoardT, operation_part: str, make_board: Callable[[], BoardT]) -> Dict[str, Union[None, str, int, float, Move, List[Move]]]:
  2728.         operations: Dict[str, Union[None, str, int, float, Move, List[Move]]] = {}
  2729.         state = "opcode"
  2730.         opcode = ""
  2731.         operand = ""
  2732.         position = None
  2733.  
  2734.         for ch in itertools.chain(operation_part, [None]):
  2735.             if state == "opcode":
  2736.                 if ch in [" ", "\t", "\r", "\n"]:
  2737.                     if opcode == "-":
  2738.                         opcode = ""
  2739.                     elif opcode:
  2740.                         state = "after_opcode"
  2741.                 elif ch is None or ch == ";":
  2742.                     if opcode == "-":
  2743.                         opcode = ""
  2744.                     elif opcode:
  2745.                         operations[opcode] = [] if opcode in ["pv", "am", "bm"] else None
  2746.                         opcode = ""
  2747.                 else:
  2748.                     opcode += ch
  2749.             elif state == "after_opcode":
  2750.                 if ch in [" ", "\t", "\r", "\n"]:
  2751.                     pass
  2752.                 elif ch == "\"":
  2753.                     state = "string"
  2754.                 elif ch is None or ch == ";":
  2755.                     if opcode:
  2756.                         operations[opcode] = [] if opcode in ["pv", "am", "bm"] else None
  2757.                         opcode = ""
  2758.                     state = "opcode"
  2759.                 elif ch in "+-.0123456789":
  2760.                     operand = ch
  2761.                     state = "numeric"
  2762.                 else:
  2763.                     operand = ch
  2764.                     state = "san"
  2765.             elif state == "numeric":
  2766.                 if ch is None or ch == ";":
  2767.                     if "." in operand or "e" in operand or "E" in operand:
  2768.                         parsed = float(operand)
  2769.                         if not math.isfinite(parsed):
  2770.                             raise ValueError(f"invalid numeric operand for epd operation {opcode!r}: {operand!r}")
  2771.                         operations[opcode] = parsed
  2772.                     else:
  2773.                         operations[opcode] = int(operand)
  2774.                     opcode = ""
  2775.                     operand = ""
  2776.                     state = "opcode"
  2777.                 else:
  2778.                     operand += ch
  2779.             elif state == "string":
  2780.                 if ch is None or ch == "\"":
  2781.                     operations[opcode] = operand
  2782.                     opcode = ""
  2783.                     operand = ""
  2784.                     state = "opcode"
  2785.                 elif ch == "\\":
  2786.                     state = "string_escape"
  2787.                 else:
  2788.                     operand += ch
  2789.             elif state == "string_escape":
  2790.                 if ch is None:
  2791.                     operations[opcode] = operand
  2792.                     opcode = ""
  2793.                     operand = ""
  2794.                     state = "opcode"
  2795.                 elif ch == "r":
  2796.                     operand += "\r"
  2797.                     state = "string"
  2798.                 elif ch == "n":
  2799.                     operand += "\n"
  2800.                     state = "string"
  2801.                 elif ch == "t":
  2802.                     operand += "\t"
  2803.                     state = "string"
  2804.                 else:
  2805.                     operand += ch
  2806.                     state = "string"
  2807.             elif state == "san":
  2808.                 if ch is None or ch == ";":
  2809.                     if position is None:
  2810.                         position = make_board()
  2811.  
  2812.                     if opcode == "pv":
  2813.                         # A variation.
  2814.                         variation = []
  2815.                         for token in operand.split():
  2816.                             move = position.parse_xboard(token)
  2817.                             variation.append(move)
  2818.                             position.push(move)
  2819.  
  2820.                         # Reset the position.
  2821.                         while position.move_stack:
  2822.                             position.pop()
  2823.  
  2824.                         operations[opcode] = variation
  2825.                     elif opcode in ["bm", "am"]:
  2826.                         # A set of moves.
  2827.                         operations[opcode] = [position.parse_xboard(token) for token in operand.split()]
  2828.                     else:
  2829.                         # A single move.
  2830.                         operations[opcode] = position.parse_xboard(operand)
  2831.  
  2832.                     opcode = ""
  2833.                     operand = ""
  2834.                     state = "opcode"
  2835.                 else:
  2836.                     operand += ch
  2837.  
  2838.         assert state == "opcode"
  2839.         return operations
  2840.  
  2841.     def set_epd(self, epd: str) -> Dict[str, Union[None, str, int, float, Move, List[Move]]]:
  2842.         """
  2843.        Parses the given EPD string and uses it to set the position.
  2844.  
  2845.        If present, ``hmvc`` and ``fmvn`` are used to set the half-move
  2846.        clock and the full-move number. Otherwise, ``0`` and ``1`` are used.
  2847.  
  2848.        Returns a dictionary of parsed operations. Values can be strings,
  2849.        integers, floats, move objects, or lists of moves.
  2850.  
  2851.        :raises: :exc:`ValueError` if the EPD string is invalid.
  2852.        """
  2853.         parts = epd.strip().rstrip(";").split(None, 4)
  2854.  
  2855.         # Parse ops.
  2856.         if len(parts) > 4:
  2857.             operations = self._parse_epd_ops(parts.pop(), lambda: type(self)(" ".join(parts) + " 0 1"))
  2858.             parts.append(str(operations["hmvc"]) if "hmvc" in operations else "0")
  2859.             parts.append(str(operations["fmvn"]) if "fmvn" in operations else "1")
  2860.             self.set_fen(" ".join(parts))
  2861.             return operations
  2862.         else:
  2863.             self.set_fen(epd)
  2864.             return {}
  2865.  
  2866.     def san(self, move: Move) -> str:
  2867.         """
  2868.        Gets the standard algebraic notation of the given move in the context
  2869.        of the current position.
  2870.        """
  2871.         return self._algebraic(move)
  2872.  
  2873.     def lan(self, move: Move) -> str:
  2874.         """
  2875.        Gets the long algebraic notation of the given move in the context of
  2876.        the current position.
  2877.        """
  2878.         return self._algebraic(move, long=True)
  2879.  
  2880.     def san_and_push(self, move: Move) -> str:
  2881.         return self._algebraic_and_push(move)
  2882.  
  2883.     def _algebraic(self, move: Move, *, long: bool = False) -> str:
  2884.         san = self._algebraic_and_push(move, long=long)
  2885.         self.pop()
  2886.         return san
  2887.  
  2888.     def _algebraic_and_push(self, move: Move, *, long: bool = False) -> str:
  2889.         san = self._algebraic_without_suffix(move, long=long)
  2890.  
  2891.         # Look ahead for check or checkmate.
  2892.         self.push(move)
  2893.         is_check = self.is_check()
  2894.         is_checkmate = (is_check and self.is_checkmate()) or self.is_variant_loss() or self.is_variant_win()
  2895.  
  2896.         # Add check or checkmate suffix.
  2897.         if is_checkmate and move:
  2898.             return san + "#"
  2899.         elif is_check and move:
  2900.             return san + "+"
  2901.         else:
  2902.             return san
  2903.  
  2904.     def _algebraic_without_suffix(self, move: Move, *, long: bool = False) -> str:
  2905.         # Null move.
  2906.         if not move:
  2907.             return "--"
  2908.  
  2909.         # Drops.
  2910.         if move.drop:
  2911.             san = ""
  2912.             if move.drop != PAWN:
  2913.                 san = piece_symbol(move.drop).upper()
  2914.             san += "@" + SQUARE_NAMES[move.to_square]
  2915.             return san
  2916.  
  2917.         # Castling.
  2918.         if self.is_castling(move):
  2919.             if square_file(move.to_square) < square_file(move.from_square):
  2920.                 return "O-O-O"
  2921.             else:
  2922.                 return "O-O"
  2923.  
  2924.         piece_type = self.piece_type_at(move.from_square)
  2925.         assert piece_type, f"san() and lan() expect move to be legal or null, but got {move} in {self.fen()}"
  2926.         capture = self.is_capture(move)
  2927.  
  2928.         if piece_type == PAWN:
  2929.             san = ""
  2930.         else:
  2931.             san = piece_symbol(piece_type).upper()
  2932.  
  2933.         if long:
  2934.             san += SQUARE_NAMES[move.from_square]
  2935.         elif piece_type != PAWN:
  2936.             # Get ambiguous move candidates.
  2937.             # Relevant candidates: not exactly the current move,
  2938.             # but to the same square.
  2939.             others = 0
  2940.             from_mask = self.pieces_mask(piece_type, self.turn)
  2941.             from_mask &= ~BB_SQUARES[move.from_square]
  2942.             to_mask = BB_SQUARES[move.to_square]
  2943.             for candidate in self.generate_legal_moves(from_mask, to_mask):
  2944.                 others |= BB_SQUARES[candidate.from_square]
  2945.  
  2946.             # Disambiguate.
  2947.             if others:
  2948.                 row, column = False, False
  2949.  
  2950.                 if others & BB_RANKS[square_rank(move.from_square)]:
  2951.                     column = True
  2952.  
  2953.                 if others & BB_FILES[square_file(move.from_square)]:
  2954.                     row = True
  2955.                 else:
  2956.                     column = True
  2957.  
  2958.                 if column:
  2959.                     san += FILE_NAMES[square_file(move.from_square)]
  2960.                 if row:
  2961.                     san += RANK_NAMES[square_rank(move.from_square)]
  2962.         elif capture:
  2963.             san += FILE_NAMES[square_file(move.from_square)]
  2964.  
  2965.         # Captures.
  2966.         if capture:
  2967.             san += "x"
  2968.         elif long:
  2969.             san += "-"
  2970.  
  2971.         # Destination square.
  2972.         san += SQUARE_NAMES[move.to_square]
  2973.  
  2974.         # Promotion.
  2975.         if move.promotion:
  2976.             san += "=" + piece_symbol(move.promotion).upper()
  2977.  
  2978.         return san
  2979.  
  2980.     def variation_san(self, variation: Iterable[Move]) -> str:
  2981.         """
  2982.        Given a sequence of moves, returns a string representing the sequence
  2983.        in standard algebraic notation (e.g., ``1. e4 e5 2. Nf3 Nc6`` or
  2984.        ``37...Bg6 38. fxg6``).
  2985.  
  2986.        The board will not be modified as a result of calling this.
  2987.  
  2988.        :raises: :exc:`IllegalMoveError` if any moves in the sequence are illegal.
  2989.        """
  2990.         board = self.copy(stack=False)
  2991.         san = []
  2992.  
  2993.         for move in variation:
  2994.             if not board.is_legal(move):
  2995.                 raise IllegalMoveError(f"illegal move {move} in position {board.fen()}")
  2996.  
  2997.             if board.turn == WHITE:
  2998.                 san.append(f"{board.fullmove_number}. {board.san_and_push(move)}")
  2999.             elif not san:
  3000.                 san.append(f"{board.fullmove_number}...{board.san_and_push(move)}")
  3001.             else:
  3002.                 san.append(board.san_and_push(move))
  3003.  
  3004.         return " ".join(san)
  3005.  
  3006.     def parse_san(self, san: str) -> Move:
  3007.         """
  3008.        Uses the current position as the context to parse a move in standard
  3009.        algebraic notation and returns the corresponding move object.
  3010.  
  3011.        Ambiguous moves are rejected. Overspecified moves (including long
  3012.        algebraic notation) are accepted. Some common syntactical deviations
  3013.        are also accepted.
  3014.  
  3015.        The returned move is guaranteed to be either legal or a null move.
  3016.  
  3017.        :raises:
  3018.            :exc:`ValueError` (specifically an exception specified below) if the SAN is invalid, illegal or ambiguous.
  3019.  
  3020.            - :exc:`InvalidMoveError` if the SAN is syntactically invalid.
  3021.            - :exc:`IllegalMoveError` if the SAN is illegal.
  3022.            - :exc:`AmbiguousMoveError` if the SAN is ambiguous.
  3023.        """
  3024.         # Castling.
  3025.         try:
  3026.             if san in ["O-O", "O-O+", "O-O#", "0-0", "0-0+", "0-0#"]:
  3027.                 return next(move for move in self.generate_castling_moves() if self.is_kingside_castling(move))
  3028.             elif san in ["O-O-O", "O-O-O+", "O-O-O#", "0-0-0", "0-0-0+", "0-0-0#"]:
  3029.                 return next(move for move in self.generate_castling_moves() if self.is_queenside_castling(move))
  3030.         except StopIteration:
  3031.             raise IllegalMoveError(f"illegal san: {san!r} in {self.fen()}")
  3032.  
  3033.         # Match normal moves.
  3034.         match = SAN_REGEX.match(san)
  3035.         if not match:
  3036.             # Null moves.
  3037.             if san in ["--", "Z0", "0000", "@@@@"]:
  3038.                 return Move.null()
  3039.             elif "," in san:
  3040.                 raise InvalidMoveError(f"unsupported multi-leg move: {san!r}")
  3041.             else:
  3042.                 raise InvalidMoveError(f"invalid san: {san!r}")
  3043.  
  3044.         # Get target square. Mask our own pieces to exclude castling moves.
  3045.         to_square = SQUARE_NAMES.index(match.group(4))
  3046.         to_mask = BB_SQUARES[to_square] & ~self.occupied_co[self.turn]
  3047.  
  3048.         # Get the promotion piece type.
  3049.         p = match.group(5)
  3050.         promotion = PIECE_SYMBOLS.index(p[-1].lower()) if p else None
  3051.  
  3052.         # Filter by original square.
  3053.         from_mask = BB_ALL
  3054.         if match.group(2):
  3055.             from_file = FILE_NAMES.index(match.group(2))
  3056.             from_mask &= BB_FILES[from_file]
  3057.         if match.group(3):
  3058.             from_rank = int(match.group(3)) - 1
  3059.             from_mask &= BB_RANKS[from_rank]
  3060.  
  3061.         # Filter by piece type.
  3062.         if match.group(1):
  3063.             piece_type = PIECE_SYMBOLS.index(match.group(1).lower())
  3064.             from_mask &= self.pieces_mask(piece_type, self.turn)
  3065.         elif match.group(2) and match.group(3):
  3066.             # Allow fully specified moves, even if they are not pawn moves,
  3067.             # including castling moves.
  3068.             move = self.find_move(square(from_file, from_rank), to_square, promotion)
  3069.             if move.promotion == promotion:
  3070.                 return move
  3071.             else:
  3072.                 raise IllegalMoveError(f"missing promotion piece type: {san!r} in {self.fen()}")
  3073.         else:
  3074.             from_mask &= self.pawns
  3075.  
  3076.             # Do not allow pawn captures if file is not specified.
  3077.             if not match.group(2):
  3078.                 from_mask &= BB_FILES[square_file(to_square)]
  3079.  
  3080.         # Match legal moves.
  3081.         matched_move = None
  3082.         for move in self.generate_legal_moves(from_mask, to_mask):
  3083.             if move.promotion != promotion:
  3084.                 continue
  3085.  
  3086.             if matched_move:
  3087.                 raise AmbiguousMoveError(f"ambiguous san: {san!r} in {self.fen()}")
  3088.  
  3089.             matched_move = move
  3090.  
  3091.         if not matched_move:
  3092.             raise IllegalMoveError(f"illegal san: {san!r} in {self.fen()}")
  3093.  
  3094.         return matched_move
  3095.  
  3096.     def push_san(self, san: str) -> Move:
  3097.         """
  3098.        Parses a move in standard algebraic notation, makes the move and puts
  3099.        it onto the move stack.
  3100.  
  3101.        Returns the move.
  3102.  
  3103.        :raises:
  3104.            :exc:`ValueError` (specifically an exception specified below) if neither legal nor a null move.
  3105.  
  3106.            - :exc:`InvalidMoveError` if the SAN is syntactically invalid.
  3107.            - :exc:`IllegalMoveError` if the SAN is illegal.
  3108.            - :exc:`AmbiguousMoveError` if the SAN is ambiguous.
  3109.        """
  3110.         move = self.parse_san(san)
  3111.         self.push(move)
  3112.         return move
  3113.  
  3114.     def uci(self, move: Move, *, chess960: Optional[bool] = None) -> str:
  3115.         """
  3116.        Gets the UCI notation of the move.
  3117.  
  3118.        *chess960* defaults to the mode of the board. Pass ``True`` to force
  3119.        Chess960 mode.
  3120.        """
  3121.         if chess960 is None:
  3122.             chess960 = self.chess960
  3123.  
  3124.         move = self._to_chess960(move)
  3125.         move = self._from_chess960(chess960, move.from_square, move.to_square, move.promotion, move.drop)
  3126.         return move.uci()
  3127.  
  3128.     def parse_uci(self, uci: str) -> Move:
  3129.         """
  3130.        Parses the given move in UCI notation.
  3131.  
  3132.        Supports both Chess960 and standard UCI notation.
  3133.  
  3134.        The returned move is guaranteed to be either legal or a null move.
  3135.  
  3136.        :raises:
  3137.            :exc:`ValueError` (specifically an exception specified below) if the move is invalid or illegal in the
  3138.            current position (but not a null move).
  3139.  
  3140.            - :exc:`InvalidMoveError` if the UCI is syntactically invalid.
  3141.            - :exc:`IllegalMoveError` if the UCI is illegal.
  3142.        """
  3143.         move = Move.from_uci(uci)
  3144.  
  3145.         if not move:
  3146.             return move
  3147.  
  3148.         move = self._to_chess960(move)
  3149.         move = self._from_chess960(self.chess960, move.from_square, move.to_square, move.promotion, move.drop)
  3150.  
  3151.         if not self.is_legal(move):
  3152.             raise IllegalMoveError(f"illegal uci: {uci!r} in {self.fen()}")
  3153.  
  3154.         return move
  3155.  
  3156.     def push_uci(self, uci: str) -> Move:
  3157.         """
  3158.        Parses a move in UCI notation and puts it on the move stack.
  3159.  
  3160.        Returns the move.
  3161.  
  3162.        :raises:
  3163.            :exc:`ValueError` (specifically an exception specified below) if the move is invalid or illegal in the
  3164.            current position (but not a null move).
  3165.  
  3166.            - :exc:`InvalidMoveError` if the UCI is syntactically invalid.
  3167.            - :exc:`IllegalMoveError` if the UCI is illegal.
  3168.        """
  3169.         move = self.parse_uci(uci)
  3170.         self.push(move)
  3171.         return move
  3172.  
  3173.     def xboard(self, move: Move, chess960: Optional[bool] = None) -> str:
  3174.         if chess960 is None:
  3175.             chess960 = self.chess960
  3176.  
  3177.         if not chess960 or not self.is_castling(move):
  3178.             return move.xboard()
  3179.         elif self.is_kingside_castling(move):
  3180.             return "O-O"
  3181.         else:
  3182.             return "O-O-O"
  3183.  
  3184.     def parse_xboard(self, xboard: str) -> Move:
  3185.         return self.parse_san(xboard)
  3186.  
  3187.     push_xboard = push_san
  3188.  
  3189.     def is_en_passant(self, move: Move) -> bool:
  3190.         """Checks if the given pseudo-legal move is an en passant capture."""
  3191.         return (self.ep_square == move.to_square and
  3192.                 bool(self.pawns & BB_SQUARES[move.from_square]) and
  3193.                 abs(move.to_square - move.from_square) in [7, 9] and
  3194.                 not self.occupied & BB_SQUARES[move.to_square])
  3195.  
  3196.     def is_capture(self, move: Move) -> bool:
  3197.         """Checks if the given pseudo-legal move is a capture."""
  3198.         touched = BB_SQUARES[move.from_square] ^ BB_SQUARES[move.to_square]
  3199.         return bool(touched & self.occupied_co[not self.turn]) or self.is_en_passant(move)
  3200.  
  3201.     def is_zeroing(self, move: Move) -> bool:
  3202.         """Checks if the given pseudo-legal move is a capture or pawn move."""
  3203.         touched = BB_SQUARES[move.from_square] ^ BB_SQUARES[move.to_square]
  3204.         return bool(touched & self.pawns or touched & self.occupied_co[not self.turn] or move.drop == PAWN)
  3205.  
  3206.     def _reduces_castling_rights(self, move: Move) -> bool:
  3207.         cr = self.clean_castling_rights()
  3208.         touched = BB_SQUARES[move.from_square] ^ BB_SQUARES[move.to_square]
  3209.         return bool(touched & cr or
  3210.                     cr & BB_RANK_1 and touched & self.kings & self.occupied_co[WHITE] & ~self.promoted or
  3211.                     cr & BB_RANK_8 and touched & self.kings & self.occupied_co[BLACK] & ~self.promoted)
  3212.  
  3213.     def is_irreversible(self, move: Move) -> bool:
  3214.         """
  3215.        Checks if the given pseudo-legal move is irreversible.
  3216.  
  3217.        In standard chess, pawn moves, captures, moves that destroy castling
  3218.        rights and moves that cede en passant are irreversible.
  3219.  
  3220.        This method has false-negatives with forced lines. For example, a check
  3221.        that will force the king to lose castling rights is not considered
  3222.        irreversible. Only the actual king move is.
  3223.        """
  3224.         return self.is_zeroing(move) or self._reduces_castling_rights(move) or self.has_legal_en_passant()
  3225.  
  3226.     def is_castling(self, move: Move) -> bool:
  3227.         """Checks if the given pseudo-legal move is a castling move."""
  3228.         if self.kings & BB_SQUARES[move.from_square]:
  3229.             diff = square_file(move.from_square) - square_file(move.to_square)
  3230.             return abs(diff) > 1 or bool(self.rooks & self.occupied_co[self.turn] & BB_SQUARES[move.to_square])
  3231.         return False
  3232.  
  3233.     def is_kingside_castling(self, move: Move) -> bool:
  3234.         """
  3235.        Checks if the given pseudo-legal move is a kingside castling move.
  3236.        """
  3237.         return self.is_castling(move) and square_file(move.to_square) > square_file(move.from_square)
  3238.  
  3239.     def is_queenside_castling(self, move: Move) -> bool:
  3240.         """
  3241.        Checks if the given pseudo-legal move is a queenside castling move.
  3242.        """
  3243.         return self.is_castling(move) and square_file(move.to_square) < square_file(move.from_square)
  3244.  
  3245.     def clean_castling_rights(self) -> Bitboard:
  3246.         """
  3247.        Returns valid castling rights filtered from
  3248.        :data:`~chess.Board.castling_rights`.
  3249.        """
  3250.         if self._stack:
  3251.             # No new castling rights are assigned in a game, so we can assume
  3252.             # they were filtered already.
  3253.             return self.castling_rights
  3254.  
  3255.         castling = self.castling_rights & self.rooks
  3256.         white_castling = castling & BB_RANK_1 & self.occupied_co[WHITE]
  3257.         black_castling = castling & BB_RANK_8 & self.occupied_co[BLACK]
  3258.  
  3259.         if not self.chess960:
  3260.             # The rooks must be on a1, h1, a8 or h8.
  3261.             white_castling &= (BB_A1 | BB_H1)
  3262.             black_castling &= (BB_A8 | BB_H8)
  3263.  
  3264.             # The kings must be on e1 or e8.
  3265.             if not self.occupied_co[WHITE] & self.kings & ~self.promoted & BB_E1:
  3266.                 white_castling = 0
  3267.             if not self.occupied_co[BLACK] & self.kings & ~self.promoted & BB_E8:
  3268.                 black_castling = 0
  3269.  
  3270.             return white_castling | black_castling
  3271.         else:
  3272.             # The kings must be on the back rank.
  3273.             white_king_mask = self.occupied_co[WHITE] & self.kings & BB_RANK_1 & ~self.promoted
  3274.             black_king_mask = self.occupied_co[BLACK] & self.kings & BB_RANK_8 & ~self.promoted
  3275.             if not white_king_mask:
  3276.                 white_castling = 0
  3277.             if not black_king_mask:
  3278.                 black_castling = 0
  3279.  
  3280.             # There are only two ways of castling, a-side and h-side, and the
  3281.             # king must be between the rooks.
  3282.             white_a_side = white_castling & -white_castling
  3283.             white_h_side = BB_SQUARES[msb(white_castling)] if white_castling else 0
  3284.  
  3285.             if white_a_side and msb(white_a_side) > msb(white_king_mask):
  3286.                 white_a_side = 0
  3287.             if white_h_side and msb(white_h_side) < msb(white_king_mask):
  3288.                 white_h_side = 0
  3289.  
  3290.             black_a_side = (black_castling & -black_castling)
  3291.             black_h_side = BB_SQUARES[msb(black_castling)] if black_castling else BB_EMPTY
  3292.  
  3293.             if black_a_side and msb(black_a_side) > msb(black_king_mask):
  3294.                 black_a_side = 0
  3295.             if black_h_side and msb(black_h_side) < msb(black_king_mask):
  3296.                 black_h_side = 0
  3297.  
  3298.             # Done.
  3299.             return black_a_side | black_h_side | white_a_side | white_h_side
  3300.  
  3301.     def has_castling_rights(self, color: Color) -> bool:
  3302.         """Checks if the given side has castling rights."""
  3303.         backrank = BB_RANK_1 if color == WHITE else BB_RANK_8
  3304.         return bool(self.clean_castling_rights() & backrank)
  3305.  
  3306.     def has_kingside_castling_rights(self, color: Color) -> bool:
  3307.         """
  3308.        Checks if the given side has kingside (that is h-side in Chess960)
  3309.        castling rights.
  3310.        """
  3311.         backrank = BB_RANK_1 if color == WHITE else BB_RANK_8
  3312.         king_mask = self.kings & self.occupied_co[color] & backrank & ~self.promoted
  3313.         if not king_mask:
  3314.             return False
  3315.  
  3316.         castling_rights = self.clean_castling_rights() & backrank
  3317.         while castling_rights:
  3318.             rook = castling_rights & -castling_rights
  3319.  
  3320.             if rook > king_mask:
  3321.                 return True
  3322.  
  3323.             castling_rights &= castling_rights - 1
  3324.  
  3325.         return False
  3326.  
  3327.     def has_queenside_castling_rights(self, color: Color) -> bool:
  3328.         """
  3329.        Checks if the given side has queenside (that is a-side in Chess960)
  3330.        castling rights.
  3331.        """
  3332.         backrank = BB_RANK_1 if color == WHITE else BB_RANK_8
  3333.         king_mask = self.kings & self.occupied_co[color] & backrank & ~self.promoted
  3334.         if not king_mask:
  3335.             return False
  3336.  
  3337.         castling_rights = self.clean_castling_rights() & backrank
  3338.         while castling_rights:
  3339.             rook = castling_rights & -castling_rights
  3340.  
  3341.             if rook < king_mask:
  3342.                 return True
  3343.  
  3344.             castling_rights &= castling_rights - 1
  3345.  
  3346.         return False
  3347.  
  3348.     def has_chess960_castling_rights(self) -> bool:
  3349.         """
  3350.        Checks if there are castling rights that are only possible in Chess960.
  3351.        """
  3352.         # Get valid Chess960 castling rights.
  3353.         chess960 = self.chess960
  3354.         self.chess960 = True
  3355.         castling_rights = self.clean_castling_rights()
  3356.         self.chess960 = chess960
  3357.  
  3358.         # Standard chess castling rights can only be on the standard
  3359.         # starting rook squares.
  3360.         if castling_rights & ~BB_CORNERS:
  3361.             return True
  3362.  
  3363.         # If there are any castling rights in standard chess, the king must be
  3364.         # on e1 or e8.
  3365.         if castling_rights & BB_RANK_1 and not self.occupied_co[WHITE] & self.kings & BB_E1:
  3366.             return True
  3367.         if castling_rights & BB_RANK_8 and not self.occupied_co[BLACK] & self.kings & BB_E8:
  3368.             return True
  3369.  
  3370.         return False
  3371.  
  3372.     def status(self) -> Status:
  3373.         """
  3374.        Gets a bitmask of possible problems with the position.
  3375.  
  3376.        :data:`~chess.STATUS_VALID` if all basic validity requirements are met.
  3377.        This does not imply that the position is actually reachable with a
  3378.        series of legal moves from the starting position.
  3379.  
  3380.        Otherwise, bitwise combinations of:
  3381.        :data:`~chess.STATUS_NO_WHITE_KING`,
  3382.        :data:`~chess.STATUS_NO_BLACK_KING`,
  3383.        :data:`~chess.STATUS_TOO_MANY_KINGS`,
  3384.        :data:`~chess.STATUS_TOO_MANY_WHITE_PAWNS`,
  3385.        :data:`~chess.STATUS_TOO_MANY_BLACK_PAWNS`,
  3386.        :data:`~chess.STATUS_PAWNS_ON_BACKRANK`,
  3387.        :data:`~chess.STATUS_TOO_MANY_WHITE_PIECES`,
  3388.        :data:`~chess.STATUS_TOO_MANY_BLACK_PIECES`,
  3389.        :data:`~chess.STATUS_BAD_CASTLING_RIGHTS`,
  3390.        :data:`~chess.STATUS_INVALID_EP_SQUARE`,
  3391.        :data:`~chess.STATUS_OPPOSITE_CHECK`,
  3392.        :data:`~chess.STATUS_EMPTY`,
  3393.        :data:`~chess.STATUS_RACE_CHECK`,
  3394.        :data:`~chess.STATUS_RACE_OVER`,
  3395.        :data:`~chess.STATUS_RACE_MATERIAL`,
  3396.        :data:`~chess.STATUS_TOO_MANY_CHECKERS`,
  3397.        :data:`~chess.STATUS_IMPOSSIBLE_CHECK`.
  3398.        """
  3399.         errors = STATUS_VALID
  3400.  
  3401.         # There must be at least one piece.
  3402.         if not self.occupied:
  3403.             errors |= STATUS_EMPTY
  3404.  
  3405.         # There must be exactly one king of each color.
  3406.         if not self.occupied_co[WHITE] & self.kings:
  3407.             errors |= STATUS_NO_WHITE_KING
  3408.         if not self.occupied_co[BLACK] & self.kings:
  3409.             errors |= STATUS_NO_BLACK_KING
  3410.         if popcount(self.occupied & self.kings) > 2:
  3411.             errors |= STATUS_TOO_MANY_KINGS
  3412.  
  3413.         # There can not be more than 16 pieces of any color.
  3414.         if popcount(self.occupied_co[WHITE]) > 16:
  3415.             errors |= STATUS_TOO_MANY_WHITE_PIECES
  3416.         if popcount(self.occupied_co[BLACK]) > 16:
  3417.             errors |= STATUS_TOO_MANY_BLACK_PIECES
  3418.  
  3419.         # There can not be more than 8 pawns of any color.
  3420.         if popcount(self.occupied_co[WHITE] & self.pawns) > 8:
  3421.             errors |= STATUS_TOO_MANY_WHITE_PAWNS
  3422.         if popcount(self.occupied_co[BLACK] & self.pawns) > 8:
  3423.             errors |= STATUS_TOO_MANY_BLACK_PAWNS
  3424.  
  3425.         # Pawns can not be on the back rank.
  3426.         if self.pawns & BB_BACKRANKS:
  3427.             errors |= STATUS_PAWNS_ON_BACKRANK
  3428.  
  3429.         # Castling rights.
  3430.         if self.castling_rights != self.clean_castling_rights():
  3431.             errors |= STATUS_BAD_CASTLING_RIGHTS
  3432.  
  3433.         # En passant.
  3434.         valid_ep_square = self._valid_ep_square()
  3435.         if self.ep_square != valid_ep_square:
  3436.             errors |= STATUS_INVALID_EP_SQUARE
  3437.  
  3438.         # Side to move giving check.
  3439.         if self.was_into_check():
  3440.             errors |= STATUS_OPPOSITE_CHECK
  3441.  
  3442.         # More than the maximum number of possible checkers in the variant.
  3443.         checkers = self.checkers_mask()
  3444.         our_kings = self.kings & self.occupied_co[self.turn] & ~self.promoted
  3445.         if checkers:
  3446.             if popcount(checkers) > 2:
  3447.                 errors |= STATUS_TOO_MANY_CHECKERS
  3448.  
  3449.             if valid_ep_square is not None:
  3450.                 pushed_to = valid_ep_square ^ A2
  3451.                 pushed_from = valid_ep_square ^ A4
  3452.                 occupied_before = (self.occupied & ~BB_SQUARES[pushed_to]) | BB_SQUARES[pushed_from]
  3453.                 if popcount(checkers) > 1 or (
  3454.                         msb(checkers) != pushed_to and
  3455.                         self._attacked_for_king(our_kings, occupied_before)):
  3456.                     errors |= STATUS_IMPOSSIBLE_CHECK
  3457.             else:
  3458.                 if popcount(checkers) > 2 or (popcount(checkers) == 2 and ray(lsb(checkers), msb(checkers)) & our_kings):
  3459.                     errors |= STATUS_IMPOSSIBLE_CHECK
  3460.  
  3461.         return errors
  3462.  
  3463.     def _valid_ep_square(self) -> Optional[Square]:
  3464.         if not self.ep_square:
  3465.             return None
  3466.  
  3467.         if self.turn == WHITE:
  3468.             ep_rank = 5
  3469.             pawn_mask = shift_down(BB_SQUARES[self.ep_square])
  3470.             seventh_rank_mask = shift_up(BB_SQUARES[self.ep_square])
  3471.         else:
  3472.             ep_rank = 2
  3473.             pawn_mask = shift_up(BB_SQUARES[self.ep_square])
  3474.             seventh_rank_mask = shift_down(BB_SQUARES[self.ep_square])
  3475.  
  3476.         # The en passant square must be on the third or sixth rank.
  3477.         if square_rank(self.ep_square) != ep_rank:
  3478.             return None
  3479.  
  3480.         # The last move must have been a double pawn push, so there must
  3481.         # be a pawn of the correct color on the fourth or fifth rank.
  3482.         if not self.pawns & self.occupied_co[not self.turn] & pawn_mask:
  3483.             return None
  3484.  
  3485.         # And the en passant square must be empty.
  3486.         if self.occupied & BB_SQUARES[self.ep_square]:
  3487.             return None
  3488.  
  3489.         # And the second rank must be empty.
  3490.         if self.occupied & seventh_rank_mask:
  3491.             return None
  3492.  
  3493.         return self.ep_square
  3494.  
  3495.     def is_valid(self) -> bool:
  3496.         """
  3497.        Checks some basic validity requirements.
  3498.  
  3499.        See :func:`~chess.Board.status()` for details.
  3500.        """
  3501.         return self.status() == STATUS_VALID
  3502.  
  3503.     def _ep_skewered(self, king: Square, capturer: Square) -> bool:
  3504.         # Handle the special case where the king would be in check if the
  3505.         # pawn and its capturer disappear from the rank.
  3506.  
  3507.         # Vertical skewers of the captured pawn are not possible. (Pins on
  3508.         # the capturer are not handled here.)
  3509.         assert self.ep_square is not None
  3510.  
  3511.         last_double = self.ep_square + (-8 if self.turn == WHITE else 8)
  3512.  
  3513.         occupancy = (self.occupied & ~BB_SQUARES[last_double] &
  3514.                      ~BB_SQUARES[capturer] | BB_SQUARES[self.ep_square])
  3515.  
  3516.         # Horizontal attack on the fifth or fourth rank.
  3517.         horizontal_attackers = self.occupied_co[not self.turn] & (self.rooks | self.queens)
  3518.         if BB_RANK_ATTACKS[king][BB_RANK_MASKS[king] & occupancy] & horizontal_attackers:
  3519.             return True
  3520.  
  3521.         # Diagonal skewers. These are not actually possible in a real game,
  3522.         # because if the latest double pawn move covers a diagonal attack,
  3523.         # then the other side would have been in check already.
  3524.         diagonal_attackers = self.occupied_co[not self.turn] & (self.bishops | self.queens)
  3525.         if BB_DIAG_ATTACKS[king][BB_DIAG_MASKS[king] & occupancy] & diagonal_attackers:
  3526.             return True
  3527.  
  3528.         return False
  3529.  
  3530.     def _slider_blockers(self, king: Square) -> Bitboard:
  3531.         rooks_and_queens = self.rooks | self.queens
  3532.         bishops_and_queens = self.bishops | self.queens
  3533.  
  3534.         snipers = ((BB_RANK_ATTACKS[king][0] & rooks_and_queens) |
  3535.                    (BB_FILE_ATTACKS[king][0] & rooks_and_queens) |
  3536.                    (BB_DIAG_ATTACKS[king][0] & bishops_and_queens))
  3537.  
  3538.         blockers = 0
  3539.  
  3540.         for sniper in scan_reversed(snipers & self.occupied_co[not self.turn]):
  3541.             b = between(king, sniper) & self.occupied
  3542.  
  3543.             # Add to blockers if exactly one piece in-between.
  3544.             if b and BB_SQUARES[msb(b)] == b:
  3545.                 blockers |= b
  3546.  
  3547.         return blockers & self.occupied_co[self.turn]
  3548.  
  3549.     def _is_safe(self, king: Square, blockers: Bitboard, move: Move) -> bool:
  3550.         if move.from_square == king:
  3551.             if self.is_castling(move):
  3552.                 return True
  3553.             else:
  3554.                 return not self.is_attacked_by(not self.turn, move.to_square)
  3555.         elif self.is_en_passant(move):
  3556.             return bool(self.pin_mask(self.turn, move.from_square) & BB_SQUARES[move.to_square] and
  3557.                         not self._ep_skewered(king, move.from_square))
  3558.         else:
  3559.             return bool(not blockers & BB_SQUARES[move.from_square] or
  3560.                         ray(move.from_square, move.to_square) & BB_SQUARES[king])
  3561.  
  3562.     def _generate_evasions(self, king: Square, checkers: Bitboard, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]:
  3563.         sliders = checkers & (self.bishops | self.rooks | self.queens)
  3564.  
  3565.         attacked = 0
  3566.         for checker in scan_reversed(sliders):
  3567.             attacked |= ray(king, checker) & ~BB_SQUARES[checker]
  3568.  
  3569.         if BB_SQUARES[king] & from_mask:
  3570.             for to_square in scan_reversed(BB_KING_ATTACKS[king] & ~self.occupied_co[self.turn] & ~attacked & to_mask):
  3571.                 yield Move(king, to_square)
  3572.  
  3573.         checker = msb(checkers)
  3574.         if BB_SQUARES[checker] == checkers:
  3575.             # Capture or block a single checker.
  3576.             target = between(king, checker) | checkers
  3577.  
  3578.             yield from self.generate_pseudo_legal_moves(~self.kings & from_mask, target & to_mask)
  3579.  
  3580.             # Capture the checking pawn en passant (but avoid yielding
  3581.             # duplicate moves).
  3582.             if self.ep_square and not BB_SQUARES[self.ep_square] & target:
  3583.                 last_double = self.ep_square + (-8 if self.turn == WHITE else 8)
  3584.                 if last_double == checker:
  3585.                     yield from self.generate_pseudo_legal_ep(from_mask, to_mask)
  3586.  
  3587.     def generate_legal_moves(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]:
  3588.         if self.is_variant_end():
  3589.             return
  3590.  
  3591.         king_mask = self.kings & self.occupied_co[self.turn]
  3592.         if king_mask:
  3593.             king = msb(king_mask)
  3594.             blockers = self._slider_blockers(king)
  3595.             checkers = self.attackers_mask(not self.turn, king)
  3596.             if checkers:
  3597.                 for move in self._generate_evasions(king, checkers, from_mask, to_mask):
  3598.                     if self._is_safe(king, blockers, move):
  3599.                         yield move
  3600.             else:
  3601.                 for move in self.generate_pseudo_legal_moves(from_mask, to_mask):
  3602.                     if self._is_safe(king, blockers, move):
  3603.                         yield move
  3604.         else:
  3605.             yield from self.generate_pseudo_legal_moves(from_mask, to_mask)
  3606.  
  3607.     def generate_legal_ep(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]:
  3608.         if self.is_variant_end():
  3609.             return
  3610.  
  3611.         for move in self.generate_pseudo_legal_ep(from_mask, to_mask):
  3612.             if not self.is_into_check(move):
  3613.                 yield move
  3614.  
  3615.     def generate_legal_captures(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]:
  3616.         return itertools.chain(
  3617.             self.generate_legal_moves(from_mask, to_mask & self.occupied_co[not self.turn]),
  3618.             self.generate_legal_ep(from_mask, to_mask))
  3619.  
  3620.     def _attacked_for_king(self, path: Bitboard, occupied: Bitboard) -> bool:
  3621.         return any(self._attackers_mask(not self.turn, sq, occupied) for sq in scan_reversed(path))
  3622.  
  3623.     def generate_castling_moves(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]:
  3624.         if self.is_variant_end():
  3625.             return
  3626.  
  3627.         backrank = BB_RANK_1 if self.turn == WHITE else BB_RANK_8
  3628.         king = self.occupied_co[self.turn] & self.kings & ~self.promoted & backrank & from_mask
  3629.         king &= -king
  3630.         if not king:
  3631.             return
  3632.  
  3633.         bb_c = BB_FILE_C & backrank
  3634.         bb_d = BB_FILE_D & backrank
  3635.         bb_f = BB_FILE_F & backrank
  3636.         bb_g = BB_FILE_G & backrank
  3637.  
  3638.         for candidate in scan_reversed(self.clean_castling_rights() & backrank & to_mask):
  3639.             rook = BB_SQUARES[candidate]
  3640.  
  3641.             a_side = rook < king
  3642.             king_to = bb_c if a_side else bb_g
  3643.             rook_to = bb_d if a_side else bb_f
  3644.  
  3645.             king_path = between(msb(king), msb(king_to))
  3646.             rook_path = between(candidate, msb(rook_to))
  3647.  
  3648.             if not ((self.occupied ^ king ^ rook) & (king_path | rook_path | king_to | rook_to) or
  3649.                     self._attacked_for_king(king_path | king, self.occupied ^ king) or
  3650.                     self._attacked_for_king(king_to, self.occupied ^ king ^ rook ^ rook_to)):
  3651.                 yield self._from_chess960(self.chess960, msb(king), candidate)
  3652.  
  3653.     def _from_chess960(self, chess960: bool, from_square: Square, to_square: Square, promotion: Optional[PieceType] = None, drop: Optional[PieceType] = None) -> Move:
  3654.         if not chess960 and promotion is None and drop is None:
  3655.             if from_square == E1 and self.kings & BB_E1:
  3656.                 if to_square == H1:
  3657.                     return Move(E1, G1)
  3658.                 elif to_square == A1:
  3659.                     return Move(E1, C1)
  3660.             elif from_square == E8 and self.kings & BB_E8:
  3661.                 if to_square == H8:
  3662.                     return Move(E8, G8)
  3663.                 elif to_square == A8:
  3664.                     return Move(E8, C8)
  3665.  
  3666.         return Move(from_square, to_square, promotion, drop)
  3667.  
  3668.     def _to_chess960(self, move: Move) -> Move:
  3669.         if move.from_square == E1 and self.kings & BB_E1:
  3670.             if move.to_square == G1 and not self.rooks & BB_G1:
  3671.                 return Move(E1, H1)
  3672.             elif move.to_square == C1 and not self.rooks & BB_C1:
  3673.                 return Move(E1, A1)
  3674.         elif move.from_square == E8 and self.kings & BB_E8:
  3675.             if move.to_square == G8 and not self.rooks & BB_G8:
  3676.                 return Move(E8, H8)
  3677.             elif move.to_square == C8 and not self.rooks & BB_C8:
  3678.                 return Move(E8, A8)
  3679.  
  3680.         return move
  3681.  
  3682.     def _transposition_key(self) -> Hashable:
  3683.         return (self.pawns, self.knights, self.bishops, self.rooks,
  3684.                 self.queens, self.kings,
  3685.                 self.occupied_co[WHITE], self.occupied_co[BLACK],
  3686.                 self.turn, self.clean_castling_rights(),
  3687.                 self.ep_square if self.has_legal_en_passant() else None)
  3688.  
  3689.     def __repr__(self) -> str:
  3690.         if not self.chess960:
  3691.             return f"{type(self).__name__}({self.fen()!r})"
  3692.         else:
  3693.             return f"{type(self).__name__}({self.fen()!r}, chess960=True)"
  3694.  
  3695.     def _repr_svg_(self) -> str:
  3696.         import chess.svg
  3697.         return chess.svg.board(
  3698.             board=self,
  3699.             size=390,
  3700.             lastmove=self.peek() if self.move_stack else None,
  3701.             check=self.king(self.turn) if self.is_check() else None)
  3702.  
  3703.     def __eq__(self, board: object) -> bool:
  3704.         if isinstance(board, Board):
  3705.             return (
  3706.                 self.halfmove_clock == board.halfmove_clock and
  3707.                 self.fullmove_number == board.fullmove_number and
  3708.                 type(self).uci_variant == type(board).uci_variant and
  3709.                 self._transposition_key() == board._transposition_key())
  3710.         else:
  3711.             return NotImplemented
  3712.  
  3713.     def apply_transform(self, f: Callable[[Bitboard], Bitboard]) -> None:
  3714.         super().apply_transform(f)
  3715.         self.clear_stack()
  3716.         self.ep_square = None if self.ep_square is None else msb(f(BB_SQUARES[self.ep_square]))
  3717.         self.castling_rights = f(self.castling_rights)
  3718.  
  3719.     def transform(self: BoardT, f: Callable[[Bitboard], Bitboard]) -> BoardT:
  3720.         board = self.copy(stack=False)
  3721.         board.apply_transform(f)
  3722.         return board
  3723.  
  3724.     def apply_mirror(self: BoardT) -> None:
  3725.         super().apply_mirror()
  3726.         self.turn = not self.turn
  3727.  
  3728.     def mirror(self: BoardT) -> BoardT:
  3729.         """
  3730.        Returns a mirrored copy of the board.
  3731.  
  3732.        The board is mirrored vertically and piece colors are swapped, so that
  3733.        the position is equivalent modulo color. Also swap the "en passant"
  3734.        square, castling rights and turn.
  3735.  
  3736.        Alternatively, :func:`~chess.Board.apply_mirror()` can be used
  3737.        to mirror the board.
  3738.        """
  3739.         board = self.copy()
  3740.         board.apply_mirror()
  3741.         return board
  3742.  
  3743.     def copy(self: BoardT, *, stack: Union[bool, int] = True) -> BoardT:
  3744.         """
  3745.        Creates a copy of the board.
  3746.  
  3747.        Defaults to copying the entire move stack. Alternatively, *stack* can
  3748.        be ``False``, or an integer to copy a limited number of moves.
  3749.        """
  3750.         board = super().copy()
  3751.  
  3752.         board.chess960 = self.chess960
  3753.  
  3754.         board.ep_square = self.ep_square
  3755.         board.castling_rights = self.castling_rights
  3756.         board.turn = self.turn
  3757.         board.fullmove_number = self.fullmove_number
  3758.         board.halfmove_clock = self.halfmove_clock
  3759.  
  3760.         if stack:
  3761.             stack = len(self.move_stack) if stack is True else stack
  3762.             board.move_stack = [copy.copy(move) for move in self.move_stack[-stack:]]
  3763.             board._stack = self._stack[-stack:]
  3764.  
  3765.         return board
  3766.  
  3767.     @classmethod
  3768.     def empty(cls: Type[BoardT], *, chess960: bool = False) -> BoardT:
  3769.         """Creates a new empty board. Also see :func:`~chess.Board.clear()`."""
  3770.         return cls(None, chess960=chess960)
  3771.  
  3772.     @classmethod
  3773.     def from_epd(cls: Type[BoardT], epd: str, *, chess960: bool = False) -> Tuple[BoardT, Dict[str, Union[None, str, int, float, Move, List[Move]]]]:
  3774.         """
  3775.        Creates a new board from an EPD string. See
  3776.        :func:`~chess.Board.set_epd()`.
  3777.  
  3778.        Returns the board and the dictionary of parsed operations as a tuple.
  3779.        """
  3780.         board = cls.empty(chess960=chess960)
  3781.         return board, board.set_epd(epd)
  3782.  
  3783.     @classmethod
  3784.     def from_chess960_pos(cls: Type[BoardT], scharnagl: int) -> BoardT:
  3785.         board = cls.empty(chess960=True)
  3786.         board.set_chess960_pos(scharnagl)
  3787.         return board
  3788.  
  3789.  
  3790. class PseudoLegalMoveGenerator:
  3791.  
  3792.     def __init__(self, board: Board) -> None:
  3793.         self.board = board
  3794.  
  3795.     def __bool__(self) -> bool:
  3796.         return any(self.board.generate_pseudo_legal_moves())
  3797.  
  3798.     def count(self) -> int:
  3799.         # List conversion is faster than iterating.
  3800.         return len(list(self))
  3801.  
  3802.     def __iter__(self) -> Iterator[Move]:
  3803.         return self.board.generate_pseudo_legal_moves()
  3804.  
  3805.     def __contains__(self, move: Move) -> bool:
  3806.         return self.board.is_pseudo_legal(move)
  3807.  
  3808.     def __repr__(self) -> str:
  3809.         builder = []
  3810.  
  3811.         for move in self:
  3812.             if self.board.is_legal(move):
  3813.                 builder.append(self.board.san(move))
  3814.             else:
  3815.                 builder.append(self.board.uci(move))
  3816.  
  3817.         sans = ", ".join(builder)
  3818.         return f"<PseudoLegalMoveGenerator at {id(self):#x} ({sans})>"
  3819.  
  3820.  
  3821. class LegalMoveGenerator:
  3822.  
  3823.     def __init__(self, board: Board) -> None:
  3824.         self.board = board
  3825.  
  3826.     def __bool__(self) -> bool:
  3827.         return any(self.board.generate_legal_moves())
  3828.  
  3829.     def count(self) -> int:
  3830.         # List conversion is faster than iterating.
  3831.         return len(list(self))
  3832.  
  3833.     def __iter__(self) -> Iterator[Move]:
  3834.         return self.board.generate_legal_moves()
  3835.  
  3836.     def __contains__(self, move: Move) -> bool:
  3837.         return self.board.is_legal(move)
  3838.  
  3839.     def __repr__(self) -> str:
  3840.         sans = ", ".join(self.board.san(move) for move in self)
  3841.         return f"<LegalMoveGenerator at {id(self):#x} ({sans})>"
  3842.  
  3843.  
  3844. IntoSquareSet = Union[SupportsInt, Iterable[Square]]
  3845.  
  3846. class SquareSet:
  3847.     """
  3848.    A set of squares.
  3849.  
  3850.    >>> import chess
  3851.    >>>
  3852.    >>> squares = chess.SquareSet([chess.A8, chess.A1])
  3853.    >>> squares
  3854.    SquareSet(0x0100_0000_0000_0001)
  3855.  
  3856.    >>> squares = chess.SquareSet(chess.BB_A8 | chess.BB_RANK_1)
  3857.    >>> squares
  3858.    SquareSet(0x0100_0000_0000_00ff)
  3859.  
  3860.    >>> print(squares)
  3861.    1 . . . . . . .
  3862.    . . . . . . . .
  3863.    . . . . . . . .
  3864.    . . . . . . . .
  3865.    . . . . . . . .
  3866.    . . . . . . . .
  3867.    . . . . . . . .
  3868.    1 1 1 1 1 1 1 1
  3869.  
  3870.    >>> len(squares)
  3871.    9
  3872.  
  3873.    >>> bool(squares)
  3874.    True
  3875.  
  3876.    >>> chess.B1 in squares
  3877.    True
  3878.  
  3879.    >>> for square in squares:
  3880.    ...     # 0 -- chess.A1
  3881.    ...     # 1 -- chess.B1
  3882.    ...     # 2 -- chess.C1
  3883.    ...     # 3 -- chess.D1
  3884.    ...     # 4 -- chess.E1
  3885.    ...     # 5 -- chess.F1
  3886.    ...     # 6 -- chess.G1
  3887.    ...     # 7 -- chess.H1
  3888.    ...     # 56 -- chess.A8
  3889.    ...     print(square)
  3890.    ...
  3891.    0
  3892.    1
  3893.    2
  3894.    3
  3895.    4
  3896.    5
  3897.    6
  3898.    7
  3899.    56
  3900.  
  3901.    >>> list(squares)
  3902.    [0, 1, 2, 3, 4, 5, 6, 7, 56]
  3903.  
  3904.    Square sets are internally represented by 64-bit integer masks of the
  3905.    included squares. Bitwise operations can be used to compute unions,
  3906.    intersections and shifts.
  3907.  
  3908.    >>> int(squares)
  3909.    72057594037928191
  3910.  
  3911.    Also supports common set operations like
  3912.    :func:`~chess.SquareSet.issubset()`, :func:`~chess.SquareSet.issuperset()`,
  3913.    :func:`~chess.SquareSet.union()`, :func:`~chess.SquareSet.intersection()`,
  3914.    :func:`~chess.SquareSet.difference()`,
  3915.    :func:`~chess.SquareSet.symmetric_difference()` and
  3916.    :func:`~chess.SquareSet.copy()` as well as
  3917.    :func:`~chess.SquareSet.update()`,
  3918.    :func:`~chess.SquareSet.intersection_update()`,
  3919.    :func:`~chess.SquareSet.difference_update()`,
  3920.    :func:`~chess.SquareSet.symmetric_difference_update()` and
  3921.    :func:`~chess.SquareSet.clear()`.
  3922.    """
  3923.  
  3924.     def __init__(self, squares: IntoSquareSet = BB_EMPTY) -> None:
  3925.         try:
  3926.             self.mask = squares.__int__() & BB_ALL  # type: ignore
  3927.             return
  3928.         except AttributeError:
  3929.             self.mask = 0
  3930.  
  3931.         # Try squares as an iterable. Not under except clause for nicer
  3932.         # backtraces.
  3933.         for square in squares:  # type: ignore
  3934.             self.add(square)
  3935.  
  3936.     # Set
  3937.  
  3938.     def __contains__(self, square: Square) -> bool:
  3939.         return bool(BB_SQUARES[square] & self.mask)
  3940.  
  3941.     def __iter__(self) -> Iterator[Square]:
  3942.         return scan_forward(self.mask)
  3943.  
  3944.     def __reversed__(self) -> Iterator[Square]:
  3945.         return scan_reversed(self.mask)
  3946.  
  3947.     def __len__(self) -> int:
  3948.         return popcount(self.mask)
  3949.  
  3950.     # MutableSet
  3951.  
  3952.     def add(self, square: Square) -> None:
  3953.         """Adds a square to the set."""
  3954.         self.mask |= BB_SQUARES[square]
  3955.  
  3956.     def discard(self, square: Square) -> None:
  3957.         """Discards a square from the set."""
  3958.         self.mask &= ~BB_SQUARES[square]
  3959.  
  3960.     # frozenset
  3961.  
  3962.     def isdisjoint(self, other: IntoSquareSet) -> bool:
  3963.         """Tests if the square sets are disjoint."""
  3964.         return not bool(self & other)
  3965.  
  3966.     def issubset(self, other: IntoSquareSet) -> bool:
  3967.         """Tests if this square set is a subset of another."""
  3968.         return not bool(self & ~SquareSet(other))
  3969.  
  3970.     def issuperset(self, other: IntoSquareSet) -> bool:
  3971.         """Tests if this square set is a superset of another."""
  3972.         return not bool(~self & other)
  3973.  
  3974.     def union(self, other: IntoSquareSet) -> SquareSet:
  3975.         return self | other
  3976.  
  3977.     def __or__(self, other: IntoSquareSet) -> SquareSet:
  3978.         r = SquareSet(other)
  3979.         r.mask |= self.mask
  3980.         return r
  3981.  
  3982.     def intersection(self, other: IntoSquareSet) -> SquareSet:
  3983.         return self & other
  3984.  
  3985.     def __and__(self, other: IntoSquareSet) -> SquareSet:
  3986.         r = SquareSet(other)
  3987.         r.mask &= self.mask
  3988.         return r
  3989.  
  3990.     def difference(self, other: IntoSquareSet) -> SquareSet:
  3991.         return self - other
  3992.  
  3993.     def __sub__(self, other: IntoSquareSet) -> SquareSet:
  3994.         r = SquareSet(other)
  3995.         r.mask = self.mask & ~r.mask
  3996.         return r
  3997.  
  3998.     def symmetric_difference(self, other: IntoSquareSet) -> SquareSet:
  3999.         return self ^ other
  4000.  
  4001.     def __xor__(self, other: IntoSquareSet) -> SquareSet:
  4002.         r = SquareSet(other)
  4003.         r.mask ^= self.mask
  4004.         return r
  4005.  
  4006.     def copy(self) -> SquareSet:
  4007.         return SquareSet(self.mask)
  4008.  
  4009.     # set
  4010.  
  4011.     def update(self, *others: IntoSquareSet) -> None:
  4012.         for other in others:
  4013.             self |= other
  4014.  
  4015.     def __ior__(self, other: IntoSquareSet) -> SquareSet:
  4016.         self.mask |= SquareSet(other).mask
  4017.         return self
  4018.  
  4019.     def intersection_update(self, *others: IntoSquareSet) -> None:
  4020.         for other in others:
  4021.             self &= other
  4022.  
  4023.     def __iand__(self, other: IntoSquareSet) -> SquareSet:
  4024.         self.mask &= SquareSet(other).mask
  4025.         return self
  4026.  
  4027.     def difference_update(self, other: IntoSquareSet) -> None:
  4028.         self -= other
  4029.  
  4030.     def __isub__(self, other: IntoSquareSet) -> SquareSet:
  4031.         self.mask &= ~SquareSet(other).mask
  4032.         return self
  4033.  
  4034.     def symmetric_difference_update(self, other: IntoSquareSet) -> None:
  4035.         self ^= other
  4036.  
  4037.     def __ixor__(self, other: IntoSquareSet) -> SquareSet:
  4038.         self.mask ^= SquareSet(other).mask
  4039.         return self
  4040.  
  4041.     def remove(self, square: Square) -> None:
  4042.         """
  4043.        Removes a square from the set.
  4044.  
  4045.        :raises: :exc:`KeyError` if the given *square* was not in the set.
  4046.        """
  4047.         mask = BB_SQUARES[square]
  4048.         if self.mask & mask:
  4049.             self.mask ^= mask
  4050.         else:
  4051.             raise KeyError(square)
  4052.  
  4053.     def pop(self) -> Square:
  4054.         """
  4055.        Removes and returns a square from the set.
  4056.  
  4057.        :raises: :exc:`KeyError` if the set is empty.
  4058.        """
  4059.         if not self.mask:
  4060.             raise KeyError("pop from empty SquareSet")
  4061.  
  4062.         square = lsb(self.mask)
  4063.         self.mask &= (self.mask - 1)
  4064.         return square
  4065.  
  4066.     def clear(self) -> None:
  4067.         """Removes all elements from this set."""
  4068.         self.mask = BB_EMPTY
  4069.  
  4070.     # SquareSet
  4071.  
  4072.     def carry_rippler(self) -> Iterator[Bitboard]:
  4073.         """Iterator over the subsets of this set."""
  4074.         return _carry_rippler(self.mask)
  4075.  
  4076.     def mirror(self) -> SquareSet:
  4077.         """Returns a vertically mirrored copy of this square set."""
  4078.         return SquareSet(flip_vertical(self.mask))
  4079.  
  4080.     def tolist(self) -> List[bool]:
  4081.         """Converts the set to a list of 64 bools."""
  4082.         result = [False] * 64
  4083.         for square in self:
  4084.             result[square] = True
  4085.         return result
  4086.  
  4087.     def __bool__(self) -> bool:
  4088.         return bool(self.mask)
  4089.  
  4090.     def __eq__(self, other: object) -> bool:
  4091.         try:
  4092.             return self.mask == SquareSet(other).mask  # type: ignore
  4093.         except (TypeError, ValueError):
  4094.             return NotImplemented
  4095.  
  4096.     def __lshift__(self, shift: int) -> SquareSet:
  4097.         return SquareSet((self.mask << shift) & BB_ALL)
  4098.  
  4099.     def __rshift__(self, shift: int) -> SquareSet:
  4100.         return SquareSet(self.mask >> shift)
  4101.  
  4102.     def __ilshift__(self, shift: int) -> SquareSet:
  4103.         self.mask = (self.mask << shift) & BB_ALL
  4104.         return self
  4105.  
  4106.     def __irshift__(self, shift: int) -> SquareSet:
  4107.         self.mask >>= shift
  4108.         return self
  4109.  
  4110.     def __invert__(self) -> SquareSet:
  4111.         return SquareSet(~self.mask & BB_ALL)
  4112.  
  4113.     def __int__(self) -> int:
  4114.         return self.mask
  4115.  
  4116.     def __index__(self) -> int:
  4117.         return self.mask
  4118.  
  4119.     def __repr__(self) -> str:
  4120.         return f"SquareSet({self.mask:#021_x})"
  4121.  
  4122.     def __str__(self) -> str:
  4123.         builder = []
  4124.  
  4125.         for square in SQUARES_180:
  4126.             mask = BB_SQUARES[square]
  4127.             builder.append("1" if self.mask & mask else ".")
  4128.  
  4129.             if not mask & BB_FILE_H:
  4130.                 builder.append(" ")
  4131.             elif square != H1:
  4132.                 builder.append("\n")
  4133.  
  4134.         return "".join(builder)
  4135.  
  4136.     def _repr_svg_(self) -> str:
  4137.         import chess.svg
  4138.         return chess.svg.board(squares=self, size=390)
  4139.  
  4140.     @classmethod
  4141.     def ray(cls, a: Square, b: Square) -> SquareSet:
  4142.         """
  4143.        All squares on the rank, file or diagonal with the two squares, if they
  4144.        are aligned.
  4145.  
  4146.        >>> import chess
  4147.        >>>
  4148.        >>> print(chess.SquareSet.ray(chess.E2, chess.B5))
  4149.        . . . . . . . .
  4150.        . . . . . . . .
  4151.        1 . . . . . . .
  4152.        . 1 . . . . . .
  4153.        . . 1 . . . . .
  4154.        . . . 1 . . . .
  4155.        . . . . 1 . . .
  4156.        . . . . . 1 . .
  4157.        """
  4158.         return cls(ray(a, b))
  4159.  
  4160.     @classmethod
  4161.     def between(cls, a: Square, b: Square) -> SquareSet:
  4162.         """
  4163.        All squares on the rank, file or diagonal between the two squares
  4164.        (bounds not included), if they are aligned.
  4165.  
  4166.        >>> import chess
  4167.        >>>
  4168.        >>> print(chess.SquareSet.between(chess.E2, chess.B5))
  4169.        . . . . . . . .
  4170.        . . . . . . . .
  4171.        . . . . . . . .
  4172.        . . . . . . . .
  4173.        . . 1 . . . . .
  4174.        . . . 1 . . . .
  4175.        . . . . . . . .
  4176.        . . . . . . . .
  4177.        """
  4178.         return cls(between(a, b))
  4179.  
  4180.     @classmethod
  4181.     def from_square(cls, square: Square) -> SquareSet:
  4182.         """
  4183.        Creates a :class:`~chess.SquareSet` from a single square.
  4184.  
  4185.        >>> import chess
  4186.        >>>
  4187.        >>> chess.SquareSet.from_square(chess.A1) == chess.BB_A1
  4188.        True
  4189.        """
  4190.         return cls(BB_SQUARES[square])
  4191.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement