diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index a37e82933..000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: CodeQL - -on: [push, pull_request] - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - steps: - - uses: actions/checkout@v3 - - uses: github/codeql-action/init@v2 - with: - languages: python - - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/setup-ubuntu-latest.sh b/.github/workflows/setup-ubuntu-latest.sh index de1d567fd..10b8d1115 100755 --- a/.github/workflows/setup-ubuntu-latest.sh +++ b/.github/workflows/setup-ubuntu-latest.sh @@ -1,19 +1,13 @@ #!/bin/sh -e # Stockfish -wget https://stockfish.s3.amazonaws.com/stockfish-11-linux.zip -unzip stockfish-11-linux.zip -mkdir -p bin -cp stockfish-11-linux/Linux/stockfish_20011801_x64_bmi2 bin/stockfish -chmod +x bin/stockfish -echo "`pwd`/bin" >> $GITHUB_PATH +sudo apt-get install -y stockfish # Crafty -git clone https://github.com/lazydroid/crafty-chess --depth 1 -cd crafty-chess -make unix-gcc -pwd >> $GITHUB_PATH -cd .. +sudo apt-get install -y crafty + +# Fairy-stockfish +sudo apt-get install -y fairy-stockfish # Gaviota libgtb git clone https://github.com/michiguel/Gaviota-Tablebases.git --depth 1 diff --git a/.github/workflows/setup-windows-latest.sh b/.github/workflows/setup-windows-latest.sh index 3ae917a5c..48037cbef 100755 --- a/.github/workflows/setup-windows-latest.sh +++ b/.github/workflows/setup-windows-latest.sh @@ -1,12 +1,16 @@ #!/bin/sh -e -echo Download ... +echo Download stockfish ... choco install wget -wget https://stockfishchess.org/files/stockfish-11-win.zip +wget https://github.com/official-stockfish/Stockfish/releases/download/sf_16/stockfish-windows-x86-64-avx2.zip echo Unzip .. -7z e stockfish-11-win.zip stockfish-11-win/Windows/*.exe +7z e stockfish-windows-x86-64-avx2.zip stockfish/stockfish-windows-x86-64-avx2.exe echo Setup path ... -mv stockfish_20011801_x64_bmi2.exe stockfish.exe +mv stockfish-windows-x86-64-avx2.exe stockfish.exe pwd >> $GITHUB_PATH + +echo Download fairy-stockfish ... +wget https://github.com/fairy-stockfish/Fairy-Stockfish/releases/latest/download/fairy-stockfish-largeboard_x86-64.exe +mv fairy-stockfish-largeboard_x86-64.exe fairy-stockfish.exe diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 68ab5bb19..9d0880bb4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,16 +2,20 @@ name: Test on: [push, pull_request] +permissions: + contents: read + jobs: test: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + fail-fast: false runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - run: .github/workflows/setup-${{ matrix.os }}.sh @@ -21,12 +25,13 @@ jobs: perft: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version: "3.14" - run: pip install -e . - run: python examples/perft/perft.py -t 1 examples/perft/random.perft --max-nodes 10000 + - run: python examples/perft/perft.py -t 1 examples/perft/chess960.perft --max-nodes 100000 - run: python examples/perft/perft.py -t 1 examples/perft/tricky.perft - run: python examples/perft/perft.py -t 1 --variant giveaway examples/perft/giveaway.perft - run: python examples/perft/perft.py -t 1 --variant atomic examples/perft/atomic.perft @@ -34,28 +39,32 @@ jobs: - run: python examples/perft/perft.py -t 1 --variant horde examples/perft/horde.perft - run: python examples/perft/perft.py -t 1 --variant crazyhouse examples/perft/crazyhouse.perft - run: python examples/perft/perft.py -t 1 --variant 3check examples/perft/3check.perft - mypy: + typing: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-version: ["3.9", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - run: pip install -e . - run: pip install mypy - run: python -m mypy --strict chess - run: python -m mypy --strict examples/**/*.py + - run: pip install pyright + - run: python -m pyright chess + - run: python -m pyright examples/**/*.py readme: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version: "3.14" - run: sudo apt-get update && sudo apt-get install -y docutils-common - run: python setup.py --long-description | rst2html --strict --no-raw > /dev/null - run: pip install -e . diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1c68c9f34..03a9555d1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,103 @@ Changelog for python-chess ========================== +New in v1.11.2 (25th Feb 2025) +------------------------------ + +Bugfixes: + +* Fix ``chess.gaviota.PythonTablebase`` does not properly resolve positions + where en passant captures are the best move. + +New in v1.11.1 (9th Oct 2024) +----------------------------- + +Bugfixes: + +* ``chess.engine``: Fix parsing of UCI options containing containing ``name``, + ``type``, ``default``, ``min``, or ``max``, e.g., ``mini``. + +New in v1.11.0 (4th Oct 2024) +----------------------------- + +Changes: + +* Drop support for Python 3.7, which has reached its end of life. +* ``chess.engine.EventLoopPolicy`` is no longer needed and now merely an alias + for the default event loop policy. +* If available and requested via ``setpgrp``, use ``process_group`` support + from Python 3.11 for engine processes. +* No longer eagerly reject 8 piece positions in ``chess.syzygy``, so that + some 8 piece positions with decisive captures can be probed successfully. +* The string wrapper returned by ``chess.svg`` functions now also implements + ``_repr_html_``. +* Significant changes to ``chess.engine`` internals: + ``chess.engine.BaseCommand`` methods other than the constructor no longer + receive ``engine: Protocol``. +* Significant changes to board state internals: Subclasses of ``chess.Board`` + can no longer hook into board state recording/restoration and need to + override relevant methods instead (``clear_stack``, ``copy``, ``root``, + ``push``, ``pop``). + +New features: + +* Add ``chess.pgn.Game.time_control()`` and related data models. +* Add model ``sf16.1`` for ``chess.engine.Score.wdl()``, the new default. + +Bugfixes: + +* Fix unsolicited engine output may cause assertion errors with regard to + command states. +* Fix handling of whitespace in UCI engine communication. +* For ``chess.Board.epd()`` and ``chess.Board.set_epd()``, require that EPD + opcodes start with a letter. + +New in v1.10.0 (27th Jul 2023) +------------------------------ + +New features: + +* Use ``chess.engine.Opponent`` to send opponent information to engines. +* Inform engines about the game result using + ``chess.engine.Protocol.send_game_result()``. +* Add ``chess.engine.Limit.clock_id``. +* Add ``chess.svg.board(..., borders=True)``. +* Avoid rendering background behind SVG boards to better support transparency. +* Add ``chess.pgn.BaseVisitor.begin_parse_san()``. +* Introduce new distance metrics ``chess.square_manhattan_distance()`` and + ``chess.square_knight_distance()``. + +Bugfixes: + +* Fix ``chess.pgn.GameNode.eval()`` sometimes off by one centipawn. +* Fix handling of additional spaces between UCI option tokens. +* Handle implicit XBoard engine resignation via output like + ``0-1 {White resigns}``. + +Changes: + +* Add model ``sf16`` for ``chess.engine.Score.wdl()``, the new default. +* Update ``lichess`` WDL model. +* Keep PGN headers that do not belong to the Seven Tag Roster in insertion + order. +* Halve the number of open file descriptors maintained by tablebases + and opening books. +* Reduce verbosity of logged ``chess.pgn`` errors. + +New in v1.9.4 (22nd Dec 2022) +----------------------------- + +Bugfixes: + +* Fix ``PovScore.wdl()`` ignored ``model`` and ``ply`` parameters. +* ``chess.syzygy``: Check that board matches tablebase variant. + +New features: + +* Add model ``sf15.1`` for ``chess.engine.Score.wdl()``. +* Raise more specific exceptions: ``chess.IllegalMoveError``, + ``chess.AmbiguousMoveError``, and ``chess.InvalidMoveError``. + New in v1.9.3 (16th Sep 2022) ----------------------------- diff --git a/README.rst b/README.rst index aae81e788..d4d51a128 100644 --- a/README.rst +++ b/README.rst @@ -59,7 +59,7 @@ python-chess: Installing ---------- -Requires Python 3.7+. Download and install the latest release: +Requires Python 3.8+. Download and install the latest release: :: @@ -314,7 +314,7 @@ If you like, share interesting things you are using python-chess for, for exampl | .. image:: https://github.com/niklasf/python-chess/blob/master/docs/images/clente-chess.png?raw=true | `clente/chess `_ | | :height: 64 | | | :width: 64 | | -| :target: https://github.com/clente/chess | Oppinionated wrapper to use python-chess from the R programming language | +| :target: https://github.com/clente/chess | Opinionated wrapper to use python-chess from the R programming language | +------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------+ | .. image:: https://github.com/niklasf/python-chess/blob/master/docs/images/crazyara.png?raw=true | https://crazyara.org/ | | :height: 64 | | @@ -331,10 +331,15 @@ If you like, share interesting things you are using python-chess for, for exampl | :height: 64 | | | :target: https://pettingzoo.farama.org/environments/classic/chess/ | A multi-agent reinforcement learning environment | +------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------+ +| .. image:: https://github.com/niklasf/python-chess/blob/master/docs/images/cli-chess.png?raw=true | `cli-chess `_ | +| :width: 64 | | +| :height: 64 | | +| :target: https://github.com/trevorbayless/cli-chess | A highly customizable way to play chess in your terminal | ++------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------+ * extensions to build engines (search and evaluation) – https://github.com/Mk-Chan/python-chess-engine-extensions -* a stand-alone chess computer based on DGT board – http://www.picochess.org/ -* a bridge between Lichess API and chess engines – https://github.com/careless25/lichess-bot +* a stand-alone chess computer based on DGT board – https://picochess.com/ +* a bridge between Lichess API and chess engines – https://github.com/lichess-bot-devs/lichess-bot * a command-line PGN annotator – https://github.com/rpdelaney/python-chess-annotator * an HTTP microservice to render board images – https://github.com/niklasf/web-boardimage * building a toy chess engine with alpha-beta pruning, piece-square tables, and move ordering – https://healeycodes.com/building-my-own-chess-engine/ @@ -345,10 +350,11 @@ If you like, share interesting things you are using python-chess for, for exampl * Django Rest Framework API for multiplayer chess – https://github.com/WorkShoft/capablanca-api * a `browser based PGN viewer `_ written in PyScript – https://github.com/nmstoker/ChessMatchViewer * an accessible chessboard that allows blind and visually impaired players to play chess against Stockfish – https://github.com/blindpandas/chessmart +* a web-based chess vision exercise – https://github.com/3d12/rookognition -Acknowledgements ----------------- +Prior art +--------- Thanks to the Stockfish authors and thanks to Sam Tannous for publishing his approach to `avoid rotated bitboards with direct lookup (PDF) `_ @@ -366,4 +372,4 @@ License ------- python-chess is licensed under the GPL 3 (or any later version at your option). -Check out LICENSE.txt for the full text. +Check out ``LICENSE.txt`` for the full text. diff --git a/chess/__init__.py b/chess/__init__.py index 4dca9d590..9ea44f36e 100644 --- a/chess/__init__.py +++ b/chess/__init__.py @@ -1,19 +1,3 @@ -# This file is part of the python-chess library. -# Copyright (C) 2012-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - """ A chess library with move generation and validation, Polyglot opening book probing, PGN reading and writing, @@ -27,7 +11,7 @@ __email__ = "niklas.fiekas@backscattering.de" -__version__ = "1.9.3" +__version__ = "1.11.2" import collections import copy @@ -38,22 +22,30 @@ import itertools import typing -from typing import ClassVar, Callable, Counter, Dict, Generic, Hashable, Iterable, Iterator, List, Mapping, Optional, SupportsInt, Tuple, Type, TypeVar, Union +from typing import ClassVar, Callable, Counter, Dict, Hashable, Iterable, Iterator, List, Literal, Mapping, Optional, SupportsInt, Tuple, Type, TypeVar, Union + +if typing.TYPE_CHECKING: + from typing_extensions import Self, TypeAlias -try: - from typing import Literal - _EnPassantSpec = Literal["legal", "fen", "xfen"] -except ImportError: - # Before Python 3.8. - _EnPassantSpec = str # type: ignore +EnPassantSpec = Literal["legal", "fen", "xfen"] -Color = bool -COLORS = [WHITE, BLACK] = [True, False] -COLOR_NAMES = ["black", "white"] -PieceType = int -PIECE_TYPES = [PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING] = range(1, 7) +Color: TypeAlias = bool +WHITE: Color = True +BLACK: Color = False +COLORS: List[Color] = [WHITE, BLACK] +ColorName = Literal["white", "black"] +COLOR_NAMES: List[ColorName] = ["black", "white"] + +PieceType: TypeAlias = int +PAWN: PieceType = 1 +KNIGHT: PieceType = 2 +BISHOP: PieceType = 3 +ROOK: PieceType = 4 +QUEEN: PieceType = 5 +KING: PieceType = 6 +PIECE_TYPES: List[PieceType] = [PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING] PIECE_SYMBOLS = [None, "p", "n", "b", "r", "q", "k"] PIECE_NAMES = [None, "pawn", "knight", "bishop", "rook", "queen", "king"] @@ -72,8 +64,28 @@ def piece_name(piece_type: PieceType) -> str: "P": "♙", "p": "♟", } +File: TypeAlias = int +FILE_A: File = 0 +FILE_B: File = 1 +FILE_C: File = 2 +FILE_D: File = 3 +FILE_E: File = 4 +FILE_F: File = 5 +FILE_G: File = 6 +FILE_H: File = 7 +FILES = [FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H] FILE_NAMES = ["a", "b", "c", "d", "e", "f", "g", "h"] +Rank: TypeAlias = int +RANK_1: Rank = 0 +RANK_2: Rank = 1 +RANK_3: Rank = 2 +RANK_4: Rank = 3 +RANK_5: Rank = 4 +RANK_6: Rank = 5 +RANK_7: Rank = 6 +RANK_8: Rank = 7 +RANKS = [RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8] RANK_NAMES = ["1", "2", "3", "4", "5", "6", "7", "8"] STARTING_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" @@ -177,17 +189,72 @@ class AmbiguousMoveError(ValueError): """Raised when the attempted move is ambiguous in the current position""" -Square = int -SQUARES = [ - A1, B1, C1, D1, E1, F1, G1, H1, - A2, B2, C2, D2, E2, F2, G2, H2, - A3, B3, C3, D3, E3, F3, G3, H3, - A4, B4, C4, D4, E4, F4, G4, H4, - A5, B5, C5, D5, E5, F5, G5, H5, - A6, B6, C6, D6, E6, F6, G6, H6, - A7, B7, C7, D7, E7, F7, G7, H7, - A8, B8, C8, D8, E8, F8, G8, H8, -] = range(64) +Square: TypeAlias = int +A1: Square = 0 +B1: Square = 1 +C1: Square = 2 +D1: Square = 3 +E1: Square = 4 +F1: Square = 5 +G1: Square = 6 +H1: Square = 7 +A2: Square = 8 +B2: Square = 9 +C2: Square = 10 +D2: Square = 11 +E2: Square = 12 +F2: Square = 13 +G2: Square = 14 +H2: Square = 15 +A3: Square = 16 +B3: Square = 17 +C3: Square = 18 +D3: Square = 19 +E3: Square = 20 +F3: Square = 21 +G3: Square = 22 +H3: Square = 23 +A4: Square = 24 +B4: Square = 25 +C4: Square = 26 +D4: Square = 27 +E4: Square = 28 +F4: Square = 29 +G4: Square = 30 +H4: Square = 31 +A5: Square = 32 +B5: Square = 33 +C5: Square = 34 +D5: Square = 35 +E5: Square = 36 +F5: Square = 37 +G5: Square = 38 +H5: Square = 39 +A6: Square = 40 +B6: Square = 41 +C6: Square = 42 +D6: Square = 43 +E6: Square = 44 +F6: Square = 45 +G6: Square = 46 +H6: Square = 47 +A7: Square = 48 +B7: Square = 49 +C7: Square = 50 +D7: Square = 51 +E7: Square = 52 +F7: Square = 53 +G7: Square = 54 +H7: Square = 55 +A8: Square = 56 +B8: Square = 57 +C8: Square = 58 +D8: Square = 59 +E8: Square = 60 +F8: Square = 61 +G8: Square = 62 +H8: Square = 63 +SQUARES: List[Square] = list(range(64)) SQUARE_NAMES = [f + r for r in RANK_NAMES for f in FILE_NAMES] @@ -204,75 +271,178 @@ def square_name(square: Square) -> str: """Gets the name of the square, like ``a3``.""" return SQUARE_NAMES[square] -def square(file_index: int, rank_index: int) -> Square: +def square(file_index: File, rank_index: Rank) -> Square: """Gets a square number by file and rank index.""" return rank_index * 8 + file_index -def square_file(square: Square) -> int: +def parse_file(name: str) -> File: + """ + Gets the file index for the given file *name* + (e.g., ``a`` returns ``0``). + + :raises: :exc:`ValueError` if the file name is invalid. + """ + return FILE_NAMES.index(name) + +def file_name(file: File) -> str: + """Gets the name of the file, like ``a``.""" + return FILE_NAMES[file] + +def parse_rank(name: str) -> File: + """ + Gets the rank index for the given rank *name* + (e.g., ``1`` returns ``0``). + + :raises: :exc:`ValueError` if the rank name is invalid. + """ + return FILE_NAMES.index(name) + +def rank_name(rank: Rank) -> str: + """Gets the name of the rank, like ``1``.""" + return FILE_NAMES[rank] + +def square_file(square: Square) -> File: """Gets the file index of the square where ``0`` is the a-file.""" return square & 7 -def square_rank(square: Square) -> int: +def square_rank(square: Square) -> Rank: """Gets the rank index of the square where ``0`` is the first rank.""" return square >> 3 def square_distance(a: Square, b: Square) -> int: """ - Gets the distance (i.e., the number of king steps) from square *a* to *b*. + Gets the Chebyshev distance (i.e., the number of king steps) from square *a* to *b*. """ return max(abs(square_file(a) - square_file(b)), abs(square_rank(a) - square_rank(b))) +def square_manhattan_distance(a: Square, b: Square) -> int: + """ + Gets the Manhattan/Taxicab distance (i.e., the number of orthogonal king steps) from square *a* to *b*. + """ + return abs(square_file(a) - square_file(b)) + abs(square_rank(a) - square_rank(b)) + +def square_knight_distance(a: Square, b: Square) -> int: + """ + Gets the Knight distance (i.e., the number of knight moves) from square *a* to *b*. + """ + dx = abs(square_file(a) - square_file(b)) + dy = abs(square_rank(a) - square_rank(b)) + + if dx + dy == 1: + return 3 + elif dx == dy == 2: + return 4 + elif dx == dy == 1: + if BB_SQUARES[a] & BB_CORNERS or BB_SQUARES[b] & BB_CORNERS: # Special case only for corner squares + return 4 + + m = math.ceil(max(dx / 2, dy / 2, (dx + dy) / 3)) + return m + ((m + dx + dy) % 2) + def square_mirror(square: Square) -> Square: """Mirrors the square vertically.""" return square ^ 0x38 -SQUARES_180 = [square_mirror(sq) for sq in SQUARES] - - -Bitboard = int -BB_EMPTY = 0 -BB_ALL = 0xffff_ffff_ffff_ffff - -BB_SQUARES = [ - BB_A1, BB_B1, BB_C1, BB_D1, BB_E1, BB_F1, BB_G1, BB_H1, - BB_A2, BB_B2, BB_C2, BB_D2, BB_E2, BB_F2, BB_G2, BB_H2, - BB_A3, BB_B3, BB_C3, BB_D3, BB_E3, BB_F3, BB_G3, BB_H3, - BB_A4, BB_B4, BB_C4, BB_D4, BB_E4, BB_F4, BB_G4, BB_H4, - BB_A5, BB_B5, BB_C5, BB_D5, BB_E5, BB_F5, BB_G5, BB_H5, - BB_A6, BB_B6, BB_C6, BB_D6, BB_E6, BB_F6, BB_G6, BB_H6, - BB_A7, BB_B7, BB_C7, BB_D7, BB_E7, BB_F7, BB_G7, BB_H7, - BB_A8, BB_B8, BB_C8, BB_D8, BB_E8, BB_F8, BB_G8, BB_H8, -] = [1 << sq for sq in SQUARES] - -BB_CORNERS = BB_A1 | BB_H1 | BB_A8 | BB_H8 -BB_CENTER = BB_D4 | BB_E4 | BB_D5 | BB_E5 - -BB_LIGHT_SQUARES = 0x55aa_55aa_55aa_55aa -BB_DARK_SQUARES = 0xaa55_aa55_aa55_aa55 - -BB_FILES = [ - BB_FILE_A, - BB_FILE_B, - BB_FILE_C, - BB_FILE_D, - BB_FILE_E, - BB_FILE_F, - BB_FILE_G, - BB_FILE_H, -] = [0x0101_0101_0101_0101 << i for i in range(8)] - -BB_RANKS = [ - BB_RANK_1, - BB_RANK_2, - BB_RANK_3, - BB_RANK_4, - BB_RANK_5, - BB_RANK_6, - BB_RANK_7, - BB_RANK_8, -] = [0xff << (8 * i) for i in range(8)] - -BB_BACKRANKS = BB_RANK_1 | BB_RANK_8 +SQUARES_180: List[Square] = [square_mirror(sq) for sq in SQUARES] + + +Bitboard: TypeAlias = int +BB_EMPTY: Bitboard = 0 +BB_ALL: Bitboard = 0xffff_ffff_ffff_ffff + +BB_A1: Bitboard = 1 << A1 +BB_B1: Bitboard = 1 << B1 +BB_C1: Bitboard = 1 << C1 +BB_D1: Bitboard = 1 << D1 +BB_E1: Bitboard = 1 << E1 +BB_F1: Bitboard = 1 << F1 +BB_G1: Bitboard = 1 << G1 +BB_H1: Bitboard = 1 << H1 +BB_A2: Bitboard = 1 << A2 +BB_B2: Bitboard = 1 << B2 +BB_C2: Bitboard = 1 << C2 +BB_D2: Bitboard = 1 << D2 +BB_E2: Bitboard = 1 << E2 +BB_F2: Bitboard = 1 << F2 +BB_G2: Bitboard = 1 << G2 +BB_H2: Bitboard = 1 << H2 +BB_A3: Bitboard = 1 << A3 +BB_B3: Bitboard = 1 << B3 +BB_C3: Bitboard = 1 << C3 +BB_D3: Bitboard = 1 << D3 +BB_E3: Bitboard = 1 << E3 +BB_F3: Bitboard = 1 << F3 +BB_G3: Bitboard = 1 << G3 +BB_H3: Bitboard = 1 << H3 +BB_A4: Bitboard = 1 << A4 +BB_B4: Bitboard = 1 << B4 +BB_C4: Bitboard = 1 << C4 +BB_D4: Bitboard = 1 << D4 +BB_E4: Bitboard = 1 << E4 +BB_F4: Bitboard = 1 << F4 +BB_G4: Bitboard = 1 << G4 +BB_H4: Bitboard = 1 << H4 +BB_A5: Bitboard = 1 << A5 +BB_B5: Bitboard = 1 << B5 +BB_C5: Bitboard = 1 << C5 +BB_D5: Bitboard = 1 << D5 +BB_E5: Bitboard = 1 << E5 +BB_F5: Bitboard = 1 << F5 +BB_G5: Bitboard = 1 << G5 +BB_H5: Bitboard = 1 << H5 +BB_A6: Bitboard = 1 << A6 +BB_B6: Bitboard = 1 << B6 +BB_C6: Bitboard = 1 << C6 +BB_D6: Bitboard = 1 << D6 +BB_E6: Bitboard = 1 << E6 +BB_F6: Bitboard = 1 << F6 +BB_G6: Bitboard = 1 << G6 +BB_H6: Bitboard = 1 << H6 +BB_A7: Bitboard = 1 << A7 +BB_B7: Bitboard = 1 << B7 +BB_C7: Bitboard = 1 << C7 +BB_D7: Bitboard = 1 << D7 +BB_E7: Bitboard = 1 << E7 +BB_F7: Bitboard = 1 << F7 +BB_G7: Bitboard = 1 << G7 +BB_H7: Bitboard = 1 << H7 +BB_A8: Bitboard = 1 << A8 +BB_B8: Bitboard = 1 << B8 +BB_C8: Bitboard = 1 << C8 +BB_D8: Bitboard = 1 << D8 +BB_E8: Bitboard = 1 << E8 +BB_F8: Bitboard = 1 << F8 +BB_G8: Bitboard = 1 << G8 +BB_H8: Bitboard = 1 << H8 +BB_SQUARES: List[Bitboard] = [1 << sq for sq in SQUARES] + +BB_CORNERS: Bitboard = BB_A1 | BB_H1 | BB_A8 | BB_H8 +BB_CENTER: Bitboard = BB_D4 | BB_E4 | BB_D5 | BB_E5 + +BB_LIGHT_SQUARES: Bitboard = 0x55aa_55aa_55aa_55aa +BB_DARK_SQUARES: Bitboard = 0xaa55_aa55_aa55_aa55 + +BB_FILE_A: Bitboard = 0x0101_0101_0101_0101 << FILE_A +BB_FILE_B: Bitboard = 0x0101_0101_0101_0101 << FILE_B +BB_FILE_C: Bitboard = 0x0101_0101_0101_0101 << FILE_C +BB_FILE_D: Bitboard = 0x0101_0101_0101_0101 << FILE_D +BB_FILE_E: Bitboard = 0x0101_0101_0101_0101 << FILE_E +BB_FILE_F: Bitboard = 0x0101_0101_0101_0101 << FILE_F +BB_FILE_G: Bitboard = 0x0101_0101_0101_0101 << FILE_G +BB_FILE_H: Bitboard = 0x0101_0101_0101_0101 << FILE_H +BB_FILES: List[Bitboard] = [BB_FILE_A, BB_FILE_B, BB_FILE_C, BB_FILE_D, BB_FILE_E, BB_FILE_F, BB_FILE_G, BB_FILE_H] + +BB_RANK_1: Bitboard = 0xff << (8 * RANK_1) +BB_RANK_2: Bitboard = 0xff << (8 * RANK_2) +BB_RANK_3: Bitboard = 0xff << (8 * RANK_3) +BB_RANK_4: Bitboard = 0xff << (8 * RANK_4) +BB_RANK_5: Bitboard = 0xff << (8 * RANK_5) +BB_RANK_6: Bitboard = 0xff << (8 * RANK_6) +BB_RANK_7: Bitboard = 0xff << (8 * RANK_7) +BB_RANK_8: Bitboard = 0xff << (8 * RANK_8) +BB_RANKS: List[Bitboard] = [BB_RANK_1, BB_RANK_2, BB_RANK_3, BB_RANK_4, BB_RANK_5, BB_RANK_6, BB_RANK_7, BB_RANK_8] + +BB_BACKRANKS: Bitboard = BB_RANK_1 | BB_RANK_8 def lsb(bb: Bitboard) -> int: @@ -389,9 +559,9 @@ def _sliding_attacks(square: Square, occupied: Bitboard, deltas: Iterable[int]) def _step_attacks(square: Square, deltas: Iterable[int]) -> Bitboard: return _sliding_attacks(square, BB_ALL, deltas) -BB_KNIGHT_ATTACKS = [_step_attacks(sq, [17, 15, 10, 6, -17, -15, -10, -6]) for sq in SQUARES] -BB_KING_ATTACKS = [_step_attacks(sq, [9, 8, 7, 1, -9, -8, -7, -1]) for sq in SQUARES] -BB_PAWN_ATTACKS = [[_step_attacks(sq, deltas) for sq in SQUARES] for deltas in [[-7, -9], [7, 9]]] +BB_KNIGHT_ATTACKS: List[Bitboard] = [_step_attacks(sq, [17, 15, 10, 6, -17, -15, -10, -6]) for sq in SQUARES] +BB_KING_ATTACKS: List[Bitboard] = [_step_attacks(sq, [9, 8, 7, 1, -9, -8, -7, -1]) for sq in SQUARES] +BB_PAWN_ATTACKS: List[List[Bitboard]] = [[_step_attacks(sq, deltas) for sq in SQUARES] for deltas in [[-7, -9], [7, 9]]] def _edges(square: Square) -> Bitboard: @@ -408,8 +578,8 @@ def _carry_rippler(mask: Bitboard) -> Iterator[Bitboard]: break def _attack_table(deltas: List[int]) -> Tuple[List[Bitboard], List[Dict[Bitboard, Bitboard]]]: - mask_table = [] - attack_table = [] + mask_table: List[Bitboard] = [] + attack_table: List[Dict[Bitboard, Bitboard]] = [] for square in SQUARES: attacks = {} @@ -429,9 +599,9 @@ def _attack_table(deltas: List[int]) -> Tuple[List[Bitboard], List[Dict[Bitboard def _rays() -> List[List[Bitboard]]: - rays = [] + rays: List[List[Bitboard]] = [] for a, bb_a in enumerate(BB_SQUARES): - rays_row = [] + rays_row: List[Bitboard] = [] for b, bb_b in enumerate(BB_SQUARES): if BB_DIAG_ATTACKS[a][0] & bb_b: rays_row.append((BB_DIAG_ATTACKS[a][0] & BB_DIAG_ATTACKS[b][0]) | bb_a | bb_b) @@ -581,7 +751,7 @@ def from_uci(cls, uci: str) -> Move: promotion = PIECE_SYMBOLS.index(uci[4]) if len(uci) == 5 else None except ValueError: raise InvalidMoveError(f"invalid uci: {uci!r}") - if from_square == to_square: + if from_square == to_square and from_square != A1: raise InvalidMoveError(f"invalid uci (use 0000 for null moves): {uci!r}") return cls(from_square, to_square, promotion=promotion) else: @@ -672,6 +842,14 @@ def clear_board(self) -> None: """ self._clear_board() + def piece_count(self) -> int: + """ + Gets the number of pieces on the board. + + Does not include Crazyhouse pockets. + """ + return popcount(self.occupied) + def pieces_mask(self, piece_type: PieceType, color: Color) -> Bitboard: if piece_type == PAWN: bb = self.pawns @@ -737,16 +915,19 @@ def color_at(self, square: Square) -> Optional[Color]: else: return None + def _effective_promoted(self) -> Bitboard: + return BB_EMPTY + def king(self, color: Color) -> Optional[Square]: """ - Finds the king square of the given side. Returns ``None`` if there - is no king of that color. + Finds the unique king square of the given side. Returns ``None`` if + there is no king or multiple kings of that color. In variants with king promotions, only non-promoted kings are considered. """ - king_mask = self.occupied_co[color] & self.kings & ~self.promoted - return msb(king_mask) if king_mask else None + king_mask = self.occupied_co[color] & self.kings & ~self._effective_promoted() + return msb(king_mask) if king_mask and not king_mask & (king_mask - 1) else None def attacks_mask(self, square: Square) -> Bitboard: bb_square = BB_SQUARES[square] @@ -778,7 +959,9 @@ def attacks(self, square: Square) -> SquareSet: """ return SquareSet(self.attacks_mask(square)) - def _attackers_mask(self, color: Color, square: Square, occupied: Bitboard) -> Bitboard: + def attackers_mask(self, color: Color, square: Square, occupied: Optional[Bitboard] = None) -> Bitboard: + occupied = self.occupied if occupied is None else occupied + rank_pieces = BB_RANK_MASKS[square] & occupied file_pieces = BB_FILE_MASKS[square] & occupied diag_pieces = BB_DIAG_MASKS[square] & occupied @@ -796,27 +979,38 @@ def _attackers_mask(self, color: Color, square: Square, occupied: Bitboard) -> B return attackers & self.occupied_co[color] - def attackers_mask(self, color: Color, square: Square) -> Bitboard: - return self._attackers_mask(color, square, self.occupied) - - def is_attacked_by(self, color: Color, square: Square) -> bool: + def is_attacked_by(self, color: Color, square: Square, occupied: Optional[IntoSquareSet] = None) -> bool: """ Checks if the given side attacks the given square. Pinned pieces still count as attackers. Pawns that can be captured en passant are **not** considered attacked. + + *occupied* determines which squares are considered to block attacks. + For example, + ``board.occupied ^ board.pieces_mask(chess.KING, board.turn)`` can be + used to consider X-ray attacks through the king. + Defaults to ``board.occupied`` (all pieces including the king, + no X-ray attacks). """ - return bool(self.attackers_mask(color, square)) + return bool(self.attackers_mask(color, square, None if occupied is None else SquareSet(occupied).mask)) - def attackers(self, color: Color, square: Square) -> SquareSet: + def attackers(self, color: Color, square: Square, occupied: Optional[IntoSquareSet] = None) -> SquareSet: """ Gets the set of attackers of the given color for the given square. Pinned pieces still count as attackers. + *occupied* determines which squares are considered to block attacks. + For example, + ``board.occupied ^ board.pieces_mask(chess.KING, board.turn)`` can be + used to consider X-ray attacks through the king. + Defaults to ``board.occupied`` (all pieces including the king, + no X-ray attacks). + Returns a :class:`set of squares `. """ - return SquareSet(self.attackers_mask(color, square)) + return SquareSet(self.attackers_mask(color, square, None if occupied is None else SquareSet(occupied).mask)) def pin_mask(self, color: Color, square: Square) -> Bitboard: king = self.king(color) @@ -952,12 +1146,12 @@ def set_piece_at(self, square: Square, piece: Optional[Piece], promoted: bool = else: self._set_piece_at(square, piece.piece_type, piece.color, promoted) - def board_fen(self, *, promoted: Optional[bool] = False) -> str: + def board_fen(self, *, promoted: Optional[bool] = None) -> str: """ Gets the board FEN (e.g., ``rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR``). """ - builder = [] + builder: List[str] = [] empty = 0 for square in SQUARES_180: @@ -970,7 +1164,14 @@ def board_fen(self, *, promoted: Optional[bool] = False) -> str: builder.append(str(empty)) empty = 0 builder.append(piece.symbol()) - if promoted and BB_SQUARES[square] & self.promoted: + + if promoted is None: + promoted_mask = self._effective_promoted() + elif promoted: + promoted_mask = self.promoted + else: + promoted_mask = BB_EMPTY + if BB_SQUARES[square] & promoted_mask: builder.append("~") if BB_SQUARES[square] & BB_FILE_H: @@ -1052,7 +1253,7 @@ def piece_map(self, *, mask: Bitboard = BB_ALL) -> Dict[Square, Piece]: """ Gets a dictionary of :class:`pieces ` by square index. """ - result = {} + result: Dict[Square, Piece] = {} for square in scan_reversed(self.occupied & mask): result[square] = typing.cast(Piece, self.piece_at(square)) return result @@ -1081,6 +1282,8 @@ def _set_chess960_pos(self, scharnagl: int) -> None: n, bb = divmod(n, 4) n, q = divmod(n, 6) + n1 = 0 + n2 = 0 for n1 in range(0, 4): n2 = n + (3 - n1) * (4 - n1) // 2 - 5 if n1 < n2 and 1 <= n2 <= 4: @@ -1150,7 +1353,7 @@ def chess960_pos(self) -> Optional[int]: return None if self.pawns != BB_RANK_2 | BB_RANK_7: return None - if self.promoted: + if self._effective_promoted(): return None # Piece counts. @@ -1221,7 +1424,7 @@ def __repr__(self) -> str: return f"{type(self).__name__}({self.board_fen()!r})" def __str__(self) -> str: - builder = [] + builder: List[str] = [] for square in SQUARES_180: piece = self.piece_at(square) @@ -1247,7 +1450,7 @@ def unicode(self, *, invert_color: bool = False, borders: bool = False, empty_sq :param invert_color: Invert color of the Unicode pieces. :param borders: Show borders and a coordinate margin. """ - builder = [] + builder: List[str] = [] for rank_index in (range(7, -1, -1) if orientation else range(8)): if borders: builder.append(" ") @@ -1318,7 +1521,7 @@ def apply_transform(self, f: Callable[[Bitboard], Bitboard]) -> None: self.occupied = f(self.occupied) self.promoted = f(self.promoted) - def transform(self: BaseBoardT, f: Callable[[Bitboard], Bitboard]) -> BaseBoardT: + def transform(self, f: Callable[[Bitboard], Bitboard]) -> Self: """ Returns a transformed copy of the board (without move stack) by applying a bitboard transformation function. @@ -1336,11 +1539,11 @@ def transform(self: BaseBoardT, f: Callable[[Bitboard], Bitboard]) -> BaseBoardT board.apply_transform(f) return board - def apply_mirror(self: BaseBoardT) -> None: + def apply_mirror(self) -> None: self.apply_transform(flip_vertical) self.occupied_co[WHITE], self.occupied_co[BLACK] = self.occupied_co[BLACK], self.occupied_co[WHITE] - def mirror(self: BaseBoardT) -> BaseBoardT: + def mirror(self) -> Self: """ Returns a mirrored copy of the board (without move stack). @@ -1354,7 +1557,7 @@ def mirror(self: BaseBoardT) -> BaseBoardT: board.apply_mirror() return board - def copy(self: BaseBoardT) -> BaseBoardT: + def copy(self) -> Self: """Creates a copy of the board.""" board = type(self)(None) @@ -1372,10 +1575,10 @@ def copy(self: BaseBoardT) -> BaseBoardT: return board - def __copy__(self: BaseBoardT) -> BaseBoardT: + def __copy__(self) -> Self: return self.copy() - def __deepcopy__(self: BaseBoardT, memo: Dict[int, object]) -> BaseBoardT: + def __deepcopy__(self, memo: Dict[int, object]) -> Self: board = self.copy() memo[id(self)] = board return board @@ -1405,9 +1608,9 @@ def from_chess960_pos(cls: Type[BaseBoardT], scharnagl: int) -> BaseBoardT: BoardT = TypeVar("BoardT", bound="Board") -class _BoardState(Generic[BoardT]): +class _BoardState: - def __init__(self, board: BoardT) -> None: + def __init__(self, board: Board) -> None: self.pawns = board.pawns self.knights = board.knights self.bishops = board.bishops @@ -1427,7 +1630,7 @@ def __init__(self, board: BoardT) -> None: self.halfmove_clock = board.halfmove_clock self.fullmove_number = board.fullmove_number - def restore(self, board: BoardT) -> None: + def restore(self, board: Board) -> None: board.pawns = self.pawns board.knights = self.knights board.bishops = self.bishops @@ -1557,14 +1760,14 @@ class Board(BaseBoard): manipulation. """ - def __init__(self: BoardT, fen: Optional[str] = STARTING_FEN, *, chess960: bool = False) -> None: + def __init__(self, fen: Optional[str] = STARTING_FEN, *, chess960: bool = False) -> None: BaseBoard.__init__(self, None) self.chess960 = chess960 self.ep_square = None self.move_stack = [] - self._stack: List[_BoardState[BoardT]] = [] + self._stack: List[_BoardState] = [] if fen is None: self.clear() @@ -1649,7 +1852,7 @@ def clear_stack(self) -> None: self.move_stack.clear() self._stack.clear() - def root(self: BoardT) -> BoardT: + def root(self) -> Self: """Returns a copy of the root position.""" if self._stack: board = type(self)(None, chess960=self.chess960) @@ -1671,11 +1874,13 @@ def ply(self) -> int: return 2 * (self.fullmove_number - 1) + (self.turn == BLACK) def remove_piece_at(self, square: Square) -> Optional[Piece]: + """Remove a piece, if any, from the given square and return the removed piece.""" piece = super().remove_piece_at(square) self.clear_stack() return piece def set_piece_at(self, square: Square, piece: Optional[Piece], promoted: bool = False) -> None: + """Place a piece on a square.""" super().set_piece_at(square, piece, promoted=promoted) self.clear_stack() @@ -1706,7 +1911,7 @@ def generate_pseudo_legal_moves(self, from_mask: Bitboard = BB_ALL, to_mask: Bit self.occupied_co[not self.turn] & to_mask) for to_square in scan_reversed(targets): - if square_rank(to_square) in [0, 7]: + if square_rank(to_square) in [RANK_1, RANK_8]: yield Move(from_square, to_square, QUEEN) yield Move(from_square, to_square, ROOK) yield Move(from_square, to_square, BISHOP) @@ -1729,7 +1934,7 @@ def generate_pseudo_legal_moves(self, from_mask: Bitboard = BB_ALL, to_mask: Bit for to_square in scan_reversed(single_moves): from_square = to_square + (8 if self.turn == BLACK else -8) - if square_rank(to_square) in [0, 7]: + if square_rank(to_square) in [RANK_1, RANK_8]: yield Move(from_square, to_square, QUEEN) yield Move(from_square, to_square, ROOK) yield Move(from_square, to_square, BISHOP) @@ -1756,7 +1961,7 @@ def generate_pseudo_legal_ep(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboa capturers = ( self.pawns & self.occupied_co[self.turn] & from_mask & BB_PAWN_ATTACKS[not self.turn][self.ep_square] & - BB_RANKS[4 if self.turn else 3]) + BB_RANKS[RANK_5 if self.turn else RANK_4]) for capturer in scan_reversed(capturers): yield Move(capturer, self.ep_square) @@ -1793,6 +1998,17 @@ def gives_check(self, move: Move) -> bool: finally: self.pop() + def gives_checkmate(self, move: Move) -> bool: + """ + Probes if the given move would put the opponent in checkmate. The move + must be at least pseudo-legal. + """ + self.push(move) + try: + return self.is_checkmate() + finally: + self.pop() + def is_into_check(self, move: Move) -> bool: king = self.king(self.turn) if king is None: @@ -1836,9 +2052,9 @@ def is_pseudo_legal(self, move: Move) -> bool: if piece != PAWN: return False - if self.turn == WHITE and square_rank(move.to_square) != 7: + if self.turn == WHITE and square_rank(move.to_square) != RANK_8: return False - elif self.turn == BLACK and square_rank(move.to_square) != 0: + elif self.turn == BLACK and square_rank(move.to_square) != RANK_1: return False # Handle castling. @@ -1859,6 +2075,7 @@ def is_pseudo_legal(self, move: Move) -> bool: return bool(self.attacks_mask(move.from_square) & to_mask) def is_legal(self, move: Move) -> bool: + """Check if a move is legal in the current position.""" return not self.is_variant_end() and self.is_pseudo_legal(move) and not self.is_into_check(move) def is_variant_end(self) -> bool: @@ -1895,9 +2112,27 @@ def is_variant_draw(self) -> bool: return False def is_game_over(self, *, claim_draw: bool = False) -> bool: + """ + Check if the game is over by any rule. + + The game is not considered to be over by the + :func:`fifty-move rule ` or + :func:`threefold repetition `, + unless *claim_draw* is given. Note that checking the latter can be + slow. + """ return self.outcome(claim_draw=claim_draw) is not None def result(self, *, claim_draw: bool = False) -> str: + """ + Return the result of a game: 1-0, 0-1, 1/2-1/2, or *. + + The game is not considered to be over by the + :func:`fifty-move rule ` or + :func:`threefold repetition `, + unless *claim_draw* is given. Note that checking the latter can be + slow. + """ outcome = self.outcome(claim_draw=claim_draw) return outcome.result() if outcome else "*" @@ -2090,7 +2325,7 @@ def can_claim_threefold_repetition(self) -> bool: transpositions.update((transposition_key, )) # Count positions. - switchyard = [] + switchyard: List[Move] = [] while self.move_stack: move = self.pop() switchyard.append(move) @@ -2143,7 +2378,7 @@ def is_repetition(self, count: int = 3) -> bool: # Check full replay. transposition_key = self._transposition_key() - switchyard = [] + switchyard: List[Move] = [] try: while True: @@ -2167,13 +2402,10 @@ def is_repetition(self, count: int = 3) -> bool: return False - def _board_state(self: BoardT) -> _BoardState[BoardT]: - return _BoardState(self) - def _push_capture(self, move: Move, capture_square: Square, piece_type: PieceType, was_promoted: bool) -> None: pass - def push(self: BoardT, move: Move) -> None: + def push(self, move: Move) -> None: """ Updates the position with the given *move* and puts it onto the move stack. @@ -2198,7 +2430,7 @@ def push(self: BoardT, move: Move) -> None: """ # Push move and remember board state. move = self._to_chess960(move) - board_state = self._board_state() + board_state = _BoardState(self) self.castling_rights = self.clean_castling_rights() # Before pushing stack self.move_stack.append(self._from_chess960(self.chess960, move.from_square, move.to_square, move.promotion, move.drop)) self._stack.append(board_state) @@ -2238,29 +2470,29 @@ def push(self: BoardT, move: Move) -> None: # Update castling rights. self.castling_rights &= ~to_bb & ~from_bb - if piece_type == KING and not promoted: + if piece_type == KING and not self._effective_promoted() & from_bb: if self.turn == WHITE: self.castling_rights &= ~BB_RANK_1 else: self.castling_rights &= ~BB_RANK_8 - elif captured_piece_type == KING and not self.promoted & to_bb: - if self.turn == WHITE and square_rank(move.to_square) == 7: + elif captured_piece_type == KING and not self._effective_promoted() & to_bb: + if self.turn == WHITE and square_rank(move.to_square) == RANK_8: self.castling_rights &= ~BB_RANK_8 - elif self.turn == BLACK and square_rank(move.to_square) == 0: + elif self.turn == BLACK and square_rank(move.to_square) == RANK_1: self.castling_rights &= ~BB_RANK_1 # Handle special pawn moves. if piece_type == PAWN: diff = move.to_square - move.from_square - if diff == 16 and square_rank(move.from_square) == 1: + if diff == 16 and square_rank(move.from_square) == RANK_2: self.ep_square = move.from_square + 8 - elif diff == -16 and square_rank(move.from_square) == 6: + elif diff == -16 and square_rank(move.from_square) == RANK_7: self.ep_square = move.from_square - 8 elif move.to_square == ep_square and abs(diff) in [7, 9] and not captured_piece_type: # Remove pawns captured en passant. down = -8 if self.turn == WHITE else 8 - capture_square = ep_square + down + capture_square = move.to_square + down captured_piece_type = self._remove_piece_at(capture_square) # Promotion. @@ -2294,7 +2526,7 @@ def push(self: BoardT, move: Move) -> None: # Swap turn. self.turn = not self.turn - def pop(self: BoardT) -> Move: + def pop(self) -> Move: """ Restores the previous position and returns the last move from the stack. @@ -2339,7 +2571,7 @@ def castling_shredder_fen(self) -> str: if not castling_rights: return "-" - builder = [] + builder: List[str] = [] for square in scan_reversed(castling_rights & BB_RANK_1): builder.append(FILE_NAMES[square_file(square)].upper()) @@ -2350,7 +2582,7 @@ def castling_shredder_fen(self) -> str: return "".join(builder) def castling_xfen(self) -> str: - builder = [] + builder: List[str] = [] for color in COLORS: king = self.king(color) @@ -2386,7 +2618,7 @@ def has_legal_en_passant(self) -> bool: """Checks if there is a legal en passant capture.""" return self.ep_square is not None and any(self.generate_legal_ep()) - def fen(self, *, shredder: bool = False, en_passant: _EnPassantSpec = "legal", promoted: Optional[bool] = None) -> str: + def fen(self, *, shredder: bool = False, en_passant: EnPassantSpec = "legal", promoted: Optional[bool] = None) -> str: """ Gets a FEN representation of the position. @@ -2418,7 +2650,7 @@ def fen(self, *, shredder: bool = False, en_passant: _EnPassantSpec = "legal", p str(self.fullmove_number) ]) - def shredder_fen(self, *, en_passant: _EnPassantSpec = "legal", promoted: Optional[bool] = None) -> str: + def shredder_fen(self, *, en_passant: EnPassantSpec = "legal", promoted: Optional[bool] = None) -> str: return " ".join([ self.epd(shredder=True, en_passant=en_passant, promoted=promoted), str(self.halfmove_clock), @@ -2611,13 +2843,11 @@ def chess960_pos(self, *, ignore_turn: bool = False, ignore_castling: bool = Fal return super().chess960_pos() def _epd_operations(self, operations: Mapping[str, Union[None, str, int, float, Move, Iterable[Move]]]) -> str: - epd = [] + epd: List[str] = [] first_op = True for opcode, operand in operations.items(): - assert opcode != "-", "dash (-) is not a valid epd opcode" - for blacklisted in [" ", "\n", "\t", "\r"]: - assert blacklisted not in opcode, f"invalid character {blacklisted!r} in epd opcode: {opcode!r}" + self._validate_epd_opcode(opcode) if not first_op: epd.append(" ") @@ -2654,7 +2884,7 @@ def _epd_operations(self, operations: Mapping[str, Union[None, str, int, float, return "".join(epd) - def epd(self, *, shredder: bool = False, en_passant: _EnPassantSpec = "legal", promoted: Optional[bool] = None, **operations: Union[None, str, int, float, Move, Iterable[Move]]) -> str: + def epd(self, *, shredder: bool = False, en_passant: EnPassantSpec = "legal", promoted: Optional[bool] = None, **operations: Union[None, str, int, float, Move, Iterable[Move]]) -> str: """ Gets an EPD representation of the current position. @@ -2695,7 +2925,18 @@ def epd(self, *, shredder: bool = False, en_passant: _EnPassantSpec = "legal", p return " ".join(epd) - def _parse_epd_ops(self: BoardT, operation_part: str, make_board: Callable[[], BoardT]) -> Dict[str, Union[None, str, int, float, Move, List[Move]]]: + def _validate_epd_opcode(self, opcode: str) -> None: + if not opcode: + raise ValueError("empty string is not a valid epd opcode") + if opcode == "-": + raise ValueError("dash (-) is not a valid epd opcode") + if not opcode[0].isalpha(): + raise ValueError(f"expected epd opcode to start with a letter, got: {opcode!r}") + for blacklisted in [" ", "\n", "\t", "\r"]: + if blacklisted in opcode: + raise ValueError(f"invalid character {blacklisted!r} in epd opcode: {opcode!r}") + + def _parse_epd_ops(self, operation_part: str, make_board: Callable[[], Self]) -> Dict[str, Union[None, str, int, float, Move, List[Move]]]: operations: Dict[str, Union[None, str, int, float, Move, List[Move]]] = {} state = "opcode" opcode = "" @@ -2708,6 +2949,7 @@ def _parse_epd_ops(self: BoardT, operation_part: str, make_board: Callable[[], B if opcode == "-": opcode = "" elif opcode: + self._validate_epd_opcode(opcode) state = "after_opcode" elif ch is None or ch == ";": if opcode == "-": @@ -2782,7 +3024,7 @@ def _parse_epd_ops(self: BoardT, operation_part: str, make_board: Callable[[], B if opcode == "pv": # A variation. - variation = [] + variation: List[Move] = [] for token in operand.split(): move = position.parse_xboard(token) variation.append(move) @@ -2959,7 +3201,7 @@ def variation_san(self, variation: Iterable[Move]) -> str: :raises: :exc:`IllegalMoveError` if any moves in the sequence are illegal. """ board = self.copy(stack=False) - san = [] + san: List[str] = [] for move in variation: if not board.is_legal(move): @@ -2980,15 +3222,17 @@ def parse_san(self, san: str) -> Move: algebraic notation and returns the corresponding move object. Ambiguous moves are rejected. Overspecified moves (including long - algebraic notation) are accepted. + algebraic notation) are accepted. Some common syntactical deviations + are also accepted. The returned move is guaranteed to be either legal or a null move. :raises: :exc:`ValueError` (specifically an exception specified below) if the SAN is invalid, illegal or ambiguous. - - :exc:`InvalidMoveError` if the SAN is syntactically invalid. - - :exc:`IllegalMoveError` if the SAN is illegal. - - :exc:`AmbiguousMoveError` if the SAN is ambiguous. + + - :exc:`InvalidMoveError` if the SAN is syntactically invalid. + - :exc:`IllegalMoveError` if the SAN is illegal. + - :exc:`AmbiguousMoveError` if the SAN is ambiguous. """ # Castling. try: @@ -3020,6 +3264,8 @@ def parse_san(self, san: str) -> Move: # Filter by original square. from_mask = BB_ALL + from_file = None + from_rank = None if match.group(2): from_file = FILE_NAMES.index(match.group(2)) from_mask &= BB_FILES[from_file] @@ -3031,7 +3277,7 @@ def parse_san(self, san: str) -> Move: if match.group(1): piece_type = PIECE_SYMBOLS.index(match.group(1).lower()) from_mask &= self.pieces_mask(piece_type, self.turn) - elif match.group(2) and match.group(3): + elif from_file is not None and from_rank is not None: # Allow fully specified moves, even if they are not pawn moves, # including castling moves. move = self.find_move(square(from_file, from_rank), to_square, promotion) @@ -3043,7 +3289,7 @@ def parse_san(self, san: str) -> Move: from_mask &= self.pawns # Do not allow pawn captures if file is not specified. - if not match.group(2): + if from_file is None: from_mask &= BB_FILES[square_file(to_square)] # Match legal moves. @@ -3071,9 +3317,10 @@ def push_san(self, san: str) -> Move: :raises: :exc:`ValueError` (specifically an exception specified below) if neither legal nor a null move. - - :exc:`InvalidMoveError` if the SAN is syntactically invalid. - - :exc:`IllegalMoveError` if the SAN is illegal. - - :exc:`AmbiguousMoveError` if the SAN is ambiguous. + + - :exc:`InvalidMoveError` if the SAN is syntactically invalid. + - :exc:`IllegalMoveError` if the SAN is illegal. + - :exc:`AmbiguousMoveError` if the SAN is ambiguous. """ move = self.parse_san(san) self.push(move) @@ -3104,8 +3351,9 @@ def parse_uci(self, uci: str) -> Move: :raises: :exc:`ValueError` (specifically an exception specified below) if the move is invalid or illegal in the current position (but not a null move). - - :exc:`InvalidMoveError` if the UCI is syntactically invalid. - - :exc:`IllegalMoveError` if the UCI is illegal. + + - :exc:`InvalidMoveError` if the UCI is syntactically invalid. + - :exc:`IllegalMoveError` if the UCI is illegal. """ move = Move.from_uci(uci) @@ -3129,8 +3377,9 @@ def push_uci(self, uci: str) -> Move: :raises: :exc:`ValueError` (specifically an exception specified below) if the move is invalid or illegal in the current position (but not a null move). - - :exc:`InvalidMoveError` if the UCI is syntactically invalid. - - :exc:`IllegalMoveError` if the UCI is illegal. + + - :exc:`InvalidMoveError` if the UCI is syntactically invalid. + - :exc:`IllegalMoveError` if the UCI is illegal. """ move = self.parse_uci(uci) self.push(move) @@ -3173,8 +3422,8 @@ def _reduces_castling_rights(self, move: Move) -> bool: cr = self.clean_castling_rights() touched = BB_SQUARES[move.from_square] ^ BB_SQUARES[move.to_square] return bool(touched & cr or - cr & BB_RANK_1 and touched & self.kings & self.occupied_co[WHITE] & ~self.promoted or - cr & BB_RANK_8 and touched & self.kings & self.occupied_co[BLACK] & ~self.promoted) + cr & BB_RANK_1 and touched & self.kings & self.occupied_co[WHITE] & ~self._effective_promoted() or + cr & BB_RANK_8 and touched & self.kings & self.occupied_co[BLACK] & ~self._effective_promoted()) def is_irreversible(self, move: Move) -> bool: """ @@ -3228,16 +3477,16 @@ def clean_castling_rights(self) -> Bitboard: black_castling &= (BB_A8 | BB_H8) # The kings must be on e1 or e8. - if not self.occupied_co[WHITE] & self.kings & ~self.promoted & BB_E1: + if not self.occupied_co[WHITE] & self.kings & ~self._effective_promoted() & BB_E1: white_castling = 0 - if not self.occupied_co[BLACK] & self.kings & ~self.promoted & BB_E8: + if not self.occupied_co[BLACK] & self.kings & ~self._effective_promoted() & BB_E8: black_castling = 0 return white_castling | black_castling else: # The kings must be on the back rank. - white_king_mask = self.occupied_co[WHITE] & self.kings & BB_RANK_1 & ~self.promoted - black_king_mask = self.occupied_co[BLACK] & self.kings & BB_RANK_8 & ~self.promoted + white_king_mask = self.occupied_co[WHITE] & self.kings & BB_RANK_1 & ~self._effective_promoted() + black_king_mask = self.occupied_co[BLACK] & self.kings & BB_RANK_8 & ~self._effective_promoted() if not white_king_mask: white_castling = 0 if not black_king_mask: @@ -3253,7 +3502,7 @@ def clean_castling_rights(self) -> Bitboard: if white_h_side and msb(white_h_side) < msb(white_king_mask): white_h_side = 0 - black_a_side = (black_castling & -black_castling) + black_a_side = black_castling & -black_castling black_h_side = BB_SQUARES[msb(black_castling)] if black_castling else BB_EMPTY if black_a_side and msb(black_a_side) > msb(black_king_mask): @@ -3275,7 +3524,7 @@ def has_kingside_castling_rights(self, color: Color) -> bool: castling rights. """ backrank = BB_RANK_1 if color == WHITE else BB_RANK_8 - king_mask = self.kings & self.occupied_co[color] & backrank & ~self.promoted + king_mask = self.kings & self.occupied_co[color] & backrank & ~self._effective_promoted() if not king_mask: return False @@ -3296,7 +3545,7 @@ def has_queenside_castling_rights(self, color: Color) -> bool: castling rights. """ backrank = BB_RANK_1 if color == WHITE else BB_RANK_8 - king_mask = self.kings & self.occupied_co[color] & backrank & ~self.promoted + king_mask = self.kings & self.occupied_co[color] & backrank & ~self._effective_promoted() if not king_mask: return False @@ -3369,11 +3618,11 @@ def status(self) -> Status: errors |= STATUS_EMPTY # There must be exactly one king of each color. - if not self.occupied_co[WHITE] & self.kings: + if not self.occupied_co[WHITE] & self.kings & ~self._effective_promoted(): errors |= STATUS_NO_WHITE_KING - if not self.occupied_co[BLACK] & self.kings: + if not self.occupied_co[BLACK] & self.kings & ~self._effective_promoted(): errors |= STATUS_NO_BLACK_KING - if popcount(self.occupied & self.kings) > 2: + if popcount(self.occupied & self.kings & ~self._effective_promoted()) > 2: errors |= STATUS_TOO_MANY_KINGS # There can not be more than 16 pieces of any color. @@ -3407,7 +3656,7 @@ def status(self) -> Status: # More than the maximum number of possible checkers in the variant. checkers = self.checkers_mask() - our_kings = self.kings & self.occupied_co[self.turn] & ~self.promoted + our_kings = self.kings & self.occupied_co[self.turn] & ~self._effective_promoted() if checkers: if popcount(checkers) > 2: errors |= STATUS_TOO_MANY_CHECKERS @@ -3431,11 +3680,11 @@ def _valid_ep_square(self) -> Optional[Square]: return None if self.turn == WHITE: - ep_rank = 5 + ep_rank = RANK_6 pawn_mask = shift_down(BB_SQUARES[self.ep_square]) seventh_rank_mask = shift_up(BB_SQUARES[self.ep_square]) else: - ep_rank = 2 + ep_rank = RANK_3 pawn_mask = shift_up(BB_SQUARES[self.ep_square]) seventh_rank_mask = shift_down(BB_SQUARES[self.ep_square]) @@ -3584,14 +3833,14 @@ def generate_legal_captures(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboar self.generate_legal_ep(from_mask, to_mask)) def _attacked_for_king(self, path: Bitboard, occupied: Bitboard) -> bool: - return any(self._attackers_mask(not self.turn, sq, occupied) for sq in scan_reversed(path)) + return any(self.attackers_mask(not self.turn, sq, occupied) for sq in scan_reversed(path)) def generate_castling_moves(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]: if self.is_variant_end(): return backrank = BB_RANK_1 if self.turn == WHITE else BB_RANK_8 - king = self.occupied_co[self.turn] & self.kings & ~self.promoted & backrank & from_mask + king = self.occupied_co[self.turn] & self.kings & ~self._effective_promoted() & backrank & from_mask king &= -king if not king: return @@ -3648,6 +3897,7 @@ def _to_chess960(self, move: Move) -> Move: def _transposition_key(self) -> Hashable: return (self.pawns, self.knights, self.bishops, self.rooks, self.queens, self.kings, + self._effective_promoted(), self.occupied_co[WHITE], self.occupied_co[BLACK], self.turn, self.clean_castling_rights(), self.ep_square if self.has_legal_en_passant() else None) @@ -3682,16 +3932,16 @@ def apply_transform(self, f: Callable[[Bitboard], Bitboard]) -> None: self.ep_square = None if self.ep_square is None else msb(f(BB_SQUARES[self.ep_square])) self.castling_rights = f(self.castling_rights) - def transform(self: BoardT, f: Callable[[Bitboard], Bitboard]) -> BoardT: + def transform(self, f: Callable[[Bitboard], Bitboard]) -> Self: board = self.copy(stack=False) board.apply_transform(f) return board - def apply_mirror(self: BoardT) -> None: + def apply_mirror(self) -> None: super().apply_mirror() self.turn = not self.turn - def mirror(self: BoardT) -> BoardT: + def mirror(self) -> Self: """ Returns a mirrored copy of the board. @@ -3706,7 +3956,7 @@ def mirror(self: BoardT) -> BoardT: board.apply_mirror() return board - def copy(self: BoardT, *, stack: Union[bool, int] = True) -> BoardT: + def copy(self, *, stack: Union[bool, int] = True) -> Self: """ Creates a copy of the board. @@ -3772,7 +4022,7 @@ def __contains__(self, move: Move) -> bool: return self.board.is_pseudo_legal(move) def __repr__(self) -> str: - builder = [] + builder: List[str] = [] for move in self: if self.board.is_legal(move): @@ -3807,7 +4057,7 @@ def __repr__(self) -> str: return f"" -IntoSquareSet = Union[SupportsInt, Iterable[Square]] +IntoSquareSet: TypeAlias = Union[SupportsInt, Iterable[Square]] class SquareSet: """ @@ -3889,7 +4139,7 @@ class SquareSet: def __init__(self, squares: IntoSquareSet = BB_EMPTY) -> None: try: - self.mask = squares.__int__() & BB_ALL # type: ignore + self.mask: Bitboard = squares.__int__() & BB_ALL # type: ignore return except AttributeError: self.mask = 0 @@ -4086,7 +4336,7 @@ def __repr__(self) -> str: return f"SquareSet({self.mask:#021_x})" def __str__(self) -> str: - builder = [] + builder: List[str] = [] for square in SQUARES_180: mask = BB_SQUARES[square] diff --git a/chess/_interactive.py b/chess/_interactive.py deleted file mode 100644 index edeef3df0..000000000 --- a/chess/_interactive.py +++ /dev/null @@ -1,190 +0,0 @@ -# TODO: Fix typing in this file. -# mypy: ignore-errors - -import chess.svg - - -class WidgetError(Exception): - """ - raised when ipywidgets is not installed - """ - - -class NotJupyter(Exception): - """ - raised when InteractiveViewer is instantiated from a non jupyter shell - """ - - -try: - from ipywidgets import Button, GridBox, Layout, HTML, Output, HBox, Select - from IPython.display import display, clear_output -except ModuleNotFoundError: - raise WidgetError("You need to have ipywidgets installed and running from Jupyter") - - -class InteractiveViewer: - def __new__(cls, game): - jupyter = True - try: - if get_ipython().__class__.__name__ != "ZMQInteractiveShell": - jupyter = False - except NameError: - jupyter = False - - if not jupyter: - raise NotJupyter("The interactive viewer only runs in Jupyter shell") - - return object.__new__(cls) - - def __init__(self, game): - self.game = game - self.__board = game.board() - self.__moves = list(game.mainline_moves()) - self.__white_moves = [str(move) for (i, move) in enumerate(self.__moves) if i % 2 == 0] - self.__black_moves = [str(move) for (i, move) in enumerate(self.__moves) if i % 2 == 1] - self.__move_list_len = len(self.__white_moves) - self.__num_moves = len(self.__moves) - self.__next_move = 0 if self.__moves else None - self.__out = Output() - - def __next_click(self, _): - move = self.__moves[self.__next_move] - self.__next_move += 1 - self.__board.push(move) - self.show() - - def __prev_click(self, _): - self.__board.pop() - self.__next_move -= 1 - self.show() - - def __reset_click(self, _): - self.__board.reset() - self.__next_move = 0 - self.show() - - def __white_select_change(self, change): - new = change["new"] - if (isinstance(new, dict)) and ("index" in new): - target = new["index"] * 2 - self.__seek(target) - self.show() - - def __black_select_change(self, change): - new = change["new"] - if (isinstance(new, dict)) and ("index" in new): - target = new["index"] * 2 + 1 - self.__seek(target) - self.show() - - def __seek(self, target): - while self.__next_move <= target: - move = self.__moves[self.__next_move] - self.__next_move += 1 - self.__board.push(move) - - while self.__next_move > target + 1: - self.__board.pop() - self.__next_move -= 1 - - def show(self): - display(self.__out) - next_move = Button( - icon="step-forward", - layout=Layout(width="60px", grid_area="right"), - disabled=self.__next_move >= self.__num_moves, - ) - - prev_move = Button( - icon="step-backward", - layout=Layout(width="60px", grid_area="left"), - disabled=self.__next_move == 0, - ) - - reset = Button( - icon="stop", - layout=Layout(width="60px", grid_area="middle"), - disabled=self.__next_move == 0, - ) - - if self.__next_move == 0: - white_move = None - black_move = None - else: - white_move = ( - self.__white_moves[self.__next_move // 2] - if (self.__next_move % 2) == 1 - else None - ) - black_move = ( - self.__black_moves[self.__next_move // 2 - 1] - if (self.__next_move % 2) == 0 - else None - ) - - white_move_list = Select( - options=self.__white_moves, - value=white_move, - rows=max(self.__move_list_len, 24), - disabled=False, - layout=Layout(width="80px"), - ) - - black_move_list = Select( - options=self.__black_moves, - value=black_move, - rows=max(self.__move_list_len, 24), - disabled=False, - layout=Layout(width="80px"), - ) - - white_move_list.observe(self.__white_select_change) - black_move_list.observe(self.__black_select_change) - - move_number_width = 3 + len(str(self.__move_list_len)) * 10 - - move_number = Select( - options=range(1, self.__move_list_len + 1), - value=None, - disabled=True, - rows=max(self.__move_list_len, 24), - layout=Layout(width=f"{move_number_width}px"), - ) - - move_list = HBox( - [move_number, white_move_list, black_move_list], - layout=Layout(height="407px", grid_area="moves"), - ) - - next_move.on_click(self.__next_click) - prev_move.on_click(self.__prev_click) - reset.on_click(self.__reset_click) - - with self.__out: - grid_box = GridBox( - children=[next_move, prev_move, reset, self.svg, move_list], - layout=Layout( - width=f"{390+move_number_width+160}px", - grid_template_rows="90% 10%", - grid_template_areas=""" - "top top top top top moves" - ". left middle right . moves" - """, - ), - ) - clear_output(wait=True) - display(grid_box) - - @property - def svg(self) -> HTML: - svg = chess.svg.board( - board=self.__board, - size=390, - lastmove=self.__board.peek() if self.__board.move_stack else None, - check=self.__board.king(self.__board.turn) - if self.__board.is_check() - else None, - ) - svg_widget = HTML(value=svg, layout=Layout(grid_area="top")) - return svg_widget diff --git a/chess/engine.py b/chess/engine.py index 5194247f6..c66bc0c45 100644 --- a/chess/engine.py +++ b/chess/engine.py @@ -1,19 +1,3 @@ -# This file is part of the python-chess library. -# Copyright (C) 2012-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - from __future__ import annotations import abc @@ -24,30 +8,34 @@ import copy import dataclasses import enum +import inspect import logging import math -import warnings import shlex import subprocess import sys import threading import time import typing -import os import re import chess from chess import Color from types import TracebackType -from typing import Any, Callable, Coroutine, Deque, Dict, Generator, Generic, Iterable, Iterator, List, Mapping, MutableMapping, Optional, Tuple, Type, TypeVar, Union +from typing import Any, Callable, Coroutine, Deque, Dict, Generator, Generic, Iterable, Iterator, List, Literal, Mapping, MutableMapping, Optional, Tuple, Type, TypedDict, TypeVar, Union + +if typing.TYPE_CHECKING: + from typing_extensions import override +else: + F = typing.TypeVar("F", bound=Callable[..., Any]) + def override(fn: F, /) -> F: + return fn + +if typing.TYPE_CHECKING: + from typing_extensions import Self -try: - from typing import Literal - _WdlModel = Literal["sf", "sf15", "sf14", "sf12", "lichess"] -except ImportError: - # Before Python 3.8. - _WdlModel = str # type: ignore +WdlModel = Literal["sf", "sf16.1", "sf16", "sf15.1", "sf15", "sf14", "sf12", "lichess"] T = TypeVar("T") @@ -63,136 +51,21 @@ MANAGED_OPTIONS = ["uci_chess960", "uci_variant", "multipv", "ponder"] -class EventLoopPolicy(asyncio.AbstractEventLoopPolicy): - """ - An event loop policy for thread-local event loops and child watchers. - Ensures each event loop is capable of spawning and watching subprocesses, - even when not running on the main thread. - - Windows: Uses :class:`~asyncio.ProactorEventLoop`. - - Unix: Uses :class:`~asyncio.SelectorEventLoop`. If available, - :class:`~asyncio.PidfdChildWatcher` is used to detect subprocess - termination (Python 3.9+ on Linux 5.3+). Otherwise, the default child - watcher is used on the main thread and relatively slow eager polling - is used on all other threads. - """ - class _Local(threading.local): - loop: Optional[asyncio.AbstractEventLoop] = None - set_called = False - watcher: Optional[asyncio.AbstractChildWatcher] = None - - def __init__(self) -> None: - self._local = self._Local() - - def get_event_loop(self) -> asyncio.AbstractEventLoop: - if self._local.loop is None and not self._local.set_called and threading.current_thread() is threading.main_thread(): - self.set_event_loop(self.new_event_loop()) - if self._local.loop is None: - raise RuntimeError(f"no current event loop in thread {threading.current_thread().name!r}") - return self._local.loop - - def set_event_loop(self, loop: Optional[asyncio.AbstractEventLoop]) -> None: - assert loop is None or isinstance(loop, asyncio.AbstractEventLoop) - self._local.set_called = True - self._local.loop = loop - if self._local.watcher is not None: - self._local.watcher.attach_loop(loop) - - def new_event_loop(self) -> asyncio.AbstractEventLoop: - return asyncio.ProactorEventLoop() if sys.platform == "win32" else asyncio.SelectorEventLoop() # type: ignore - - def get_child_watcher(self) -> asyncio.AbstractChildWatcher: - if self._local.watcher is None: - self._local.watcher = self._init_watcher() - self._local.watcher.attach_loop(self._local.loop) - return self._local.watcher - - def set_child_watcher(self, watcher: Optional[asyncio.AbstractChildWatcher]) -> None: - assert watcher is None or isinstance(watcher, asyncio.AbstractChildWatcher) - if self._local.watcher is not None: - self._local.watcher.close() - self._local.watcher = watcher - - def _init_watcher(self) -> asyncio.AbstractChildWatcher: - if sys.platform == "win32": - raise NotImplementedError - - try: - os.close(os.pidfd_open(os.getpid())) - watcher: asyncio.AbstractChildWatcher = asyncio.PidfdChildWatcher() - LOGGER.debug("Using PidfdChildWatcher") - return watcher - except (AttributeError, OSError): - # Before Python 3.9 or before Linux 5.3 or the syscall is not - # permitted. - pass - - if threading.current_thread() is threading.main_thread(): - try: - watcher = asyncio.ThreadedChildWatcher() - LOGGER.debug("Using ThreadedChildWatcher") - return watcher - except AttributeError: - # Before Python 3.8. - LOGGER.debug("Using SafeChildWatcher") - return asyncio.SafeChildWatcher() - - class PollingChildWatcher(asyncio.SafeChildWatcher): - _loop: Optional[asyncio.AbstractEventLoop] - _callbacks: Dict[int, Any] - - def __init__(self) -> None: - super().__init__() - self._poll_handle: Optional[asyncio.Handle] = None - self._poll_delay = 0.001 - - def attach_loop(self, loop: Optional[asyncio.AbstractEventLoop]) -> None: - assert loop is None or isinstance(loop, asyncio.AbstractEventLoop) - - if self._loop is not None and loop is None and self._callbacks: - warnings.warn("A loop is being detached from a child watcher with pending handlers", RuntimeWarning) - - if self._poll_handle is not None: - self._poll_handle.cancel() - - self._loop = loop - if self._loop is not None: - self._poll_handle = self._loop.call_soon(self._poll) - self._do_waitpid_all() # type: ignore - - def _poll(self) -> None: - if self._loop: - self._do_waitpid_all() # type: ignore - self._poll_delay = min(self._poll_delay * 2, 1.0) - self._poll_handle = self._loop.call_later(self._poll_delay, self._poll) - - LOGGER.debug("Using PollingChildWatcher") - return PollingChildWatcher() - - -def run_in_background(coroutine: Callable[[concurrent.futures.Future[T]], Coroutine[Any, Any, None]], *, name: Optional[str] = None, debug: bool = False, _policy_lock: threading.Lock = threading.Lock()) -> T: +def run_in_background(coroutine: Callable[[concurrent.futures.Future[T]], Coroutine[Any, Any, None]], *, name: Optional[str] = None, debug: Optional[bool] = None) -> T: """ Runs ``coroutine(future)`` in a new event loop on a background thread. Blocks on *future* and returns the result as soon as it is resolved. The coroutine and all remaining tasks continue running in the background until complete. - - Note: This installs a :class:`chess.engine.EventLoopPolicy` for the entire - process. """ - assert asyncio.iscoroutinefunction(coroutine) - - with _policy_lock: - if not isinstance(asyncio.get_event_loop_policy(), EventLoopPolicy): - asyncio.set_event_loop_policy(EventLoopPolicy()) + assert inspect.iscoroutinefunction(coroutine) future: concurrent.futures.Future[T] = concurrent.futures.Future() def background() -> None: try: - asyncio.run(coroutine(future)) + asyncio.run(coroutine(future), debug=debug) future.cancel() except Exception as exc: future.set_exception(exc) @@ -332,6 +205,14 @@ class Limit: *white_clock* and *black_clock* are, then it is sudden death. """ + clock_id: object = None + """ + An identifier to use with XBoard engines to signal that the time + control has changed. When this field changes, Xboard engines are + sent level or st commands as appropriate. Otherwise, only time + and otim commands are sent to update the engine's clock. + """ + def __repr__(self) -> str: # Like default __repr__, but without None values. return "{}({})".format( @@ -341,41 +222,37 @@ def __repr__(self) -> str: if getattr(self, attr) is not None)) -try: - class InfoDict(typing.TypedDict, total=False): - """ - Dictionary of aggregated information sent by the engine. +class InfoDict(TypedDict, total=False): + """ + Dictionary of aggregated information sent by the engine. - Commonly used keys are: ``score`` (a :class:`~chess.engine.PovScore`), - ``pv`` (a list of :class:`~chess.Move` objects), ``depth``, - ``seldepth``, ``time`` (in seconds), ``nodes``, ``nps``, ``multipv`` - (``1`` for the mainline). + Commonly used keys are: ``score`` (a :class:`~chess.engine.PovScore`), + ``pv`` (a list of :class:`~chess.Move` objects), ``depth``, + ``seldepth``, ``time`` (in seconds), ``nodes``, ``nps``, ``multipv`` + (``1`` for the mainline). - Others: ``tbhits``, ``currmove``, ``currmovenumber``, ``hashfull``, - ``cpuload``, ``refutation``, ``currline``, ``ebf`` (effective branching factor), - ``wdl`` (a :class:`~chess.engine.PovWdl`), and ``string``. - """ - score: PovScore - pv: List[chess.Move] - depth: int - seldepth: int - time: float - nodes: int - nps: int - tbhits: int - multipv: int - currmove: chess.Move - currmovenumber: int - hashfull: int - cpuload: int - refutation: Dict[chess.Move, List[chess.Move]] - currline: Dict[int, List[chess.Move]] - ebf: float - wdl: PovWdl - string: str -except AttributeError: - # Before Python 3.8. - InfoDict = dict # type: ignore + Others: ``tbhits``, ``currmove``, ``currmovenumber``, ``hashfull``, + ``cpuload``, ``refutation``, ``currline``, ``ebf`` (effective branching factor), + ``wdl`` (a :class:`~chess.engine.PovWdl`), and ``string``. + """ + score: PovScore + pv: List[chess.Move] + depth: int + seldepth: int + time: float + nodes: int + nps: int + tbhits: int + multipv: int + currmove: chess.Move + currmovenumber: int + hashfull: int + cpuload: int + refutation: Dict[chess.Move, List[chess.Move]] + currline: Dict[int, List[chess.Move]] + ebf: float + wdl: PovWdl + string: str class PlayResult: @@ -438,6 +315,23 @@ class Info(enum.IntFlag): INFO_ALL = Info.ALL +@dataclasses.dataclass +class Opponent: + """Used to store information about an engine's opponent.""" + + name: Optional[str] + """The name of the opponent.""" + + title: Optional[str] + """The opponent's title--for example, GM, IM, or BOT.""" + + rating: Optional[int] + """The opponent's ELO rating.""" + + is_engine: Optional[bool] + """Whether the opponent is a chess engine/computer program.""" + + class PovScore: """A relative :class:`~chess.engine.Score` and the point of view.""" @@ -467,9 +361,9 @@ def is_mate(self) -> bool: """Tests if this is a mate score.""" return self.relative.is_mate() - def wdl(self, *, model: _WdlModel = "sf", ply: int = 30) -> PovWdl: + def wdl(self, *, model: WdlModel = "sf", ply: int = 30) -> PovWdl: """See :func:`~chess.engine.Score.wdl()`.""" - return PovWdl(self.relative.wdl(), self.turn) + return PovWdl(self.relative.wdl(model=model, ply=ply), self.turn) def __repr__(self) -> str: return "PovScore({!r}, {})".format(self.relative, "WHITE" if self.turn else "BLACK") @@ -509,8 +403,10 @@ class Score(abc.ABC): """ @typing.overload + @abc.abstractmethod def score(self, *, mate_score: int) -> int: ... @typing.overload + @abc.abstractmethod def score(self, *, mate_score: Optional[int] = None) -> Optional[int]: ... @abc.abstractmethod def score(self, *, mate_score: Optional[int] = None) -> Optional[int]: @@ -544,7 +440,7 @@ def is_mate(self) -> bool: return self.mate() is not None @abc.abstractmethod - def wdl(self, *, model: _WdlModel = "sf", ply: int = 30) -> Wdl: + def wdl(self, *, model: WdlModel = "sf", ply: int = 30) -> Wdl: """ Returns statistics for the expected outcome of this game, based on a *model*, given that this score is reached at *ply*. @@ -564,7 +460,9 @@ def wdl(self, *, model: _WdlModel = "sf", ply: int = 30) -> Wdl: :param model: * ``sf``, the WDL model used by the latest Stockfish - (currently ``sf15``). + (currently ``sf16``). + * ``sf16``, the WDL model used by Stockfish 16. + * ``sf15.1``, the WDL model used by Stockfish 15.1. * ``sf15``, the WDL model used by Stockfish 15. * ``sf14``, the WDL model used by Stockfish 14. * ``sf12``, the WDL model used by Stockfish 12. @@ -627,6 +525,35 @@ def __ge__(self, other: object) -> bool: else: return NotImplemented +def _sf16_1_wins(cp: int, *, ply: int) -> int: + # https://github.com/official-stockfish/Stockfish/blob/sf_16.1/src/uci.cpp#L48 + NormalizeToPawnValue = 356 + # https://github.com/official-stockfish/Stockfish/blob/sf_16.1/src/uci.cpp#L383-L384 + m = min(120, max(8, ply / 2 + 1)) / 32 + a = (((-1.06249702 * m + 7.42016937) * m + 0.89425629) * m) + 348.60356174 + b = (((-5.33122190 * m + 39.57831533) * m + -90.84473771) * m) + 123.40620748 + x = min(4000, max(cp * NormalizeToPawnValue / 100, -4000)) + return int(0.5 + 1000 / (1 + math.exp((a - x) / b))) + +def _sf16_wins(cp: int, *, ply: int) -> int: + # https://github.com/official-stockfish/Stockfish/blob/sf_16/src/uci.h#L38 + NormalizeToPawnValue = 328 + # https://github.com/official-stockfish/Stockfish/blob/sf_16/src/uci.cpp#L200-L224 + m = min(240, max(ply, 0)) / 64 + a = (((0.38036525 * m + -2.82015070) * m + 23.17882135) * m) + 307.36768407 + b = (((-2.29434733 * m + 13.27689788) * m + -14.26828904) * m) + 63.45318330 + x = min(4000, max(cp * NormalizeToPawnValue / 100, -4000)) + return int(0.5 + 1000 / (1 + math.exp((a - x) / b))) + +def _sf15_1_wins(cp: int, *, ply: int) -> int: + # https://github.com/official-stockfish/Stockfish/blob/sf_15.1/src/uci.h#L38 + NormalizeToPawnValue = 361 + # https://github.com/official-stockfish/Stockfish/blob/sf_15.1/src/uci.cpp#L200-L224 + m = min(240, max(ply, 0)) / 64 + a = (((-0.58270499 * m + 2.68512549) * m + 15.24638015) * m) + 344.49745382 + b = (((-2.65734562 * m + 15.96509799) * m + -20.69040836) * m) + 73.61029937 + x = min(4000, max(cp * NormalizeToPawnValue / 100, -4000)) + return int(0.5 + 1000 / (1 + math.exp((a - x) / b))) def _sf15_wins(cp: int, *, ply: int) -> int: # https://github.com/official-stockfish/Stockfish/blob/sf_15/src/uci.cpp#L200-L220 @@ -653,7 +580,9 @@ def _sf12_wins(cp: int, *, ply: int) -> int: return int(0.5 + 1000 / (1 + math.exp((a - x) / b))) def _lichess_raw_wins(cp: int) -> int: - return round(1000 / (1 + math.exp(-0.004 * cp))) + # https://github.com/lichess-org/lila/pull/11148 + # https://github.com/lichess-org/lila/blob/2242b0a08faa06e7be5508d338ede7bb09049777/modules/analyse/src/main/WinPercent.scala#L26-L30 + return round(1000 / (1 + math.exp(-0.00368208 * cp))) class Cp(Score): @@ -668,7 +597,7 @@ def mate(self) -> None: def score(self, *, mate_score: Optional[int] = None) -> int: return self.cp - def wdl(self, *, model: _WdlModel = "sf", ply: int = 30) -> Wdl: + def wdl(self, *, model: WdlModel = "sf", ply: int = 30) -> Wdl: if model == "lichess": wins = _lichess_raw_wins(max(-1000, min(self.cp, 1000))) losses = 1000 - wins @@ -678,9 +607,18 @@ def wdl(self, *, model: _WdlModel = "sf", ply: int = 30) -> Wdl: elif model == "sf14": wins = _sf14_wins(self.cp, ply=ply) losses = _sf14_wins(-self.cp, ply=ply) - else: + elif model == "sf15": wins = _sf15_wins(self.cp, ply=ply) losses = _sf15_wins(-self.cp, ply=ply) + elif model == "sf15.1": + wins = _sf15_1_wins(self.cp, ply=ply) + losses = _sf15_1_wins(-self.cp, ply=ply) + elif model == "sf16": + wins = _sf16_wins(self.cp, ply=ply) + losses = _sf16_wins(-self.cp, ply=ply) + else: + wins = _sf16_1_wins(self.cp, ply=ply) + losses = _sf16_1_wins(-self.cp, ply=ply) draws = 1000 - wins - losses return Wdl(wins, draws, losses) @@ -721,7 +659,7 @@ def score(self, *, mate_score: Optional[int] = None) -> Optional[int]: else: return -mate_score - self.moves - def wdl(self, *, model: _WdlModel = "sf", ply: int = 30) -> Wdl: + def wdl(self, *, model: WdlModel = "sf", ply: int = 30) -> Wdl: if model == "lichess": cp = (21 - min(10, abs(self.moves))) * 100 wins = _lichess_raw_wins(cp) @@ -758,7 +696,7 @@ def score(self, *, mate_score: Optional[int] = None) -> Optional[int]: ... def score(self, *, mate_score: Optional[int] = None) -> Optional[int]: return mate_score - def wdl(self, *, model: _WdlModel = "sf", ply: int = 30) -> Wdl: + def wdl(self, *, model: WdlModel = "sf", ply: int = 30) -> Wdl: return Wdl(1000, 0, 0) def __neg__(self) -> Mate: @@ -779,16 +717,11 @@ def __str__(self) -> str: MateGiven = MateGivenType() +@dataclasses.dataclass class PovWdl: """ Relative :class:`win/draw/loss statistics ` and the point of view. - - .. deprecated:: 1.2 - Behaves like a tuple - ``(wdl.relative.wins, wdl.relative.draws, wdl.relative.losses)`` - for backwards compatibility. But it is recommended to use the provided - fields and methods instead. """ relative: Wdl @@ -797,10 +730,6 @@ class PovWdl: turn: Color """The point of view (``chess.WHITE`` or ``chess.BLACK``).""" - def __init__(self, relative: Wdl, turn: Color) -> None: - self.relative = relative - self.turn = turn - def white(self) -> Wdl: """Gets the :class:`~chess.engine.Wdl` from White's point of view.""" return self.pov(chess.WHITE) @@ -822,30 +751,6 @@ def __bool__(self) -> bool: def __repr__(self) -> str: return "PovWdl({!r}, {})".format(self.relative, "WHITE" if self.turn else "BLACK") - # Unfortunately in python-chess v1.1.0, info["wdl"] was a simple tuple - # of the relative permille values, so we have to support __iter__, - # __len__, __getitem__, and equality comparisons with other tuples. - # Never mind the ordering, because that's not a sensible operation, anyway. - - def __iter__(self) -> Iterator[int]: - yield self.relative.wins - yield self.relative.draws - yield self.relative.losses - - def __len__(self) -> int: - return 3 - - def __getitem__(self, idx: int) -> int: - return (self.relative.wins, self.relative.draws, self.relative.losses)[idx] - - def __eq__(self, other: object) -> bool: - if isinstance(other, PovWdl): - return self.white() == other.white() - elif isinstance(other, tuple): - return (self.relative.wins, self.relative.draws, self.relative.losses) == other - else: - return NotImplemented - @dataclasses.dataclass class Wdl: @@ -889,16 +794,6 @@ def expectation(self) -> float: def __bool__(self) -> bool: return bool(self.total()) - def __iter__(self) -> Iterator[int]: - yield self.wins - yield self.draws - yield self.losses - - def __reversed__(self) -> Iterator[int]: - yield self.losses - yield self.draws - yield self.wins - def __pos__(self) -> Wdl: return self @@ -928,7 +823,7 @@ def get_pipe_transport(self, fd: int) -> Optional[asyncio.BaseTransport]: assert fd == 0, f"expected 0 for stdin, got {fd}" return self - def write(self, data: bytes) -> None: + def write(self, data: bytes | bytearray | memoryview) -> None: self.stdin_buffer.extend(data) while b"\n" in self.stdin_buffer: line_bytes, self.stdin_buffer = self.stdin_buffer.split(b"\n", 1) @@ -942,7 +837,7 @@ def write(self, data: bytes) -> None: expectation, responses = self.expectations.popleft() assert expectation == line, f"expected {expectation}, got: {line}" if responses: - self.protocol.pipe_data_received(1, "\n".join(responses + [""]).encode("utf-8")) + self.protocol.loop.call_soon(self.protocol.pipe_data_received, 1, "\n".join(responses + [""]).encode("utf-8")) def get_pid(self) -> int: return id(self) @@ -954,9 +849,6 @@ def get_returncode(self) -> Optional[int]: class Protocol(asyncio.SubprocessProtocol, metaclass=abc.ABCMeta): """Protocol for communicating with a chess engine process.""" - options: MutableMapping[str, Option] - """Dictionary of available options.""" - id: Dict[str, str] """ Dictionary of information about the engine. Common keys are ``name`` @@ -966,7 +858,7 @@ class Protocol(asyncio.SubprocessProtocol, metaclass=abc.ABCMeta): returncode: asyncio.Future[int] """Future: Exit code of the process.""" - def __init__(self: ProtocolT) -> None: + def __init__(self) -> None: self.loop = asyncio.get_running_loop() self.transport: Optional[asyncio.SubprocessTransport] = None @@ -975,8 +867,8 @@ def __init__(self: ProtocolT) -> None: 2: bytearray(), # stderr } - self.command: Optional[BaseCommand[ProtocolT, Any]] = None - self.next_command: Optional[BaseCommand[ProtocolT, Any]] = None + self.command: Optional[BaseCommand[Any]] = None + self.next_command: Optional[BaseCommand[Any]] = None self.initialized = False self.returncode: asyncio.Future[int] = asyncio.Future() @@ -986,19 +878,19 @@ def connection_made(self, transport: asyncio.BaseTransport) -> None: self.transport = transport # type: ignore LOGGER.debug("%s: Connection made", self) - def connection_lost(self: ProtocolT, exc: Optional[Exception]) -> None: + def connection_lost(self, exc: Optional[Exception]) -> None: assert self.transport is not None code = self.transport.get_returncode() assert code is not None, "connect lost, but got no returncode" LOGGER.debug("%s: Connection lost (exit code: %d, error: %s)", self, code, exc) # Terminate commands. - if self.command is not None: - self.command._engine_terminated(self, code) - self.command = None - if self.next_command is not None: - self.next_command._engine_terminated(self, code) - self.next_command = None + command, self.command = self.command, None + next_command, self.next_command = self.next_command, None + if command: + command._engine_terminated(code) + if next_command: + next_command._engine_terminated(code) self.returncode.set_result(code) @@ -1024,31 +916,31 @@ def pipe_data_received(self, fd: int, data: Union[bytes, str]) -> None: LOGGER.warning("%s: >> %r (%s)", self, bytes(line_bytes), err) else: if fd == 1: - self.loop.call_soon(self._line_received, line) + self._line_received(line) else: - self.loop.call_soon(self.error_line_received, line) + self.error_line_received(line) def error_line_received(self, line: str) -> None: LOGGER.warning("%s: stderr >> %s", self, line) - def _line_received(self: ProtocolT, line: str) -> None: + def _line_received(self, line: str) -> None: LOGGER.debug("%s: >> %s", self, line) self.line_received(line) if self.command: - self.command._line_received(self, line) + self.command._line_received(line) def line_received(self, line: str) -> None: pass - async def communicate(self: ProtocolT, command_factory: Callable[[ProtocolT], BaseCommand[ProtocolT, T]]) -> T: + async def communicate(self, command_factory: Callable[[Self], BaseCommand[T]]) -> T: command = command_factory(self) if self.returncode.done(): raise EngineTerminatedError(f"engine process dead (exit code: {self.returncode.result()})") - assert command.state == CommandState.NEW + assert command.state == CommandState.NEW, command.state if self.next_command is not None: self.next_command.result.cancel() @@ -1057,25 +949,25 @@ async def communicate(self: ProtocolT, command_factory: Callable[[ProtocolT], Ba self.next_command = command - def previous_command_finished(_: Optional[asyncio.Future[None]]) -> None: + def previous_command_finished() -> None: self.command, self.next_command = self.next_command, None if self.command is not None: cmd = self.command def cancel_if_cancelled(result: asyncio.Future[T]) -> None: if result.cancelled(): - cmd._cancel(self) + cmd._cancel() cmd.result.add_done_callback(cancel_if_cancelled) - cmd.finished.add_done_callback(previous_command_finished) - cmd._start(self) + cmd._start() + cmd.add_finished_callback(previous_command_finished) if self.command is None: - previous_command_finished(None) + previous_command_finished() elif not self.command.result.done(): self.command.result.cancel() elif not self.command.result.cancelled(): - self.command._cancel(self) + self.command._cancel() return await command.result @@ -1083,6 +975,11 @@ def __repr__(self) -> str: pid = self.transport.get_pid() if self.transport is not None else "?" return f"<{type(self).__name__} (pid={pid})>" + @property + @abc.abstractmethod + def options(self) -> MutableMapping[str, Option]: + """Dictionary of available options.""" + @abc.abstractmethod async def initialize(self) -> None: """Initializes the engine.""" @@ -1106,7 +1003,19 @@ async def configure(self, options: ConfigMapping) -> None: """ @abc.abstractmethod - async def play(self, board: chess.Board, limit: Limit, *, game: object = None, info: Info = INFO_NONE, ponder: bool = False, draw_offered: bool = False, root_moves: Optional[Iterable[chess.Move]] = None, options: ConfigMapping = {}) -> PlayResult: + async def send_opponent_information(self, *, opponent: Optional[Opponent] = None, engine_rating: Optional[int] = None) -> None: + """ + Sends the engine information about its opponent. The information will + be sent after a new game is announced and before the first move. This + method should be called before the first move of a game--i.e., the + first call to :func:`chess.engine.Protocol.play()`. + + :param opponent: Optional. An instance of :class:`chess.engine.Opponent` that has the opponent's information. + :param engine_rating: Optional. This engine's own rating. Only used by XBoard engines. + """ + + @abc.abstractmethod + async def play(self, board: chess.Board, limit: Limit, *, game: object = None, info: Info = INFO_NONE, ponder: bool = False, draw_offered: bool = False, root_moves: Optional[Iterable[chess.Move]] = None, options: ConfigMapping = {}, opponent: Optional[Opponent] = None) -> PlayResult: """ Plays a position. @@ -1118,7 +1027,7 @@ async def play(self, board: chess.Board, limit: Limit, *, game: object = None, i Will automatically inform the engine if the object is not equal to the previous game (e.g., ``ucinewgame``, ``new``). :param info: Selects which additional information to retrieve from the - engine. ``INFO_NONE``, ``INFO_BASE`` (basic information that is + engine. ``INFO_NONE``, ``INFO_BASIC`` (basic information that is trivial to obtain), ``INFO_SCORE``, ``INFO_PV``, ``INFO_REFUTATION``, ``INFO_CURRLINE``, ``INFO_ALL`` or any bitwise combination. Some overhead is associated with parsing @@ -1132,6 +1041,10 @@ async def play(self, board: chess.Board, limit: Limit, *, game: object = None, i analysis. The previous configuration will be restored after the analysis is complete. You can permanently apply a configuration with :func:`~chess.engine.Protocol.configure()`. + :param opponent: Optional. Information about a new opponent. Information + about the original opponent will be restored once the move is + complete. New opponent information can be made permanent with + :func:`~chess.engine.Protocol.send_opponent_information()`. """ @typing.overload @@ -1156,7 +1069,7 @@ async def analyse(self, board: chess.Board, limit: Limit, *, multipv: Optional[i Will automatically inform the engine if the object is not equal to the previous game (e.g., ``ucinewgame``, ``new``). :param info: Selects which information to retrieve from the - engine. ``INFO_NONE``, ``INFO_BASE`` (basic information that is + engine. ``INFO_NONE``, ``INFO_BASIC`` (basic information that is trivial to obtain), ``INFO_SCORE``, ``INFO_PV``, ``INFO_REFUTATION``, ``INFO_CURRLINE``, ``INFO_ALL`` or any bitwise combination. Some overhead is associated with parsing @@ -1189,7 +1102,7 @@ async def analysis(self, board: chess.Board, limit: Optional[Limit] = None, *, m Will automatically inform the engine if the object is not equal to the previous game (e.g., ``ucinewgame``, ``new``). :param info: Selects which information to retrieve from the - engine. ``INFO_NONE``, ``INFO_BASE`` (basic information that is + engine. ``INFO_NONE``, ``INFO_BASIC`` (basic information that is trivial to obtain), ``INFO_SCORE``, ``INFO_PV``, ``INFO_REFUTATION``, ``INFO_CURRLINE``, ``INFO_ALL`` or any bitwise combination. Some overhead is associated with parsing @@ -1205,6 +1118,32 @@ async def analysis(self, board: chess.Board, limit: Optional[Limit] = None, *, m and stopping the analysis at any time. """ + @abc.abstractmethod + async def send_game_result(self, board: chess.Board, winner: Optional[Color] = None, game_ending: Optional[str] = None, game_complete: bool = True) -> None: + """ + Sends the engine the result of the game. + + XBoard engines receive the final moves and a line of the form + ``result {}``. The ```` field is one of ``1-0``, + ``0-1``, ``1/2-1/2``, or ``*`` to indicate white won, black won, draw, + or adjournment, respectively. The ```` field is a description + of the specific reason for the end of the game: "White mates", + "Time forfeiture", "Stalemate", etc. + + UCI engines do not expect end-of-game information and so are not + sent anything. + + :param board: The final state of the board. + :param winner: Optional. Specify the winner of the game. This is useful + if the result of the game is not evident from the board--e.g., time + forfeiture or draw by agreement. If not ``None``, this parameter + overrides any winner derivable from the board. + :param game_ending: Optional. Text describing the reason for the game + ending. Similarly to the winner parameter, this overrides any game + result derivable from the board. + :param game_complete: Optional. Whether the game reached completion. + """ + @abc.abstractmethod async def quit(self) -> None: """Asks the engine to shut down.""" @@ -1220,7 +1159,11 @@ async def popen(cls: Type[ProtocolT], command: Union[str, List[str]], *, setpgrp popen_args["creationflags"] = popen_args.get("creationflags", 0) | subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore except AttributeError: # Unix. - popen_args["start_new_session"] = True + if sys.version_info >= (3, 11): + popen_args["process_group"] = 0 + else: + # Before Python 3.11 + popen_args["start_new_session"] = True return await asyncio.get_running_loop().subprocess_exec(cls, *command, **popen_args) @@ -1232,81 +1175,97 @@ class CommandState(enum.Enum): DONE = enum.auto() -class BaseCommand(Generic[ProtocolT, T]): - def __init__(self, engine: ProtocolT) -> None: +class BaseCommand(Generic[T]): + def __init__(self, engine: Protocol) -> None: + self._engine = engine + self.state = CommandState.NEW self.result: asyncio.Future[T] = asyncio.Future() self.finished: asyncio.Future[None] = asyncio.Future() - def _engine_terminated(self, engine: ProtocolT, code: int) -> None: + self._finished_callbacks: List[Callable[[], None]] = [] + + def add_finished_callback(self, callback: Callable[[], None]) -> None: + self._finished_callbacks.append(callback) + self._dispatch_finished() + + def _dispatch_finished(self) -> None: + if self.finished.done(): + while self._finished_callbacks: + self._finished_callbacks.pop()() + + def _engine_terminated(self, code: int) -> None: hint = ", binary not compatible with cpu?" if code in [-4, 0xc000001d] else "" exc = EngineTerminatedError(f"engine process died unexpectedly (exit code: {code}{hint})") if self.state == CommandState.ACTIVE: - self.engine_terminated(engine, exc) + self.engine_terminated(exc) elif self.state == CommandState.CANCELLING: self.finished.set_result(None) + self._dispatch_finished() elif self.state == CommandState.NEW: - self._handle_exception(engine, exc) + self._handle_exception(exc) - def _handle_exception(self, engine: ProtocolT, exc: Exception) -> None: + def _handle_exception(self, exc: Exception) -> None: if not self.result.done(): self.result.set_exception(exc) else: - engine.loop.call_exception_handler({ + self._engine.loop.call_exception_handler({ # XXX "message": f"{type(self).__name__} failed after returning preliminary result ({self.result!r})", "exception": exc, - "protocol": engine, - "transport": engine.transport, + "protocol": self._engine, + "transport": self._engine.transport, }) if not self.finished.done(): self.finished.set_result(None) + self._dispatch_finished() def set_finished(self) -> None: - assert self.state in [CommandState.ACTIVE, CommandState.CANCELLING] + assert self.state in [CommandState.ACTIVE, CommandState.CANCELLING], self.state if not self.result.done(): self.result.set_exception(EngineError(f"engine command finished before returning result: {self!r}")) - self.finished.set_result(None) self.state = CommandState.DONE + self.finished.set_result(None) + self._dispatch_finished() - def _cancel(self, engine: ProtocolT) -> None: + def _cancel(self) -> None: if self.state != CommandState.CANCELLING and self.state != CommandState.DONE: - assert self.state == CommandState.ACTIVE + assert self.state == CommandState.ACTIVE, self.state self.state = CommandState.CANCELLING - self.cancel(engine) + self.cancel() - def _start(self, engine: ProtocolT) -> None: - assert self.state == CommandState.NEW + def _start(self) -> None: + assert self.state == CommandState.NEW, self.state self.state = CommandState.ACTIVE try: - self.check_initialized(engine) - self.start(engine) + self.check_initialized() + self.start() except EngineError as err: - self._handle_exception(engine, err) + self._handle_exception(err) - def _line_received(self, engine: ProtocolT, line: str) -> None: - assert self.state in [CommandState.ACTIVE, CommandState.CANCELLING] + def _line_received(self, line: str) -> None: + assert self.state in [CommandState.ACTIVE, CommandState.CANCELLING], self.state try: - self.line_received(engine, line) + self.line_received(line) except EngineError as err: - self._handle_exception(engine, err) + self._handle_exception(err) - def cancel(self, engine: ProtocolT) -> None: + def cancel(self) -> None: pass - def check_initialized(self, engine: ProtocolT) -> None: - if not engine.initialized: + def check_initialized(self) -> None: + if not self._engine.initialized: raise EngineError("tried to run command, but engine is not initialized") - def start(self, engine: ProtocolT) -> None: + def start(self) -> None: raise NotImplementedError - def line_received(self, engine: ProtocolT, line: str) -> None: + def line_received(self, line: str) -> None: pass - def engine_terminated(self, engine: ProtocolT, exc: Exception) -> None: - self._handle_exception(engine, exc) + def engine_terminated(self, exc: Exception) -> None: + self._handle_exception(exc) def __repr__(self) -> str: return "<{} at {:#x} (state={}, result={}, finished={}>".format(type(self).__name__, id(self), self.state, self.result, self.finished) @@ -1321,7 +1280,7 @@ class UciProtocol(Protocol): def __init__(self) -> None: super().__init__() - self.options: UciOptionMap[Option] = UciOptionMap() + self._options: UciOptionMap[Option] = UciOptionMap() self.config: UciOptionMap[ConfigValue] = UciOptionMap() self.target_config: UciOptionMap[ConfigValue] = UciOptionMap() self.id = {} @@ -1331,94 +1290,94 @@ def __init__(self) -> None: self.may_ponderhit: Optional[chess.Board] = None self.ponderhit = False + @property + @override + def options(self) -> UciOptionMap[Option]: + return self._options + async def initialize(self) -> None: - class UciInitializeCommand(BaseCommand[UciProtocol, None]): - def check_initialized(self, engine: UciProtocol) -> None: - if engine.initialized: + class UciInitializeCommand(BaseCommand[None]): + def __init__(self, engine: UciProtocol): + super().__init__(engine) + self.engine = engine + + @override + def check_initialized(self) -> None: + if self.engine.initialized: raise EngineError("engine already initialized") - def start(self, engine: UciProtocol) -> None: - engine.send_line("uci") + @override + def start(self) -> None: + self.engine.send_line("uci") - def line_received(self, engine: UciProtocol, line: str) -> None: - if line == "uciok" and not self.result.done(): - engine.initialized = True + @override + def line_received(self, line: str) -> None: + token, remaining = _next_token(line) + if line.strip() == "uciok" and not self.result.done(): + self.engine.initialized = True self.result.set_result(None) self.set_finished() - elif line.startswith("option "): - self._option(engine, line.split(" ", 1)[1]) - elif line.startswith("id "): - self._id(engine, line.split(" ", 1)[1]) + elif token == "option": + self._option(remaining) + elif token == "id": + self._id(remaining) - def _option(self, engine: UciProtocol, arg: str) -> None: + def _option(self, arg: str) -> None: current_parameter = None - - name: List[str] = [] - type: List[str] = [] - default: List[str] = [] - min = None - max = None - current_var = None + option_parts: dict[str, str] = {k: "" for k in ["name", "type", "default", "min", "max"]} var = [] - for token in arg.split(" "): - if token == "name" and not name: - current_parameter = "name" - elif token == "type" and not type: - current_parameter = "type" - elif token == "default" and not default: - current_parameter = "default" - elif token == "min" and min is None: - current_parameter = "min" - elif token == "max" and max is None: - current_parameter = "max" - elif token == "var": - current_parameter = "var" - if current_var is not None: - var.append(" ".join(current_var)) - current_var = [] - elif current_parameter == "name": - name.append(token) - elif current_parameter == "type": - type.append(token) - elif current_parameter == "default": - default.append(token) + parameters = list(option_parts.keys()) + ['var'] + inner_regex = '|'.join([fr"\b{parameter}\b" for parameter in parameters]) + option_regex = fr"\s*({inner_regex})\s*" + for token in re.split(option_regex, arg.strip()): + if token == "var" or (token in option_parts and not option_parts[token]): + current_parameter = token elif current_parameter == "var": - current_var.append(token) - elif current_parameter == "min": - try: - min = int(token) - except ValueError: - LOGGER.exception("Exception parsing option min") - elif current_parameter == "max": - try: - max = int(token) - except ValueError: - LOGGER.exception("Exception parsing option max") + var.append(token) + elif current_parameter: + option_parts[current_parameter] = token + + def parse_min_max_value(option_parts: dict[str, str], which: Literal["min", "max"]) -> Optional[int]: + try: + number = option_parts[which] + return int(number) if number else None + except ValueError: + LOGGER.exception(f"Exception parsing option {which}") + return None - if current_var is not None: - var.append(" ".join(current_var)) + name = option_parts["name"] + type = option_parts["type"] + default = option_parts["default"] + min = parse_min_max_value(option_parts, "min") + max = parse_min_max_value(option_parts, "max") - without_default = Option(" ".join(name), " ".join(type), None, min, max, var) - option = Option(without_default.name, without_default.type, without_default.parse(" ".join(default)), min, max, var) - engine.options[option.name] = option + without_default = Option(name, type, None, min, max, var) + option = Option(without_default.name, without_default.type, without_default.parse(default), min, max, var) + self.engine.options[option.name] = option if option.default is not None: - engine.config[option.name] = option.default + self.engine.config[option.name] = option.default if option.default is not None and not option.is_managed() and option.name.lower() != "uci_analysemode": - engine.target_config[option.name] = option.default + self.engine.target_config[option.name] = option.default - def _id(self, engine: UciProtocol, arg: str) -> None: - key, value = arg.split(" ", 1) - engine.id[key] = value + def _id(self, arg: str) -> None: + key, value = _next_token(arg) + self.engine.id[key] = value.strip() return await self.communicate(UciInitializeCommand) def _isready(self) -> None: self.send_line("isready") + def _opponent_info(self) -> None: + opponent_info = self.config.get("UCI_Opponent") or self.target_config.get("UCI_Opponent") + if opponent_info: + self.send_line(f"setoption name UCI_Opponent value {opponent_info}") + def _ucinewgame(self) -> None: self.send_line("ucinewgame") + self._opponent_info() self.first_game = False self.ponderhit = False @@ -1433,16 +1392,21 @@ def debug(self, on: bool = True) -> None: self.send_line("debug off") async def ping(self) -> None: - class UciPingCommand(BaseCommand[UciProtocol, None]): - def start(self, engine: UciProtocol) -> None: - engine._isready() + class UciPingCommand(BaseCommand[None]): + def __init__(self, engine: UciProtocol) -> None: + super().__init__(engine) + self.engine = engine + + def start(self) -> None: + self.engine._isready() - def line_received(self, engine: UciProtocol, line: str) -> None: - if line == "readyok": + @override + def line_received(self, line: str) -> None: + if line.strip() == "readyok": self.result.set_result(None) self.set_finished() else: - LOGGER.warning("%s: Unexpected engine output: %r", engine, line) + LOGGER.warning("%s: Unexpected engine output: %r", self.engine, line) return await self.communicate(UciPingCommand) @@ -1465,7 +1429,8 @@ def _setoption(self, name: str, value: ConfigValue) -> None: builder.append("value") builder.append(str(value)) - self.send_line(" ".join(builder)) + if name != "UCI_Opponent": # sent after ucinewgame + self.send_line(" ".join(builder)) self.config[name] = value def _configure(self, options: ConfigMapping) -> None: @@ -1475,15 +1440,31 @@ def _configure(self, options: ConfigMapping) -> None: self._setoption(name, value) async def configure(self, options: ConfigMapping) -> None: - class UciConfigureCommand(BaseCommand[UciProtocol, None]): - def start(self, engine: UciProtocol) -> None: - engine._configure(options) - engine.target_config.update({name: value for name, value in options.items() if value is not None}) + class UciConfigureCommand(BaseCommand[None]): + def __init__(self, engine: UciProtocol): + super().__init__(engine) + self.engine = engine + + def start(self) -> None: + self.engine._configure(options) + self.engine.target_config.update({name: value for name, value in options.items() if value is not None}) self.result.set_result(None) self.set_finished() return await self.communicate(UciConfigureCommand) + def _opponent_configuration(self, *, opponent: Optional[Opponent] = None) -> ConfigMapping: + if opponent and opponent.name and "UCI_Opponent" in self.options: + rating = opponent.rating or "none" + title = opponent.title or "none" + player_type = "computer" if opponent.is_engine else "human" + return {"UCI_Opponent": f"{title} {rating} {player_type} {opponent.name}"} + else: + return {} + + async def send_opponent_information(self, *, opponent: Optional[Opponent] = None, engine_rating: Optional[int] = None) -> None: + return await self.configure(self._opponent_configuration(opponent=opponent)) + def _position(self, board: chess.Board) -> None: # Select UCI_Variant and UCI_Chess960. uci_variant = type(board).uci_variant @@ -1524,16 +1505,16 @@ def _go(self, limit: Limit, *, root_moves: Optional[Iterable[chess.Move]] = None builder.append("ponder") if limit.white_clock is not None: builder.append("wtime") - builder.append(str(max(1, int(limit.white_clock * 1000)))) + builder.append(str(max(1, round(limit.white_clock * 1000)))) if limit.black_clock is not None: builder.append("btime") - builder.append(str(max(1, int(limit.black_clock * 1000)))) + builder.append(str(max(1, round(limit.black_clock * 1000)))) if limit.white_inc is not None: builder.append("winc") - builder.append(str(int(limit.white_inc * 1000))) + builder.append(str(round(limit.white_inc * 1000))) if limit.black_inc is not None: builder.append("binc") - builder.append(str(int(limit.black_inc * 1000))) + builder.append(str(round(limit.black_inc * 1000))) if limit.remaining_moves is not None and int(limit.remaining_moves) > 0: builder.append("movestogo") builder.append(str(int(limit.remaining_moves))) @@ -1548,7 +1529,7 @@ def _go(self, limit: Limit, *, root_moves: Optional[Iterable[chess.Move]] = None builder.append(str(max(1, int(limit.mate)))) if limit.time is not None: builder.append("movetime") - builder.append(str(max(1, int(limit.time * 1000)))) + builder.append(str(max(1, round(limit.time * 1000)))) if infinite: builder.append("infinite") if root_moves is not None: @@ -1560,78 +1541,88 @@ def _go(self, limit: Limit, *, root_moves: Optional[Iterable[chess.Move]] = None builder.append("0000") self.send_line(" ".join(builder)) - async def play(self, board: chess.Board, limit: Limit, *, game: object = None, info: Info = INFO_NONE, ponder: bool = False, draw_offered: bool = False, root_moves: Optional[Iterable[chess.Move]] = None, options: ConfigMapping = {}) -> PlayResult: - same_game = not self.first_game and game == self.game and not options - self.last_move = board.move_stack[-1] if (same_game and ponder and board.move_stack) else chess.Move.null() + async def play(self, board: chess.Board, limit: Limit, *, game: object = None, info: Info = INFO_NONE, ponder: bool = False, draw_offered: bool = False, root_moves: Optional[Iterable[chess.Move]] = None, options: ConfigMapping = {}, opponent: Optional[Opponent] = None) -> PlayResult: + new_options: Dict[str, ConfigValue] = {} + for name, value in options.items(): + new_options[name] = value + new_options.update(self._opponent_configuration(opponent=opponent)) - class UciPlayCommand(BaseCommand[UciProtocol, PlayResult]): + engine = self + + class UciPlayCommand(BaseCommand[PlayResult]): def __init__(self, engine: UciProtocol): super().__init__(engine) + self.engine = engine # May ponderhit only in the same game and with unchanged target # options. The managed options UCI_AnalyseMode, Ponder, and # MultiPV never change between pondering play commands. - engine.may_ponderhit = board if ponder and not engine.first_game and game == engine.game and not engine._changed_options(options) else None + engine.may_ponderhit = board if ponder and not engine.first_game and game == engine.game and not engine._changed_options(new_options) else None - def start(self, engine: UciProtocol) -> None: + @override + def start(self) -> None: self.info: InfoDict = {} self.pondering: Optional[chess.Board] = None self.sent_isready = False self.start_time = time.perf_counter() - if engine.ponderhit: - engine.ponderhit = False - engine.send_line("ponderhit") + if self.engine.ponderhit: + self.engine.ponderhit = False + self.engine.send_line("ponderhit") return - if "UCI_AnalyseMode" in engine.options and "UCI_AnalyseMode" not in engine.target_config and all(name.lower() != "uci_analysemode" for name in options): - engine._setoption("UCI_AnalyseMode", False) - if "Ponder" in engine.options: - engine._setoption("Ponder", ponder) - if "MultiPV" in engine.options: - engine._setoption("MultiPV", engine.options["MultiPV"].default) + if "UCI_AnalyseMode" in self.engine.options and "UCI_AnalyseMode" not in self.engine.target_config and all(name.lower() != "uci_analysemode" for name in new_options): + self.engine._setoption("UCI_AnalyseMode", False) + if "Ponder" in self.engine.options: + self.engine._setoption("Ponder", ponder) + if "MultiPV" in self.engine.options: + self.engine._setoption("MultiPV", self.engine.options["MultiPV"].default) - engine._configure(options) + new_opponent = new_options.get("UCI_Opponent") or self.engine.target_config.get("UCI_Opponent") + opponent_changed = new_opponent != self.engine.config.get("UCI_Opponent") + self.engine._configure(new_options) - if engine.first_game or engine.game != game: - engine.game = game - engine._ucinewgame() + if self.engine.first_game or self.engine.game != game or opponent_changed: + self.engine.game = game + self.engine._ucinewgame() self.sent_isready = True - engine._isready() + self.engine._isready() else: - self._readyok(engine) - - def line_received(self, engine: UciProtocol, line: str) -> None: - if line.startswith("info "): - self._info(engine, line.split(" ", 1)[1]) - elif line.startswith("bestmove "): - self._bestmove(engine, line.split(" ", 1)[1]) - elif line == "readyok" and self.sent_isready: - self._readyok(engine) + self._readyok() + + @override + def line_received(self, line: str) -> None: + token, remaining = _next_token(line) + if token == "info": + self._info(remaining) + elif token == "bestmove": + self._bestmove(remaining) + elif line.strip() == "readyok" and self.sent_isready: + self._readyok() else: - LOGGER.warning("%s: Unexpected engine output: %r", engine, line) + LOGGER.warning("%s: Unexpected engine output: %r", self.engine, line) - def _readyok(self, engine: UciProtocol) -> None: + def _readyok(self) -> None: self.sent_isready = False engine._position(board) engine._go(limit, root_moves=root_moves) - def _info(self, engine: UciProtocol, arg: str) -> None: + def _info(self, arg: str) -> None: if not self.pondering: - self.info.update(_parse_uci_info(arg, engine.board, info)) + self.info.update(_parse_uci_info(arg, self.engine.board, info)) - def _bestmove(self, engine: UciProtocol, arg: str) -> None: + def _bestmove(self, arg: str) -> None: if self.pondering: self.pondering = None elif not self.result.cancelled(): - best = _parse_uci_bestmove(engine.board, arg) + best = _parse_uci_bestmove(self.engine.board, arg) self.result.set_result(PlayResult(best.move, best.ponder, self.info)) if ponder and best.move and best.ponder: self.pondering = board.copy() self.pondering.push(best.move) self.pondering.push(best.ponder) - engine._position(self.pondering) + self.engine._position(self.pondering) # Adjust clocks for pondering. time_used = time.perf_counter() - self.start_time @@ -1647,92 +1638,105 @@ def _bestmove(self, engine: UciProtocol, arg: str) -> None: if ponder_limit.remaining_moves: ponder_limit.remaining_moves -= 1 - engine._go(ponder_limit, ponder=True) + self.engine._go(ponder_limit, ponder=True) if not self.pondering: - self.end(engine) + self.end() - def end(self, engine: UciProtocol) -> None: + def end(self) -> None: engine.may_ponderhit = None self.set_finished() - def cancel(self, engine: UciProtocol) -> None: - if engine.may_ponderhit and self.pondering and engine.may_ponderhit.move_stack == self.pondering.move_stack and engine.may_ponderhit == self.pondering: - engine.ponderhit = True - self.end(engine) + @override + def cancel(self) -> None: + if self.engine.may_ponderhit and self.pondering and self.engine.may_ponderhit.move_stack == self.pondering.move_stack and self.engine.may_ponderhit == self.pondering: + self.engine.ponderhit = True + self.end() else: - engine.send_line("stop") + self.engine.send_line("stop") - def engine_terminated(self, engine: UciProtocol, exc: Exception) -> None: + @override + def engine_terminated(self, exc: Exception) -> None: # Allow terminating engine while pondering. if not self.result.done(): - super().engine_terminated(engine, exc) + super().engine_terminated(exc) return await self.communicate(UciPlayCommand) async def analysis(self, board: chess.Board, limit: Optional[Limit] = None, *, multipv: Optional[int] = None, game: object = None, info: Info = INFO_ALL, root_moves: Optional[Iterable[chess.Move]] = None, options: ConfigMapping = {}) -> AnalysisResult: - class UciAnalysisCommand(BaseCommand[UciProtocol, AnalysisResult]): - def start(self, engine: UciProtocol) -> None: - self.analysis = AnalysisResult(stop=lambda: self.cancel(engine)) + class UciAnalysisCommand(BaseCommand[AnalysisResult]): + def __init__(self, engine: UciProtocol): + super().__init__(engine) + self.engine = engine + + def start(self) -> None: + self.analysis = AnalysisResult(stop=lambda: self.cancel()) self.sent_isready = False - if "Ponder" in engine.options: - engine._setoption("Ponder", False) - if "UCI_AnalyseMode" in engine.options and "UCI_AnalyseMode" not in engine.target_config and all(name.lower() != "uci_analysemode" for name in options): - engine._setoption("UCI_AnalyseMode", True) - if "MultiPV" in engine.options or (multipv and multipv > 1): - engine._setoption("MultiPV", 1 if multipv is None else multipv) + if "Ponder" in self.engine.options: + self.engine._setoption("Ponder", False) + if "UCI_AnalyseMode" in self.engine.options and "UCI_AnalyseMode" not in self.engine.target_config and all(name.lower() != "uci_analysemode" for name in options): + self.engine._setoption("UCI_AnalyseMode", True) + if "MultiPV" in self.engine.options or (multipv and multipv > 1): + self.engine._setoption("MultiPV", 1 if multipv is None else multipv) - engine._configure(options) + self.engine._configure(options) - if engine.first_game or engine.game != game: - engine.game = game - engine._ucinewgame() + if self.engine.first_game or self.engine.game != game: + self.engine.game = game + self.engine._ucinewgame() self.sent_isready = True - engine._isready() + self.engine._isready() else: - self._readyok(engine) - - def line_received(self, engine: UciProtocol, line: str) -> None: - if line.startswith("info "): - self._info(engine, line.split(" ", 1)[1]) - elif line.startswith("bestmove "): - self._bestmove(engine, line.split(" ", 1)[1]) - elif line == "readyok" and self.sent_isready: - self._readyok(engine) + self._readyok() + + @override + def line_received(self, line: str) -> None: + token, remaining = _next_token(line) + if token == "info": + self._info(remaining) + elif token == "bestmove": + self._bestmove(remaining) + elif line.strip() == "readyok" and self.sent_isready: + self._readyok() else: - LOGGER.warning("%s: Unexpected engine output: %r", engine, line) + LOGGER.warning("%s: Unexpected engine output: %r", self.engine, line) - def _readyok(self, engine: UciProtocol) -> None: + def _readyok(self) -> None: self.sent_isready = False - engine._position(board) + self.engine._position(board) if limit: - engine._go(limit, root_moves=root_moves) + self.engine._go(limit, root_moves=root_moves) else: - engine._go(Limit(), root_moves=root_moves, infinite=True) + self.engine._go(Limit(), root_moves=root_moves, infinite=True) self.result.set_result(self.analysis) - def _info(self, engine: UciProtocol, arg: str) -> None: - self.analysis.post(_parse_uci_info(arg, engine.board, info)) + def _info(self, arg: str) -> None: + self.analysis.post(_parse_uci_info(arg, self.engine.board, info)) - def _bestmove(self, engine: UciProtocol, arg: str) -> None: + def _bestmove(self, arg: str) -> None: if not self.result.done(): raise EngineError("was not searching, but engine sent bestmove") - best = _parse_uci_bestmove(engine.board, arg) + best = _parse_uci_bestmove(self.engine.board, arg) self.set_finished() self.analysis.set_finished(best) - def cancel(self, engine: UciProtocol) -> None: - engine.send_line("stop") + @override + def cancel(self) -> None: + self.engine.send_line("stop") - def engine_terminated(self, engine: UciProtocol, exc: Exception) -> None: - LOGGER.debug("%s: Closing analysis because engine has been terminated (error: %s)", engine, exc) + @override + def engine_terminated(self, exc: Exception) -> None: + LOGGER.debug("%s: Closing analysis because engine has been terminated (error: %s)", self.engine, exc) self.analysis.set_exception(exc) return await self.communicate(UciAnalysisCommand) + async def send_game_result(self, board: chess.Board, winner: Optional[Color] = None, game_ending: Optional[str] = None, game_complete: bool = True) -> None: + pass + async def quit(self) -> None: self.send_line("quit") await asyncio.shield(self.returncode) @@ -1740,39 +1744,57 @@ async def quit(self) -> None: UCI_REGEX = re.compile(r"^[a-h][1-8][a-h][1-8][pnbrqk]?|[PNBRQK]@[a-h][1-8]|0000\Z") +def _create_variation_line(root_board: chess.Board, line: str) -> tuple[list[chess.Move], str]: + board = root_board.copy(stack=False) + currline: list[chess.Move] = [] + while True: + next_move, remaining_line_after_move = _next_token(line) + if UCI_REGEX.match(next_move): + currline.append(board.push_uci(next_move)) + line = remaining_line_after_move + else: + return currline, line + + def _parse_uci_info(arg: str, root_board: chess.Board, selector: Info = INFO_ALL) -> InfoDict: info: InfoDict = {} if not selector: return info - tokens = arg.split(" ") - while tokens: - parameter = tokens.pop(0) + remaining_line = arg + while remaining_line: + parameter, remaining_line = _next_token(remaining_line) if parameter == "string": - info["string"] = " ".join(tokens) + info["string"] = remaining_line break - elif parameter in ["depth", "seldepth", "nodes", "multipv", "currmovenumber", "hashfull", "nps", "tbhits", "cpuload"]: + elif parameter in ["depth", "seldepth", "nodes", "multipv", "currmovenumber", + "hashfull", "nps", "tbhits", "cpuload", "movesleft"]: try: - info[parameter] = int(tokens.pop(0)) # type: ignore + number, remaining_line = _next_token(remaining_line) + info[parameter] = int(number) # type: ignore except (ValueError, IndexError): LOGGER.error("Exception parsing %s from info: %r", parameter, arg) elif parameter == "time": try: - info["time"] = int(tokens.pop(0)) / 1000.0 + time_ms, remaining_line = _next_token(remaining_line) + info["time"] = int(time_ms) / 1000.0 except (ValueError, IndexError): LOGGER.error("Exception parsing %s from info: %r", parameter, arg) elif parameter == "ebf": try: - info["ebf"] = float(tokens.pop(0)) + number, remaining_line = _next_token(remaining_line) + info["ebf"] = float(number) except (ValueError, IndexError): LOGGER.error("Exception parsing %s from info: %r", parameter, arg) elif parameter == "score" and selector & INFO_SCORE: try: - kind = tokens.pop(0) - value = tokens.pop(0) - if tokens and tokens[0] in ["lowerbound", "upperbound"]: - info[tokens.pop(0)] = True # type: ignore + kind, remaining_line = _next_token(remaining_line) + value, remaining_line = _next_token(remaining_line) + token, remaining_after_token = _next_token(remaining_line) + if token in ["lowerbound", "upperbound"]: + info[token] = True # type: ignore + remaining_line = remaining_after_token if kind == "cp": info["score"] = PovScore(Cp(int(value)), root_board.turn) elif kind == "mate": @@ -1783,7 +1805,8 @@ def _parse_uci_info(arg: str, root_board: chess.Board, selector: Info = INFO_ALL LOGGER.error("Exception parsing score from info: %r", arg) elif parameter == "currmove": try: - info["currmove"] = chess.Move.from_uci(tokens.pop(0)) + current_move, remaining_line = _next_token(remaining_line) + info["currmove"] = chess.Move.from_uci(current_move) except (ValueError, IndexError): LOGGER.error("Exception parsing currmove from info: %r", arg) elif parameter == "currline" and selector & INFO_CURRLINE: @@ -1791,13 +1814,10 @@ def _parse_uci_info(arg: str, root_board: chess.Board, selector: Info = INFO_ALL if "currline" not in info: info["currline"] = {} - cpunr = int(tokens.pop(0)) - currline: List[chess.Move] = [] + cpunr_text, remaining_line = _next_token(remaining_line) + cpunr = int(cpunr_text) + currline, remaining_line = _create_variation_line(root_board, remaining_line) info["currline"][cpunr] = currline - - board = root_board.copy(stack=False) - while tokens and UCI_REGEX.match(tokens[0]): - currline.append(board.push_uci(tokens.pop(0))) except (ValueError, IndexError): LOGGER.error("Exception parsing currline from info: %r, position at root: %s", arg, root_board.fen()) elif parameter == "refutation" and selector & INFO_REFUTATION: @@ -1806,27 +1826,25 @@ def _parse_uci_info(arg: str, root_board: chess.Board, selector: Info = INFO_ALL info["refutation"] = {} board = root_board.copy(stack=False) - refuted = board.push_uci(tokens.pop(0)) + refuted_text, remaining_line = _next_token(remaining_line) + refuted = board.push_uci(refuted_text) - refuted_by: List[chess.Move] = [] + refuted_by, remaining_line = _create_variation_line(board, remaining_line) info["refutation"][refuted] = refuted_by - - while tokens and UCI_REGEX.match(tokens[0]): - refuted_by.append(board.push_uci(tokens.pop(0))) except (ValueError, IndexError): LOGGER.error("Exception parsing refutation from info: %r, position at root: %s", arg, root_board.fen()) elif parameter == "pv" and selector & INFO_PV: try: - pv: List[chess.Move] = [] + pv, remaining_line = _create_variation_line(root_board, remaining_line) info["pv"] = pv - board = root_board.copy(stack=False) - while tokens and UCI_REGEX.match(tokens[0]): - pv.append(board.push_uci(tokens.pop(0))) except (ValueError, IndexError): LOGGER.error("Exception parsing pv from info: %r, position at root: %s", arg, root_board.fen()) elif parameter == "wdl": try: - info["wdl"] = PovWdl(Wdl(int(tokens.pop(0)), int(tokens.pop(0)), int(tokens.pop(0))), root_board.turn) + wins, remaining_line = _next_token(remaining_line) + draws, remaining_line = _next_token(remaining_line) + losses, remaining_line = _next_token(remaining_line) + info["wdl"] = PovWdl(Wdl(int(wins), int(draws), int(losses)), root_board.turn) except (ValueError, IndexError): LOGGER.error("Exception parsing wdl from info: %r", arg) @@ -1857,18 +1875,22 @@ def _parse_uci_bestmove(board: chess.Board, args: str) -> BestMove: return BestMove(move, ponder) -def _chain_config(a: ConfigMapping, b: ConfigMapping) -> Iterator[Tuple[str, ConfigValue]]: - for name, value in a.items(): - yield name, value - for name, value in b.items(): - if name not in a: - yield name, value +def _chain_config(a: ConfigMapping, b: ConfigMapping) -> Iterable[Tuple[str, ConfigValue]]: + merged = dict(a) + for k, v in b.items(): + merged.setdefault(k, v) + if "Hash" in merged and "Threads" in merged: + # Move Hash after Threads, as recommended by Stockfish. + hash_val = merged["Hash"] + del merged["Hash"] + merged["Hash"] = hash_val + return merged.items() class UciOptionMap(MutableMapping[str, T]): """Dictionary with case-insensitive keys.""" - def __init__(self, data: Optional[Union[Iterable[Tuple[str, T]]]] = None, **kwargs: T) -> None: + def __init__(self, data: Optional[Iterable[Tuple[str, T]]] = None, **kwargs: T) -> None: self._store: Dict[str, Tuple[str, T]] = {} if data is None: data = {} @@ -1884,7 +1906,7 @@ def __delitem__(self, key: str) -> None: del self._store[key.lower()] def __iter__(self) -> Iterator[str]: - return (casedkey for casedkey, mappedvalue in self._store.values()) + return (casedkey for casedkey, _ in self._store.values()) def __len__(self) -> int: return len(self._store) @@ -1926,99 +1948,116 @@ def __init__(self) -> None: super().__init__() self.features: Dict[str, Union[int, str]] = {} self.id = {} - self.options = { + self._options = { "random": Option("random", "check", False, None, None, None), "computer": Option("computer", "check", False, None, None, None), + "name": Option("name", "string", "", None, None, None), + "engine_rating": Option("engine_rating", "spin", 0, None, None, None), + "opponent_rating": Option("opponent_rating", "spin", 0, None, None, None) } self.config: Dict[str, ConfigValue] = {} self.target_config: Dict[str, ConfigValue] = {} self.board = chess.Board() self.game: object = None + self.clock_id: object = None self.first_game = True + @property + @override + def options(self) -> Dict[str, Option]: + return self._options + async def initialize(self) -> None: - class XBoardInitializeCommand(BaseCommand[XBoardProtocol, None]): - def check_initialized(self, engine: XBoardProtocol) -> None: - if engine.initialized: + class XBoardInitializeCommand(BaseCommand[None]): + def __init__(self, engine: XBoardProtocol): + super().__init__(engine) + self.engine = engine + + @override + def check_initialized(self) -> None: + if self.engine.initialized: raise EngineError("engine already initialized") - def start(self, engine: XBoardProtocol) -> None: - engine.send_line("xboard") - engine.send_line("protover 2") - self.timeout_handle = engine.loop.call_later(2.0, lambda: self.timeout(engine)) + @override + def start(self) -> None: + self.engine.send_line("xboard") + self.engine.send_line("protover 2") + self.timeout_handle = self.engine.loop.call_later(2.0, lambda: self.timeout()) - def timeout(self, engine: XBoardProtocol) -> None: - LOGGER.error("%s: Timeout during initialization", engine) - self.end(engine) + def timeout(self) -> None: + LOGGER.error("%s: Timeout during initialization", self.engine) + self.end() - def line_received(self, engine: XBoardProtocol, line: str) -> None: - if line.startswith("#"): + @override + def line_received(self, line: str) -> None: + token, remaining = _next_token(line) + if token.startswith("#"): pass - elif line.startswith("feature "): - self._feature(engine, line.split(" ", 1)[1]) + elif token == "feature": + self._feature(remaining) elif XBOARD_ERROR_REGEX.match(line): raise EngineError(line) - def _feature(self, engine: XBoardProtocol, arg: str) -> None: + def _feature(self, arg: str) -> None: for feature in shlex.split(arg): key, value = feature.split("=", 1) if key == "option": option = _parse_xboard_option(value) if option.name not in ["random", "computer", "cores", "memory"]: - engine.options[option.name] = option + self.engine.options[option.name] = option else: try: - engine.features[key] = int(value) + self.engine.features[key] = int(value) except ValueError: - engine.features[key] = value + self.engine.features[key] = value - if "done" in engine.features: + if "done" in self.engine.features: self.timeout_handle.cancel() - if engine.features.get("done"): - self.end(engine) + if self.engine.features.get("done"): + self.end() - def end(self, engine: XBoardProtocol) -> None: - if not engine.features.get("ping", 0): + def end(self) -> None: + if not self.engine.features.get("ping", 0): self.result.set_exception(EngineError("xboard engine did not declare required feature: ping")) self.set_finished() return - if not engine.features.get("setboard", 0): + if not self.engine.features.get("setboard", 0): self.result.set_exception(EngineError("xboard engine did not declare required feature: setboard")) self.set_finished() return - if not engine.features.get("reuse", 1): - LOGGER.warning("%s: Rejecting feature reuse=0", engine) - engine.send_line("rejected reuse") - if not engine.features.get("sigterm", 1): - LOGGER.warning("%s: Rejecting feature sigterm=0", engine) - engine.send_line("rejected sigterm") - if engine.features.get("san", 0): - LOGGER.warning("%s: Rejecting feature san=1", engine) - engine.send_line("rejected san") - - if "myname" in engine.features: - engine.id["name"] = str(engine.features["myname"]) - - if engine.features.get("memory", 0): - engine.options["memory"] = Option("memory", "spin", 16, 1, None, None) - engine.send_line("accepted memory") - if engine.features.get("smp", 0): - engine.options["cores"] = Option("cores", "spin", 1, 1, None, None) - engine.send_line("accepted smp") - if engine.features.get("egt"): - for egt in str(engine.features["egt"]).split(","): + if not self.engine.features.get("reuse", 1): + LOGGER.warning("%s: Rejecting feature reuse=0", self.engine) + self.engine.send_line("rejected reuse") + if not self.engine.features.get("sigterm", 1): + LOGGER.warning("%s: Rejecting feature sigterm=0", self.engine) + self.engine.send_line("rejected sigterm") + if self.engine.features.get("san", 0): + LOGGER.warning("%s: Rejecting feature san=1", self.engine) + self.engine.send_line("rejected san") + + if "myname" in self.engine.features: + self.engine.id["name"] = str(self.engine.features["myname"]) + + if self.engine.features.get("memory", 0): + self.engine.options["memory"] = Option("memory", "spin", 16, 1, None, None) + self.engine.send_line("accepted memory") + if self.engine.features.get("smp", 0): + self.engine.options["cores"] = Option("cores", "spin", 1, 1, None, None) + self.engine.send_line("accepted smp") + if self.engine.features.get("egt"): + for egt in str(self.engine.features["egt"]).split(","): name = f"egtpath {egt}" - engine.options[name] = Option(name, "path", None, None, None, None) - engine.send_line("accepted egt") + self.engine.options[name] = Option(name, "path", None, None, None, None) + self.engine.send_line("accepted egt") - for option in engine.options.values(): + for option in self.engine.options.values(): if option.default is not None: - engine.config[option.name] = option.default + self.engine.config[option.name] = option.default if option.default is not None and not option.is_managed(): - engine.target_config[option.name] = option.default + self.engine.target_config[option.name] = option.default - engine.initialized = True + self.engine.initialized = True self.result.set_result(None) self.set_finished() @@ -2034,13 +2073,14 @@ def _variant(self, variant: Optional[str]) -> None: self.send_line(f"variant {variant}") - def _new(self, board: chess.Board, game: object, options: ConfigMapping) -> None: + def _new(self, board: chess.Board, game: object, options: ConfigMapping, opponent: Optional[Opponent] = None) -> None: self._configure(options) + self._configure(self._opponent_configuration(opponent=opponent)) # Set up starting position. root = board.root() - new_options = "random" in options or "computer" in options - new_game = self.first_game or self.game != game or new_options or root != self.board.root() + new_options = any(param in options for param in ("random", "computer")) + new_game = self.first_game or self.game != game or new_options or opponent or root != self.board.root() self.game = game self.first_game = False if new_game: @@ -2055,15 +2095,26 @@ def _new(self, board: chess.Board, game: object, options: ConfigMapping) -> None if self.config.get("random"): self.send_line("random") + + opponent_name = self.config.get("name") + if opponent_name and self.features.get("name", True): + self.send_line(f"name {opponent_name}") + + opponent_rating = self.config.get("opponent_rating") + engine_rating = self.config.get("engine_rating") + if engine_rating or opponent_rating: + self.send_line(f"rating {engine_rating or 0} {opponent_rating or 0}") + if self.config.get("computer"): self.send_line("computer") - self.send_line("force") + self.send_line("force") - if new_game: fen = root.fen(shredder=board.chess960, en_passant="fen") if variant != "normal" or fen != chess.STARTING_FEN or board.chess960: self.send_line(f"setboard {fen}") + else: + self.send_line("force") # Undo moves until common position. common_stack_len = 0 @@ -2092,158 +2143,181 @@ def _new(self, board: chess.Board, game: object, options: ConfigMapping) -> None self.board.push(move) async def ping(self) -> None: - class XBoardPingCommand(BaseCommand[XBoardProtocol, None]): - def start(self, engine: XBoardProtocol) -> None: + class XBoardPingCommand(BaseCommand[None]): + def __init__(self, engine: XBoardProtocol): + super().__init__(engine) + self.engine = engine + + @override + def start(self) -> None: n = id(self) & 0xffff self.pong = f"pong {n}" - engine._ping(n) + self.engine._ping(n) - def line_received(self, engine: XBoardProtocol, line: str) -> None: + @override + def line_received(self, line: str) -> None: if line == self.pong: self.result.set_result(None) self.set_finished() elif not line.startswith("#"): - LOGGER.warning("%s: Unexpected engine output: %r", engine, line) + LOGGER.warning("%s: Unexpected engine output: %r", self.engine, line) elif XBOARD_ERROR_REGEX.match(line): raise EngineError(line) return await self.communicate(XBoardPingCommand) - async def play(self, board: chess.Board, limit: Limit, *, game: object = None, info: Info = INFO_NONE, ponder: bool = False, draw_offered: bool = False, root_moves: Optional[Iterable[chess.Move]] = None, options: ConfigMapping = {}) -> PlayResult: + async def play(self, board: chess.Board, limit: Limit, *, game: object = None, info: Info = INFO_NONE, ponder: bool = False, draw_offered: bool = False, root_moves: Optional[Iterable[chess.Move]] = None, options: ConfigMapping = {}, opponent: Optional[Opponent] = None) -> PlayResult: if root_moves is not None: raise EngineError("play with root_moves, but xboard supports 'include' only in analysis mode") - class XBoardPlayCommand(BaseCommand[XBoardProtocol, PlayResult]): - def start(self, engine: XBoardProtocol) -> None: + class XBoardPlayCommand(BaseCommand[PlayResult]): + def __init__(self, engine: XBoardProtocol): + super().__init__(engine) + self.engine = engine + + @override + def start(self) -> None: self.play_result = PlayResult(None, None) self.stopped = False self.pong_after_move: Optional[str] = None self.pong_after_ponder: Optional[str] = None # Set game, position and configure. - engine._new(board, game, options) + self.engine._new(board, game, options, opponent) # Limit or time control. clock = limit.white_clock if board.turn else limit.black_clock increment = limit.white_inc if board.turn else limit.black_inc - if limit.remaining_moves or clock is not None or increment is not None: - base_mins, base_secs = divmod(int(clock or 0), 60) - engine.send_line(f"level {limit.remaining_moves or 0} {base_mins}:{base_secs:02d} {increment or 0}") - + if limit.clock_id is None or limit.clock_id != self.engine.clock_id: + self._send_time_control(clock, increment) + self.engine.clock_id = limit.clock_id if limit.nodes is not None: if limit.time is not None or limit.white_clock is not None or limit.black_clock is not None or increment is not None: raise EngineError("xboard does not support mixing node limits with time limits") - if "nps" not in engine.features: + if "nps" not in self.engine.features: LOGGER.warning("%s: Engine did not explicitly declare support for node limits (feature nps=?)") - elif not engine.features["nps"]: + elif not self.engine.features["nps"]: raise EngineError("xboard engine does not support node limits (feature nps=0)") - engine.send_line("nps 1") - engine.send_line(f"st {max(1, int(limit.nodes))}") - if limit.time is not None: - engine.send_line(f"st {max(0.01, limit.time)}") + self.engine.send_line("nps 1") + self.engine.send_line(f"st {max(1, int(limit.nodes))}") if limit.depth is not None: - engine.send_line(f"sd {max(1, int(limit.depth))}") + self.engine.send_line(f"sd {max(1, int(limit.depth))}") if limit.white_clock is not None: - engine.send_line("{} {}".format("time" if board.turn else "otim", max(1, int(limit.white_clock * 100)))) + self.engine.send_line("{} {}".format("time" if board.turn else "otim", max(1, round(limit.white_clock * 100)))) if limit.black_clock is not None: - engine.send_line("{} {}".format("otim" if board.turn else "time", max(1, int(limit.black_clock * 100)))) + self.engine.send_line("{} {}".format("otim" if board.turn else "time", max(1, round(limit.black_clock * 100)))) - if draw_offered and engine.features.get("draw", 1): - engine.send_line("draw") + if draw_offered and self.engine.features.get("draw", 1): + self.engine.send_line("draw") # Start thinking. - engine.send_line("post" if info else "nopost") - engine.send_line("hard" if ponder else "easy") - engine.send_line("go") - - def line_received(self, engine: XBoardProtocol, line: str) -> None: - if line.startswith("move "): - self._move(engine, line.split(" ", 1)[1]) - elif line.startswith("Hint: "): - self._hint(engine, line.split(" ", 1)[1]) - elif line == self.pong_after_move: - if not self.result.done(): - self.result.set_result(self.play_result) - if not ponder: + self.engine.send_line("post" if info else "nopost") + self.engine.send_line("hard" if ponder else "easy") + self.engine.send_line("go") + + @override + def line_received(self, line: str) -> None: + token, remaining = _next_token(line) + if token == "move": + self._move(remaining.strip()) + elif token == "Hint:": + self._hint(remaining.strip()) + elif token == "pong": + pong_line = f"{token} {remaining.strip()}" + if pong_line == self.pong_after_move: + if not self.result.done(): + self.result.set_result(self.play_result) + if not ponder: + self.set_finished() + elif pong_line == self.pong_after_ponder: + if not self.result.done(): + self.result.set_result(self.play_result) self.set_finished() - elif line == self.pong_after_ponder: - if not self.result.done(): - self.result.set_result(self.play_result) - self.set_finished() - elif line == "offer draw": + elif f"{token} {remaining.strip()}" == "offer draw": if not self.result.done(): self.play_result.draw_offered = True - self._ping_after_move(engine) - elif line == "resign": + self._ping_after_move() + elif line.strip() == "resign": if not self.result.done(): self.play_result.resigned = True - self._ping_after_move(engine) - elif line.startswith("1-0") or line.startswith("0-1") or line.startswith("1/2-1/2"): - self._ping_after_move(engine) - elif line.startswith("#"): + self._ping_after_move() + elif token in ["1-0", "0-1", "1/2-1/2"]: + if "resign" in line and not self.result.done(): + self.play_result.resigned = True + self._ping_after_move() + elif token.startswith("#"): pass elif XBOARD_ERROR_REGEX.match(line): - engine.first_game = True # Board state might no longer be in sync + self.engine.first_game = True # Board state might no longer be in sync raise EngineError(line) elif len(line.split()) >= 4 and line.lstrip()[0].isdigit(): - self._post(engine, line) + self._post(line) else: - LOGGER.warning("%s: Unexpected engine output: %r", engine, line) + LOGGER.warning("%s: Unexpected engine output: %r", self.engine, line) - def _post(self, engine: XBoardProtocol, line: str) -> None: + def _send_time_control(self, clock: Optional[float], increment: Optional[float]) -> None: + if limit.remaining_moves or clock is not None or increment is not None: + base_mins, base_secs = divmod(int(clock or 0), 60) + self.engine.send_line(f"level {limit.remaining_moves or 0} {base_mins}:{base_secs:02d} {increment or 0}") + if limit.time is not None: + self.engine.send_line(f"st {max(0.01, limit.time)}") + + def _post(self, line: str) -> None: if not self.result.done(): - self.play_result.info = _parse_xboard_post(line, engine.board, info) + self.play_result.info = _parse_xboard_post(line, self.engine.board, info) - def _move(self, engine: XBoardProtocol, arg: str) -> None: + def _move(self, arg: str) -> None: if not self.result.done() and self.play_result.move is None: try: - self.play_result.move = engine.board.push_xboard(arg) + self.play_result.move = self.engine.board.push_xboard(arg) except ValueError as err: self.result.set_exception(EngineError(err)) else: - self._ping_after_move(engine) + self._ping_after_move() else: try: - engine.board.push_xboard(arg) + self.engine.board.push_xboard(arg) except ValueError: LOGGER.exception("Exception playing unexpected move") - def _hint(self, engine: XBoardProtocol, arg: str) -> None: + def _hint(self, arg: str) -> None: if not self.result.done() and self.play_result.move is not None and self.play_result.ponder is None: try: - self.play_result.ponder = engine.board.parse_xboard(arg) + self.play_result.ponder = self.engine.board.parse_xboard(arg) except ValueError: LOGGER.exception("Exception parsing hint") else: LOGGER.warning("Unexpected hint: %r", arg) - def _ping_after_move(self, engine: XBoardProtocol) -> None: + def _ping_after_move(self) -> None: if self.pong_after_move is None: n = id(self) & 0xffff self.pong_after_move = f"pong {n}" - engine._ping(n) + self.engine._ping(n) - def cancel(self, engine: XBoardProtocol) -> None: + @override + def cancel(self) -> None: if self.stopped: return self.stopped = True if self.result.cancelled(): - engine.send_line("?") + self.engine.send_line("?") if ponder: - engine.send_line("easy") + self.engine.send_line("easy") n = (id(self) + 1) & 0xffff self.pong_after_ponder = f"pong {n}" - engine._ping(n) + self.engine._ping(n) - def engine_terminated(self, engine: XBoardProtocol, exc: Exception) -> None: + @override + def engine_terminated(self, exc: Exception) -> None: # Allow terminating engine while pondering. if not self.result.done(): - super().engine_terminated(engine, exc) + super().engine_terminated(exc) return await self.communicate(XBoardPlayCommand) @@ -2254,48 +2328,55 @@ async def analysis(self, board: chess.Board, limit: Optional[Limit] = None, *, m if limit is not None and (limit.white_clock is not None or limit.black_clock is not None): raise EngineError("xboard analysis does not support clock limits") - class XBoardAnalysisCommand(BaseCommand[XBoardProtocol, AnalysisResult]): - def start(self, engine: XBoardProtocol) -> None: + class XBoardAnalysisCommand(BaseCommand[AnalysisResult]): + def __init__(self, engine: XBoardProtocol): + super().__init__(engine) + self.engine = engine + + @override + def start(self) -> None: self.stopped = False self.best_move: Optional[chess.Move] = None - self.analysis = AnalysisResult(stop=lambda: self.cancel(engine)) + self.analysis = AnalysisResult(stop=lambda: self.cancel()) self.final_pong: Optional[str] = None - engine._new(board, game, options) + self.engine._new(board, game, options) if root_moves is not None: - if not engine.features.get("exclude", 0): + if not self.engine.features.get("exclude", 0): raise EngineError("xboard engine does not support root_moves (feature exclude=0)") - engine.send_line("exclude all") + self.engine.send_line("exclude all") for move in root_moves: - engine.send_line(f"include {engine.board.xboard(move)}") + self.engine.send_line(f"include {self.engine.board.xboard(move)}") - engine.send_line("post") - engine.send_line("analyze") + self.engine.send_line("post") + self.engine.send_line("analyze") self.result.set_result(self.analysis) if limit is not None and limit.time is not None: - self.time_limit_handle: Optional[asyncio.Handle] = engine.loop.call_later(limit.time, lambda: self.cancel(engine)) + self.time_limit_handle: Optional[asyncio.Handle] = self.engine.loop.call_later(limit.time, lambda: self.cancel()) else: self.time_limit_handle = None - def line_received(self, engine: XBoardProtocol, line: str) -> None: - if line.startswith("#"): + @override + def line_received(self, line: str) -> None: + token, remaining = _next_token(line) + if token.startswith("#"): pass elif len(line.split()) >= 4 and line.lstrip()[0].isdigit(): - self._post(engine, line) - elif line == self.final_pong: - self.end(engine) + self._post(line) + elif f"{token} {remaining.strip()}" == self.final_pong: + self.end() elif XBOARD_ERROR_REGEX.match(line): - engine.first_game = True # Board state might no longer be in sync + self.engine.first_game = True # Board state might no longer be in sync raise EngineError(line) else: - LOGGER.warning("%s: Unexpected engine output: %r", engine, line) + LOGGER.warning("%s: Unexpected engine output: %r", self.engine, line) - def _post(self, engine: XBoardProtocol, line: str) -> None: - post_info = _parse_xboard_post(line, engine.board, info) + def _post(self, line: str) -> None: + post_info = _parse_xboard_post(line, self.engine.board, info) self.analysis.post(post_info) pv = post_info.get("pv") @@ -2304,36 +2385,38 @@ def _post(self, engine: XBoardProtocol, line: str) -> None: if limit is not None: if limit.time is not None and post_info.get("time", 0) >= limit.time: - self.cancel(engine) + self.cancel() elif limit.nodes is not None and post_info.get("nodes", 0) >= limit.nodes: - self.cancel(engine) + self.cancel() elif limit.depth is not None and post_info.get("depth", 0) >= limit.depth: - self.cancel(engine) + self.cancel() elif limit.mate is not None and "score" in post_info: if post_info["score"].relative >= Mate(limit.mate): - self.cancel(engine) + self.cancel() - def end(self, engine: XBoardProtocol) -> None: + def end(self) -> None: if self.time_limit_handle: self.time_limit_handle.cancel() self.set_finished() self.analysis.set_finished(BestMove(self.best_move, None)) - def cancel(self, engine: XBoardProtocol) -> None: + @override + def cancel(self) -> None: if self.stopped: return self.stopped = True - engine.send_line(".") - engine.send_line("exit") + self.engine.send_line(".") + self.engine.send_line("exit") n = id(self) & 0xffff self.final_pong = f"pong {n}" - engine._ping(n) + self.engine._ping(n) - def engine_terminated(self, engine: XBoardProtocol, exc: Exception) -> None: - LOGGER.debug("%s: Closing analysis because engine has been terminated (error: %s)", engine, exc) + @override + def engine_terminated(self, exc: Exception) -> None: + LOGGER.debug("%s: Closing analysis because engine has been terminated (error: %s)", self.engine, exc) if self.time_limit_handle: self.time_limit_handle.cancel() @@ -2353,7 +2436,7 @@ def _setoption(self, name: str, value: ConfigValue) -> None: self.config[name] = value = option.parse(value) - if name in ["random", "computer"]: + if name in ["random", "computer", "name", "engine_rating", "opponent_rating"]: # Applied in _new. pass elif name in ["memory", "cores"] or name.startswith("egtpath "): @@ -2374,15 +2457,76 @@ def _configure(self, options: ConfigMapping) -> None: self._setoption(name, value) async def configure(self, options: ConfigMapping) -> None: - class XBoardConfigureCommand(BaseCommand[XBoardProtocol, None]): - def start(self, engine: XBoardProtocol) -> None: - engine._configure(options) - engine.target_config.update({name: value for name, value in options.items() if value is not None}) + class XBoardConfigureCommand(BaseCommand[None]): + def __init__(self, engine: XBoardProtocol): + super().__init__(engine) + self.engine = engine + + @override + def start(self) -> None: + self.engine._configure(options) + self.engine.target_config.update({name: value for name, value in options.items() if value is not None}) self.result.set_result(None) self.set_finished() return await self.communicate(XBoardConfigureCommand) + def _opponent_configuration(self, *, opponent: Optional[Opponent] = None, engine_rating: Optional[int] = None) -> ConfigMapping: + if opponent is None: + return {} + + opponent_info: Dict[str, Union[int, bool, str]] = {"engine_rating": engine_rating or self.target_config.get("engine_rating") or 0, + "opponent_rating": opponent.rating or 0, + "computer": opponent.is_engine or False} + + if opponent.name and self.features.get("name", True): + opponent_info["name"] = f"{opponent.title or ''} {opponent.name}".strip() + + return opponent_info + + async def send_opponent_information(self, *, opponent: Optional[Opponent] = None, engine_rating: Optional[int] = None) -> None: + return await self.configure(self._opponent_configuration(opponent=opponent, engine_rating=engine_rating)) + + async def send_game_result(self, board: chess.Board, winner: Optional[Color] = None, game_ending: Optional[str] = None, game_complete: bool = True) -> None: + class XBoardGameResultCommand(BaseCommand[None]): + def __init__(self, engine: XBoardProtocol): + super().__init__(engine) + self.engine = engine + + @override + def start(self) -> None: + if game_ending and any(c in game_ending for c in "{}\n\r"): + raise EngineError(f"invalid line break or curly braces in game ending message: {game_ending!r}") + + self.engine._new(board, self.engine.game, {}) # Send final moves to engine. + + outcome = board.outcome(claim_draw=True) + + if not game_complete: + result = "*" + ending = game_ending or "" + elif winner is not None or game_ending: + result = "1-0" if winner == chess.WHITE else "0-1" if winner == chess.BLACK else "1/2-1/2" + ending = game_ending or "" + elif outcome is not None and outcome.winner is not None: + result = outcome.result() + winning_color = "White" if outcome.winner == chess.WHITE else "Black" + is_checkmate = outcome.termination == chess.Termination.CHECKMATE + ending = f"{winning_color} {'mates' if is_checkmate else 'variant win'}" + elif outcome is not None: + result = outcome.result() + ending = outcome.termination.name.capitalize().replace("_", " ") + else: + result = "*" + ending = "" + + ending_text = f"{{{ending}}}" if ending else "" + self.engine.send_line(f"result {result} {ending_text}".strip()) + self.result.set_result(None) + self.set_finished() + + return await self.communicate(XBoardGameResultCommand) + async def quit(self) -> None: self.send_line("quit") await asyncio.shield(self.returncode) @@ -2491,6 +2635,24 @@ def _parse_xboard_post(line: str, root_board: chess.Board, selector: Info = INFO return info +def _next_token(line: str) -> tuple[str, str]: + """ + Get the next token in a whitespace-delimited line of text. + + The result is returned as a 2-part tuple of strings. + + If the input line is empty or all whitespace, then the result is two + empty strings. + + If the input line is not empty and not completely whitespace, then + the first element of the returned tuple is a single word with + leading and trailing whitespace removed. The second element is the + unchanged rest of the line. + """ + parts = line.split(maxsplit=1) + return parts[0] if parts else "", parts[1] if len(parts) == 2 else "" + + class BestMove: """Returned by :func:`chess.engine.AnalysisResult.wait()`.""" @@ -2519,7 +2681,7 @@ class AnalysisResult: Automatically stops the analysis when used as a context manager. """ - multipv: List[chess.engine.InfoDict] + multipv: List[InfoDict] """ A list of dictionaries with aggregated information sent by the engine. One item for each root move. @@ -2754,7 +2916,7 @@ def id(self) -> Mapping[str, str]: future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop) return future.result() - def communicate(self, command_factory: Callable[[Protocol], BaseCommand[Protocol, T]]) -> T: + def communicate(self, command_factory: Callable[[Protocol], BaseCommand[T]]) -> T: with self._not_shut_down(): coro = self.protocol.communicate(command_factory) future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop) @@ -2766,16 +2928,24 @@ def configure(self, options: ConfigMapping) -> None: future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop) return future.result() + def send_opponent_information(self, *, opponent: Optional[Opponent] = None, engine_rating: Optional[int] = None) -> None: + with self._not_shut_down(): + coro = asyncio.wait_for( + self.protocol.send_opponent_information(opponent=opponent, engine_rating=engine_rating), + self.timeout) + future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop) + return future.result() + def ping(self) -> None: with self._not_shut_down(): coro = asyncio.wait_for(self.protocol.ping(), self.timeout) future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop) return future.result() - def play(self, board: chess.Board, limit: Limit, *, game: object = None, info: Info = INFO_NONE, ponder: bool = False, draw_offered: bool = False, root_moves: Optional[Iterable[chess.Move]] = None, options: ConfigMapping = {}) -> PlayResult: + def play(self, board: chess.Board, limit: Limit, *, game: object = None, info: Info = INFO_NONE, ponder: bool = False, draw_offered: bool = False, root_moves: Optional[Iterable[chess.Move]] = None, options: ConfigMapping = {}, opponent: Optional[Opponent] = None) -> PlayResult: with self._not_shut_down(): coro = asyncio.wait_for( - self.protocol.play(board, limit, game=game, info=info, ponder=ponder, draw_offered=draw_offered, root_moves=root_moves, options=options), + self.protocol.play(board, limit, game=game, info=info, ponder=ponder, draw_offered=draw_offered, root_moves=root_moves, options=options, opponent=opponent), self._timeout_for(limit)) future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop) return future.result() @@ -2802,6 +2972,12 @@ def analysis(self, board: chess.Board, limit: Optional[Limit] = None, *, multipv future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop) return SimpleAnalysisResult(self, future.result()) + def send_game_result(self, board: chess.Board, winner: Optional[Color] = None, game_ending: Optional[str] = None, game_complete: bool = True) -> None: + with self._not_shut_down(): + coro = asyncio.wait_for(self.protocol.send_game_result(board, winner, game_ending, game_complete), self.timeout) + future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop) + return future.result() + def quit(self) -> None: with self._not_shut_down(): coro = asyncio.wait_for(self.protocol.quit(), self.timeout) @@ -2822,7 +2998,7 @@ def _shutdown() -> None: self.protocol.loop.call_soon_threadsafe(_shutdown) @classmethod - def popen(cls, Protocol: Type[Protocol], command: Union[str, List[str]], *, timeout: Optional[float] = 10.0, debug: bool = False, setpgrp: bool = False, **popen_args: Any) -> SimpleEngine: + def popen(cls, Protocol: Type[Protocol], command: Union[str, List[str]], *, timeout: Optional[float] = 10.0, debug: Optional[bool] = None, setpgrp: bool = False, **popen_args: Any) -> SimpleEngine: async def background(future: concurrent.futures.Future[SimpleEngine]) -> None: transport, protocol = await Protocol.popen(command, setpgrp=setpgrp, **popen_args) threading.current_thread().name = f"{cls.__name__} (pid={transport.get_pid()})" @@ -2839,7 +3015,7 @@ async def background(future: concurrent.futures.Future[SimpleEngine]) -> None: return run_in_background(background, name=f"{cls.__name__} (command={command!r})", debug=debug) @classmethod - def popen_uci(cls, command: Union[str, List[str]], *, timeout: Optional[float] = 10.0, debug: bool = False, setpgrp: bool = False, **popen_args: Any) -> SimpleEngine: + def popen_uci(cls, command: Union[str, List[str]], *, timeout: Optional[float] = 10.0, debug: Optional[bool] = None, setpgrp: bool = False, **popen_args: Any) -> SimpleEngine: """ Spawns and initializes a UCI engine. Returns a :class:`~chess.engine.SimpleEngine` instance. @@ -2847,7 +3023,7 @@ def popen_uci(cls, command: Union[str, List[str]], *, timeout: Optional[float] = return cls.popen(UciProtocol, command, timeout=timeout, debug=debug, setpgrp=setpgrp, **popen_args) @classmethod - def popen_xboard(cls, command: Union[str, List[str]], *, timeout: Optional[float] = 10.0, debug: bool = False, setpgrp: bool = False, **popen_args: Any) -> SimpleEngine: + def popen_xboard(cls, command: Union[str, List[str]], *, timeout: Optional[float] = 10.0, debug: Optional[bool] = None, setpgrp: bool = False, **popen_args: Any) -> SimpleEngine: """ Spawns and initializes an XBoard engine. Returns a :class:`~chess.engine.SimpleEngine` instance. diff --git a/chess/gaviota.py b/chess/gaviota.py index 454949795..7152a18f0 100644 --- a/chess/gaviota.py +++ b/chess/gaviota.py @@ -1,20 +1,3 @@ -# This file is part of the python-chess library. -# Copyright (C) 2015 Jean-Noël Avila -# Copyright (C) 2015-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - from __future__ import annotations import ctypes @@ -127,12 +110,12 @@ def idx_is_empty(x: int) -> int: def flip_type(x: chess.Square, y: chess.Square) -> int: ret = 0 - if chess.square_file(x) > 3: + if chess.square_file(x) > chess.FILE_D: x = flip_we(x) y = flip_we(y) ret |= 1 - if chess.square_rank(x) > 3: + if chess.square_rank(x) > chess.RANK_4: x = flip_ns(x) y = flip_ns(y) ret |= 2 @@ -161,10 +144,9 @@ def init_flipt() -> List[List[int]]: def init_pp48_idx() -> Tuple[List[List[int]], List[int], List[int]]: - MAX_I = 48 - MAX_J = 48 + MAX_I = MAX_J = 48 idx = 0 - pp48_idx = [[-1] * MAX_J for i in range(MAX_I)] + pp48_idx = [[-1] * MAX_J for _ in range(MAX_I)] pp48_sq_x = [NOSQUARE] * MAX_PP48_INDEX pp48_sq_y = [NOSQUARE] * MAX_PP48_INDEX @@ -187,10 +169,8 @@ def init_pp48_idx() -> Tuple[List[List[int]], List[int], List[int]]: def init_ppp48_idx() -> Tuple[List[List[List[int]]], List[int], List[int], List[int]]: - MAX_I = 48 - MAX_J = 48 - MAX_K = 48 - ppp48_idx = [[[-1] * MAX_I for j in range(MAX_J)] for k in range(MAX_K)] + MAX_I = MAX_J = MAX_K = 48 + ppp48_idx = [[[-1] * MAX_I for _ in range(MAX_J)] for _ in range(MAX_K)] ppp48_sq_x = [NOSQUARE] * MAX_PPP48_INDEX ppp48_sq_y = [NOSQUARE] * MAX_PPP48_INDEX ppp48_sq_z = [NOSQUARE] * MAX_PPP48_INDEX @@ -227,7 +207,7 @@ def init_ppp48_idx() -> Tuple[List[List[List[int]]], List[int], List[int], List[ def init_aaidx() -> Tuple[List[int], List[List[int]]]: - aaidx = [[-1] * 64 for y in range(64)] + aaidx = [[-1] * 64 for _ in range(64)] aabase = [0] * MAX_AAINDEX idx = 0 @@ -257,7 +237,7 @@ def init_aaa() -> Tuple[List[int], List[List[int]]]: aaa_base[a + 1] = accum # Get aaa_xyz. - aaa_xyz = [[-1] * 3 for idx in range(MAX_AAAINDEX)] + aaa_xyz = [[-1] * 3 for _ in range(MAX_AAAINDEX)] idx = 0 for z in range(64): @@ -336,7 +316,7 @@ def wsq_to_pidx48(pawn: int) -> int: return idx48 def init_ppidx() -> Tuple[List[List[int]], List[int], List[int]]: - ppidx = [[-1] * 48 for i in range(24)] + ppidx = [[-1] * 48 for _ in range(24)] pp_hi24 = [-1] * MAX_PPINDEX pp_lo48 = [-1] * MAX_PPINDEX @@ -371,11 +351,11 @@ def init_ppidx() -> Tuple[List[List[int]], List[int], List[int]]: def norm_kkindex(x: chess.Square, y: chess.Square) -> Tuple[int, int]: - if chess.square_file(x) > 3: + if chess.square_file(x) > chess.FILE_D: x = flip_we(x) y = flip_we(y) - if chess.square_rank(x) > 3: + if chess.square_rank(x) > chess.RANK_4: x = flip_ns(x) y = flip_ns(y) @@ -396,7 +376,7 @@ def norm_kkindex(x: chess.Square, y: chess.Square) -> Tuple[int, int]: return x, y def init_kkidx() -> Tuple[List[List[int]], List[int], List[int]]: - kkidx = [[-1] * 64 for x in range(64)] + kkidx = [[-1] * 64 for _ in range(64)] bksq = [-1] * MAX_KKINDEX wksq = [-1] * MAX_KKINDEX idx = 0 @@ -1376,51 +1356,9 @@ def split_index(i: int) -> Tuple[int, int]: iBMATEt = tb_BMATE | 4 -def removepiece(ys: List[int], yp: List[int], j: int) -> None: - del ys[j] - del yp[j] - def opp(side: int) -> int: return 1 if side == 0 else 0 -def adjust_up(dist: int) -> int: - udist = dist - sw = udist & INFOMASK - - if sw in [iWMATE, iWMATEt, iBMATE, iBMATEt]: - udist += (1 << PLYSHIFT) - - return udist - -def bestx(side: int, a: int, b: int) -> int: - # 0 = selectfirst - # 1 = selectlowest - # 2 = selecthighest - # 3 = selectsecond - comparison = [ - # draw, wmate, bmate, forbid - [0, 3, 0, 0], # draw - [0, 1, 0, 0], # wmate - [3, 3, 2, 0], # bmate - [3, 3, 3, 0], # forbid - ] - - xorkey = [0, 3] - - if a == iFORBID: - return b - if b == iFORBID: - return a - - retu = [a, a, b, b] - - if b < a: - retu[1] = b - retu[2] = a - - key = comparison[a & 3][b & 3] ^ xorkey[side] - return retu[key] - def unpackdist(d: int) -> Tuple[int, int]: return d >> PLYSHIFT, d & INFOMASK @@ -1512,12 +1450,11 @@ class Request: black_piece_types: List[int] is_reversed: bool - def __init__(self, white_squares: List[int], white_types: List[chess.PieceType], black_squares: List[int], black_types: List[chess.PieceType], side: int, epsq: int): + def __init__(self, white_squares: List[int], white_types: List[chess.PieceType], black_squares: List[int], black_types: List[chess.PieceType], side: int): self.white_squares, self.white_types = sortlists(white_squares, white_types) self.black_squares, self.black_types = sortlists(black_squares, black_types) self.realside = side self.side = side - self.epsq = epsq @dataclasses.dataclass @@ -1556,8 +1493,8 @@ def probe_dtm(self, board: chess.Board) -> int: Probes for depth to mate information. The absolute value is the number of half-moves until forced mate - (or ``0`` in drawn positions). The value is positive if the - side to move is winning, otherwise it is negative. + (or ``0`` in drawn or checkmated positions). The value is positive if + the side to move is winning, otherwise it is negative or 0. In the example position, white to move will get mated in 10 half-moves: @@ -1582,24 +1519,44 @@ def probe_dtm(self, board: chess.Board) -> int: raise KeyError(f"gaviota tables do not contain positions with castling rights: {board.fen()}") # Supports only up to 5 pieces. - if chess.popcount(board.occupied) > 5: - raise KeyError(f"gaviota tables support up to 5 pieces, not {chess.popcount(board.occupied)}: {board.fen()}") + if board.piece_count() > 5: + raise KeyError(f"gaviota tables support up to 5 pieces, not {board.piece_count()}: {board.fen()}") # KvK is a draw. if board.occupied == board.kings: return 0 + # Resolve en passant. + dtm = self._probe_dtm_no_ep(board) + for move in board.generate_legal_ep(): + try: + board.push(move) + + if board.is_checkmate(): + child_dtm = 1 + else: + child_dtm = -self._probe_dtm_no_ep(board) + if child_dtm > 0: + child_dtm += 1 + elif child_dtm < 0: + child_dtm -= 1 + + dtm = min(dtm, child_dtm) if dtm * child_dtm > 0 else max(dtm, child_dtm) + finally: + board.pop() + return dtm + + def _probe_dtm_no_ep(self, board: chess.Board) -> int: # Prepare the tablebase request. white_squares = list(chess.SquareSet(board.occupied_co[chess.WHITE])) white_types = [typing.cast(chess.PieceType, board.piece_type_at(sq)) for sq in white_squares] black_squares = list(chess.SquareSet(board.occupied_co[chess.BLACK])) black_types = [typing.cast(chess.PieceType, board.piece_type_at(sq)) for sq in black_squares] side = 0 if (board.turn == chess.WHITE) else 1 - epsq = board.ep_square if board.ep_square else NOSQUARE - req = Request(white_squares, white_types, black_squares, black_types, side, epsq) + req = Request(white_squares, white_types, black_squares, black_types, side) # Probe. - dtm = self.egtb_get_dtm(req) + dtm = self._tb_probe(req) ply, res = unpackdist(dtm) if res == iWMATE: @@ -1695,10 +1652,7 @@ def _setup_tablebase(self, req: Request) -> BinaryIO: req.white_piece_types = req.black_types req.black_piece_squares = [flip_ns(s) for s in req.white_squares] req.black_piece_types = req.white_types - req.side = opp(req.side) - if req.epsq != NOSQUARE: - req.epsq = flip_ns(req.epsq) else: raise MissingTableError(f"no gaviota table available for: {white_letters.upper()}v{black_letters.upper()}") @@ -1709,7 +1663,7 @@ def _open_tablebase(self, req: Request) -> BinaryIO: if stream is None: path = self.available_tables[req.egkey] - stream = open(path, "rb+") + stream = open(path, "rb") self.egtb_loadindexes(req.egkey, stream) self.streams[req.egkey] = stream @@ -1728,77 +1682,6 @@ def close(self) -> None: _, stream = self.streams.popitem() stream.close() - def egtb_get_dtm(self, req: Request) -> int: - dtm = self._tb_probe(req) - - if req.epsq != NOSQUARE: - capturer_a = 0 - capturer_b = 0 - xed = 0 - - # Flip for move generation. - if req.side == 0: - xs = list(req.white_piece_squares) - xp = list(req.white_piece_types) - ys = list(req.black_piece_squares) - yp = list(req.black_piece_types) - else: - xs = list(req.black_piece_squares) - xp = list(req.black_piece_types) - ys = list(req.white_piece_squares) - yp = list(req.white_piece_types) - - # Captured pawn trick: from ep square to captured. - xed = req.epsq ^ (1 << 3) - - # Find captured index (j). - try: - j = ys.index(xed) - except ValueError: - j = -1 - - # Try first possible ep capture. - if 0 == (0x88 & (map88(xed) + 1)): - capturer_a = xed + 1 - - # Try second possible ep capture. - if 0 == (0x88 & (map88(xed) - 1)): - capturer_b = xed - 1 - - if (j > -1) and (ys[j] == xed): - # Find capturers (i). - for i in range(len(xs)): - if xp[i] == chess.PAWN and (xs[i] == capturer_a or xs[i] == capturer_b): - epscore = iFORBID - - # Copy position. - xs_after = xs[:] - ys_after = ys[:] - xp_after = xp[:] - yp_after = yp[:] - - # Execute capture. - xs_after[i] = req.epsq - removepiece(ys_after, yp_after, j) - - # Flip back. - if req.side == 1: - xs_after, ys_after = ys_after, xs_after - xp_after, yp_after = yp_after, xp_after - - # Make subrequest. - subreq = Request(xs_after, xp_after, ys_after, yp_after, opp(req.side), NOSQUARE) - try: - epscore = self._tb_probe(subreq) - epscore = adjust_up(epscore) - - # Choose to ep or not. - dtm = bestx(req.side, epscore, dtm) - except IndexError: - break - - return dtm - def egtb_block_getnumber(self, req: Request, idx: int) -> int: maxindex = EGKEY[req.egkey].maxindex @@ -1833,7 +1716,7 @@ def _tb_probe(self, req: Request) -> int: z = self.egtb_block_getsize_zipped(req.egkey, block) self.egtb_block_park(req.egkey, block, stream) - buffer_zipped = stream.read(z) + buffer_zipped: bytearray | bytes = stream.read(z) if buffer_zipped[0] == 0: # If flag is zero, plain LZMA is following. @@ -2002,8 +1885,8 @@ def _probe_hard(self, board: chess.Board, wdl_only: bool = False) -> int: if board.castling_rights: raise KeyError(f"gaviota tables do not contain positions with castling rights: {board.fen()}") - if chess.popcount(board.occupied) > 5: - raise KeyError(f"gaviota tables support up to 5 pieces, not {chess.popcount(board.occupied)}: {board.fen()}") + if board.piece_count() > 5: + raise KeyError(f"gaviota tables support up to 5 pieces, not {board.piece_count()}: {board.fen()}") stm = ctypes.c_uint(0 if board.turn == chess.WHITE else 1) ep_square = ctypes.c_uint(board.ep_square if board.ep_square else 64) diff --git a/chess/pgn.py b/chess/pgn.py index da97866c3..5ae5b43b0 100644 --- a/chess/pgn.py +++ b/chess/pgn.py @@ -1,22 +1,7 @@ -# This file is part of the python-chess library. -# Copyright (C) 2012-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - from __future__ import annotations import abc +import dataclasses import enum import itertools import logging @@ -27,15 +12,15 @@ import chess.engine import chess.svg -from typing import Any, Callable, Dict, Generic, Iterable, Iterator, List, Mapping, MutableMapping, Set, TextIO, Tuple, Type, TypeVar, Optional, Union +from typing import Any, Callable, Dict, Generic, Iterable, Iterator, List, Literal, Mapping, MutableMapping, Set, TextIO, Tuple, Type, TypeVar, Optional, Union from chess import Color, Square -try: - from typing import Literal - _TrueLiteral = Literal[True] -except ImportError: - # Before Python 3.8. - _TrueLiteral = bool # type: ignore +if typing.TYPE_CHECKING: + from typing_extensions import Self, override +else: + F = typing.TypeVar("F", bound=Callable[..., Any]) + def override(fn: F, /) -> F: + return fn LOGGER = logging.getLogger(__name__) @@ -152,6 +137,10 @@ def repl(match: typing.Match[str]) -> str: return repl +def _standardize_comments(comment: Union[str, list[str]]) -> list[str]: + return [] if not comment else [comment] if isinstance(comment, str) else comment + + TAG_ROSTER = ["Event", "Site", "Date", "Round", "White", "Black", "Result"] @@ -164,6 +153,39 @@ class SkipType(enum.Enum): ResultT = TypeVar("ResultT", covariant=True) +class TimeControlType(enum.Enum): + UNKNOWN = 0 + UNLIMITED = 1 + STANDARD = 2 + RAPID = 3 + BLITZ = 4 + BULLET = 5 + + +@dataclasses.dataclass +class TimeControlPart: + moves: int = 0 + time: int = 0 + increment: float = 0 + delay: float = 0 + + +@dataclasses.dataclass +class TimeControl: + """ + PGN TimeControl Parser + Spec: http://www.saremba.de/chessgml/standards/pgn/pgn-complete.htm#c9.6 + + Not Yet Implemented: + - Hourglass/Sandclock ('*' prefix) + - Differentiating between Bronstein and Simple Delay (Not part of the PGN Spec) + - More Info: https://en.wikipedia.org/wiki/Chess_clock#Timing_methods + """ + + parts: list[TimeControlPart] = dataclasses.field(default_factory=list) + type: TimeControlType = TimeControlType.UNKNOWN + + class _AcceptFrame: def __init__(self, node: ChildNode, *, is_variation: bool = False, sidelines: bool = True): self.state = "pre" @@ -174,38 +196,41 @@ def __init__(self, node: ChildNode, *, is_variation: bool = False, sidelines: bo class GameNode(abc.ABC): - parent: Optional[GameNode] - """The parent node or ``None`` if this is the root node of the game.""" - - move: Optional[chess.Move] - """ - The move leading to this node or ``None`` if this is the root node of the - game. - """ - variations: List[ChildNode] """A list of child nodes.""" - comment: str + comments: list[str] """ A comment that goes behind the move leading to this node. Comments that occur before any moves are assigned to the root node. """ - starting_comment: str + starting_comments: list[str] + nags: Set[int] - def __init__(self, *, comment: str = "") -> None: - self.parent = None - self.move = None + def __init__(self, *, comment: Union[str, list[str]] = "") -> None: self.variations = [] - self.comment = comment + self.comments = _standardize_comments(comment) # Deprecated: These should be properties of ChildNode, but need to # remain here for backwards compatibility. - self.starting_comment = "" + self.starting_comments = [] self.nags = set() + @property + @abc.abstractmethod + def parent(self) -> Optional[GameNode]: + """The parent node or ``None`` if this is the root node of the game.""" + + @property + @abc.abstractmethod + def move(self) -> Optional[chess.Move]: + """ + The move leading to this node or ``None`` if this is the root node of + the game. + """ + @abc.abstractmethod def board(self) -> chess.Board: """ @@ -215,6 +240,8 @@ def board(self) -> chess.Board: ``Variant``) unless the ``FEN`` header tag is set. It's a copy, so modifying the board will not alter the game. + + Complexity is `O(n)`. """ @abc.abstractmethod @@ -226,11 +253,15 @@ def ply(self) -> int: Usually this is equal to the number of parent nodes, but it may be more if the game was started from a custom position. + + Complexity is `O(n)`. """ def turn(self) -> Color: """ Gets the color to move at this node. See :data:`chess.Board.turn`. + + Complexity is `O(n)`. """ return self.ply() % 2 == 0 @@ -241,13 +272,21 @@ def root(self) -> GameNode: return node def game(self) -> Game: - """Gets the root node, i.e., the game.""" + """ + Gets the root node, i.e., the game. + + Complexity is `O(n)`. + """ root = self.root() assert isinstance(root, Game), "GameNode not rooted in Game" return root def end(self) -> GameNode: - """Follows the main variation to the end and returns the last node.""" + """ + Follows the main variation to the end and returns the last node. + + Complexity is `O(n)`. + """ node = self while node.variations: @@ -256,7 +295,11 @@ def end(self) -> GameNode: return node def is_end(self) -> bool: - """Checks if this node is the last node in the current variation.""" + """ + Checks if this node is the last node in the current variation. + + Complexity is `O(1)`. + """ return not self.variations def starts_variation(self) -> bool: @@ -267,6 +310,8 @@ def starts_variation(self) -> bool: For example, in ``1. e4 e5 (1... c5 2. Nf3) 2. Nf3``, the node holding 1... c5 starts a variation. + + Complexity is `O(1)`. """ if not self.parent or not self.parent.variations: return False @@ -274,7 +319,11 @@ def starts_variation(self) -> bool: return self.parent.variations[0] != self def is_mainline(self) -> bool: - """Checks if the node is in the mainline of the game.""" + """ + Checks if the node is in the mainline of the game. + + Complexity is `O(n)`. + """ node = self while node.parent: @@ -291,6 +340,8 @@ def is_main_variation(self) -> bool: """ Checks if this node is the first variation from the point of view of its parent. The root node is also in the main variation. + + Complexity is `O(1)`. """ if not self.parent: return True @@ -349,9 +400,9 @@ def remove_variation(self, move: Union[int, chess.Move, GameNode]) -> None: """Removes a variation.""" self.variations.remove(self.variation(move)) - def add_variation(self, move: chess.Move, *, comment: str = "", starting_comment: str = "", nags: Iterable[int] = []) -> ChildNode: + def add_variation(self, move: chess.Move, *, comment: Union[str, list[str]] = "", starting_comment: Union[str, list[str]] = "", nags: Iterable[int] = []) -> ChildNode: """Creates a child node with the given attributes.""" - # Instanciate ChildNode only in this method. + # Instantiate ChildNode only in this method. return ChildNode(self, move, comment=comment, starting_comment=starting_comment, nags=nags) def add_main_variation(self, move: chess.Move, *, comment: str = "", nags: Iterable[int] = []) -> ChildNode: @@ -367,6 +418,8 @@ def next(self) -> Optional[ChildNode]: """ Returns the first node of the mainline after this node, or ``None`` if this node does not have any children. + + Complexity is `O(1)`. """ return self.variations[0] if self.variations else None @@ -378,7 +431,7 @@ def mainline_moves(self) -> Mainline[chess.Move]: """Returns an iterable over the main moves after this node.""" return Mainline(self, lambda node: node.move) - def add_line(self, moves: Iterable[chess.Move], *, comment: str = "", starting_comment: str = "", nags: Iterable[int] = []) -> GameNode: + def add_line(self, moves: Iterable[chess.Move], *, comment: Union[str, list[str]] = "", starting_comment: Union[str, list[str]] = "", nags: Iterable[int] = []) -> GameNode: """ Creates a sequence of child nodes for the given list of moves. Adds *comment* and *nags* to the last node of the line and returns it. @@ -391,11 +444,8 @@ def add_line(self, moves: Iterable[chess.Move], *, comment: str = "", starting_c starting_comment = "" # Merge comment and NAGs. - if node.comment: - node.comment += " " + comment - else: - node.comment = comment - + comments = _standardize_comments(comment) + node.comments.extend(comments) node.nags.update(nags) return node @@ -404,8 +454,10 @@ def eval(self) -> Optional[chess.engine.PovScore]: """ Parses the first valid ``[%eval ...]`` annotation in the comment of this node, if any. + + Complexity is `O(n)`. """ - match = EVAL_REGEX.search(self.comment) + match = EVAL_REGEX.search(" ".join(self.comments)) if not match: return None @@ -420,7 +472,7 @@ def eval(self) -> Optional[chess.engine.PovScore]: # who has been mated. return chess.engine.PovScore(score, turn) else: - score = chess.engine.Cp(int(float(match.group("cp")) * 100)) + score = chess.engine.Cp(round(float(match.group("cp")) * 100)) return chess.engine.PovScore(score if turn else -score, turn) @@ -428,8 +480,10 @@ def eval_depth(self) -> Optional[int]: """ Parses the first valid ``[%eval ...]`` annotation in the comment of this node and returns the corresponding depth, if any. + + Complexity is `O(1)`. """ - match = EVAL_REGEX.search(self.comment) + match = EVAL_REGEX.search(" ".join(self.comments)) return int(match.group("depth")) if match and match.group("depth") else None def set_eval(self, score: Optional[chess.engine.PovScore], depth: Optional[int] = None) -> None: @@ -446,12 +500,7 @@ def set_eval(self, score: Optional[chess.engine.PovScore], depth: Optional[int] elif score.white().mate(): eval = f"[%eval #{score.white().mate()}{depth_suffix}]" - self.comment, found = EVAL_REGEX.subn(_condense_affix(eval), self.comment, count=1) - - if not found and eval: - if self.comment and not self.comment.endswith(" "): - self.comment += " " - self.comment += eval + self._replace_or_add_annotation(eval, EVAL_REGEX) def arrows(self) -> List[chess.svg.Arrow]: """ @@ -461,7 +510,7 @@ def arrows(self) -> List[chess.svg.Arrow]: Returns a list of :class:`arrows `. """ arrows = [] - for match in ARROWS_REGEX.finditer(self.comment): + for match in ARROWS_REGEX.finditer(" ".join(self.comments)): for group in match.group("arrows").split(","): arrows.append(chess.svg.Arrow.from_pgn(group)) @@ -483,7 +532,10 @@ def set_arrows(self, arrows: Iterable[Union[chess.svg.Arrow, Tuple[Square, Squar pass (csl if arrow.tail == arrow.head else cal).append(arrow.pgn()) # type: ignore - self.comment = ARROWS_REGEX.sub(_condense_affix(""), self.comment) + for index in range(len(self.comments)): + self.comments[index] = ARROWS_REGEX.sub(_condense_affix(""), self.comments[index]) + + self.comments = list(filter(None, self.comments)) prefix = "" if csl: @@ -491,10 +543,8 @@ def set_arrows(self, arrows: Iterable[Union[chess.svg.Arrow, Tuple[Square, Squar if cal: prefix += f"[%cal {','.join(cal)}]" - if prefix and self.comment and not self.comment.startswith(" ") and not self.comment.startswith("\n"): - self.comment = prefix + " " + self.comment - else: - self.comment = prefix + self.comment + if prefix: + self.comments.insert(0, prefix) def clock(self) -> Optional[float]: """ @@ -504,7 +554,7 @@ def clock(self) -> Optional[float]: Returns the player's remaining time to the next time control after this move, in seconds. """ - match = CLOCK_REGEX.search(self.comment) + match = CLOCK_REGEX.search(" ".join(self.comments)) if match is None: return None return int(match.group("hours")) * 3600 + int(match.group("minutes")) * 60 + float(match.group("seconds")) @@ -523,12 +573,7 @@ def set_clock(self, seconds: Optional[float]) -> None: seconds_part = f"{seconds:06.3f}".rstrip("0").rstrip(".") clk = f"[%clk {hours:d}:{minutes:02d}:{seconds_part}]" - self.comment, found = CLOCK_REGEX.subn(_condense_affix(clk), self.comment, count=1) - - if not found and clk: - if self.comment and not self.comment.endswith(" ") and not self.comment.endswith("\n"): - self.comment += " " - self.comment += clk + self._replace_or_add_annotation(clk, CLOCK_REGEX) def emt(self) -> Optional[float]: """ @@ -538,7 +583,7 @@ def emt(self) -> Optional[float]: Returns the player's elapsed move time use for the comment of this move, in seconds. """ - match = EMT_REGEX.search(self.comment) + match = EMT_REGEX.search(" ".join(self.comments)) if match is None: return None return int(match.group("hours")) * 3600 + int(match.group("minutes")) * 60 + float(match.group("seconds")) @@ -557,12 +602,19 @@ def set_emt(self, seconds: Optional[float]) -> None: seconds_part = f"{seconds:06.3f}".rstrip("0").rstrip(".") emt = f"[%emt {hours:d}:{minutes:02d}:{seconds_part}]" - self.comment, found = EMT_REGEX.subn(_condense_affix(emt), self.comment, count=1) + self._replace_or_add_annotation(emt, EMT_REGEX) + + def _replace_or_add_annotation(self, text: str, regex: re.Pattern[str]) -> None: + found = 0 + for index in range(len(self.comments)): + self.comments[index], found = regex.subn(_condense_affix(text), self.comments[index], count=1) + if found: + break + + self.comments = list(filter(None, self.comments)) - if not found and emt: - if self.comment and not self.comment.endswith(" ") and not self.comment.endswith("\n"): - self.comment += " " - self.comment += emt + if not found and text: + self.comments.append(text) @abc.abstractmethod def accept(self, visitor: BaseVisitor[ResultT]) -> ResultT: @@ -612,13 +664,7 @@ class ChildNode(GameNode): Extends :class:`~chess.pgn.GameNode`. """ - parent: GameNode - """The parent node.""" - - move: chess.Move - """The move leading to this node.""" - - starting_comment: str + starting_comments: list[str] """ A comment for the start of a variation. Only nodes that actually start a variation (:func:`~chess.pgn.GameNode.starts_variation()` @@ -632,15 +678,28 @@ class ChildNode(GameNode): node of the game will never have NAGs. """ - def __init__(self, parent: GameNode, move: chess.Move, *, comment: str = "", starting_comment: str = "", nags: Iterable[int] = []) -> None: + def __init__(self, parent: GameNode, move: chess.Move, *, comment: Union[str, list[str]] = "", starting_comment: Union[str, list[str]] = "", nags: Iterable[int] = []) -> None: super().__init__(comment=comment) - self.parent = parent - self.move = move + self._parent = parent + self._move = move self.parent.variations.append(self) self.nags.update(nags) - self.starting_comment = starting_comment + self.starting_comments = _standardize_comments(starting_comment) + + @property + @override + def parent(self) -> GameNode: + """The parent node.""" + return self._parent + + @property + @override + def move(self) -> chess.Move: + """The move leading to this node.""" + return self._move + @override def board(self) -> chess.Board: stack: List[chess.Move] = [] node: GameNode = self @@ -656,6 +715,7 @@ def board(self) -> chess.Board: return board + @override def ply(self) -> int: ply = 0 node: GameNode = self @@ -670,6 +730,8 @@ def san(self) -> str: See :func:`chess.Board.san()`. Do not call this on the root node. + + Complexity is `O(n)`. """ return self.parent.board().san(self.move) @@ -679,16 +741,23 @@ def uci(self, *, chess960: Optional[bool] = None) -> str: See :func:`chess.Board.uci()`. Do not call this on the root node. + + Complexity is `O(n)`. """ return self.parent.board().uci(self.move, chess960=chess960) + @override def end(self) -> ChildNode: - """Follows the main variation to the end and returns the last node.""" + """ + Follows the main variation to the end and returns the last node. + + Complexity is `O(n)`. + """ return typing.cast(ChildNode, super().end()) def _accept_node(self, parent_board: chess.Board, visitor: BaseVisitor[ResultT]) -> None: - if self.starting_comment: - visitor.visit_comment(self.starting_comment) + if self.starting_comments: + visitor.visit_comment(self.starting_comments) visitor.visit_move(parent_board, self.move) @@ -699,8 +768,8 @@ def _accept_node(self, parent_board: chess.Board, visitor: BaseVisitor[ResultT]) for nag in sorted(self.nags): visitor.visit_nag(nag) - if self.comment: - visitor.visit_comment(self.comment) + if self.comments: + visitor.visit_comment(self.comments) def _accept(self, parent_board: chess.Board, visitor: BaseVisitor[ResultT], *, sidelines: bool = True) -> None: stack = [_AcceptFrame(self, sidelines=sidelines)] @@ -735,6 +804,7 @@ def _accept(self, parent_board: chess.Board, visitor: BaseVisitor[ResultT], *, s else: stack.pop() + @override def accept(self, visitor: BaseVisitor[ResultT]) -> ResultT: self._accept(self.parent.board(), visitor, sidelines=False) return visitor.result() @@ -784,14 +854,21 @@ def __init__(self, headers: Optional[Union[Mapping[str, str], Iterable[Tuple[str self.headers = Headers(headers) self.errors = [] + @property + @override + def parent(self) -> None: + return None + + @property + @override + def move(self) -> None: + return None + + @override def board(self) -> chess.Board: return self.headers.board() - # TODO: Consider naming. - def _interactive_viewer(self) -> Any: - from chess._interactive import InteractiveViewer - return InteractiveViewer(self) # type: ignore - + @override def ply(self) -> int: # Optimization: Parse FEN only for custom starting positions. return self.board().ply() if "FEN" in self.headers else 0 @@ -810,11 +887,11 @@ def setup(self, board: Union[chess.Board, str]) -> None: fen = setup.fen() if fen == type(setup).starting_fen: - self.headers.pop("SetUp", None) self.headers.pop("FEN", None) + self.headers.pop("SetUp", None) else: - self.headers["SetUp"] = "1" self.headers["FEN"] = fen + self.headers["SetUp"] = "1" if type(setup).aliases[0] == "Standard" and setup.chess960: self.headers["Variant"] = "Chess960" @@ -824,6 +901,7 @@ def setup(self, board: Union[chess.Board, str]) -> None: else: self.headers.pop("Variant", None) + @override def accept(self, visitor: BaseVisitor[ResultT]) -> ResultT: """ Traverses the game in PGN order using the given *visitor*. Returns @@ -836,8 +914,8 @@ def accept(self, visitor: BaseVisitor[ResultT]) -> ResultT: board = self.board() visitor.visit_board(board) - if self.comment: - visitor.visit_comment(self.comment) + if self.comments: + visitor.visit_comment(self.comments) if self.variations: self.variations[0]._accept(board, visitor) @@ -847,6 +925,14 @@ def accept(self, visitor: BaseVisitor[ResultT]) -> ResultT: visitor.end_game() return visitor.result() + def time_control(self) -> TimeControl: + """ + Returns the time control of the game. If the game has no time control + information, the default time control ('UNKNOWN') is returned. + """ + time_control_header = self.headers.get("TimeControl", "") + return parse_time_control(time_control_header) + @classmethod def from_board(cls: Type[GameT], board: chess.Board) -> GameT: """Creates a game from the move stack of a :class:`~chess.Board()`.""" @@ -872,12 +958,13 @@ def builder(cls: Type[GameT]) -> GameBuilder[GameT]: return GameBuilder(Game=cls) def __repr__(self) -> str: - return "<{} at {:#x} ({!r} vs. {!r}, {!r}{})>".format( + return "<{} at {:#x} ({!r} vs. {!r}, {!r} at {!r}{})>".format( type(self).__name__, id(self), self.headers.get("White", "?"), self.headers.get("Black", "?"), self.headers.get("Date", "????.??.??"), + self.headers.get("Site", "?"), f", {len(self.errors)} errors" if self.errors else "") @@ -941,10 +1028,7 @@ def __setitem__(self, key: str, value: str) -> None: self._others[key] = value def __getitem__(self, key: str) -> str: - if key in TAG_ROSTER: - return self._tag_roster[key] - else: - return self._others[key] + return self._tag_roster[key] if key in TAG_ROSTER else self._others[key] def __delitem__(self, key: str) -> None: if key in TAG_ROSTER: @@ -957,15 +1041,15 @@ def __iter__(self) -> Iterator[str]: if key in self._tag_roster: yield key - yield from sorted(self._others) + yield from self._others def __len__(self) -> int: return len(self._tag_roster) + len(self._others) - def copy(self: HeadersT) -> HeadersT: + def copy(self) -> Self: return type(self)(self) - def __copy__(self: HeadersT) -> HeadersT: + def __copy__(self) -> Self: return self.copy() def __repr__(self) -> str: @@ -1042,21 +1126,12 @@ def end_headers(self) -> Optional[SkipType]: """Called after visiting game headers.""" pass - def parse_san(self, board: chess.Board, san: str) -> chess.Move: + def begin_parse_san(self, board: chess.Board, san: str) -> Optional[SkipType]: """ - When the visitor is used by a parser, this is called to parse a move - in standard algebraic notation. - - You can override the default implementation to work around specific - quirks of your input format. - - .. deprecated:: 1.1 - This method is very limited, because it is only called on moves - that the parser recognizes in the first place. Instead of adding - workarounds here, please report common quirks so that - they can be handled for everyone. + When the visitor is used by a parser, this is called at the start of + each standard algebraic notation detailing a move. """ - return board.parse_san(san) + pass def visit_move(self, board: chess.Board, move: chess.Move) -> None: """ @@ -1075,7 +1150,7 @@ def visit_board(self, board: chess.Board) -> None: """ pass - def visit_comment(self, comment: str) -> None: + def visit_comment(self, comment: list[str]) -> None: """Called for each comment.""" pass @@ -1121,57 +1196,68 @@ class GameBuilder(BaseVisitor[GameT]): @typing.overload def __init__(self: GameBuilder[Game]) -> None: ... @typing.overload - def __init__(self: GameBuilder[GameT], *, Game: Type[GameT]) -> None: ... + def __init__(self, *, Game: Type[GameT]) -> None: ... def __init__(self, *, Game: Any = Game) -> None: self.Game = Game + @override def begin_game(self) -> None: self.game: GameT = self.Game() self.variation_stack: List[GameNode] = [self.game] - self.starting_comment = "" + self.starting_comments: list[str] = [] self.in_variation = False + @override def begin_headers(self) -> Headers: return self.game.headers + @override def visit_header(self, tagname: str, tagvalue: str) -> None: self.game.headers[tagname] = tagvalue + @override def visit_nag(self, nag: int) -> None: self.variation_stack[-1].nags.add(nag) + @override def begin_variation(self) -> None: parent = self.variation_stack[-1].parent assert parent is not None, "begin_variation called, but root node on top of stack" self.variation_stack.append(parent) self.in_variation = False + @override def end_variation(self) -> None: self.variation_stack.pop() + @override def visit_result(self, result: str) -> None: if self.game.headers.get("Result", "*") == "*": self.game.headers["Result"] = result - def visit_comment(self, comment: str) -> None: + @override + def visit_comment(self, comment: Union[str, list[str]]) -> None: + comments = _standardize_comments(comment) if self.in_variation or (self.variation_stack[-1].parent is None and self.variation_stack[-1].is_end()): # Add as a comment for the current node if in the middle of # a variation. Add as a comment for the game if the comment # starts before any move. - new_comment = [self.variation_stack[-1].comment, comment] - self.variation_stack[-1].comment = " ".join(filter(None, new_comment)) + self.variation_stack[-1].comments.extend(comments) + self.variation_stack[-1].comments = list(filter(None, self.variation_stack[-1].comments)) else: # Otherwise, it is a starting comment. - new_comment = [self.starting_comment, comment] - self.starting_comment = " ".join(filter(None, new_comment)) + self.starting_comments.extend(comments) + self.starting_comments = list(filter(None, self.starting_comments)) + @override def visit_move(self, board: chess.Board, move: chess.Move) -> None: self.variation_stack[-1] = self.variation_stack[-1].add_variation(move) - self.variation_stack[-1].starting_comment = self.starting_comment - self.starting_comment = "" + self.variation_stack[-1].starting_comments = self.starting_comments + self.starting_comments = [] self.in_variation = True + @override def handle_error(self, error: Exception) -> None: """ Populates :data:`chess.pgn.Game.errors` with encountered errors and @@ -1202,9 +1288,10 @@ def handle_error(self, error: Exception) -> None: >>> >>> game = chess.pgn.read_game(pgn, Visitor=MyGameBuilder) """ - LOGGER.exception("error during pgn parsing") + LOGGER.error("%s while parsing %r", error, self.game) self.game.errors.append(error) + @override def result(self) -> GameT: """ Returns the visited :class:`~chess.pgn.Game()`. @@ -1218,20 +1305,24 @@ class HeadersBuilder(BaseVisitor[HeadersT]): @typing.overload def __init__(self: HeadersBuilder[Headers]) -> None: ... @typing.overload - def __init__(self: HeadersBuilder[HeadersT], *, Headers: Type[Headers]) -> None: ... + def __init__(self, *, Headers: Type[HeadersT]) -> None: ... def __init__(self, *, Headers: Any = Headers) -> None: self.Headers = Headers + @override def begin_headers(self) -> HeadersT: self.headers: HeadersT = self.Headers({}) return self.headers + @override def visit_header(self, tagname: str, tagvalue: str) -> None: self.headers[tagname] = tagvalue + @override def end_headers(self) -> SkipType: return SKIP + @override def result(self) -> HeadersT: return self.headers @@ -1242,37 +1333,46 @@ class BoardBuilder(BaseVisitor[chess.Board]): on the move stack. """ + @override def begin_game(self) -> None: self.skip_variation_depth = 0 + @override def begin_variation(self) -> SkipType: self.skip_variation_depth += 1 return SKIP + @override def end_variation(self) -> None: self.skip_variation_depth = max(self.skip_variation_depth - 1, 0) + @override def visit_board(self, board: chess.Board) -> None: if not self.skip_variation_depth: self.board = board + @override def result(self) -> chess.Board: return self.board -class SkipVisitor(BaseVisitor[_TrueLiteral]): +class SkipVisitor(BaseVisitor[Literal[True]]): """Skips a game.""" + @override def begin_game(self) -> SkipType: return SKIP + @override def end_headers(self) -> SkipType: return SKIP + @override def begin_variation(self) -> SkipType: return SKIP - def result(self) -> _TrueLiteral: + @override + def result(self) -> Literal[True]: return True @@ -1337,9 +1437,14 @@ def end_variation(self) -> None: self.write_token(") ") self.force_movenumber = True - def visit_comment(self, comment: str) -> None: + def visit_comment(self, comment: Union[str, list[str]]) -> None: if self.comments and (self.variations or not self.variation_depth): - self.write_token("{ " + comment.replace("}", "").strip() + " } ") + def pgn_format(comments: list[str]) -> str: + edit = map(lambda s: s.replace("{", "").replace("}", ""), comments) + return " ".join(f"{{ {comment} }}" for comment in edit if comment) + + comments = _standardize_comments(comment) + self.write_token(pgn_format(comments) + " ") self.force_movenumber = True def visit_nag(self, nag: int) -> None: @@ -1381,6 +1486,7 @@ class StringExporter(StringExporterMixin, BaseVisitor[str]): There will be no newline characters at the end of the string. """ + @override def result(self) -> str: if self.current_line: return "\n".join(itertools.chain(self.lines, [self.current_line.rstrip()])).rstrip() @@ -1412,6 +1518,7 @@ def __init__(self, handle: TextIO, *, columns: Optional[int] = 80, headers: bool super().__init__(columns=columns, headers=headers, comments=comments, variations=variations) self.handle = handle + @override def begin_game(self) -> None: self.written: int = 0 super().begin_game() @@ -1427,6 +1534,7 @@ def write_line(self, line: str = "") -> None: self.written += self.handle.write(line.rstrip()) self.written += self.handle.write("\n") + @override def result(self) -> int: return self.written @@ -1498,6 +1606,7 @@ def read_game(handle: TextIO, *, Visitor: Any = GameBuilder) -> Any: skipping_game = False managed_headers: Optional[Headers] = None unmanaged_headers: Optional[Headers] = None + board_stack: List[chess.Board] = [] # Ignore leading empty lines and comments. line = handle.readline().lstrip("\ufeff") @@ -1681,14 +1790,15 @@ def read_game(handle: TextIO, *, Visitor: Any = GameBuilder) -> Any: visitor.visit_result(token) else: # Parse SAN tokens. - try: - move = visitor.parse_san(board_stack[-1], token) - except ValueError as error: - visitor.handle_error(error) - skip_variation_depth = 1 - else: - visitor.visit_move(board_stack[-1], move) - board_stack[-1].push(move) + if visitor.begin_parse_san(board_stack[-1], token) is not SKIP: + try: + move = board_stack[-1].parse_san(token) + except ValueError as error: + visitor.handle_error(error) + skip_variation_depth = 1 + else: + visitor.visit_move(board_stack[-1], move) + board_stack[-1].push(move) visitor.visit_board(board_stack[-1]) if fresh_line: @@ -1745,3 +1855,61 @@ def skip_game(handle: TextIO) -> bool: Skips a game. Returns ``True`` if a game was found and skipped. """ return bool(read_game(handle, Visitor=SkipVisitor)) + + +def parse_time_control(time_control: str) -> TimeControl: + tc = TimeControl() + + if not time_control: + return tc + + if time_control.startswith("?"): + return tc + + if time_control.startswith("-"): + tc.type = TimeControlType.UNLIMITED + return tc + + def _parse_part(part: str) -> TimeControlPart: + tcp = TimeControlPart() + + moves_time, *bonus = part.split("+") + + if bonus: + _bonus = bonus[0] + if _bonus.lower().endswith("d"): + tcp.delay = float(_bonus[:-1]) + else: + tcp.increment = float(_bonus) + + moves, *time = moves_time.split("/") + if time: + tcp.moves = int(moves) + tcp.time = int(time[0]) + else: + tcp.moves = 0 + tcp.time = int(moves) + + return tcp + + tc.parts = [_parse_part(part) for part in time_control.split(":")] + + if len(tc.parts) > 1: + for part in tc.parts[:-1]: + if part.moves == 0: + raise ValueError("Only last part can be 'sudden death'.") + + # Classification according to https://www.fide.com/FIDE/handbook/LawsOfChess.pdf + # (Bullet added) + base_time = tc.parts[0].time + increment = tc.parts[0].increment + if (base_time + 60 * increment) < 3 * 60: + tc.type = TimeControlType.BULLET + elif (base_time + 60 * increment) < 15 * 60: + tc.type = TimeControlType.BLITZ + elif (base_time + 60 * increment) < 60 * 60: + tc.type = TimeControlType.RAPID + else: + tc.type = TimeControlType.STANDARD + + return tc diff --git a/chess/polyglot.py b/chess/polyglot.py index 1562d5306..a7d6807c4 100644 --- a/chess/polyglot.py +++ b/chess/polyglot.py @@ -1,19 +1,3 @@ -# This file is part of the python-chess library. -# Copyright (C) 2012-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - from __future__ import annotations import chess @@ -27,7 +11,7 @@ from typing import Callable, Container, Iterator, List, NamedTuple, Optional, Type, Union -PathLike = Union[str, bytes, os.PathLike] +StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"] ENTRY_STRUCT = struct.Struct(">QHHI") @@ -328,6 +312,9 @@ def size(self) -> int: def close(self) -> None: pass + def madvise(self, option: int) -> None: + pass + def _randint(rng: Optional[random.Random], a: int, b: int) -> int: return random.randint(a, b) if rng is None else rng.randint(a, b) @@ -336,20 +323,21 @@ def _randint(rng: Optional[random.Random], a: int, b: int) -> int: class MemoryMappedReader: """Maps a Polyglot opening book to memory.""" - def __init__(self, filename: PathLike) -> None: - self.fd = os.open(filename, os.O_RDONLY | os.O_BINARY if hasattr(os, "O_BINARY") else os.O_RDONLY) - + def __init__(self, filename: StrOrBytesPath) -> None: + fd = os.open(filename, os.O_RDONLY | getattr(os, "O_BINARY", 0)) try: - self.mmap: Union[mmap.mmap, _EmptyMmap] = mmap.mmap(self.fd, 0, access=mmap.ACCESS_READ) + self.mmap: Union[mmap.mmap, _EmptyMmap] = mmap.mmap(fd, 0, access=mmap.ACCESS_READ) except (ValueError, OSError): self.mmap = _EmptyMmap() # Workaround for empty opening books. + finally: + os.close(fd) if self.mmap.size() % ENTRY_STRUCT.size != 0: raise IOError(f"invalid file size: ensure {filename!r} is a valid polyglot opening book") try: - # Python 3.8 - self.mmap.madvise(mmap.MADV_RANDOM) # type: ignore + # Unix + self.mmap.madvise(mmap.MADV_RANDOM) except AttributeError: pass @@ -390,11 +378,8 @@ def __getitem__(self, index: int) -> Entry: return Entry(key, raw_move, weight, learn, move) def __iter__(self) -> Iterator[Entry]: - i = 0 - size = len(self) - while i < size: + for i in range(len(self)): yield self[i] - i += 1 def bisect_key_left(self, key: int) -> int: lo = 0 @@ -514,13 +499,8 @@ def close(self) -> None: """Closes the reader.""" self.mmap.close() - try: - os.close(self.fd) - except OSError: - pass - -def open_reader(path: PathLike) -> MemoryMappedReader: +def open_reader(path: StrOrBytesPath) -> MemoryMappedReader: """ Creates a reader for the file at the given path. diff --git a/chess/svg.py b/chess/svg.py index 6021e2054..7e8facf99 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -1,23 +1,3 @@ -# This file is part of the python-chess library. -# Copyright (C) 2016-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Piece vector graphics are copyright (C) Colin M.L. Burnett -# and also licensed under the -# GNU General Public License. - from __future__ import annotations import math @@ -76,6 +56,8 @@ "square dark lastmove": "#aaa23b", "square light lastmove": "#cdd16a", "margin": "#212121", + "inner border": "#111", + "outer border": "#111", "coord": "#e5e5e5", "arrow green": "#15781B80", "arrow red": "#88202080", @@ -162,13 +144,14 @@ class SvgWrapper(str): def _repr_svg_(self) -> SvgWrapper: return self + def _repr_html_(self) -> SvgWrapper: + return self + def _svg(viewbox: int, size: Optional[int]) -> ET.Element: svg = ET.Element("svg", { "xmlns": "http://www.w3.org/2000/svg", "xmlns:xlink": "http://www.w3.org/1999/xlink", - "version": "1.2", - "baseProfile": "tiny", "viewBox": f"0 0 {viewbox:d} {viewbox:d}", }) @@ -244,7 +227,7 @@ def board(board: Optional[chess.BaseBoard] = None, *, size: Optional[int] = None, coordinates: bool = True, colors: Dict[str, str] = {}, - flipped: bool = False, + borders: bool = False, style: Optional[str] = None) -> str: """ Renders a board with pieces and/or selected squares as an SVG image. @@ -267,10 +250,12 @@ def board(board: Optional[chess.BaseBoard] = None, *, :param coordinates: Pass ``False`` to disable the coordinate margin. :param colors: A dictionary to override default colors. Possible keys are ``square light``, ``square dark``, ``square light lastmove``, - ``square dark lastmove``, ``margin``, ``coord``, ``arrow green``, - ``arrow blue``, ``arrow red``, and ``arrow yellow``. Values should look - like ``#ffce9e`` (opaque), or ``#15781B80`` (transparent). - :param flipped: Pass ``True`` to flip the board. + ``square dark lastmove``, ``margin``, ``coord``, ``inner border``, + ``outer border``, ``arrow green``, ``arrow blue``, ``arrow red``, + and ``arrow yellow``. Values should look like ``#ffce9e`` (opaque), + or ``#15781B80`` (transparent). + :param borders: Pass ``True`` to enable a border around the board and, + (if *coordinates* is enabled) the coordinate margin. :param style: A CSS stylesheet to include in the SVG image. >>> import chess @@ -288,13 +273,13 @@ def board(board: Optional[chess.BaseBoard] = None, *, .. image:: ../docs/Ne4.svg :alt: 8/8/8/8/4N3/8/8/8 - - .. deprecated:: 1.1 - Use *orientation* with a color instead of the *flipped* toggle. """ - orientation ^= flipped + inner_border = 1 if borders and coordinates else 0 + outer_border = 1 if borders else 0 margin = 15 if coordinates else 0 - svg = _svg(8 * SQUARE_SIZE + 2 * margin, size) + board_offset = inner_border + margin + outer_border + full_size = 2 * outer_border + 2 * margin + 2 * inner_border + 8 * SQUARE_SIZE + svg = _svg(full_size, size) if style: ET.SubElement(svg, "style").text = style @@ -318,34 +303,65 @@ def board(board: Optional[chess.BaseBoard] = None, *, if check is not None: defs.append(ET.fromstring(CHECK_GRADIENT)) - # Render coordinates. - if coordinates: + if outer_border: + outer_border_color, outer_border_opacity = _select_color(colors, "outer border") + ET.SubElement(svg, "rect", _attrs({ + "x": outer_border / 2, + "y": outer_border / 2, + "width": full_size - outer_border, + "height": full_size - outer_border, + "fill": "none", + "stroke": outer_border_color, + "stroke-width": outer_border, + "opacity": outer_border_opacity if outer_border_opacity < 1.0 else None, + })) + + if margin: margin_color, margin_opacity = _select_color(colors, "margin") ET.SubElement(svg, "rect", _attrs({ - "x": 0, - "y": 0, - "width": 2 * margin + 8 * SQUARE_SIZE, - "height": 2 * margin + 8 * SQUARE_SIZE, - "fill": margin_color, + "x": outer_border + margin / 2, + "y": outer_border + margin / 2, + "width": full_size - 2 * outer_border - margin, + "height": full_size - 2 * outer_border - margin, + "fill": "none", + "stroke": margin_color, + "stroke-width": margin, "opacity": margin_opacity if margin_opacity < 1.0 else None, })) + + if inner_border: + inner_border_color, inner_border_opacity = _select_color(colors, "inner border") + ET.SubElement(svg, "rect", _attrs({ + "x": outer_border + margin + inner_border / 2, + "y": outer_border + margin + inner_border / 2, + "width": full_size - 2 * outer_border - 2 * margin - inner_border, + "height": full_size - 2 * outer_border - 2 * margin - inner_border, + "fill": "none", + "stroke": inner_border_color, + "stroke-width": inner_border, + "opacity": inner_border_opacity if inner_border_opacity < 1.0 else None, + })) + + # Render coordinates. + if coordinates: coord_color, coord_opacity = _select_color(colors, "coord") for file_index, file_name in enumerate(chess.FILE_NAMES): - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin - svg.append(_coord(file_name, x, 0, SQUARE_SIZE, margin, True, margin, color=coord_color, opacity=coord_opacity)) - svg.append(_coord(file_name, x, margin + 8 * SQUARE_SIZE, SQUARE_SIZE, margin, True, margin, color=coord_color, opacity=coord_opacity)) + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + board_offset + # Keep some padding here to separate the ascender from the border + svg.append(_coord(file_name, x, 1, SQUARE_SIZE, margin, True, margin, color=coord_color, opacity=coord_opacity)) + svg.append(_coord(file_name, x, full_size - outer_border - margin, SQUARE_SIZE, margin, True, margin, color=coord_color, opacity=coord_opacity)) for rank_index, rank_name in enumerate(chess.RANK_NAMES): - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + board_offset svg.append(_coord(rank_name, 0, y, margin, SQUARE_SIZE, False, margin, color=coord_color, opacity=coord_opacity)) - svg.append(_coord(rank_name, margin + 8 * SQUARE_SIZE, y, margin, SQUARE_SIZE, False, margin, color=coord_color, opacity=coord_opacity)) + svg.append(_coord(rank_name, full_size - outer_border - margin, y, margin, SQUARE_SIZE, False, margin, color=coord_color, opacity=coord_opacity)) # Render board. for square, bb in enumerate(chess.BB_SQUARES): file_index = chess.square_file(square) rank_index = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + board_offset + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + board_offset cls = ["square", "light" if chess.BB_LIGHT_SQUARES & bb else "dark"] if lastmove and square in [lastmove.from_square, lastmove.to_square]: @@ -385,8 +401,8 @@ def board(board: Optional[chess.BaseBoard] = None, *, file_index = chess.square_file(check) rank_index = chess.square_rank(check) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + board_offset + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + board_offset ET.SubElement(svg, "rect", _attrs({ "x": x, @@ -402,8 +418,8 @@ def board(board: Optional[chess.BaseBoard] = None, *, file_index = chess.square_file(square) rank_index = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + board_offset + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + board_offset if board is not None: piece = board.piece_at(square) @@ -416,7 +432,7 @@ def board(board: Optional[chess.BaseBoard] = None, *, }) # Render selected squares. - if squares is not None and square in squares: + if square in squares: ET.SubElement(svg, "use", _attrs({ "href": "#xx", "xlink:href": "#xx", @@ -442,10 +458,10 @@ def board(board: Optional[chess.BaseBoard] = None, *, head_file = chess.square_file(head) head_rank = chess.square_rank(head) - xtail = margin + (tail_file + 0.5 if orientation else 7.5 - tail_file) * SQUARE_SIZE - ytail = margin + (7.5 - tail_rank if orientation else tail_rank + 0.5) * SQUARE_SIZE - xhead = margin + (head_file + 0.5 if orientation else 7.5 - head_file) * SQUARE_SIZE - yhead = margin + (7.5 - head_rank if orientation else head_rank + 0.5) * SQUARE_SIZE + xtail = board_offset + (tail_file + 0.5 if orientation else 7.5 - tail_file) * SQUARE_SIZE + ytail = board_offset + (7.5 - tail_rank if orientation else tail_rank + 0.5) * SQUARE_SIZE + xhead = board_offset + (head_file + 0.5 if orientation else 7.5 - head_file) * SQUARE_SIZE + yhead = board_offset + (7.5 - head_rank if orientation else head_rank + 0.5) * SQUARE_SIZE if (head_file, head_rank) == (tail_file, tail_rank): ET.SubElement(svg, "circle", _attrs({ diff --git a/chess/syzygy.py b/chess/syzygy.py index 0266ea422..2250db5b5 100644 --- a/chess/syzygy.py +++ b/chess/syzygy.py @@ -1,19 +1,3 @@ -# This file is part of the python-chess library. -# Copyright (C) 2012-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - from __future__ import annotations import collections @@ -28,7 +12,10 @@ import chess from types import TracebackType -from typing import Deque, Dict, Iterable, Iterator, List, Optional, Tuple, Type, TypeVar, Union +from typing import Deque, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type, TypeVar, Union + +if typing.TYPE_CHECKING: + from typing_extensions import Self UINT64_BE = struct.Struct(">Q") @@ -384,7 +371,7 @@ def is_tablename(name: str, *, one_king: bool = True, piece_count: Optional[int] def tablenames(*, one_king: bool = True, piece_count: int = 6) -> Iterator[str]: first = "K" if one_king else "P" - targets = [] + targets: List[str] = [] white_pieces = piece_count - 2 black_pieces = 0 @@ -427,7 +414,7 @@ def _dependencies(target: str, *, one_king: bool = True) -> Iterator[str]: def dependencies(target: str, *, one_king: bool = True) -> Iterator[str]: - closed = set() + closed: Set[str] = set() if one_king: closed.add("KvK") @@ -438,7 +425,7 @@ def dependencies(target: str, *, one_king: bool = True) -> Iterator[str]: def all_dependencies(targets: Iterable[str], *, one_king: bool = True) -> Iterator[str]: - closed = set() + closed: Set[str] = set() if one_king: closed.add("KvK") @@ -548,8 +535,6 @@ class PawnFileDataDtz: norm: List[int] -TableT = TypeVar("TableT", bound="Table") - class Table: size: List[int] @@ -559,7 +544,6 @@ def __init__(self, path: str, *, variant: Type[chess.Board] = chess.Board) -> No self.write_lock = threading.RLock() self.initialized = False - self.fd: Optional[int] = None self.data: Optional[mmap.mmap] = None self.read_condition = threading.Condition() @@ -602,23 +586,23 @@ def __init__(self, path: str, *, variant: Type[chess.Board] = chess.Board) -> No self.enc_type = 1 + j def init_mmap(self) -> None: - with self.write_lock: - # Open fd. - if self.fd is None: - self.fd = os.open(self.path, os.O_RDONLY | os.O_BINARY if hasattr(os, "O_BINARY") else os.O_RDONLY) + if self.data is None: + fd = os.open(self.path, os.O_RDONLY | getattr(os, "O_BINARY", 0)) + try: + data = mmap.mmap(fd, 0, access=mmap.ACCESS_READ) + finally: + os.close(fd) - # Open mmap. - if self.data is None: - data = mmap.mmap(self.fd, 0, access=mmap.ACCESS_READ) - if data.size() % 64 != 16: - raise IOError(f"invalid file size: ensure {self.path!r} is a valid syzygy tablebase file") - self.data = data + if data.size() % 64 != 16: + raise IOError(f"invalid file size: ensure {self.path!r} is a valid syzygy tablebase file") - try: - # Python 3.8 - self.data.madvise(mmap.MADV_RANDOM) - except AttributeError: - pass + try: + # Unix + data.madvise(mmap.MADV_RANDOM) + except AttributeError: + pass + + self.data = data def check_magic(self, magic: Optional[bytes], pawnless_magic: Optional[bytes]) -> None: assert self.data @@ -701,10 +685,7 @@ def set_norm_piece(self, norm: List[int], pieces: List[int]) -> None: i += norm[i] def calc_factors_piece(self, factor: List[int], order: int, norm: List[int]) -> int: - if not self.variant.connected_kings: - PIVFAC = [31332, 28056, 462] - else: - PIVFAC = [31332, 0, 518, 278] + PIVFAC = [31332, 0, 518, 278] if self.variant.connected_kings else [31332, 28056, 462] n = 64 - norm[0] @@ -780,7 +761,7 @@ def calc_symlen(self, d: PairsData, s: int, tmp: List[int]) -> None: d.symlen[s] = d.symlen[s1] + d.symlen[s2] + 1 tmp[s] = 1 - def pawn_file(self, pos: List[chess.Square]) -> int: + def pawn_file(self, pos: List[chess.Square]) -> chess.File: for i in range(1, self.pawns[0]): if FLAP[pos[0]] > FLAP[pos[i]]: pos[0], pos[i] = pos[i], pos[0] @@ -799,6 +780,7 @@ def encode_piece(self, norm: List[int], pos: List[chess.Square], factor: List[in for i in range(n): pos[i] ^= 0x38 + i = 0 for i in range(n): if offdiag(pos[i]): break @@ -1045,11 +1027,7 @@ def close(self) -> None: self.data.close() self.data = None - if self.fd is not None: - os.close(self.fd) - self.fd = None - - def __enter__(self: TableT) -> TableT: + def __enter__(self) -> Self: return self def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[TracebackType]) -> None: @@ -1278,14 +1256,14 @@ def init_table_dtz(self) -> None: self.norm = [0 for _ in range(self.num)] self.tb_size = [0, 0, 0, 0] self.size = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - self.files = [PawnFileDataDtz() for f in range(4)] + self.files = [PawnFileDataDtz() for _ in range(4)] files = 4 if self.data[4] & 0x02 else 1 p_data = 5 if not self.has_pawns: - self.map_idx = [[0, 0, 0, 0]] + self.map_idx: List[List[int]] = [[0, 0, 0, 0]] self.setup_pieces_piece_dtz(p_data, 0) p_data += self.num + 1 @@ -1487,10 +1465,6 @@ def setup_pieces_pawn_dtz(self, p_data: int, p_tb_size: int, f: int) -> None: class Tablebase: """ Manages a collection of tablebase files for probing. - - If *max_fds* is not ``None``, will at most use *max_fds* open file - descriptors at any given time. The least recently used tables are closed, - if necessary. """ def __init__(self, *, max_fds: Optional[int] = 128, VariantBoard: Type[chess.Board] = chess.Board) -> None: self.variant = VariantBoard @@ -1539,27 +1513,23 @@ def add_directory(self, directory: str, *, load_wdl: bool = True, load_dtz: bool Returns the number of table files that were found. """ - num = 0 directory = os.path.abspath(directory) - - for filename in os.listdir(directory): - path = os.path.join(directory, filename) - tablename, ext = os.path.splitext(filename) - - if is_tablename(tablename, one_king=self.variant.one_king) and os.path.isfile(path): - if load_wdl: - if ext == self.variant.tbw_suffix: - num += self._open_table(self.wdl, WdlTable, path) - elif "P" not in tablename and ext == self.variant.pawnless_tbw_suffix: - num += self._open_table(self.wdl, WdlTable, path) - - if load_dtz: - if ext == self.variant.tbz_suffix: - num += self._open_table(self.dtz, DtzTable, path) - elif "P" not in tablename and ext == self.variant.pawnless_tbz_suffix: - num += self._open_table(self.dtz, DtzTable, path) - - return num + return sum(self.add_file(os.path.join(directory, filename), load_wdl=load_wdl, load_dtz=load_dtz) for filename in os.listdir(directory)) + + def add_file(self, path: str, *, load_wdl: bool = True, load_dtz: bool = True) -> int: + tablename, ext = os.path.splitext(os.path.basename(path)) + if is_tablename(tablename, one_king=self.variant.one_king) and os.path.isfile(path): + if load_wdl: + if ext == self.variant.tbw_suffix: + return self._open_table(self.wdl, WdlTable, path) + elif "P" not in tablename and ext == self.variant.pawnless_tbw_suffix: + return self._open_table(self.wdl, WdlTable, path) + if load_dtz: + if ext == self.variant.tbz_suffix: + return self._open_table(self.dtz, DtzTable, path) + elif "P" not in tablename and ext == self.variant.pawnless_tbz_suffix: + return self._open_table(self.dtz, DtzTable, path) + return 0 def probe_wdl_table(self, board: chess.Board) -> int: # Test for variant end. @@ -1578,6 +1548,8 @@ def probe_wdl_table(self, board: chess.Board) -> int: try: table = typing.cast(WdlTable, self.wdl[key]) except KeyError: + if board.piece_count() > TBPIECES: + raise KeyError(f"syzygy tables support up to {TBPIECES} pieces, not {board.piece_count()}: {board.fen()}") raise MissingTableError(f"did not find wdl table {key}") self._bump_lru(table) @@ -1590,8 +1562,13 @@ def probe_ab(self, board: chess.Board, alpha: int, beta: int, threats: bool = Fa raise KeyError(f"tablebase has been opened for {self.variant.uci_variant}, probed with: {board.uci_variant}") if board.castling_rights: raise KeyError(f"syzygy tables do not contain positions with castling rights: {board.fen()}") - if chess.popcount(board.occupied) > TBPIECES: - raise KeyError(f"syzygy tables support up to {TBPIECES} pieces, not {chess.popcount(board.occupied)}: {board.fen()}") + + # Probing resolves captures, so sometimes we can obtain results for + # positions that have more pieces than the maximum number of supported + # pieces. We artificially limit this to one additional level, to + # make sure search remains somewhat bounded. + if board.piece_count() > TBPIECES + 1: + raise KeyError(f"syzygy tables support up to {TBPIECES} pieces, not {board.piece_count()}: {board.fen()}") # Special case: Variant with compulsory captures. if self.variant.captures_compulsory: @@ -1636,7 +1613,7 @@ def sprobe_ab(self, board: chess.Board, alpha: int, beta: int, threats: bool = F threats_found = False - if threats or chess.popcount(board.occupied) >= 6: + if threats or board.piece_count() >= 6: for threat in board.generate_legal_moves(~board.pawns): board.push(threat) try: @@ -1873,7 +1850,7 @@ def probe_dtz(self, board: chess.Board) -> int: +-----+------------------+--------------------------------------------+ The return value can be off by one: a return value -n can mean a - losing zeroing move in in n + 1 plies and a return value +n can mean a + losing zeroing move in n + 1 plies and a return value +n can mean a winning zeroing move in n + 1 plies. This implies some primary tablebase lines may waste up to 1 ply. Rounding is never used for endgame phases where it would change the @@ -1986,6 +1963,10 @@ def open_tablebase(directory: str, *, load_wdl: bool = True, load_dtz: bool = Tr are often distributed separately, but are both required for 6-piece positions. Use :func:`~chess.syzygy.Tablebase.add_directory()` to load tables from additional directories. + + :param max_fds: If *max_fds* is not ``None``, will at most use *max_fds* + open file descriptors at any given time. The least recently used tables + are closed, if necessary. """ tables = Tablebase(max_fds=max_fds, VariantBoard=VariantBoard) tables.add_directory(directory, load_wdl=load_wdl, load_dtz=load_dtz) diff --git a/chess/variant.py b/chess/variant.py index cd1670480..ba4c0f1ce 100644 --- a/chess/variant.py +++ b/chess/variant.py @@ -1,27 +1,14 @@ -# This file is part of the python-chess library. -# Copyright (C) 2016-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - from __future__ import annotations import chess -import copy import itertools +import typing from typing import Dict, Generic, Hashable, Iterable, Iterator, List, Optional, Type, TypeVar, Union +if typing.TYPE_CHECKING: + from typing_extensions import Self + class SuicideBoard(chess.Board): @@ -140,16 +127,8 @@ def is_legal(self, move: chess.Move) -> bool: else: return not any(self.generate_pseudo_legal_captures()) - def _transposition_key(self) -> Hashable: - if self.has_chess960_castling_rights(): - return (super()._transposition_key(), self.kings & self.promoted) - else: - return super()._transposition_key() - - def board_fen(self, promoted: Optional[bool] = None) -> str: - if promoted is None: - promoted = self.has_chess960_castling_rights() - return super().board_fen(promoted=promoted) + def _effective_promoted(self) -> chess.Bitboard: + return self.kings & self.promoted if self.castling_rights else chess.BB_EMPTY def status(self) -> chess.Status: status = super().status() @@ -274,9 +253,9 @@ def _push_capture(self, move: chess.Move, capture_square: chess.Square, piece_ty # Destroy castling rights. self.castling_rights &= ~explosion_radius - if explosion_radius & self.kings & self.occupied_co[chess.WHITE] & ~self.promoted: + if explosion_radius & self.kings & self.occupied_co[chess.WHITE] & ~self._effective_promoted(): self.castling_rights &= ~chess.BB_RANK_1 - if explosion_radius & self.kings & self.occupied_co[chess.BLACK] & ~self.promoted: + if explosion_radius & self.kings & self.occupied_co[chess.BLACK] & ~self._effective_promoted(): self.castling_rights &= ~chess.BB_RANK_8 # Explode the capturing piece. @@ -690,14 +669,12 @@ def status(self) -> chess.Status: ThreeCheckBoardT = TypeVar("ThreeCheckBoardT", bound="ThreeCheckBoard") -class _ThreeCheckBoardState(Generic[ThreeCheckBoardT], chess._BoardState[ThreeCheckBoardT]): - def __init__(self, board: ThreeCheckBoardT) -> None: - super().__init__(board) +class _ThreeCheckBoardState: + def __init__(self, board: ThreeCheckBoard) -> None: self.remaining_checks_w = board.remaining_checks[chess.WHITE] self.remaining_checks_b = board.remaining_checks[chess.BLACK] - def restore(self, board: ThreeCheckBoardT) -> None: - super().restore(board) + def restore(self, board: ThreeCheckBoard) -> None: board.remaining_checks[chess.WHITE] = self.remaining_checks_w board.remaining_checks[chess.BLACK] = self.remaining_checks_b @@ -715,8 +692,13 @@ class ThreeCheckBoard(chess.Board): def __init__(self, fen: Optional[str] = starting_fen, chess960: bool = False) -> None: self.remaining_checks = [3, 3] + self._three_check_stack: List[_ThreeCheckBoardState] = [] super().__init__(fen, chess960=chess960) + def clear_stack(self) -> None: + super().clear_stack() + self._three_check_stack.clear() + def reset_board(self) -> None: super().reset_board() self.remaining_checks[chess.WHITE] = 3 @@ -727,14 +709,17 @@ def clear_board(self) -> None: self.remaining_checks[chess.WHITE] = 3 self.remaining_checks[chess.BLACK] = 3 - def _board_state(self: ThreeCheckBoardT) -> _ThreeCheckBoardState[ThreeCheckBoardT]: - return _ThreeCheckBoardState(self) - def push(self, move: chess.Move) -> None: + self._three_check_stack.append(_ThreeCheckBoardState(self)) super().push(move) if self.is_check(): self.remaining_checks[not self.turn] -= 1 + def pop(self) -> chess.Move: + move = super().pop() + self._three_check_stack.pop().restore(self) + return move + def has_insufficient_material(self, color: chess.Color) -> bool: # Any remaining piece can give check. return not (self.occupied_co[color] & ~self.kings) @@ -779,7 +764,7 @@ def set_fen(self, fen: str) -> None: self.remaining_checks[chess.WHITE] = wc self.remaining_checks[chess.BLACK] = bc - def epd(self, shredder: bool = False, en_passant: chess._EnPassantSpec = "legal", promoted: Optional[bool] = None, **operations: Union[None, str, int, float, chess.Move, Iterable[chess.Move]]) -> str: + def epd(self, shredder: bool = False, en_passant: chess.EnPassantSpec = "legal", promoted: Optional[bool] = None, **operations: Union[None, str, int, float, chess.Move, Iterable[chess.Move]]) -> str: epd = [super().epd(shredder=shredder, en_passant=en_passant, promoted=promoted), "{:d}+{:d}".format(max(self.remaining_checks[chess.WHITE], 0), max(self.remaining_checks[chess.BLACK], 0))] @@ -806,12 +791,23 @@ def _transposition_key(self) -> Hashable: return (super()._transposition_key(), self.remaining_checks[chess.WHITE], self.remaining_checks[chess.BLACK]) - def copy(self: ThreeCheckBoardT, stack: Union[bool, int] = True) -> ThreeCheckBoardT: + def copy(self, *, stack: Union[bool, int] = True) -> Self: board = super().copy(stack=stack) board.remaining_checks = self.remaining_checks.copy() + if stack: + stack = len(self.move_stack) if stack is True else stack + board._three_check_stack = self._three_check_stack[-stack:] return board - def mirror(self: ThreeCheckBoardT) -> ThreeCheckBoardT: + def root(self) -> Self: + if self._three_check_stack: + board = super().root() + self._three_check_stack[0].restore(board) + return board + else: + return self.copy(stack=False) + + def mirror(self) -> Self: board = super().mirror() board.remaining_checks[chess.WHITE] = self.remaining_checks[chess.BLACK] board.remaining_checks[chess.BLACK] = self.remaining_checks[chess.WHITE] @@ -820,14 +816,12 @@ def mirror(self: ThreeCheckBoardT) -> ThreeCheckBoardT: CrazyhouseBoardT = TypeVar("CrazyhouseBoardT", bound="CrazyhouseBoard") -class _CrazyhouseBoardState(Generic[CrazyhouseBoardT], chess._BoardState[CrazyhouseBoardT]): - def __init__(self, board: CrazyhouseBoardT) -> None: - super().__init__(board) +class _CrazyhouseBoardState: + def __init__(self, board: CrazyhouseBoard) -> None: self.pockets_w = board.pockets[chess.WHITE].copy() self.pockets_b = board.pockets[chess.BLACK].copy() - def restore(self, board: CrazyhouseBoardT) -> None: - super().restore(board) + def restore(self, board: CrazyhouseBoard) -> None: board.pockets[chess.WHITE] = self.pockets_w board.pockets[chess.BLACK] = self.pockets_b @@ -867,7 +861,7 @@ def __len__(self) -> int: def __repr__(self) -> str: return f"CrazyhousePocket('{self}')" - def copy(self: CrazyhousePocketT) -> CrazyhousePocketT: + def copy(self) -> Self: """Returns a copy of this pocket.""" pocket = type(self)() pocket._pieces = self._pieces[:] @@ -887,8 +881,13 @@ class CrazyhouseBoard(chess.Board): def __init__(self, fen: Optional[str] = starting_fen, chess960: bool = False) -> None: self.pockets = [CrazyhousePocket(), CrazyhousePocket()] + self._crazyhouse_stack: List[_CrazyhouseBoardState] = [] super().__init__(fen, chess960=chess960) + def clear_stack(self) -> None: + super().clear_stack() + self._crazyhouse_stack.clear() + def reset_board(self) -> None: super().reset_board() self.pockets[chess.WHITE].reset() @@ -899,10 +898,8 @@ def clear_board(self) -> None: self.pockets[chess.WHITE].reset() self.pockets[chess.BLACK].reset() - def _board_state(self: CrazyhouseBoardT) -> _CrazyhouseBoardState[CrazyhouseBoardT]: - return _CrazyhouseBoardState(self) - def push(self, move: chess.Move) -> None: + self._crazyhouse_stack.append(_CrazyhouseBoardState(self)) super().push(move) if move.drop: self.pockets[not self.turn].remove(move.drop) @@ -913,6 +910,11 @@ def _push_capture(self, move: chess.Move, capture_square: chess.Square, piece_ty else: self.pockets[self.turn].add(piece_type) + def pop(self) -> chess.Move: + move = super().pop() + self._crazyhouse_stack.pop().restore(self) + return move + def _is_halfmoves(self, n: int) -> bool: # No draw by 50-move rule or 75-move rule. return False @@ -920,9 +922,11 @@ def _is_halfmoves(self, n: int) -> bool: def is_irreversible(self, move: chess.Move) -> bool: return self._reduces_castling_rights(move) + def _effective_promoted(self) -> chess.Bitboard: + return self.promoted & ~self.kings & ~self.pawns + def _transposition_key(self) -> Hashable: return (super()._transposition_key(), - self.promoted, str(self.pockets[chess.WHITE]), str(self.pockets[chess.BLACK])) def legal_drop_squares_mask(self) -> chess.Bitboard: @@ -999,7 +1003,7 @@ def has_insufficient_material(self, color: chess.Color) -> bool: # a different color complex. return ( chess.popcount(self.occupied) + sum(len(pocket) for pocket in self.pockets) <= 3 and - not self.promoted and + not self._effective_promoted() and not self.pawns and not self.rooks and not self.queens and @@ -1031,23 +1035,29 @@ def set_fen(self, fen: str) -> None: self.pockets[chess.WHITE] = white_pocket self.pockets[chess.BLACK] = black_pocket - def board_fen(self, promoted: Optional[bool] = None) -> str: - if promoted is None: - promoted = True - return super().board_fen(promoted=promoted) - - def epd(self, shredder: bool = False, en_passant: chess._EnPassantSpec = "legal", promoted: Optional[bool] = None, **operations: Union[None, str, int, float, chess.Move, Iterable[chess.Move]]) -> str: + def epd(self, shredder: bool = False, en_passant: chess.EnPassantSpec = "legal", promoted: Optional[bool] = None, **operations: Union[None, str, int, float, chess.Move, Iterable[chess.Move]]) -> str: epd = super().epd(shredder=shredder, en_passant=en_passant, promoted=promoted) board_part, info_part = epd.split(" ", 1) return f"{board_part}[{str(self.pockets[chess.WHITE]).upper()}{self.pockets[chess.BLACK]}] {info_part}" - def copy(self: CrazyhouseBoardT, stack: Union[bool, int] = True) -> CrazyhouseBoardT: + def copy(self, *, stack: Union[bool, int] = True) -> Self: board = super().copy(stack=stack) board.pockets[chess.WHITE] = self.pockets[chess.WHITE].copy() board.pockets[chess.BLACK] = self.pockets[chess.BLACK].copy() + if stack: + stack = len(self.move_stack) if stack is True else stack + board._crazyhouse_stack = self._crazyhouse_stack[-stack:] return board - def mirror(self: CrazyhouseBoardT) -> CrazyhouseBoardT: + def root(self) -> Self: + if self._crazyhouse_stack: + board = super().root() + self._crazyhouse_stack[0].restore(board) + return board + else: + return self.copy(stack=False) + + def mirror(self) -> Self: board = super().mirror() board.pockets[chess.WHITE] = self.pockets[chess.BLACK].copy() board.pockets[chess.BLACK] = self.pockets[chess.WHITE].copy() diff --git a/data/gaviota/SOURCE.txt b/data/gaviota/SOURCE.txt index dda9c83c7..706756322 100644 --- a/data/gaviota/SOURCE.txt +++ b/data/gaviota/SOURCE.txt @@ -1,145 +1,145 @@ -https://syzygy-tables.info/gaviota/kbk.gtb.cp4 -https://syzygy-tables.info/gaviota/knk.gtb.cp4 -https://syzygy-tables.info/gaviota/kpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqk.gtb.cp4 -https://syzygy-tables.info/gaviota/krk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbbk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kbkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kbkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbpk.gtb.cp4 -https://syzygy-tables.info/gaviota/knkn.gtb.cp4 -https://syzygy-tables.info/gaviota/knkp.gtb.cp4 -https://syzygy-tables.info/gaviota/knnk.gtb.cp4 -https://syzygy-tables.info/gaviota/knpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kpkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kppk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kqnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrk.gtb.cp4 -https://syzygy-tables.info/gaviota/krbk.gtb.cp4 -https://syzygy-tables.info/gaviota/krkb.gtb.cp4 -https://syzygy-tables.info/gaviota/krkn.gtb.cp4 -https://syzygy-tables.info/gaviota/krkp.gtb.cp4 -https://syzygy-tables.info/gaviota/krkr.gtb.cp4 -https://syzygy-tables.info/gaviota/krnk.gtb.cp4 -https://syzygy-tables.info/gaviota/krpk.gtb.cp4 -https://syzygy-tables.info/gaviota/krrk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbbbk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbbkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kbbkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kbbkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kbbkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kbbkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kbbnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbbpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbpkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kbpkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kbpkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kbpkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kbpkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kbppk.gtb.cp4 -https://syzygy-tables.info/gaviota/knnkb.gtb.cp4 -https://syzygy-tables.info/gaviota/knnkn.gtb.cp4 -https://syzygy-tables.info/gaviota/knnkp.gtb.cp4 -https://syzygy-tables.info/gaviota/knnkq.gtb.cp4 -https://syzygy-tables.info/gaviota/knnkr.gtb.cp4 -https://syzygy-tables.info/gaviota/knnnk.gtb.cp4 -https://syzygy-tables.info/gaviota/knnpk.gtb.cp4 -https://syzygy-tables.info/gaviota/knpkb.gtb.cp4 -https://syzygy-tables.info/gaviota/knpkn.gtb.cp4 -https://syzygy-tables.info/gaviota/knpkp.gtb.cp4 -https://syzygy-tables.info/gaviota/knpkq.gtb.cp4 -https://syzygy-tables.info/gaviota/knpkr.gtb.cp4 -https://syzygy-tables.info/gaviota/knppk.gtb.cp4 -https://syzygy-tables.info/gaviota/kppkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kppkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kppkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kppkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kppkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kpppk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbbk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqnkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kqnkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kqnkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kqnkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kqnkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kqnnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqnpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqpkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kqpkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kqpkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kqpkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kqpkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kqppk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqbk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqqk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqrk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrbk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrrk.gtb.cp4 -https://syzygy-tables.info/gaviota/krbbk.gtb.cp4 -https://syzygy-tables.info/gaviota/krbkb.gtb.cp4 -https://syzygy-tables.info/gaviota/krbkn.gtb.cp4 -https://syzygy-tables.info/gaviota/krbkp.gtb.cp4 -https://syzygy-tables.info/gaviota/krbkq.gtb.cp4 -https://syzygy-tables.info/gaviota/krbkr.gtb.cp4 -https://syzygy-tables.info/gaviota/krbnk.gtb.cp4 -https://syzygy-tables.info/gaviota/krbpk.gtb.cp4 -https://syzygy-tables.info/gaviota/krnkb.gtb.cp4 -https://syzygy-tables.info/gaviota/krnkn.gtb.cp4 -https://syzygy-tables.info/gaviota/krnkp.gtb.cp4 -https://syzygy-tables.info/gaviota/krnkq.gtb.cp4 -https://syzygy-tables.info/gaviota/krnkr.gtb.cp4 -https://syzygy-tables.info/gaviota/krnnk.gtb.cp4 -https://syzygy-tables.info/gaviota/krnpk.gtb.cp4 -https://syzygy-tables.info/gaviota/krpkb.gtb.cp4 -https://syzygy-tables.info/gaviota/krpkn.gtb.cp4 -https://syzygy-tables.info/gaviota/krpkp.gtb.cp4 -https://syzygy-tables.info/gaviota/krpkq.gtb.cp4 -https://syzygy-tables.info/gaviota/krpkr.gtb.cp4 -https://syzygy-tables.info/gaviota/krppk.gtb.cp4 -https://syzygy-tables.info/gaviota/krrbk.gtb.cp4 -https://syzygy-tables.info/gaviota/krrkb.gtb.cp4 -https://syzygy-tables.info/gaviota/krrkn.gtb.cp4 -https://syzygy-tables.info/gaviota/krrkp.gtb.cp4 -https://syzygy-tables.info/gaviota/krrkq.gtb.cp4 -https://syzygy-tables.info/gaviota/krrkr.gtb.cp4 -https://syzygy-tables.info/gaviota/krrnk.gtb.cp4 -https://syzygy-tables.info/gaviota/krrpk.gtb.cp4 -https://syzygy-tables.info/gaviota/krrrk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kpkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kppk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbpkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbpkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbpkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbpkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbpkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbppk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knpkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knpkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knpkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knpkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knpkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knppk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kppkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kppkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kppkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kppkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kppkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kpppk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqnkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqnkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqnkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqnkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqnkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqnnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqnpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqpkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqpkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqpkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqpkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqpkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqppk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqqk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqrk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrrk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krpkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krpkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krpkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krpkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krpkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krppk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrrk.gtb.cp4 diff --git a/data/gaviota/TEST-SOURCE.txt b/data/gaviota/TEST-SOURCE.txt index f7d29a546..36c83d39a 100644 --- a/data/gaviota/TEST-SOURCE.txt +++ b/data/gaviota/TEST-SOURCE.txt @@ -1,47 +1,47 @@ -https://syzygy-tables.info/gaviota/kbbk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kbkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kbkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbpk.gtb.cp4 -https://syzygy-tables.info/gaviota/knkn.gtb.cp4 -https://syzygy-tables.info/gaviota/knkp.gtb.cp4 -https://syzygy-tables.info/gaviota/knnk.gtb.cp4 -https://syzygy-tables.info/gaviota/knpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kpkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kppk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkb.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkq.gtb.cp4 -https://syzygy-tables.info/gaviota/kqkr.gtb.cp4 -https://syzygy-tables.info/gaviota/kqnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqpk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqqk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrk.gtb.cp4 -https://syzygy-tables.info/gaviota/krbk.gtb.cp4 -https://syzygy-tables.info/gaviota/krkb.gtb.cp4 -https://syzygy-tables.info/gaviota/krkn.gtb.cp4 -https://syzygy-tables.info/gaviota/krkp.gtb.cp4 -https://syzygy-tables.info/gaviota/krkr.gtb.cp4 -https://syzygy-tables.info/gaviota/krnk.gtb.cp4 -https://syzygy-tables.info/gaviota/krpk.gtb.cp4 -https://syzygy-tables.info/gaviota/krrk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqrnk.gtb.cp4 -https://syzygy-tables.info/gaviota/krpkn.gtb.cp4 -https://syzygy-tables.info/gaviota/kbpkp.gtb.cp4 -https://syzygy-tables.info/gaviota/krrpk.gtb.cp4 -https://syzygy-tables.info/gaviota/knppk.gtb.cp4 -https://syzygy-tables.info/gaviota/kbnkb.gtb.cp4 -https://syzygy-tables.info/gaviota/knnkn.gtb.cp4 -https://syzygy-tables.info/gaviota/krrnk.gtb.cp4 -https://syzygy-tables.info/gaviota/kqbbk.gtb.cp4 -https://syzygy-tables.info/gaviota/kppkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kppkq.gtb.cp4 -https://syzygy-tables.info/gaviota/krrrk.gtb.cp4 -https://syzygy-tables.info/gaviota/krnkp.gtb.cp4 -https://syzygy-tables.info/gaviota/kpppk.gtb.cp4 -https://syzygy-tables.info/gaviota/knnkp.gtb.cp4 -https://syzygy-tables.info/gaviota/krnpk.gtb.cp4 -https://syzygy-tables.info/gaviota/knpkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kpkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kppk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqqk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krkr.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqrnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krpkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbpkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knppk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kbnkb.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnkn.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrnk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kqbbk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kppkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kppkq.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krrrk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/kpppk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knnkp.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/krnpk.gtb.cp4 +https://tablebase.lichess.ovh/tables/standard/Gaviota/knpkp.gtb.cp4 diff --git a/data/pgn/nepomniachtchi-liren-game1.pgn b/data/pgn/nepomniachtchi-liren-game1.pgn new file mode 100644 index 000000000..ea3234154 --- /dev/null +++ b/data/pgn/nepomniachtchi-liren-game1.pgn @@ -0,0 +1,22 @@ +[Event "FIDE World Championship 2023"] +[Site "Astana KAZ"] +[Date "2023.04.09"] +[Round "1"] +[White "Nepomniachtchi, Ian"] +[Black "Liren, Ding"] +[Result "1/2-1/2"] +[TimeControl "40/7200:20/3600:900+30"] +[WhiteFideId "4168119"] +[BlackFideId "8603677"] +[WhiteElo "2795"] +[BlackElo "2788"] + +1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Bxc6 dxc6 +7. Re1 Nd7 8. d4 exd4 9. Qxd4 O-O 10. Bf4 Nc5 11. Qe3 Bg4 12. Nd4 +Qd7 13. Nc3 Rad8 14. Nf5 Ne6 15. Nxe7+ Qxe7 16. Bg3 Bh5 17. f3 f6 +18. h3 h6 19. Kh2 Bf7 20. Rad1 b6 21. a3 a5 22. Ne2 Rxd1 23. Rxd1 +Rd8 24. Rd3 c5 25. Qd2 c6 26. Rxd8+ Nxd8 27. Qf4 b5 28. Qb8 Kh7 +29. Bd6 Qd7 30. Ng3 Ne6 31. f4 h5 32. c3 c4 33. h4 Qd8 34. Qb7 +Be8 35. Nf5 Qd7 36. Qb8 Qd8 37. Qxd8 Nxd8 38. Nd4 Nb7 39. e5 Kg8 +40. Kg3 Bd7 41. Bc7 Nc5 42. Bxa5 Kf7 43. Bb4 Nd3 44. e6+ Bxe6 +45. Nxc6 Bd7 46. Nd4 Nxb2 47. Kf3 Nd3 48. g3 Nc1 49. Ke3 1/2-1/2 diff --git a/data/pgn/utf8-bom.pgn b/data/pgn/utf8-bom.pgn new file mode 100644 index 000000000..665e7775a --- /dev/null +++ b/data/pgn/utf8-bom.pgn @@ -0,0 +1,26 @@ +[Event "A"] +[Site "?"] +[Date "2024.04.25"] +[Round "?"] +[White "White vs 1...c5"] +[Black "?"] +[Result "*"] +[ECO "A00"] +[PlyCount "0"] +[SourceVersionDate "2024.04.25"] + + * + +[Event "B"] +[Site "?"] +[Date "2024.04.25"] +[Round "?"] +[White "White vs 1...c5"] +[Black "?"] +[Result "*"] +[ECO "A00"] +[PlyCount "0"] +[SourceVersionDate "2024.04.25"] + + * + diff --git a/docs/conf.py b/docs/conf.py index bde54010b..98e926efa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,7 +15,12 @@ } # Autodoc. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.intersphinx"] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinxcontrib.jquery" +] autodoc_member_order = "bysource" intersphinx_mapping = { "python": ("https://docs.python.org/3", None), @@ -29,7 +34,7 @@ # General information about the project. project = "python-chess" -copyright = "2014–2022, Niklas Fiekas" +copyright = "2014–2024, Niklas Fiekas" # The version. version = chess.__version__ @@ -44,4 +49,4 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of built-in themes. -html_theme = "default" +html_theme = "sphinx_rtd_theme" diff --git a/docs/core.rst b/docs/core.rst index d5790820a..8bb045bbb 100644 --- a/docs/core.rst +++ b/docs/core.rst @@ -85,6 +85,10 @@ and so on to .. autofunction:: chess.square_distance +.. autofunction:: chess.square_manhattan_distance + +.. autofunction:: chess.square_knight_distance + .. autofunction:: chess.square_mirror Pieces diff --git a/docs/engine.rst b/docs/engine.rst index 979d0e915..a326430e0 100644 --- a/docs/engine.rst +++ b/docs/engine.rst @@ -19,13 +19,15 @@ The preferred way to use the API is with an The examples also show a synchronous wrapper :class:`~chess.engine.SimpleEngine` that automatically spawns an event loop in the background. +:class:`~chess.engine.SimpleEngine` methods block until there is a result. Playing ------- Example: Let Stockfish play against itself, 100 milliseconds per move. -.. code:: python +.. code-block:: python + :caption: Using synchronous :class:`~chess.engine.SimpleEngine` import chess import chess.engine @@ -39,7 +41,8 @@ Example: Let Stockfish play against itself, 100 milliseconds per move. engine.quit() -.. code:: python +.. code-block:: python + :caption: Using asyncio import asyncio import chess @@ -55,7 +58,6 @@ Example: Let Stockfish play against itself, 100 milliseconds per move. await engine.quit() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) .. autoclass:: chess.engine.Protocol @@ -67,12 +69,22 @@ Example: Let Stockfish play against itself, 100 milliseconds per move. .. autoclass:: chess.engine.PlayResult :members: +.. autoclass:: chess.engine.Protocol + :members: send_opponent_information + +.. autoclass:: chess.engine.Opponent + :members: + +.. autoclass:: chess.engine.Protocol + :members: send_game_result + Analysing and evaluating a position ----------------------------------- Example: -.. code:: python +.. code-block:: python + :caption: Using synchronous :class:`~chess.engine.SimpleEngine` import chess import chess.engine @@ -91,7 +103,8 @@ Example: engine.quit() -.. code:: python +.. code-block:: python + :caption: Using asyncio import asyncio import chess @@ -112,7 +125,6 @@ Example: await engine.quit() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) .. autoclass:: chess.engine.Protocol @@ -137,7 +149,8 @@ Indefinite or infinite analysis Example: Stream information from the engine and stop on an arbitrary condition. -.. code:: python +.. code-block:: python + :caption: Using synchronous :class:`~chess.engine.SimpleEngine` import chess import chess.engine @@ -154,7 +167,8 @@ Example: Stream information from the engine and stop on an arbitrary condition. engine.quit() -.. code:: python +.. code-block:: python + :caption: Using asyncio import asyncio import chess @@ -173,7 +187,6 @@ Example: Stream information from the engine and stop on an arbitrary condition. await engine.quit() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) .. autoclass:: chess.engine.Protocol @@ -193,20 +206,24 @@ Options :func:`~chess.Protocol.analyse()` and :func:`~chess.Protocol.analysis()` accept a dictionary of options. ->>> import chess.engine ->>> ->>> engine = chess.engine.SimpleEngine.popen_uci("/usr/bin/stockfish") ->>> ->>> # Check available options. ->>> engine.options["Hash"] -Option(name='Hash', type='spin', default=16, min=1, max=131072, var=[]) ->>> ->>> # Set an option. ->>> engine.configure({"Hash": 32}) ->>> ->>> # [...] +.. code-block:: python + :caption: Using synchronous :class:`~chess.engine.SimpleEngine` -.. code:: python + import chess.engine + + engine = chess.engine.SimpleEngine.popen_uci("/usr/bin/stockfish") + + # Check available options. + engine.options["Hash"] + # Option(name='Hash', type='spin', default=16, min=1, max=131072, var=[]) + + # Set an option. + engine.configure({"Hash": 32}) + + # [...] + +.. code-block:: python + :caption: Using asyncio import asyncio import chess.engine @@ -223,7 +240,6 @@ Option(name='Hash', type='spin', default=16, min=1, max=131072, var=[]) # [...] - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) .. autoclass:: chess.engine.Protocol @@ -295,5 +311,3 @@ Reference .. autoclass:: chess.engine.SimpleAnalysisResult :members: - -.. autofunction:: chess.engine.EventLoopPolicy diff --git a/docs/images/cli-chess.png b/docs/images/cli-chess.png new file mode 100644 index 000000000..eaa7ff306 Binary files /dev/null and b/docs/images/cli-chess.png differ diff --git a/docs/pgn.rst b/docs/pgn.rst index 9ebe96594..dd5a6fa47 100644 --- a/docs/pgn.rst +++ b/docs/pgn.rst @@ -48,6 +48,10 @@ position of the game. The tree consists of one root node nodes (:class:`~chess.pgn.ChildNode`). Both extend :class:`~chess.pgn.GameNode`. +.. note:: Some basic methods have complexity `O(n)` for a game with n moves. + When following a variation, it is often more efficient to use visitors + or incrementally update state (like board, ply counter, or turn). + .. autoclass:: chess.pgn.GameNode :members: diff --git a/docs/requirements.txt b/docs/requirements.txt index dd8706637..c04e3554a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,3 @@ -Sphinx==5.3.0 +Sphinx==8.1.2 +sphinxcontrib-jquery==4.1 +sphinx-rtd-theme==3.0.1 diff --git a/examples/bratko_kopec/bratko_kopec.py b/examples/bratko_kopec/bratko_kopec.py index 62e3655a6..2553f4399 100755 --- a/examples/bratko_kopec/bratko_kopec.py +++ b/examples/bratko_kopec/bratko_kopec.py @@ -152,5 +152,4 @@ async def main() -> None: if __name__ == "__main__": - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) diff --git a/examples/perft/chess960.perft b/examples/perft/chess960.perft new file mode 100644 index 000000000..12276f256 --- /dev/null +++ b/examples/perft/chess960.perft @@ -0,0 +1,8642 @@ +# https://www.chessprogramming.org/Chess960_Perft_Results +# https://github.com/AndyGrant/Ethereal/blob/master/src/perft/fischer.epd + +id 0 +epd bqnb1rkr/pp3ppp/3ppn2/2p5/5P2/P2P4/NPP1P1PP/BQ1BNRKR w HFhf - +perft 1 21 +perft 2 528 +perft 3 12189 +perft 4 326672 +perft 5 8146062 +perft 6 227689589 + +id 1 +epd 2nnrbkr/p1qppppp/8/1ppb4/6PP/3PP3/PPP2P2/BQNNRBKR w HEhe - +perft 1 21 +perft 2 807 +perft 3 18002 +perft 4 667366 +perft 5 16253601 +perft 6 590751109 + +id 2 +epd b1q1rrkb/pppppppp/3nn3/8/P7/1PPP4/4PPPP/BQNNRKRB w GE - +perft 1 20 +perft 2 479 +perft 3 10471 +perft 4 273318 +perft 5 6417013 +perft 6 177654692 + +id 3 +epd qbbnnrkr/2pp2pp/p7/1p2pp2/8/P3PP2/1PPP1KPP/QBBNNR1R w hf - +perft 1 22 +perft 2 593 +perft 3 13440 +perft 4 382958 +perft 5 9183776 +perft 6 274103539 + +id 4 +epd 1nbbnrkr/p1p1ppp1/3p4/1p3P1p/3Pq2P/8/PPP1P1P1/QNBBNRKR w HFhf - +perft 1 28 +perft 2 1120 +perft 3 31058 +perft 4 1171749 +perft 5 34030312 +perft 6 1250970898 + +id 5 +epd qnbnr1kr/ppp1b1pp/4p3/3p1p2/8/2NPP3/PPP1BPPP/QNB1R1KR w HEhe - +perft 1 29 +perft 2 899 +perft 3 26578 +perft 4 824055 +perft 5 24851983 +perft 6 775718317 + +id 6 +epd q1bnrkr1/ppppp2p/2n2p2/4b1p1/2NP4/8/PPP1PPPP/QNB1RRKB w ge - +perft 1 30 +perft 2 860 +perft 3 24566 +perft 4 732757 +perft 5 21093346 +perft 6 649209803 + +id 7 +epd qbn1brkr/ppp1p1p1/2n4p/3p1p2/P7/6PP/QPPPPP2/1BNNBRKR w HFhf - +perft 1 25 +perft 2 635 +perft 3 17054 +perft 4 465806 +perft 5 13203304 +perft 6 377184252 + +id 8 +epd qnnbbrkr/1p2ppp1/2pp3p/p7/1P5P/2NP4/P1P1PPP1/Q1NBBRKR w HFhf - +perft 1 24 +perft 2 572 +perft 3 15243 +perft 4 384260 +perft 5 11110203 +perft 6 293989890 + +id 9 +epd qn1rbbkr/ppp2p1p/1n1pp1p1/8/3P4/P6P/1PP1PPPK/QNNRBB1R w hd - +perft 1 28 +perft 2 811 +perft 3 23175 +perft 4 679699 +perft 5 19836606 +perft 6 594527992 + +id 10 +epd qnr1bkrb/pppp2pp/3np3/5p2/8/P2P2P1/NPP1PP1P/QN1RBKRB w GDg - +perft 1 33 +perft 2 823 +perft 3 26895 +perft 4 713420 +perft 5 23114629 +perft 6 646390782 + +id 11 +epd qb1nrkbr/1pppp1p1/1n3p2/p1B4p/8/3P1P1P/PPP1P1P1/QBNNRK1R w HEhe - +perft 1 31 +perft 2 855 +perft 3 25620 +perft 4 735703 +perft 5 21796206 +perft 6 651054626 + +id 12 +epd qnnbrk1r/1p1ppbpp/2p5/p4p2/2NP3P/8/PPP1PPP1/Q1NBRKBR w HEhe - +perft 1 26 +perft 2 790 +perft 3 21238 +perft 4 642367 +perft 5 17819770 +perft 6 544866674 + +id 13 +epd 1qnrkbbr/1pppppp1/p1n4p/8/P7/1P1N1P2/2PPP1PP/QN1RKBBR w HDhd - +perft 1 37 +perft 2 883 +perft 3 32187 +perft 4 815535 +perft 5 29370838 +perft 6 783201510 + +id 14 +epd qn1rkrbb/pp1p1ppp/2p1p3/3n4/4P2P/2NP4/PPP2PP1/Q1NRKRBB w FDfd - +perft 1 24 +perft 2 585 +perft 3 14769 +perft 4 356950 +perft 5 9482310 +perft 6 233468620 + +id 15 +epd bb1qnrkr/pp1p1pp1/1np1p3/4N2p/8/1P4P1/P1PPPP1P/BBNQ1RKR w HFhf - +perft 1 29 +perft 2 864 +perft 3 25747 +perft 4 799727 +perft 5 24219627 +perft 6 776836316 + +id 16 +epd bnqbnr1r/p1p1ppkp/3p4/1p4p1/P7/3NP2P/1PPP1PP1/BNQB1RKR w HF - +perft 1 26 +perft 2 889 +perft 3 24353 +perft 4 832956 +perft 5 23701014 +perft 6 809194268 + +id 17 +epd bnqnrbkr/1pp2pp1/p7/3pP2p/4P1P1/8/PPPP3P/BNQNRBKR w HEhe d6 +perft 1 31 +perft 2 984 +perft 3 28677 +perft 4 962591 +perft 5 29032175 +perft 6 1008880643 + +id 18 +epd b1qnrrkb/ppp1pp1p/n2p1Pp1/8/8/P7/1PPPP1PP/BNQNRKRB w GE - +perft 1 20 +perft 2 484 +perft 3 10532 +perft 4 281606 +perft 5 6718715 +perft 6 193594729 + +id 19 +epd n1bqnrkr/pp1ppp1p/2p5/6p1/2P2b2/PN6/1PNPPPPP/1BBQ1RKR w HFhf - +perft 1 23 +perft 2 732 +perft 3 17746 +perft 4 558191 +perft 5 14481581 +perft 6 457140569 + +id 20 +epd n1bb1rkr/qpnppppp/2p5/p7/P1P5/5P2/1P1PPRPP/NQBBN1KR w Hhf - +perft 1 27 +perft 2 697 +perft 3 18724 +perft 4 505089 +perft 5 14226907 +perft 6 400942568 + +id 21 +epd nqb1rbkr/pppppp1p/4n3/6p1/4P3/1NP4P/PP1P1PP1/1QBNRBKR w HEhe - +perft 1 28 +perft 2 641 +perft 3 18811 +perft 4 456916 +perft 5 13780398 +perft 6 354122358 + +id 22 +epd n1bnrrkb/pp1pp2p/2p2p2/6p1/5B2/3P4/PPP1PPPP/NQ1NRKRB w GE - +perft 1 28 +perft 2 606 +perft 3 16883 +perft 4 381646 +perft 5 10815324 +perft 6 254026570 + +id 23 +epd nbqnbrkr/2ppp1p1/pp3p1p/8/4N2P/1N6/PPPPPPP1/1BQ1BRKR w HFhf - +perft 1 26 +perft 2 626 +perft 3 17268 +perft 4 437525 +perft 5 12719546 +perft 6 339132046 + +id 24 +epd nq1bbrkr/pp2nppp/2pp4/4p3/1PP1P3/1B6/P2P1PPP/NQN1BRKR w HFhf - +perft 1 21 +perft 2 504 +perft 3 11812 +perft 4 302230 +perft 5 7697880 +perft 6 207028745 + +id 25 +epd nqnrb1kr/2pp1ppp/1p1bp3/p1B5/5P2/3N4/PPPPP1PP/NQ1R1BKR w HDhd - +perft 1 30 +perft 2 672 +perft 3 19307 +perft 4 465317 +perft 5 13454573 +perft 6 345445468 + +id 26 +epd nqn2krb/p1prpppp/1pbp4/7P/5P2/8/PPPPPKP1/NQNRB1RB w g - +perft 1 21 +perft 2 461 +perft 3 10608 +perft 4 248069 +perft 5 6194124 +perft 6 152861936 + +id 27 +epd nb1n1kbr/ppp1rppp/3pq3/P3p3/8/4P3/1PPPRPPP/NBQN1KBR w Hh - +perft 1 19 +perft 2 566 +perft 3 11786 +perft 4 358337 +perft 5 8047916 +perft 6 249171636 + +id 28 +epd nqnbrkbr/1ppppp1p/p7/6p1/6P1/P6P/1PPPPP2/NQNBRKBR w HEhe - +perft 1 20 +perft 2 382 +perft 3 8694 +perft 4 187263 +perft 5 4708975 +perft 6 112278808 + +id 29 +epd nq1rkb1r/pp1pp1pp/1n2bp1B/2p5/8/5P1P/PPPPP1P1/NQNRKB1R w HDhd - +perft 1 24 +perft 2 809 +perft 3 20090 +perft 4 673811 +perft 5 17647882 +perft 6 593457788 + +id 30 +epd nqnrkrb1/pppppp2/7p/4b1p1/8/PN1NP3/1PPP1PPP/1Q1RKRBB w FDfd - +perft 1 26 +perft 2 683 +perft 3 18102 +perft 4 473911 +perft 5 13055173 +perft 6 352398011 + +id 31 +epd bb1nqrkr/1pp1ppp1/pn5p/3p4/8/P2NNP2/1PPPP1PP/BB2QRKR w HFhf - +perft 1 29 +perft 2 695 +perft 3 21193 +perft 4 552634 +perft 5 17454857 +perft 6 483785639 + +id 32 +epd bnn1qrkr/pp1ppp1p/2p5/b3Q1p1/8/5P1P/PPPPP1P1/BNNB1RKR w HFhf - +perft 1 44 +perft 2 920 +perft 3 35830 +perft 4 795317 +perft 5 29742670 +perft 6 702867204 + +id 33 +epd bnnqrbkr/pp1p2p1/2p1p2p/5p2/1P5P/1R6/P1PPPPP1/BNNQRBK1 w Ehe - +perft 1 33 +perft 2 1022 +perft 3 32724 +perft 4 1024721 +perft 5 32898113 +perft 6 1047360456 + +id 34 +epd b1nqrkrb/2pppppp/p7/1P6/1n6/P4P2/1P1PP1PP/BNNQRKRB w GEge - +perft 1 23 +perft 2 638 +perft 3 15744 +perft 4 446539 +perft 5 11735969 +perft 6 344211589 + +id 35 +epd n1bnqrkr/3ppppp/1p6/pNp1b3/2P3P1/8/PP1PPP1P/NBB1QRKR w HFhf - +perft 1 29 +perft 2 728 +perft 3 20768 +perft 4 532084 +perft 5 15621236 +perft 6 415766465 + +id 36 +epd n2bqrkr/p1p1pppp/1pn5/3p1b2/P6P/1NP5/1P1PPPP1/1NBBQRKR w HFhf - +perft 1 20 +perft 2 533 +perft 3 12152 +perft 4 325059 +perft 5 8088751 +perft 6 223068417 + +id 37 +epd nnbqrbkr/1pp1p1p1/p2p4/5p1p/2P1P3/N7/PPQP1PPP/N1B1RBKR w HEhe - +perft 1 27 +perft 2 619 +perft 3 18098 +perft 4 444421 +perft 5 13755384 +perft 6 357222394 + +id 38 +epd nnbqrkr1/pp1pp2p/2p2b2/5pp1/1P5P/4P1P1/P1PP1P2/NNBQRKRB w GEge - +perft 1 32 +perft 2 1046 +perft 3 33721 +perft 4 1111186 +perft 5 36218182 +perft 6 1202830851 + +id 39 +epd nb1qbrkr/p1pppp2/1p1n2pp/8/1P6/2PN3P/P2PPPP1/NB1QBRKR w HFhf - +perft 1 25 +perft 2 521 +perft 3 14021 +perft 4 306427 +perft 5 8697700 +perft 6 201455191 + +id 40 +epd nnq1brkr/pp1pppp1/8/2p4P/8/5K2/PPPbPP1P/NNQBBR1R w hf - +perft 1 23 +perft 2 724 +perft 3 18263 +perft 4 571072 +perft 5 15338230 +perft 6 484638597 + +id 41 +epd nnqrbb1r/pppppk2/5pp1/7p/1P6/3P2PP/P1P1PP2/NNQRBBKR w HD - +perft 1 30 +perft 2 717 +perft 3 21945 +perft 4 547145 +perft 5 17166700 +perft 6 450069742 + +id 42 +epd nnqr1krb/p1p1pppp/2bp4/8/1p1P4/4P3/PPP2PPP/NNQRBKRB w GDgd - +perft 1 25 +perft 2 873 +perft 3 20796 +perft 4 728628 +perft 5 18162741 +perft 6 641708630 + +id 43 +epd nbnqrkbr/p2ppp2/1p4p1/2p4p/3P3P/3N4/PPP1PPPR/NB1QRKB1 w Ehe - +perft 1 24 +perft 2 589 +perft 3 15190 +perft 4 382317 +perft 5 10630667 +perft 6 279474189 + +id 44 +epd n1qbrkbr/p1ppp2p/2n2pp1/1p6/1P6/2P3P1/P2PPP1P/NNQBRKBR w HEhe - +perft 1 22 +perft 2 592 +perft 3 14269 +perft 4 401976 +perft 5 10356818 +perft 6 301583306 + +id 45 +epd 2qrkbbr/ppn1pppp/n1p5/3p4/5P2/P1PP4/1P2P1PP/NNQRKBBR w HDhd - +perft 1 27 +perft 2 750 +perft 3 20584 +perft 4 605458 +perft 5 16819085 +perft 6 516796736 + +id 46 +epd 1nqr1rbb/pppkp1pp/1n3p2/3p4/1P6/5P1P/P1PPPKP1/NNQR1RBB w - - +perft 1 24 +perft 2 623 +perft 3 15921 +perft 4 429446 +perft 5 11594634 +perft 6 322745925 + +id 47 +epd bbn1rqkr/pp1pp2p/4npp1/2p5/1P6/2BPP3/P1P2PPP/1BNNRQKR w HEhe - +perft 1 23 +perft 2 730 +perft 3 17743 +perft 4 565340 +perft 5 14496370 +perft 6 468608864 + +id 48 +epd bn1brqkr/pppp2p1/3npp2/7p/PPP5/8/3PPPPP/BNNBRQKR w HEhe - +perft 1 25 +perft 2 673 +perft 3 17835 +perft 4 513696 +perft 5 14284338 +perft 6 434008567 + +id 49 +epd bn1rqbkr/ppp1ppp1/1n6/2p4p/7P/3P4/PPP1PPP1/BN1RQBKR w HDhd - +perft 1 25 +perft 2 776 +perft 3 20562 +perft 4 660217 +perft 5 18486027 +perft 6 616653869 + +id 50 +epd bnnr1krb/ppp2ppp/3p4/3Bp3/q1P3PP/8/PP1PPP2/BNNRQKR1 w GDgd - +perft 1 29 +perft 2 1040 +perft 3 30772 +perft 4 1053113 +perft 5 31801525 +perft 6 1075147725 + +id 51 +epd 1bbnrqkr/pp1ppppp/8/2p5/n7/3PNPP1/PPP1P2P/NBB1RQKR w HEhe - +perft 1 24 +perft 2 598 +perft 3 15673 +perft 4 409766 +perft 5 11394778 +perft 6 310589129 + +id 52 +epd nnbbrqkr/p2ppp1p/1pp5/8/6p1/N1P5/PPBPPPPP/N1B1RQKR w HEhe - +perft 1 26 +perft 2 530 +perft 3 14031 +perft 4 326312 +perft 5 8846766 +perft 6 229270702 + +id 53 +epd nnbrqbkr/2p1p1pp/p4p2/1p1p4/8/NP6/P1PPPPPP/N1BRQBKR w HDhd - +perft 1 17 +perft 2 496 +perft 3 10220 +perft 4 303310 +perft 5 7103549 +perft 6 217108001 + +id 54 +epd nnbrqk1b/pp2pprp/2pp2p1/8/3PP1P1/8/PPP2P1P/NNBRQRKB w d - +perft 1 33 +perft 2 820 +perft 3 27856 +perft 4 706784 +perft 5 24714401 +perft 6 645835197 + +id 55 +epd 1bnrbqkr/ppnpp1p1/2p2p1p/8/1P6/4PPP1/P1PP3P/NBNRBQKR w HDhd - +perft 1 27 +perft 2 705 +perft 3 19760 +perft 4 548680 +perft 5 15964771 +perft 6 464662032 + +id 56 +epd n1rbbqkr/pp1pppp1/7p/P1p5/1n6/2PP4/1P2PPPP/NNRBBQKR w HChc - +perft 1 22 +perft 2 631 +perft 3 14978 +perft 4 431801 +perft 5 10911545 +perft 6 320838556 + +id 57 +epd n1rqb1kr/p1pppp1p/1pn4b/3P2p1/P7/1P6/2P1PPPP/NNRQBBKR w HChc - +perft 1 24 +perft 2 477 +perft 3 12506 +perft 4 263189 +perft 5 7419372 +perft 6 165945904 + +id 58 +epd nnrqbkrb/pppp1pp1/7p/4p3/6P1/2N2B2/PPPPPP1P/NR1QBKR1 w Ggc - +perft 1 29 +perft 2 658 +perft 3 19364 +perft 4 476620 +perft 5 14233587 +perft 6 373744834 + +id 59 +epd n1nrqkbr/ppb2ppp/3pp3/2p5/2P3P1/5P2/PP1PPB1P/NBNRQK1R w HDhd - +perft 1 32 +perft 2 801 +perft 3 25861 +perft 4 681428 +perft 5 22318948 +perft 6 619857455 + +id 60 +epd 2rbqkbr/p1pppppp/1nn5/1p6/7P/P4P2/1PPPP1PB/NNRBQK1R w HChc - +perft 1 27 +perft 2 647 +perft 3 18030 +perft 4 458057 +perft 5 13189156 +perft 6 354689323 + +id 61 +epd nn1qkbbr/pp2ppp1/2rp4/2p4p/P2P4/1N5P/1PP1PPP1/1NRQKBBR w HCh - +perft 1 24 +perft 2 738 +perft 3 18916 +perft 4 586009 +perft 5 16420659 +perft 6 519075930 + +id 62 +epd nnrqk1bb/p1ppp2p/5rp1/1p3p2/1P4P1/5P1P/P1PPP3/NNRQKRBB w FCc - +perft 1 25 +perft 2 795 +perft 3 20510 +perft 4 648945 +perft 5 17342527 +perft 6 556144017 + +id 63 +epd bb1nrkqr/ppppn2p/4ppp1/8/1P4P1/4P3/P1PPKP1P/BBNNR1QR w he - +perft 1 29 +perft 2 664 +perft 3 20024 +perft 4 498376 +perft 5 15373803 +perft 6 406016364 + +id 64 +epd bnnbrkqr/1p1ppp2/8/p1p3pp/1P6/N4P2/PBPPP1PP/2NBRKQR w HEhe - +perft 1 31 +perft 2 770 +perft 3 24850 +perft 4 677212 +perft 5 22562080 +perft 6 662029574 + +id 65 +epd 1nnrkbqr/p1pp1ppp/4p3/1p6/1Pb1P3/6PB/P1PP1P1P/BNNRK1QR w HDhd - +perft 1 27 +perft 2 776 +perft 3 22133 +perft 4 641002 +perft 5 19153245 +perft 6 562738257 + +id 66 +epd bnr1kqrb/pppp1pp1/1n5p/4p3/P3P3/3P2P1/1PP2P1P/BNNRKQRB w GDg - +perft 1 26 +perft 2 624 +perft 3 16411 +perft 4 435426 +perft 5 11906515 +perft 6 338092952 + +id 67 +epd nbbnrkqr/p1ppp1pp/1p3p2/8/2P5/4P3/PP1P1PPP/NBBNRKQR w HEhe - +perft 1 25 +perft 2 624 +perft 3 15561 +perft 4 419635 +perft 5 10817378 +perft 6 311138112 + +id 68 +epd nn1brkqr/pp1bpppp/8/2pp4/P4P2/1PN5/2PPP1PP/N1BBRKQR w HEhe - +perft 1 23 +perft 2 659 +perft 3 16958 +perft 4 476567 +perft 5 13242252 +perft 6 373557073 + +id 69 +epd n1brkbqr/ppp1pp1p/6pB/3p4/2Pn4/8/PP2PPPP/NN1RKBQR w HDhd - +perft 1 32 +perft 2 1026 +perft 3 30360 +perft 4 978278 +perft 5 29436320 +perft 6 957904151 + +id 70 +epd nnbrkqrb/p2ppp2/Q5pp/1pp5/4PP2/2N5/PPPP2PP/N1BRK1RB w GDgd - +perft 1 36 +perft 2 843 +perft 3 29017 +perft 4 715537 +perft 5 24321197 +perft 6 630396940 + +id 71 +epd nbnrbk1r/pppppppq/8/7p/8/1N2QPP1/PPPPP2P/NB1RBK1R w HDhd - +perft 1 36 +perft 2 973 +perft 3 35403 +perft 4 1018054 +perft 5 37143354 +perft 6 1124883780 + +id 72 +epd nnrbbkqr/2pppp1p/p7/6p1/1p2P3/4QPP1/PPPP3P/NNRBBK1R w HChc - +perft 1 36 +perft 2 649 +perft 3 22524 +perft 4 489526 +perft 5 16836636 +perft 6 416139320 + +id 73 +epd nnrkbbqr/1p2pppp/p2p4/2p5/8/1N2P1P1/PPPP1P1P/1NKRBBQR w hc - +perft 1 26 +perft 2 672 +perft 3 18136 +perft 4 477801 +perft 5 13342771 +perft 6 363074681 + +id 74 +epd n1rkbqrb/pp1ppp2/2n3p1/2p4p/P5PP/1P6/2PPPP2/NNRKBQRB w GCgc - +perft 1 24 +perft 2 804 +perft 3 20712 +perft 4 684001 +perft 5 18761475 +perft 6 617932151 + +id 75 +epd nbkr1qbr/1pp1pppp/pn1p4/8/3P2P1/5R2/PPP1PP1P/NBN1KQBR w H - +perft 1 30 +perft 2 627 +perft 3 18669 +perft 4 423329 +perft 5 12815016 +perft 6 312798696 + +id 76 +epd nnr1kqbr/pp1pp1p1/2p5/b4p1p/P7/1PNP4/2P1PPPP/N1RBKQBR w HChc - +perft 1 12 +perft 2 421 +perft 3 6530 +perft 4 227044 +perft 5 4266410 +perft 6 149176979 + +id 77 +epd n1rkqbbr/p1pp1pp1/np2p2p/8/8/N4PP1/PPPPP1BP/N1RKQ1BR w HChc - +perft 1 27 +perft 2 670 +perft 3 19119 +perft 4 494690 +perft 5 14708490 +perft 6 397268628 + +id 78 +epd nnr1qrbb/p2kpppp/1p1p4/2p5/6P1/PP1P4/2P1PP1P/NNRKQRBB w FC - +perft 1 27 +perft 2 604 +perft 3 17043 +perft 4 409665 +perft 5 11993332 +perft 6 308518181 + +id 79 +epd bbnnrkrq/ppp1pp2/6p1/3p4/7p/7P/PPPPPPP1/BBNNRRKQ w ge - +perft 1 20 +perft 2 559 +perft 3 12242 +perft 4 355326 +perft 5 8427161 +perft 6 252274233 + +id 80 +epd bnnbrkr1/ppp2p1p/5q2/3pp1p1/4P3/1N4P1/PPPPRP1P/BN1B1KRQ w Gge - +perft 1 26 +perft 2 1036 +perft 3 27228 +perft 4 1028084 +perft 5 28286576 +perft 6 1042120495 + +id 81 +epd bn1rkbrq/1pppppp1/p6p/1n6/3P4/6PP/PPPRPP2/BNN1KBRQ w Ggd - +perft 1 29 +perft 2 633 +perft 3 19278 +perft 4 455476 +perft 5 14333034 +perft 6 361900466 + +id 82 +epd b1nrkrqb/1p1npppp/p2p4/2p5/5P2/4P2P/PPPP1RP1/BNNRK1QB w Dfd - +perft 1 25 +perft 2 475 +perft 3 12603 +perft 4 270909 +perft 5 7545536 +perft 6 179579818 + +id 83 +epd 1bbnrkrq/ppppppp1/8/7p/1n4P1/1PN5/P1PPPP1P/NBBR1KRQ w Gge - +perft 1 30 +perft 2 803 +perft 3 25473 +perft 4 709716 +perft 5 23443854 +perft 6 686365049 + +id 84 +epd nnbbrkrq/2pp1pp1/1p5p/pP2p3/7P/N7/P1PPPPP1/N1BBRKRQ w GEge - +perft 1 18 +perft 2 432 +perft 3 9638 +perft 4 242350 +perft 5 6131124 +perft 6 160393505 + +id 85 +epd nnbrkbrq/1pppp1p1/p7/7p/1P2Pp2/BN6/P1PP1PPP/1N1RKBRQ w GDgd - +perft 1 27 +perft 2 482 +perft 3 13441 +perft 4 282259 +perft 5 8084701 +perft 6 193484216 + +id 86 +epd n1brkrqb/pppp3p/n3pp2/6p1/3P1P2/N1P5/PP2P1PP/N1BRKRQB w FDfd - +perft 1 28 +perft 2 642 +perft 3 19005 +perft 4 471729 +perft 5 14529434 +perft 6 384837696 + +id 87 +epd nbnrbk2/p1pppp1p/1p3qr1/6p1/1B1P4/1N6/PPP1PPPP/1BNR1RKQ w d - +perft 1 30 +perft 2 796 +perft 3 22780 +perft 4 687302 +perft 5 20120565 +perft 6 641832725 + +id 88 +epd nnrbbrkq/1pp2ppp/3p4/p3p3/3P1P2/1P2P3/P1P3PP/NNRBBKRQ w GC - +perft 1 31 +perft 2 827 +perft 3 24538 +perft 4 663082 +perft 5 19979594 +perft 6 549437308 + +id 89 +epd nnrkbbrq/1pp2p1p/p2pp1p1/2P5/8/8/PP1PPPPP/NNRKBBRQ w Ggc - +perft 1 24 +perft 2 762 +perft 3 19283 +perft 4 624598 +perft 5 16838099 +perft 6 555230555 + +id 90 +epd nnr1brqb/1ppkp1pp/8/p2p1p2/1P1P4/N1P5/P3PPPP/N1RKBRQB w FC - +perft 1 23 +perft 2 640 +perft 3 15471 +perft 4 444905 +perft 5 11343507 +perft 6 334123513 + +id 91 +epd nbnrkrbq/2ppp2p/p4p2/1P4p1/4PP2/8/1PPP2PP/NBNRKRBQ w FDfd - +perft 1 31 +perft 2 826 +perft 3 26137 +perft 4 732175 +perft 5 23555139 +perft 6 686250413 + +id 92 +epd 1nrbkr1q/1pppp1pp/1n6/p4p2/N1b4P/8/PPPPPPPB/N1RBKR1Q w FCfc - +perft 1 27 +perft 2 862 +perft 3 24141 +perft 4 755171 +perft 5 22027695 +perft 6 696353497 + +id 93 +epd nnrkrbbq/pppp2pp/8/4pp2/4P3/P7/1PPPBPPP/NNKRR1BQ w c - +perft 1 25 +perft 2 792 +perft 3 19883 +perft 4 636041 +perft 5 16473376 +perft 6 532214177 + +id 94 +epd n1rk1qbb/pppprpp1/2n4p/4p3/2PP3P/8/PP2PPP1/NNRKRQBB w ECc - +perft 1 25 +perft 2 622 +perft 3 16031 +perft 4 425247 +perft 5 11420973 +perft 6 321855685 + +id 95 +epd bbq1rnkr/pnp1pp1p/1p1p4/6p1/2P5/2Q1P2P/PP1P1PP1/BB1NRNKR w HEhe - +perft 1 36 +perft 2 870 +perft 3 30516 +perft 4 811047 +perft 5 28127620 +perft 6 799738334 + +id 96 +epd bq1brnkr/1p1ppp1p/1np5/p5p1/8/1N5P/PPPPPPP1/BQ1BRNKR w HEhe - +perft 1 22 +perft 2 588 +perft 3 13524 +perft 4 380068 +perft 5 9359618 +perft 6 273795898 + +id 97 +epd bq1rn1kr/1pppppbp/Nn4p1/8/8/P7/1PPPPPPP/BQ1RNBKR w HDhd - +perft 1 24 +perft 2 711 +perft 3 18197 +perft 4 542570 +perft 5 14692779 +perft 6 445827351 + +id 98 +epd bqnr1kr1/pppppp1p/6p1/5n2/4B3/3N2PP/PbPPPP2/BQNR1KR1 w GDgd - +perft 1 31 +perft 2 1132 +perft 3 36559 +perft 4 1261476 +perft 5 43256823 +perft 6 1456721391 + +id 99 +epd qbb1rnkr/ppp3pp/4n3/3ppp2/1P3PP1/8/P1PPPN1P/QBB1RNKR w HEhe - +perft 1 28 +perft 2 696 +perft 3 20502 +perft 4 541886 +perft 5 16492398 +perft 6 456983120 + +id 100 +epd qnbbr1kr/pp1ppp1p/4n3/6p1/2p3P1/2PP1P2/PP2P2P/QNBBRNKR w HEhe - +perft 1 25 +perft 2 655 +perft 3 16520 +perft 4 450189 +perft 5 11767038 +perft 6 335414976 + +id 101 +epd 1nbrnbkr/p1ppp1pp/1p6/5p2/4q1PP/3P4/PPP1PP2/QNBRNBKR w HDhd - +perft 1 30 +perft 2 1162 +perft 3 33199 +perft 4 1217278 +perft 5 36048727 +perft 6 1290346802 + +id 102 +epd q1brnkrb/p1pppppp/n7/1p6/P7/3P1P2/QPP1P1PP/1NBRNKRB w GDgd - +perft 1 32 +perft 2 827 +perft 3 26106 +perft 4 718243 +perft 5 23143989 +perft 6 673147648 + +id 103 +epd qbnrb1kr/ppp1pp1p/3p4/2n3p1/1P6/6N1/P1PPPPPP/QBNRB1KR w HDhd - +perft 1 29 +perft 2 751 +perft 3 23132 +perft 4 610397 +perft 5 19555214 +perft 6 530475036 + +id 104 +epd q1rbbnkr/pppp1p2/2n3pp/2P1p3/3P4/8/PP1NPPPP/Q1RBBNKR w HChc - +perft 1 29 +perft 2 806 +perft 3 24540 +perft 4 687251 +perft 5 21694330 +perft 6 619907316 + +id 105 +epd q1r1bbkr/pnpp1ppp/2n1p3/1p6/2P2P2/2N1N3/PP1PP1PP/Q1R1BBKR w HChc - +perft 1 32 +perft 2 1017 +perft 3 32098 +perft 4 986028 +perft 5 31204371 +perft 6 958455898 + +id 106 +epd 2rnbkrb/pqppppp1/1pn5/7p/2P5/P1R5/QP1PPPPP/1N1NBKRB w Ggc - +perft 1 26 +perft 2 625 +perft 3 16506 +perft 4 434635 +perft 5 11856964 +perft 6 336672890 + +id 107 +epd qbnr1kbr/p2ppppp/2p5/1p6/4n2P/P4N2/1PPP1PP1/QBNR1KBR w HDhd - +perft 1 27 +perft 2 885 +perft 3 23828 +perft 4 767273 +perft 5 21855658 +perft 6 706272554 + +id 108 +epd qnrbnk1r/pp1pp2p/5p2/2pbP1p1/3P4/1P6/P1P2PPP/QNRBNKBR w HChc - +perft 1 26 +perft 2 954 +perft 3 24832 +perft 4 892456 +perft 5 24415089 +perft 6 866744329 + +id 109 +epd qnrnk1br/p1p2ppp/8/1pbpp3/8/PP2N3/1QPPPPPP/1NR1KBBR w HChc - +perft 1 26 +perft 2 783 +perft 3 20828 +perft 4 634267 +perft 5 17477825 +perft 6 539674275 + +id 110 +epd qnrnkrbb/Bpppp2p/6p1/5p2/5P2/3PP3/PPP3PP/QNRNKR1B w FCfc - +perft 1 28 +perft 2 908 +perft 3 25730 +perft 4 861240 +perft 5 25251641 +perft 6 869525254 + +id 111 +epd bbnqrn1r/ppppp2k/5p2/6pp/7P/1QP5/PP1PPPP1/B1N1RNKR w HE - +perft 1 33 +perft 2 643 +perft 3 21790 +perft 4 487109 +perft 5 16693640 +perft 6 410115900 + +id 112 +epd b1qbrnkr/ppp1pp2/2np4/6pp/4P3/2N4P/PPPP1PP1/BQ1BRNKR w HEhe - +perft 1 28 +perft 2 837 +perft 3 24253 +perft 4 745617 +perft 5 22197063 +perft 6 696399065 + +id 113 +epd bnqr1bkr/pp1ppppp/2p5/4N3/5P2/P7/1PPPPnPP/BNQR1BKR w HDhd - +perft 1 25 +perft 2 579 +perft 3 13909 +perft 4 341444 +perft 5 8601011 +perft 6 225530258 + +id 114 +epd b1qr1krb/pp1ppppp/n2n4/8/2p5/2P3P1/PP1PPP1P/BNQRNKRB w GDgd - +perft 1 28 +perft 2 707 +perft 3 19721 +perft 4 549506 +perft 5 15583376 +perft 6 468399900 + +id 115 +epd nbbqr1kr/1pppp1pp/8/p1n2p2/4P3/PN6/1PPPQPPP/1BB1RNKR w HEhe - +perft 1 30 +perft 2 745 +perft 3 23416 +perft 4 597858 +perft 5 19478789 +perft 6 515473678 + +id 116 +epd nqbbrn1r/p1pppp1k/1p4p1/7p/4P3/1R3B2/PPPP1PPP/NQB2NKR w H - +perft 1 24 +perft 2 504 +perft 3 13512 +perft 4 317355 +perft 5 9002073 +perft 6 228726497 + +id 117 +epd nqbr1bkr/p1p1ppp1/1p1n4/3pN2p/1P6/8/P1PPPPPP/NQBR1BKR w HDhd - +perft 1 29 +perft 2 898 +perft 3 26532 +perft 4 809605 +perft 5 24703467 +perft 6 757166494 + +id 118 +epd nqbrn1rb/pppp1kp1/5p1p/4p3/P4B2/3P2P1/1PP1PP1P/NQ1RNKRB w GD - +perft 1 34 +perft 2 671 +perft 3 22332 +perft 4 473110 +perft 5 15556806 +perft 6 353235120 + +id 119 +epd nb1r1nkr/ppp1ppp1/2bp4/7p/3P2qP/P6R/1PP1PPP1/NBQRBNK1 w Dhd - +perft 1 38 +perft 2 1691 +perft 3 60060 +perft 4 2526992 +perft 5 88557078 +perft 6 3589649998 + +id 120 +epd n1rbbnkr/1p1pp1pp/p7/2p1qp2/1B3P2/3P4/PPP1P1PP/NQRB1NKR w HChc - +perft 1 24 +perft 2 913 +perft 3 21595 +perft 4 807544 +perft 5 19866918 +perft 6 737239330 + +id 121 +epd nqrnbbkr/p2p1p1p/1pp5/1B2p1p1/1P3P2/4P3/P1PP2PP/NQRNB1KR w HChc - +perft 1 33 +perft 2 913 +perft 3 30159 +perft 4 843874 +perft 5 28053260 +perft 6 804687975 + +id 122 +epd nqr1bkrb/ppp1pp2/2np2p1/P6p/8/2P4P/1P1PPPP1/NQRNBKRB w GCgc - +perft 1 24 +perft 2 623 +perft 3 16569 +perft 4 442531 +perft 5 12681936 +perft 6 351623879 + +id 123 +epd nb1rnkbr/pqppppp1/1p5p/8/1PP4P/8/P2PPPP1/NBQRNKBR w HDhd - +perft 1 31 +perft 2 798 +perft 3 24862 +perft 4 694386 +perft 5 22616076 +perft 6 666227466 + +id 124 +epd nqrbnkbr/2p1p1pp/3p4/pp3p2/6PP/3P1N2/PPP1PP2/NQRB1KBR w HChc - +perft 1 24 +perft 2 590 +perft 3 14409 +perft 4 383690 +perft 5 9698432 +perft 6 274064911 + +id 125 +epd nqrnkbbr/pp1p1p1p/4p1p1/1p6/8/5P1P/P1PPP1P1/NQRNKBBR w HChc - +perft 1 30 +perft 2 1032 +perft 3 31481 +perft 4 1098116 +perft 5 34914919 +perft 6 1233362066 + +id 126 +epd nqrnkrbb/p2ppppp/1p6/2p5/2P3P1/5P2/PP1PPN1P/NQR1KRBB w FCfc - +perft 1 30 +perft 2 775 +perft 3 23958 +perft 4 668000 +perft 5 21141738 +perft 6 621142773 + +id 127 +epd bbnrqrk1/pp2pppp/4n3/2pp4/P7/1N5P/BPPPPPP1/B2RQNKR w HD - +perft 1 23 +perft 2 708 +perft 3 17164 +perft 4 554089 +perft 5 14343443 +perft 6 481405144 + +id 128 +epd bnr1qnkr/p1pp1p1p/1p4p1/4p1b1/2P1P3/1P6/PB1P1PPP/1NRBQNKR w HChc - +perft 1 30 +perft 2 931 +perft 3 29249 +perft 4 921746 +perft 5 30026687 +perft 6 968109774 + +id 129 +epd b1rqnbkr/ppp1ppp1/3p3p/2n5/P3P3/2NP4/1PP2PPP/B1RQNBKR w HChc - +perft 1 24 +perft 2 596 +perft 3 15533 +perft 4 396123 +perft 5 11099382 +perft 6 294180723 + +id 130 +epd bnrqnr1b/pp1pkppp/2p1p3/P7/2P5/7P/1P1PPPP1/BNRQNKRB w GC - +perft 1 24 +perft 2 572 +perft 3 15293 +perft 4 390903 +perft 5 11208688 +perft 6 302955778 + +id 131 +epd n1brq1kr/bppppppp/p7/8/4P1Pn/8/PPPP1P2/NBBRQNKR w HDhd - +perft 1 20 +perft 2 570 +perft 3 13139 +perft 4 371247 +perft 5 9919113 +perft 6 284592289 + +id 132 +epd 1rbbqnkr/ppn1ppp1/3p3p/2p5/3P4/1N4P1/PPPBPP1P/1R1BQNKR w HBhb - +perft 1 29 +perft 2 1009 +perft 3 29547 +perft 4 1040816 +perft 5 31059587 +perft 6 1111986835 + +id 133 +epd nrbq2kr/ppppppb1/5n1p/5Pp1/8/P5P1/1PPPP2P/NRBQNBKR w HBhb - +perft 1 20 +perft 2 520 +perft 3 11745 +perft 4 316332 +perft 5 7809837 +perft 6 216997152 + +id 134 +epd nrb1nkrb/pp3ppp/1qBpp3/2p5/8/P5P1/1PPPPP1P/NRBQNKR1 w GBgb - +perft 1 32 +perft 2 850 +perft 3 25642 +perft 4 734088 +perft 5 21981567 +perft 6 664886187 + +id 135 +epd 1br1bnkr/ppqppp1p/1np3p1/8/1PP4P/4N3/P2PPPP1/NBRQB1KR w HChc - +perft 1 32 +perft 2 798 +perft 3 24765 +perft 4 691488 +perft 5 22076141 +perft 6 670296871 + +id 136 +epd nrqbb1kr/1p1pp1pp/2p3n1/p4p2/3PP3/P5N1/1PP2PPP/NRQBB1KR w HBhb - +perft 1 32 +perft 2 791 +perft 3 26213 +perft 4 684890 +perft 5 23239122 +perft 6 634260266 + +id 137 +epd nrqn1bkr/ppppp1pp/4b3/8/4P1p1/5P2/PPPP3P/NRQNBBKR w HBhb - +perft 1 29 +perft 2 687 +perft 3 20223 +perft 4 506088 +perft 5 15236287 +perft 6 398759980 + +id 138 +epd nrqnbrkb/pppp1p2/4p2p/3B2p1/8/1P4P1/PQPPPP1P/NR1NBKR1 w GB - +perft 1 37 +perft 2 764 +perft 3 27073 +perft 4 610950 +perft 5 21284835 +perft 6 514864869 + +id 139 +epd nbrq1kbr/Bp3ppp/2pnp3/3p4/5P2/2P4P/PP1PP1P1/NBRQNK1R w HChc - +perft 1 40 +perft 2 1271 +perft 3 48022 +perft 4 1547741 +perft 5 56588117 +perft 6 1850696281 + +id 140 +epd nrqbnkbr/1p2ppp1/p1p4p/3p4/1P6/8/PQPPPPPP/1RNBNKBR w HBhb - +perft 1 28 +perft 2 757 +perft 3 23135 +perft 4 668025 +perft 5 21427496 +perft 6 650939962 + +id 141 +epd nrqn1bbr/2ppkppp/4p3/pB6/8/2P1P3/PP1P1PPP/NRQNK1BR w HB - +perft 1 27 +perft 2 642 +perft 3 17096 +perft 4 442653 +perft 5 11872805 +perft 6 327545120 + +id 142 +epd nrqnkrb1/p1ppp2p/1p4p1/4bp2/4PP1P/4N3/PPPP2P1/NRQ1KRBB w FBfb - +perft 1 27 +perft 2 958 +perft 3 27397 +perft 4 960350 +perft 5 28520172 +perft 6 995356563 + +id 143 +epd 1bnrnqkr/pbpp2pp/8/1p2pp2/P6P/3P1N2/1PP1PPP1/BBNR1QKR w HDhd - +perft 1 27 +perft 2 859 +perft 3 23475 +perft 4 773232 +perft 5 21581178 +perft 6 732696327 + +id 144 +epd b1rbnqkr/1pp1ppp1/2n4p/p2p4/5P2/1PBP4/P1P1P1PP/1NRBNQKR w HChc - +perft 1 26 +perft 2 545 +perft 3 14817 +perft 4 336470 +perft 5 9537260 +perft 6 233549184 + +id 145 +epd 1nrnqbkr/p1pppppp/1p6/8/2b2P2/P1N5/1PP1P1PP/BNR1QBKR w HChc - +perft 1 24 +perft 2 668 +perft 3 17716 +perft 4 494866 +perft 5 14216070 +perft 6 406225409 + +id 146 +epd 1nrnqkrb/2ppp1pp/p7/1p3p2/5P2/N5K1/PPPPP2P/B1RNQ1RB w gc - +perft 1 33 +perft 2 725 +perft 3 23572 +perft 4 559823 +perft 5 18547476 +perft 6 471443091 + +id 147 +epd nbbr1qkr/p1pppppp/8/1p1n4/3P4/1N3PP1/PPP1P2P/1BBRNQKR w HDhd - +perft 1 28 +perft 2 698 +perft 3 20527 +perft 4 539625 +perft 5 16555068 +perft 6 458045505 + +id 148 +epd 1rbbnqkr/1pnppp1p/p5p1/2p5/2P4P/5P2/PP1PP1PR/NRBBNQK1 w Bhb - +perft 1 24 +perft 2 554 +perft 3 14221 +perft 4 362516 +perft 5 9863080 +perft 6 269284081 + +id 149 +epd nrb1qbkr/2pppppp/2n5/p7/2p5/4P3/PPNP1PPP/1RBNQBKR w HBhb - +perft 1 23 +perft 2 618 +perft 3 15572 +perft 4 443718 +perft 5 12044358 +perft 6 360311412 + +id 150 +epd nrb1qkrb/2ppppp1/p3n3/1p1B3p/2P5/6P1/PP1PPPRP/NRBNQK2 w Bgb - +perft 1 27 +perft 2 593 +perft 3 16770 +perft 4 401967 +perft 5 11806808 +perft 6 303338935 + +id 151 +epd nbrn1qkr/ppp1pp2/3p2p1/3Q3P/b7/8/PPPPPP1P/NBRNB1KR w HChc - +perft 1 39 +perft 2 1056 +perft 3 40157 +perft 4 1133446 +perft 5 42201531 +perft 6 1239888683 + +id 152 +epd nr1bbqkr/pp1pp2p/1n3pp1/2p5/8/1P4P1/P1PPPPQP/NRNBBK1R w hb - +perft 1 25 +perft 2 585 +perft 3 15719 +perft 4 406544 +perft 5 11582539 +perft 6 320997679 + +id 153 +epd nr2bbkr/ppp1pppp/1n1p4/8/6PP/1NP4q/PP1PPP2/1RNQBBKR w HBhb - +perft 1 22 +perft 2 742 +perft 3 15984 +perft 4 545231 +perft 5 13287051 +perft 6 457010195 + +id 154 +epd 1rnqbkrb/ppp1p1p1/1n3p2/3p3p/P6P/4P3/1PPP1PP1/NRNQBRKB w gb - +perft 1 22 +perft 2 574 +perft 3 14044 +perft 4 379648 +perft 5 9968830 +perft 6 281344367 + +id 155 +epd nb1rqkbr/1pppp1pp/4n3/p4p2/6PP/5P2/PPPPPN2/NBR1QKBR w HCh - +perft 1 25 +perft 2 621 +perft 3 16789 +perft 4 462600 +perft 5 13378840 +perft 6 396575613 + +id 156 +epd nrnbqkbr/2pp2pp/4pp2/pp6/8/1P3P2/P1PPPBPP/NRNBQ1KR w hb - +perft 1 25 +perft 2 656 +perft 3 16951 +perft 4 466493 +perft 5 12525939 +perft 6 358763789 + +id 157 +epd nrnqkbbr/ppppp1p1/7p/5p2/8/P4PP1/NPPPP2P/NR1QKBBR w HBhb - +perft 1 28 +perft 2 723 +perft 3 20621 +perft 4 547522 +perft 5 15952533 +perft 6 439046803 + +id 158 +epd 1rnqkr1b/ppppp2p/1n3pp1/8/2P3P1/Pb1N4/1P1PPP1P/NR1QKRBB w FBfb - +perft 1 26 +perft 2 713 +perft 3 19671 +perft 4 548875 +perft 5 15865528 +perft 6 454532806 + +id 159 +epd bbnrnkqr/1pppp1pp/5p2/p7/7P/1P6/PBPPPPPR/1BNRNKQ1 w D - +perft 1 26 +perft 2 649 +perft 3 17834 +perft 4 502279 +perft 5 14375839 +perft 6 435585252 + +id 160 +epd bnrbk1qr/1ppp1ppp/p2np3/8/P7/2N2P2/1PPPP1PP/B1RBNKQR w HC - +perft 1 26 +perft 2 621 +perft 3 17569 +perft 4 451452 +perft 5 13514201 +perft 6 364421088 + +id 161 +epd br1nkbqr/ppppppp1/8/n6p/8/N1P2PP1/PP1PP2P/B1RNKBQR w HCh - +perft 1 29 +perft 2 664 +perft 3 20182 +perft 4 512316 +perft 5 16125924 +perft 6 442508159 + +id 162 +epd bnr1kqrb/pp1pppp1/2n5/2p5/1P4Pp/4N3/P1PPPP1P/BNKR1QRB w gc - +perft 1 36 +perft 2 888 +perft 3 31630 +perft 4 789863 +perft 5 27792175 +perft 6 719015345 + +id 163 +epd 1bbrnkqr/pp1p1ppp/2p1p3/1n6/5P2/3Q4/PPPPP1PP/NBBRNK1R w HDhd - +perft 1 36 +perft 2 891 +perft 3 31075 +perft 4 781792 +perft 5 26998966 +perft 6 702903862 + +id 164 +epd nrbbnk1r/pp2pppq/8/2pp3p/3P2P1/1N6/PPP1PP1P/1RBBNKQR w HBhb - +perft 1 29 +perft 2 1036 +perft 3 31344 +perft 4 1139166 +perft 5 35627310 +perft 6 1310683359 + +id 165 +epd nr1nkbqr/ppp3pp/5p2/3pp3/6b1/3PP3/PPP2PPP/NRBNKBQR w hb - +perft 1 18 +perft 2 664 +perft 3 13306 +perft 4 483892 +perft 5 10658989 +perft 6 386307449 + +id 166 +epd nrbnk1rb/ppp1pq1p/3p4/5pp1/2P1P3/1N6/PP1PKPPP/1RBN1QRB w gb - +perft 1 25 +perft 2 966 +perft 3 24026 +perft 4 920345 +perft 5 23957242 +perft 6 913710194 + +id 167 +epd 1brnbkqr/pppppp2/6p1/7p/1Pn5/P1NP4/2P1PPPP/NBR1BKQR w HChc - +perft 1 22 +perft 2 627 +perft 3 13760 +perft 4 395829 +perft 5 9627826 +perft 6 285900573 + +id 168 +epd nrnbbk1r/p1pppppq/8/7p/1p6/P5PP/1PPPPPQ1/NRNBBK1R w HBhb - +perft 1 29 +perft 2 888 +perft 3 26742 +perft 4 874270 +perft 5 27229468 +perft 6 930799376 + +id 169 +epd n1nkb1qr/prppppbp/6p1/1p6/2P2P2/P7/1P1PP1PP/NRNKBBQR w HBh - +perft 1 29 +perft 2 804 +perft 3 24701 +perft 4 688520 +perft 5 21952444 +perft 6 623156747 + +id 170 +epd nr2bqrb/ppkpp1pp/1np5/5p1P/5P2/2P5/PP1PP1P1/NRNKBQRB w GB - +perft 1 22 +perft 2 530 +perft 3 13055 +perft 4 347657 +perft 5 9244693 +perft 6 264088392 + +id 171 +epd nbr1kqbr/p3pppp/2ppn3/1p4P1/4P3/1P6/P1PP1P1P/NBRNKQBR w HChc - +perft 1 23 +perft 2 555 +perft 3 14291 +perft 4 350917 +perft 5 9692630 +perft 6 247479180 + +id 172 +epd nr1bkqbr/1p1pp1pp/pnp2p2/8/6P1/P1PP4/1P2PP1P/NRNBKQBR w HBhb - +perft 1 22 +perft 2 565 +perft 3 13343 +perft 4 365663 +perft 5 9305533 +perft 6 268612479 + +id 173 +epd nr1kqbbr/np2pppp/p1p5/1B1p1P2/8/4P3/PPPP2PP/NRNKQ1BR w HBhb - +perft 1 32 +perft 2 730 +perft 3 23391 +perft 4 556995 +perft 5 18103280 +perft 6 454569900 + +id 174 +epd nrnk1rbb/p1p2ppp/3pq3/Qp2p3/1P1P4/8/P1P1PPPP/NRN1KRBB w fb - +perft 1 28 +perft 2 873 +perft 3 25683 +perft 4 791823 +perft 5 23868737 +perft 6 747991356 + +id 175 +epd bbnrnkrq/pp1ppp1p/6p1/2p5/6P1/P5RP/1PPPPP2/BBNRNK1Q w Dgd - +perft 1 37 +perft 2 1260 +perft 3 45060 +perft 4 1542086 +perft 5 54843403 +perft 6 1898432768 + +id 176 +epd bnrb1rkq/ppnpppp1/3Q4/2p4p/7P/N7/PPPPPPP1/B1RBNKR1 w GC - +perft 1 38 +perft 2 878 +perft 3 31944 +perft 4 800440 +perft 5 28784300 +perft 6 784569826 + +id 177 +epd bnrnkbrq/p1ppppp1/1p5p/8/P2PP3/5P2/1PP3PP/BNRNKBRQ w GCgc - +perft 1 26 +perft 2 617 +perft 3 16992 +perft 4 419099 +perft 5 11965544 +perft 6 311309576 + +id 178 +epd bnrnkrqb/pp2p2p/2pp1pp1/8/P7/2PP1P2/1P2P1PP/BNRNKRQB w FCfc - +perft 1 26 +perft 2 721 +perft 3 19726 +perft 4 560824 +perft 5 15966934 +perft 6 467132503 + +id 179 +epd nbbrnkr1/1pppp1p1/p6q/P4p1p/8/5P2/1PPPP1PP/NBBRNRKQ w gd - +perft 1 18 +perft 2 556 +perft 3 10484 +perft 4 316634 +perft 5 6629293 +perft 6 202528241 + +id 180 +epd nrb1nkrq/2pp1ppp/p4b2/1p2p3/P4B2/3P4/1PP1PPPP/NR1BNRKQ w gb - +perft 1 24 +perft 2 562 +perft 3 14017 +perft 4 355433 +perft 5 9227883 +perft 6 247634489 + +id 181 +epd nrbnkbrq/p3p1pp/1p6/2pp1P2/8/3PP3/PPP2P1P/NRBNKBRQ w GBgb - +perft 1 31 +perft 2 746 +perft 3 24819 +perft 4 608523 +perft 5 21019301 +perft 6 542954168 + +id 182 +epd nrbnkrqb/pppp1p1p/4p1p1/8/7P/2P1P3/PPNP1PP1/1RBNKRQB w FBfb - +perft 1 20 +perft 2 459 +perft 3 9998 +perft 4 242762 +perft 5 5760165 +perft 6 146614723 + +id 183 +epd nbrn1krq/ppp1p2p/6b1/3p1pp1/8/4N1PP/PPPPPP2/NBR1BRKQ w gc - +perft 1 27 +perft 2 835 +perft 3 23632 +perft 4 766397 +perft 5 22667987 +perft 6 760795567 + +id 184 +epd nrnbbkrq/p1pp2pp/5p2/1p6/2P1pP1B/1P6/P2PP1PP/NRNB1KRQ w GBgb - +perft 1 24 +perft 2 646 +perft 3 16102 +perft 4 444472 +perft 5 11489727 +perft 6 324948755 + +id 185 +epd nrn1bbrq/1ppkppp1/p2p3p/8/1P3N2/4P3/P1PP1PPP/NR1KBBRQ w GB - +perft 1 32 +perft 2 591 +perft 3 18722 +perft 4 381683 +perft 5 12069159 +perft 6 269922838 + +id 186 +epd n1krbrqb/1ppppppp/p7/8/4n3/P4P1P/1PPPPQP1/NRNKBR1B w FB - +perft 1 26 +perft 2 639 +perft 3 16988 +perft 4 417190 +perft 5 12167153 +perft 6 312633873 + +id 187 +epd n1rnkrbq/1p1ppp1p/8/p1p1b1p1/3PQ1P1/4N3/PPP1PP1P/NBR1KRB1 w FCfc - +perft 1 35 +perft 2 1027 +perft 3 35731 +perft 4 1040417 +perft 5 35738410 +perft 6 1060661628 + +id 188 +epd nrnbkrbq/2pp1pp1/pp6/4p2p/P7/5PPP/1PPPP3/NRNBKRBQ w FBfb - +perft 1 26 +perft 2 628 +perft 3 16731 +perft 4 436075 +perft 5 11920087 +perft 6 331498921 + +id 189 +epd 1rnkrbbq/pp1p2pp/1n3p2/1Bp1p3/1P6/1N2P3/P1PP1PPP/1RNKR1BQ w EBeb - +perft 1 33 +perft 2 992 +perft 3 32244 +perft 4 983481 +perft 5 31703749 +perft 6 980306735 + +id 190 +epd nr1krqbb/p1ppppp1/8/1p5p/1Pn5/5P2/P1PPP1PP/NRNKRQBB w EBeb - +perft 1 24 +perft 2 670 +perft 3 15985 +perft 4 445492 +perft 5 11371067 +perft 6 325556465 + +id 191 +epd bbq1rkr1/1ppppppp/p1n2n2/8/2P2P2/1P6/PQ1PP1PP/BB1NRKNR w HEe - +perft 1 32 +perft 2 794 +perft 3 26846 +perft 4 689334 +perft 5 24085223 +perft 6 645633370 + +id 192 +epd b1nbrknr/1qppp1pp/p4p2/1p6/6P1/P2NP3/1PPP1P1P/BQ1BRKNR w HEhe - +perft 1 25 +perft 2 663 +perft 3 17138 +perft 4 482994 +perft 5 13157826 +perft 6 389603029 + +id 193 +epd bqnrk1nr/pp2ppbp/6p1/2pp4/2P5/5P2/PPQPP1PP/B1NRKBNR w HDhd - +perft 1 26 +perft 2 850 +perft 3 22876 +perft 4 759768 +perft 5 21341087 +perft 6 719712622 + +id 194 +epd bqnrknrb/1ppp1p1p/p7/6p1/1P2p3/P1PN4/3PPPPP/BQ1RKNRB w GDgd - +perft 1 25 +perft 2 721 +perft 3 19290 +perft 4 581913 +perft 5 16391601 +perft 6 511725087 + +id 195 +epd q1b1rknr/pp1pppp1/4n2p/2p1b3/1PP5/4P3/PQ1P1PPP/1BBNRKNR w HEhe - +perft 1 32 +perft 2 975 +perft 3 32566 +perft 4 955493 +perft 5 32649943 +perft 6 962536105 + +id 196 +epd qnbbrknr/1p1ppppp/8/p1p5/5P2/PP1P4/2P1P1PP/QNBBRKNR w HEhe - +perft 1 27 +perft 2 573 +perft 3 16331 +perft 4 391656 +perft 5 11562434 +perft 6 301166330 + +id 197 +epd q1brkb1r/p1pppppp/np3B2/8/6n1/1P5N/P1PPPPPP/QN1RKB1R w HDhd - +perft 1 32 +perft 2 984 +perft 3 31549 +perft 4 1007217 +perft 5 32597704 +perft 6 1075429389 + +id 198 +epd qn1rk1rb/p1pppppp/1p2n3/8/2b5/4NPP1/PPPPP1RP/QNBRK2B w Dgd - +perft 1 22 +perft 2 802 +perft 3 19156 +perft 4 697722 +perft 5 17761431 +perft 6 650603534 + +id 199 +epd qbnrbknr/ppp2p1p/8/3pp1p1/1PP1B3/5N2/P2PPPPP/Q1NRBK1R w HDhd - +perft 1 34 +perft 2 943 +perft 3 32506 +perft 4 930619 +perft 5 32523099 +perft 6 955802240 + +id 200 +epd qnrbb1nr/pp1p1ppp/2p2k2/4p3/4P3/5PPP/PPPP4/QNRBBKNR w HC - +perft 1 20 +perft 2 460 +perft 3 10287 +perft 4 241640 +perft 5 5846781 +perft 6 140714047 + +id 201 +epd qnr1bbnr/ppk1p1pp/3p4/2p2p2/8/2P5/PP1PPPPP/QNKRBBNR w - - +perft 1 19 +perft 2 572 +perft 3 11834 +perft 4 357340 +perft 5 7994547 +perft 6 243724815 + +id 202 +epd qnrkbnrb/1p1p1ppp/2p5/4p3/p7/N1BP4/PPP1PPPP/Q1R1KNRB w gc - +perft 1 27 +perft 2 579 +perft 3 16233 +perft 4 375168 +perft 5 10845146 +perft 6 268229097 + +id 203 +epd qbnrkn1r/1pppp1p1/p3bp2/2BN3p/8/5P2/PPPPP1PP/QBNRK2R w HDhd - +perft 1 40 +perft 2 1027 +perft 3 38728 +perft 4 1059229 +perft 5 38511307 +perft 6 1104094381 + +id 204 +epd qnrbknbr/1pp2ppp/4p3/p6N/2p5/8/PPPPPPPP/Q1RBK1BR w HChc - +perft 1 22 +perft 2 510 +perft 3 11844 +perft 4 300180 +perft 5 7403327 +perft 6 200581103 + +id 205 +epd 1qkrnbbr/p1pppppp/2n5/1p6/8/5NP1/PPPPPP1P/QNRK1BBR w HC - +perft 1 24 +perft 2 549 +perft 3 13987 +perft 4 352037 +perft 5 9396521 +perft 6 255676649 + +id 206 +epd q1rknr1b/1ppppppb/2n5/p2B3p/8/1PN3P1/P1PPPP1P/Q1RKNRB1 w FCfc - +perft 1 31 +perft 2 924 +perft 3 28520 +perft 4 861944 +perft 5 27463479 +perft 6 847726572 + +id 207 +epd bbnqrk1r/pp1pppp1/2p4p/8/6n1/1N1P1P2/PPP1P1PP/BBQ1RKNR w HEhe - +perft 1 24 +perft 2 804 +perft 3 20147 +perft 4 666341 +perft 5 18024195 +perft 6 595947631 + +id 208 +epd bn1brknr/ppp1p1pp/5p2/3p4/6qQ/3P3P/PPP1PPP1/BN1BRKNR w HEhe - +perft 1 25 +perft 2 854 +perft 3 22991 +perft 4 704173 +perft 5 20290974 +perft 6 600195008 + +id 209 +epd 1nqrkbnr/2pp1ppp/pp2p3/3b4/2P5/N7/PP1PPPPP/B1QRKBNR w HDhd - +perft 1 22 +perft 2 651 +perft 3 16173 +perft 4 479152 +perft 5 13133439 +perft 6 390886040 + +id 210 +epd bnqrk1rb/1pp1pppp/p2p4/4n3/2PPP3/8/PP3PPP/BNQRKNRB w GDgd - +perft 1 30 +perft 2 950 +perft 3 28169 +perft 4 889687 +perft 5 27610213 +perft 6 880739164 + +id 211 +epd nbb1rknr/1ppq1ppp/3p4/p3p3/4P3/1N2R3/PPPP1PPP/1BBQ1KNR w Hhe - +perft 1 33 +perft 2 988 +perft 3 31293 +perft 4 967575 +perft 5 30894863 +perft 6 985384035 + +id 212 +epd nqbbrknr/2ppp2p/pp4p1/5p2/7P/3P1P2/PPPBP1P1/NQ1BRKNR w HEhe - +perft 1 27 +perft 2 492 +perft 3 13266 +perft 4 276569 +perft 5 7583292 +perft 6 175376176 + +id 213 +epd 1qbrkb1r/pppppppp/8/3n4/4P1n1/PN6/1PPP1P1P/1QBRKBNR w HDhd - +perft 1 28 +perft 2 800 +perft 3 21982 +perft 4 630374 +perft 5 17313279 +perft 6 507140861 + +id 214 +epd 1qbrknrb/1p1ppppp/1np5/8/p4P1P/4P1N1/PPPP2P1/NQBRK1RB w GDgd - +perft 1 21 +perft 2 482 +perft 3 10581 +perft 4 267935 +perft 5 6218644 +perft 6 168704845 + +id 215 +epd nbqrbkr1/ppp1pppp/8/3p4/6n1/2P2PPN/PP1PP2P/NBQRBK1R w HDd - +perft 1 29 +perft 2 921 +perft 3 25748 +perft 4 840262 +perft 5 24138518 +perft 6 806554650 + +id 216 +epd nqrb1knr/1ppbpp1p/p7/3p2p1/2P3P1/5P1P/PP1PP3/NQRBBKNR w HChc - +perft 1 31 +perft 2 803 +perft 3 25857 +perft 4 665799 +perft 5 21998733 +perft 6 583349773 + +id 217 +epd 1qrkbbr1/pppp1ppp/1n3n2/4p3/5P2/1N6/PPPPP1PP/1QRKBBNR w HCc - +perft 1 25 +perft 2 715 +perft 3 19118 +perft 4 556325 +perft 5 15514933 +perft 6 459533767 + +id 218 +epd nqrkb1rb/pp2pppp/2p1n3/3p4/3PP1N1/8/PPP2PPP/NQRKB1RB w GCgc - +perft 1 26 +perft 2 795 +perft 3 21752 +perft 4 679387 +perft 5 19185851 +perft 6 616508881 + +id 219 +epd nb1rknbr/pp2ppp1/8/2Bp3p/6P1/2P2P1q/PP1PP2P/NBQRKN1R w HDhd - +perft 1 35 +perft 2 1391 +perft 3 43025 +perft 4 1726888 +perft 5 53033675 +perft 6 2139267832 + +id 220 +epd nqrbkn1r/pp1pp1pp/8/2p2p2/5P2/P3B2P/1PbPP1P1/NQRBKN1R w HChc - +perft 1 23 +perft 2 758 +perft 3 19439 +perft 4 653854 +perft 5 18296195 +perft 6 628403401 + +id 221 +epd nqrknbbr/pp1pppp1/7p/2p5/7P/1P1N4/P1PPPPPB/NQRK1B1R w HChc - +perft 1 29 +perft 2 824 +perft 3 23137 +perft 4 683686 +perft 5 19429491 +perft 6 595493802 + +id 222 +epd 1qrknrbb/B1p1pppp/8/1p1p4/2n2P2/1P6/P1PPP1PP/NQRKNR1B w FCfc - +perft 1 28 +perft 2 771 +perft 3 20237 +perft 4 581721 +perft 5 16065378 +perft 6 483037840 + +id 223 +epd bbnrqk1r/1ppppppp/8/7n/1p6/P6P/1BPPPPP1/1BNRQKNR w HDhd - +perft 1 25 +perft 2 601 +perft 3 15471 +perft 4 396661 +perft 5 10697065 +perft 6 289472497 + +id 224 +epd bnrbqknr/ppp3p1/3ppp1Q/7p/3P4/1P6/P1P1PPPP/BNRB1KNR w HChc - +perft 1 32 +perft 2 845 +perft 3 26876 +perft 4 742888 +perft 5 23717883 +perft 6 682154649 + +id 225 +epd bn1qkb1r/pprppppp/8/2p5/2PPP1n1/8/PPR2PPP/BN1QKBNR w Hh - +perft 1 32 +perft 2 856 +perft 3 27829 +perft 4 768595 +perft 5 25245957 +perft 6 727424329 + +id 226 +epd 1nrqknrb/p1pp1ppp/1p2p3/3N4/5P1P/5b2/PPPPP3/B1RQKNRB w GCgc - +perft 1 33 +perft 2 873 +perft 3 27685 +perft 4 779473 +perft 5 25128076 +perft 6 745401024 + +id 227 +epd nbbrqrk1/pppppppp/8/2N1n3/P7/6P1/1PPPPP1P/1BBRQKNR w HD - +perft 1 25 +perft 2 555 +perft 3 14339 +perft 4 342296 +perft 5 9153089 +perft 6 234841945 + +id 228 +epd 1rbbqknr/1ppp1pp1/1n2p3/p6p/4P1P1/P6N/1PPP1P1P/NRBBQK1R w HBhb - +perft 1 25 +perft 2 693 +perft 3 18652 +perft 4 528070 +perft 5 15133381 +perft 6 439344945 + +id 229 +epd nrq1kbnr/p1pbpppp/3p4/1p6/6P1/1N3N2/PPPPPP1P/1RBQKB1R w HBhb - +perft 1 24 +perft 2 648 +perft 3 16640 +perft 4 471192 +perft 5 12871967 +perft 6 380436777 + +id 230 +epd nr1qknr1/p1pppp1p/b5p1/1p6/8/P4PP1/1bPPP1RP/NRBQKN1B w Bgb - +perft 1 18 +perft 2 533 +perft 3 11215 +perft 4 331243 +perft 5 7777833 +perft 6 234905172 + +id 231 +epd nbrqbknr/1ppp2pp/8/4pp2/p2PP1P1/7N/PPP2P1P/NBRQBK1R w HChc - +perft 1 29 +perft 2 803 +perft 3 24416 +perft 4 706648 +perft 5 22305910 +perft 6 672322762 + +id 232 +epd nr1b1k1r/ppp1pppp/2bp1n2/6P1/2P3q1/5P2/PP1PP2P/NRQBBKNR w HBhb - +perft 1 27 +perft 2 1199 +perft 3 30908 +perft 4 1296241 +perft 5 35121759 +perft 6 1418677099 + +id 233 +epd nrqkbbnr/2pppp1p/p7/1p6/2P1Pp2/8/PPNP2PP/1RQKBBNR w HBhb - +perft 1 28 +perft 2 613 +perft 3 17874 +perft 4 432750 +perft 5 13097064 +perft 6 345294379 + +id 234 +epd 1rqkbnrb/pp1ppp1p/1n4p1/B1p5/3PP3/4N3/PPP2PPP/NRQK2RB w GBgb - +perft 1 33 +perft 2 723 +perft 3 23991 +perft 4 590970 +perft 5 19715083 +perft 6 535650233 + +id 235 +epd nbrqkn1r/1pppp2p/5pp1/p2b4/5P2/P2PN3/1PP1P1PP/NBRQK1BR w HChc - +perft 1 23 +perft 2 607 +perft 3 15482 +perft 4 400970 +perft 5 11026383 +perft 6 290708878 + +id 236 +epd nrqbknbr/pp1pppp1/8/2p4p/P3PP2/8/1PPP2PP/NRQBKNBR w HBhb - +perft 1 26 +perft 2 700 +perft 3 19371 +perft 4 556026 +perft 5 16058815 +perft 6 485460242 + +id 237 +epd nrqknbbr/p2pppp1/1pp5/6Qp/3P4/1P3P2/P1P1P1PP/NR1KNBBR w HBhb - +perft 1 40 +perft 2 905 +perft 3 32932 +perft 4 829746 +perft 5 29263502 +perft 6 791963709 + +id 238 +epd nrqknrbb/1p3ppp/p2p4/2p1p3/1P6/3PP1P1/P1P2P1P/NRQKNRBB w FBfb - +perft 1 29 +perft 2 780 +perft 3 22643 +perft 4 654495 +perft 5 19532077 +perft 6 593181101 + +id 239 +epd 1bnrkqnr/p1pppp2/7p/1p4p1/4b3/7N/PPPP1PPP/BBNRKQ1R w HDhd - +perft 1 25 +perft 2 725 +perft 3 19808 +perft 4 565006 +perft 5 16661676 +perft 6 487354613 + +id 240 +epd bnrbkq1r/pp2p1pp/5n2/2pp1p2/P7/N1PP4/1P2PPPP/B1RBKQNR w HChc - +perft 1 24 +perft 2 745 +perft 3 18494 +perft 4 584015 +perft 5 15079602 +perft 6 488924040 + +id 241 +epd 2rkqbnr/p1pppppp/2b5/1pn5/1P3P1Q/2B5/P1PPP1PP/1NRK1BNR w HChc - +perft 1 33 +perft 2 904 +perft 3 30111 +perft 4 840025 +perft 5 28194726 +perft 6 801757709 + +id 242 +epd bnrkqnrb/2pppp2/8/pp4pp/1P5P/6P1/P1PPPPB1/BNRKQNR1 w GCgc - +perft 1 34 +perft 2 1059 +perft 3 34090 +perft 4 1054311 +perft 5 33195397 +perft 6 1036498304 + +id 243 +epd 1bbrkq1r/pppp2pp/1n2pp1n/8/2PP4/1N4P1/PP2PP1P/1BBRKQNR w HDhd - +perft 1 33 +perft 2 891 +perft 3 28907 +perft 4 814247 +perft 5 26970098 +perft 6 788040469 + +id 244 +epd nrbbkqnr/1p2pp1p/p1p3p1/3p4/8/1PP5/P2PPPPP/NRBBKQNR w HBhb - +perft 1 21 +perft 2 567 +perft 3 13212 +perft 4 376487 +perft 5 9539687 +perft 6 284426039 + +id 245 +epd 1rbkqbr1/ppp1pppp/1n5n/3p4/3P4/1PP3P1/P3PP1P/NRBKQBNR w HBb - +perft 1 27 +perft 2 752 +perft 3 20686 +perft 4 606783 +perft 5 16986290 +perft 6 521817800 + +id 246 +epd nrbkq1rb/1ppp1pp1/4p1n1/p6p/2PP4/5P2/PPK1P1PP/NRB1QNRB w gb - +perft 1 35 +perft 2 697 +perft 3 23678 +perft 4 505836 +perft 5 16906409 +perft 6 390324794 + +id 247 +epd nbrkbqnr/p2pp1p1/5p2/1pp4p/7P/3P2P1/PPP1PP2/NBKRBQNR w hc - +perft 1 25 +perft 2 679 +perft 3 17223 +perft 4 484921 +perft 5 12879258 +perft 6 376652259 + +id 248 +epd nrkb1qnr/ppppp1p1/6bp/5p2/1PP1P1P1/8/P2P1P1P/NRKBBQNR w HBhb - +perft 1 32 +perft 2 761 +perft 3 24586 +perft 4 632916 +perft 5 20671433 +perft 6 568524724 + +id 249 +epd nrk1bbnr/p1q1pppp/1ppp4/8/3P3P/4K3/PPP1PPP1/NR1QBBNR w hb - +perft 1 30 +perft 2 719 +perft 3 21683 +perft 4 541389 +perft 5 16278120 +perft 6 423649784 + +id 250 +epd nrkqbr1b/1pppp1pp/5pn1/p6N/1P3P2/8/P1PPP1PP/NRKQB1RB w GBb - +perft 1 26 +perft 2 494 +perft 3 13815 +perft 4 296170 +perft 5 8763742 +perft 6 206993496 + +id 251 +epd nbrkq2r/pppp1bpp/4p1n1/5p2/7P/2P3N1/PP1PPPP1/NBKRQ1BR w hc - +perft 1 27 +perft 2 701 +perft 3 19536 +perft 4 535052 +perft 5 15394667 +perft 6 443506342 + +id 252 +epd nrkbqnbr/2ppp2p/pp6/5pp1/P1P5/8/1P1PPPPP/NRKBQNBR w HBhb - +perft 1 21 +perft 2 487 +perft 3 11341 +perft 4 285387 +perft 5 7218486 +perft 6 193586674 + +id 253 +epd nr1qnbbr/pk1pppp1/1pp4p/8/3P4/5P1P/PPP1P1P1/NRKQNBBR w HB - +perft 1 22 +perft 2 546 +perft 3 13615 +perft 4 352855 +perft 5 9587439 +perft 6 259830255 + +id 254 +epd nrkq1rbb/pp1ppp1p/2pn4/8/PP3Pp1/7P/2PPP1P1/NRKQNRBB w FBfb - +perft 1 26 +perft 2 839 +perft 3 22075 +perft 4 723845 +perft 5 19867117 +perft 6 658535326 + +id 255 +epd b2rknqr/pp1ppppp/8/2P5/n7/P7/1PPNPPPb/BBNRK1QR w HDhd - +perft 1 24 +perft 2 699 +perft 3 19523 +perft 4 575172 +perft 5 17734818 +perft 6 535094237 + +id 256 +epd bnrbknqr/pp2p2p/2p3p1/3p1p2/8/3P4/PPPNPPPP/B1RBKNQR w HChc - +perft 1 23 +perft 2 580 +perft 3 14320 +perft 4 385917 +perft 5 10133092 +perft 6 288041554 + +id 257 +epd bnrknb1r/pppp2pp/8/4pp2/6P1/3P3P/qPP1PPQ1/BNRKNB1R w HChc - +perft 1 28 +perft 2 1100 +perft 3 31813 +perft 4 1217514 +perft 5 36142423 +perft 6 1361341249 + +id 258 +epd b1rknqrb/ppp1p1p1/2np1p1p/8/4N3/6PQ/PPPPPP1P/B1RKN1RB w GCgc - +perft 1 36 +perft 2 629 +perft 3 23082 +perft 4 453064 +perft 5 16897544 +perft 6 367503974 + +id 259 +epd nb1rknqr/pbppp2p/6p1/1p3p2/5P2/3KP3/PPPP2PP/NBBR1NQR w hd - +perft 1 18 +perft 2 557 +perft 3 9779 +perft 4 300744 +perft 5 5822387 +perft 6 180936551 + +id 260 +epd nr1bknqr/1ppb1ppp/p7/3pp3/B7/2P3NP/PP1PPPP1/NRB1K1QR w HBhb - +perft 1 28 +perft 2 688 +perft 3 19541 +perft 4 519785 +perft 5 15153092 +perft 6 425149249 + +id 261 +epd nrbkn2r/pppp1pqp/4p1p1/8/3P2P1/P3B3/P1P1PP1P/NR1KNBQR w HBhb - +perft 1 32 +perft 2 808 +perft 3 25578 +perft 4 676525 +perft 5 22094260 +perft 6 609377239 + +id 262 +epd nrbknqrb/2p1ppp1/1p6/p2p2Bp/1P6/3P1P2/P1P1P1PP/NR1KNQRB w GBgb - +perft 1 30 +perft 2 625 +perft 3 18288 +perft 4 418895 +perft 5 12225742 +perft 6 301834282 + +id 263 +epd nbr1knqr/1pp1p1pp/3p1pb1/8/7P/5P2/PPPPPQP1/NBRKBN1R w HC - +perft 1 29 +perft 2 863 +perft 3 25767 +perft 4 800239 +perft 5 24965592 +perft 6 799182442 + +id 264 +epd n1kbbnqr/prp2ppp/1p1p4/4p3/1P2P3/3P1B2/P1P2PPP/NRK1BNQR w HBh - +perft 1 26 +perft 2 653 +perft 3 17020 +perft 4 449719 +perft 5 12187583 +perft 6 336872952 + +id 265 +epd nrknbbqr/pp3p1p/B3p1p1/2pp4/4P3/2N3P1/PPPP1P1P/NRK1B1QR w HBhb - +perft 1 29 +perft 2 683 +perft 3 19755 +perft 4 501807 +perft 5 14684565 +perft 6 394951291 + +id 266 +epd n1knbqrb/pr1p1ppp/Qp6/2p1p3/4P3/6P1/PPPP1P1P/NRKNB1RB w GBg - +perft 1 31 +perft 2 552 +perft 3 17197 +perft 4 371343 +perft 5 11663330 +perft 6 283583340 + +id 267 +epd nbrknqbr/p3p1pp/1p1p1p2/2p5/2Q1PP2/8/PPPP2PP/NBRKN1BR w HChc - +perft 1 37 +perft 2 913 +perft 3 32470 +perft 4 825748 +perft 5 28899548 +perft 6 759875563 + +id 268 +epd nrkb1qbr/pp1pppp1/5n2/7p/2p5/1N1NPP2/PPPP2PP/1RKB1QBR w HBhb - +perft 1 25 +perft 2 712 +perft 3 18813 +perft 4 543870 +perft 5 15045589 +perft 6 445074372 + +id 269 +epd nrk2bbr/pppqpppp/3p4/8/1P3nP1/3P4/P1P1PP1P/NRKNQBBR w HBhb - +perft 1 24 +perft 2 814 +perft 3 19954 +perft 4 670162 +perft 5 17603960 +perft 6 592121050 + +id 270 +epd nrknqrbb/1p2ppp1/2pp4/Q6p/P2P3P/8/1PP1PPP1/NRKN1RBB w FBfb - +perft 1 34 +perft 2 513 +perft 3 16111 +perft 4 303908 +perft 5 9569590 +perft 6 206509331 + +id 271 +epd bbnrk1rq/pp2p1pp/2ppn3/5p2/8/3NNP1P/PPPPP1P1/BB1RK1RQ w GDgd - +perft 1 28 +perft 2 697 +perft 3 20141 +perft 4 517917 +perft 5 15301879 +perft 6 410843713 + +id 272 +epd bnrbknrq/ppppp2p/6p1/5p2/4QPP1/8/PPPPP2P/BNRBKNR1 w GCgc - +perft 1 37 +perft 2 901 +perft 3 32612 +perft 4 877372 +perft 5 31385912 +perft 6 903831981 + +id 273 +epd bnkrnbrq/ppppp1p1/B6p/5p2/8/4P3/PPPP1PPP/BNKRN1RQ w - - +perft 1 26 +perft 2 417 +perft 3 11124 +perft 4 217095 +perft 5 5980981 +perft 6 133080499 + +id 274 +epd bnrk1rqb/2pppp1p/3n4/pp4p1/3Q1P2/2N3P1/PPPPP2P/B1RKNR1B w FCfc - +perft 1 49 +perft 2 1655 +perft 3 74590 +perft 4 2512003 +perft 5 107234294 +perft 6 3651608327 + +id 275 +epd nbbrk1rq/pp2pppp/2pp4/8/2P2n2/6N1/PP1PP1PP/NBBRKR1Q w Dgd - +perft 1 28 +perft 2 960 +perft 3 26841 +perft 4 884237 +perft 5 26083252 +perft 6 846682836 + +id 276 +epd nrbb2rq/pppk1ppp/4p1n1/3p4/6P1/1BP5/PP1PPPQP/NRB1KNR1 w GB - +perft 1 28 +perft 2 735 +perft 3 22048 +perft 4 593839 +perft 5 18588316 +perft 6 512048946 + +id 277 +epd nrbk1brq/p1ppppp1/7p/1p6/4P1nP/P7/1PPP1PP1/NRBKNBRQ w GBgb - +perft 1 22 +perft 2 572 +perft 3 12739 +perft 4 351494 +perft 5 8525056 +perft 6 247615348 + +id 278 +epd nrbk1rqb/1pp2ppp/5n2/p2pp3/5B2/1N1P2P1/PPP1PP1P/1R1KNRQB w FBfb - +perft 1 35 +perft 2 927 +perft 3 31559 +perft 4 849932 +perft 5 28465693 +perft 6 783048748 + +id 279 +epd nbrkb1rq/p1pp1ppp/4n3/4p3/Pp6/6N1/1PPPPPPP/NBRKBRQ1 w Cgc - +perft 1 20 +perft 2 456 +perft 3 10271 +perft 4 247733 +perft 5 6124625 +perft 6 154766108 + +id 280 +epd nrkb1nrq/p2pp1pp/1pp2p2/7b/6PP/5P2/PPPPP2N/NRKBB1RQ w GBgb - +perft 1 21 +perft 2 479 +perft 3 11152 +perft 4 264493 +perft 5 6696458 +perft 6 165253524 + +id 281 +epd nr1nbbr1/pppkpp1p/6p1/3p4/P6P/1P6/1RPPPPP1/N1KNBBRQ w G - +perft 1 20 +perft 2 498 +perft 3 11304 +perft 4 288813 +perft 5 7197322 +perft 6 188021682 + +id 282 +epd nrknbrqb/3p1ppp/ppN1p3/8/6P1/8/PPPPPP1P/1RKNBRQB w FBfb - +perft 1 32 +perft 2 526 +perft 3 17267 +perft 4 319836 +perft 5 10755190 +perft 6 220058991 + +id 283 +epd nbrkn1bq/p1pppr1p/1p6/5pp1/8/1N2PP2/PPPP2PP/1BKRNRBQ w c - +perft 1 19 +perft 2 491 +perft 3 10090 +perft 4 277313 +perft 5 6230616 +perft 6 180748649 + +id 284 +epd nrkbnrbq/ppppppp1/8/8/7p/PP3P2/2PPPRPP/NRKBN1BQ w Bfb - +perft 1 16 +perft 2 353 +perft 3 6189 +perft 4 156002 +perft 5 3008668 +perft 6 82706705 + +id 285 +epd nrknrbbq/p4ppp/2p1p3/1p1p4/1P2P3/2P5/P1NP1PPP/1RKNRBBQ w EBeb - +perft 1 29 +perft 2 728 +perft 3 21915 +perft 4 587668 +perft 5 18231199 +perft 6 511686397 + +id 286 +epd nrknr1bb/pppp1p2/7p/2qPp1p1/8/1P5P/P1P1PPP1/NRKNRQBB w EBeb - +perft 1 20 +perft 2 714 +perft 3 14336 +perft 4 500458 +perft 5 11132758 +perft 6 386064577 + +id 287 +epd bbqnrrkn/ppp2p1p/3pp1p1/8/1PP5/2Q5/P1BPPPPP/B2NRKRN w GE - +perft 1 39 +perft 2 593 +perft 3 23446 +perft 4 424799 +perft 5 16764576 +perft 6 346185058 + +id 288 +epd bqn1rkrn/p1p2ppp/1p1p4/4p3/3PP2b/8/PPP2PPP/BQNBRKRN w GEge - +perft 1 25 +perft 2 773 +perft 3 20042 +perft 4 616817 +perft 5 16632403 +perft 6 515838333 + +id 289 +epd bqnrkb1n/p1p1pprp/3p4/1p2P1p1/2PP4/8/PP3PPP/BQNRKBRN w GDd - +perft 1 31 +perft 2 860 +perft 3 28102 +perft 4 810379 +perft 5 27233018 +perft 6 813751250 + +id 290 +epd bqr1krnb/ppppppp1/7p/3n4/1P4P1/P4N2/2PPPP1P/BQNRKR1B w FDf - +perft 1 31 +perft 2 709 +perft 3 22936 +perft 4 559830 +perft 5 18608857 +perft 6 480498340 + +id 291 +epd qbbn1krn/pp3ppp/4r3/2ppp3/P1P4P/8/1P1PPPP1/QBBNRKRN w GEg - +perft 1 26 +perft 2 775 +perft 3 21100 +perft 4 649673 +perft 5 18476807 +perft 6 582542257 + +id 292 +epd qnbbrkrn/1p1pp2p/p7/2p2pp1/8/4P2P/PPPP1PPK/QNBBRR1N w ge - +perft 1 25 +perft 2 599 +perft 3 15139 +perft 4 389104 +perft 5 10260500 +perft 6 279222412 + +id 293 +epd qnbrkbrn/1ppp2p1/p3p2p/5p2/P4P2/1P6/2PPP1PP/QNBRKBRN w GDgd - +perft 1 27 +perft 2 588 +perft 3 16735 +perft 4 394829 +perft 5 11640416 +perft 6 293541380 + +id 294 +epd 1nbrkrnb/p1pppp1p/1pq3p1/8/4P3/P1P4N/1P1P1PPP/QNBRKR1B w FDfd - +perft 1 18 +perft 2 609 +perft 3 11789 +perft 4 406831 +perft 5 8604788 +perft 6 299491047 + +id 295 +epd qb1r1krn/pppp2pp/1n2ppb1/4P3/7P/8/PPPP1PP1/QBNRBKRN w GDgd - +perft 1 20 +perft 2 578 +perft 3 12205 +perft 4 349453 +perft 5 7939483 +perft 6 229142178 + +id 296 +epd qnr1bkrn/p3pppp/1bpp4/1p6/2P2PP1/8/PP1PPN1P/QNRBBKR1 w GCgc - +perft 1 30 +perft 2 865 +perft 3 26617 +perft 4 771705 +perft 5 24475596 +perft 6 719842237 + +id 297 +epd 1nkrbbrn/qppppppp/8/8/p2P4/1P5P/P1P1PPP1/QNKRBBRN w - - +perft 1 27 +perft 2 672 +perft 3 18371 +perft 4 505278 +perft 5 14065717 +perft 6 410130412 + +id 298 +epd 1qrkbrnb/ppp1p1pp/n2p4/5p2/4N3/8/PPPPPPPP/Q1RKBRNB w Ffc - +perft 1 25 +perft 2 718 +perft 3 18573 +perft 4 536771 +perft 5 14404324 +perft 6 424279467 + +id 299 +epd q1nrkrbn/pp1pppp1/2p4p/8/P7/5Pb1/BPPPPNPP/Q1NRKRB1 w FDfd - +perft 1 22 +perft 2 558 +perft 3 12911 +perft 4 336042 +perft 5 8516966 +perft 6 228074630 + +id 300 +epd qnrbkrbn/1p1p1pp1/p1p5/4p2p/8/3P1P2/PPP1P1PP/QNRBKRBN w FCfc - +perft 1 28 +perft 2 669 +perft 3 17713 +perft 4 440930 +perft 5 12055174 +perft 6 313276304 + +id 301 +epd qnrkr1bn/p1pp1ppp/8/1p2p3/3P1P2/bP4P1/P1P1P2P/QNRKRBBN w ECec - +perft 1 23 +perft 2 845 +perft 3 20973 +perft 4 759778 +perft 5 19939053 +perft 6 718075943 + +id 302 +epd q1krrnbb/p1p1pppp/2np4/1pB5/5P2/8/PPPPP1PP/QNRKRN1B w EC - +perft 1 29 +perft 2 776 +perft 3 21966 +perft 4 631941 +perft 5 18110831 +perft 6 549019739 + +id 303 +epd bbn1rkrn/pp1p1ppp/8/2p1p1q1/6P1/P7/BPPPPP1P/B1NQRKRN w GEge - +perft 1 26 +perft 2 936 +perft 3 25177 +perft 4 906801 +perft 5 24984621 +perft 6 901444251 + +id 304 +epd bn1brkrn/pp1qpp1p/2p3p1/3p4/1PPP4/P7/4PPPP/BNQBRKRN w GEge - +perft 1 29 +perft 2 755 +perft 3 22858 +perft 4 645963 +perft 5 20128587 +perft 6 600207069 + +id 305 +epd b2rkbrn/p1pppppp/qp6/8/1n6/2B2P2/P1PPP1PP/1NQRKBRN w GDgd - +perft 1 24 +perft 2 878 +perft 3 21440 +perft 4 791007 +perft 5 20840078 +perft 6 775795187 + +id 306 +epd b2rkrnb/pqp1pppp/n7/1p1p4/P7/N1P2N2/1P1PPPPP/B1QRKR1B w FDfd - +perft 1 26 +perft 2 724 +perft 3 19558 +perft 4 571891 +perft 5 16109522 +perft 6 492933398 + +id 307 +epd 1bbqrkrn/ppppp1p1/8/5p1p/P1n3P1/3P4/1PP1PP1P/NBBQRRKN w ge - +perft 1 25 +perft 2 678 +perft 3 17351 +perft 4 461211 +perft 5 12173245 +perft 6 329661421 + +id 308 +epd nqb1rrkn/ppp1bppp/3pp3/8/3P4/1P6/PQP1PPPP/N1BBRRKN w - - +perft 1 23 +perft 2 503 +perft 3 12465 +perft 4 290341 +perft 5 7626054 +perft 6 188215608 + +id 309 +epd nqbrkbr1/p1pppppp/1p6/2N2n2/2P5/5P2/PP1PP1PP/1QBRKBRN w GDgd - +perft 1 29 +perft 2 688 +perft 3 20289 +perft 4 506302 +perft 5 15167248 +perft 6 399015237 + +id 310 +epd nqbrkrn1/1ppppp2/6pp/p7/1P6/2Q5/P1PPPPPP/N1BRKRNB w FDfd - +perft 1 36 +perft 2 602 +perft 3 20985 +perft 4 397340 +perft 5 13706856 +perft 6 291708797 + +id 311 +epd nbqrbrkn/pp1p1pp1/2p5/4p2p/2P3P1/1P3P2/P2PP2P/NBQRBKRN w GD - +perft 1 34 +perft 2 655 +perft 3 22581 +perft 4 474396 +perft 5 16613630 +perft 6 379344541 + +id 312 +epd nqrbbrkn/1p1pppp1/8/p1p4p/4P2P/1N4P1/PPPP1P2/1QRBBKRN w GC - +perft 1 23 +perft 2 597 +perft 3 14468 +perft 4 400357 +perft 5 10096863 +perft 6 294900903 + +id 313 +epd nqrkbbrn/2p1p1pp/pp1p1p2/8/P2N4/2P5/1P1PPPPP/1QRKBBRN w GCgc - +perft 1 32 +perft 2 744 +perft 3 23310 +perft 4 550728 +perft 5 17597164 +perft 6 428786656 + +id 314 +epd n1krbrnb/q1pppppp/p7/1p6/3Q4/2P2P2/PP1PP1PP/N1RKBRNB w FC - +perft 1 43 +perft 2 1038 +perft 3 41327 +perft 4 1074450 +perft 5 40918952 +perft 6 1126603824 + +id 315 +epd nb1rkrbn/p1pp1p1p/qp6/4p1p1/5PP1/P7/1PPPPB1P/NBQRKR1N w FDfd - +perft 1 26 +perft 2 645 +perft 3 16463 +perft 4 445464 +perft 5 11911314 +perft 6 342563372 + +id 316 +epd nqr1krbn/pppp1ppp/8/8/3pP3/5P2/PPPb1NPP/NQRBKRB1 w FCfc - +perft 1 2 +perft 2 51 +perft 3 1047 +perft 4 27743 +perft 5 612305 +perft 6 17040200 + +id 317 +epd n1rkrbbn/pqppppp1/7p/1p6/8/1NPP4/PP1KPPPP/1QR1RBBN w ec - +perft 1 25 +perft 2 674 +perft 3 17553 +perft 4 505337 +perft 5 13421727 +perft 6 403551903 + +id 318 +epd 1qrkrnbb/1p1p1ppp/pnp1p3/8/3PP3/P6P/1PP2PP1/NQRKRNBB w ECec - +perft 1 24 +perft 2 688 +perft 3 17342 +perft 4 511444 +perft 5 13322502 +perft 6 403441498 + +id 319 +epd 1bnrqkrn/2ppppp1/p7/1p1b3p/3PP1P1/8/PPPQ1P1P/BBNR1KRN w GDgd - +perft 1 35 +perft 2 925 +perft 3 32238 +perft 4 857060 +perft 5 30458921 +perft 6 824344087 + +id 320 +epd bnrbqkr1/ppp2pp1/6n1/3pp2p/1P6/2N3N1/P1PPPPPP/B1RBQRK1 w gc - +perft 1 23 +perft 2 704 +perft 3 17345 +perft 4 539587 +perft 5 14154852 +perft 6 450893738 + +id 321 +epd 1nrqkbrn/p1pppppp/8/1p1b4/P6P/5P2/1PPPP1P1/BNRQKBRN w GCgc - +perft 1 19 +perft 2 505 +perft 3 10619 +perft 4 281422 +perft 5 6450025 +perft 6 175593967 + +id 322 +epd b1rqkrnb/ppppppp1/8/6p1/3n4/NP6/P1PPPP1P/B1RQKRNB w FCfc - +perft 1 25 +perft 2 614 +perft 3 15578 +perft 4 377660 +perft 5 10391021 +perft 6 259629603 + +id 323 +epd nbbrqkrn/ppp3p1/3pp3/5p1p/1P2P3/P7/2PPQPPP/NBBR1KRN w GDgd - +perft 1 30 +perft 2 833 +perft 3 25719 +perft 4 717713 +perft 5 22873901 +perft 6 649556666 + +id 324 +epd nr1bqrk1/ppp1pppp/6n1/3pP3/8/5PQb/PPPP2PP/NRBB1KRN w GB - +perft 1 26 +perft 2 734 +perft 3 20161 +perft 4 582591 +perft 5 17199594 +perft 6 512134836 + +id 325 +epd 1rbqkbr1/ppppp1pp/1n6/4np2/3P1P2/6P1/PPPQP2P/NRB1KBRN w GBgb - +perft 1 27 +perft 2 662 +perft 3 17897 +perft 4 447464 +perft 5 13038519 +perft 6 338365642 + +id 326 +epd nr1qkr1b/ppp1pp1p/4bn2/3p2p1/4P3/1Q6/PPPP1PPP/NRB1KRNB w FBfb - +perft 1 33 +perft 2 939 +perft 3 30923 +perft 4 942138 +perft 5 30995969 +perft 6 991509814 + +id 327 +epd nb1qbkrn/pprp1pp1/7p/2p1pB2/Q1PP4/8/PP2PPPP/N1R1BKRN w GCg - +perft 1 47 +perft 2 1128 +perft 3 50723 +perft 4 1306753 +perft 5 56747878 +perft 6 1560584212 + +id 328 +epd nrqb1rkn/pp2pppp/2bp4/2p5/6P1/2P3N1/PP1PPP1P/NRQBBRK1 w - - +perft 1 24 +perft 2 828 +perft 3 21148 +perft 4 723705 +perft 5 19506135 +perft 6 668969549 + +id 329 +epd nrq1bbrn/ppkpp2p/2p3p1/P4p2/8/4P1N1/1PPP1PPP/NRQKBBR1 w GB - +perft 1 25 +perft 2 525 +perft 3 13533 +perft 4 309994 +perft 5 8250997 +perft 6 201795680 + +id 330 +epd Br1kbrn1/pqpppp2/8/6pp/3b2P1/1N6/PPPPPP1P/1RQKBRN1 w FBfb - +perft 1 20 +perft 2 790 +perft 3 18175 +perft 4 695905 +perft 5 17735648 +perft 6 669854148 + +id 331 +epd nbrqkrbn/2p1p1pp/p7/1p1p1p2/4P1P1/5P2/PPPP3P/NBRQKRBN w FCfc - +perft 1 29 +perft 2 771 +perft 3 22489 +perft 4 647106 +perft 5 19192982 +perft 6 591335970 + +id 332 +epd 1rqbkrbn/1ppppp1p/1n6/p1N3p1/8/2P4P/PP1PPPP1/1RQBKRBN w FBfb - +perft 1 29 +perft 2 502 +perft 3 14569 +perft 4 287739 +perft 5 8652810 +perft 6 191762235 + +id 333 +epd 1rqkrbbn/ppnpp1pp/8/2p5/6p1/3P4/PPP1PPPP/NRK1RBBN w eb - +perft 1 19 +perft 2 531 +perft 3 10812 +perft 4 300384 +perft 5 6506674 +perft 6 184309316 + +id 334 +epd nrqkrnbb/p1pp2pp/5p2/4P3/2p5/4N3/PP1PP1PP/NRQKR1BB w EBeb - +perft 1 26 +perft 2 800 +perft 3 23256 +perft 4 756695 +perft 5 23952941 +perft 6 809841274 + +id 335 +epd bbnrkqrn/pp3pp1/4p2p/2pp4/4P1P1/1PB5/P1PP1P1P/1BNRKQRN w GDgd - +perft 1 33 +perft 2 915 +perft 3 30536 +perft 4 878648 +perft 5 29602610 +perft 6 881898159 + +id 336 +epd bnrbkqr1/1p2pppp/6n1/p1pp4/7P/P3P3/1PPPKPP1/BNRB1QRN w gc - +perft 1 19 +perft 2 457 +perft 3 9332 +perft 4 238944 +perft 5 5356253 +perft 6 144653627 + +id 337 +epd b1rkqbrn/pp1p2pp/2n1p3/2p2p2/3P2PP/8/PPP1PP2/BNKRQBRN w gc - +perft 1 30 +perft 2 985 +perft 3 30831 +perft 4 1011700 +perft 5 32684185 +perft 6 1080607773 + +id 338 +epd b1rkqrnb/2ppppp1/np6/p6p/1P6/P2P3P/2P1PPP1/BNRKQRNB w FCfc - +perft 1 26 +perft 2 692 +perft 3 18732 +perft 4 517703 +perft 5 14561181 +perft 6 413226841 + +id 339 +epd nbbrkqrn/1ppp1p2/p6p/4p1p1/5P2/1P5P/P1PPPNP1/NBBRKQR1 w GDgd - +perft 1 22 +perft 2 561 +perft 3 13222 +perft 4 367487 +perft 5 9307003 +perft 6 273928315 + +id 340 +epd nrbbkqrn/p1pppppp/8/1p6/4P3/7Q/PPPP1PPP/NRBBK1RN w GBgb - +perft 1 38 +perft 2 769 +perft 3 28418 +perft 4 632310 +perft 5 23091070 +perft 6 560139600 + +id 341 +epd nrbkqbrn/1pppp2p/8/p4pp1/P4PQ1/8/1PPPP1PP/NRBK1BRN w GBgb - +perft 1 23 +perft 2 507 +perft 3 13067 +perft 4 321423 +perft 5 8887567 +perft 6 237475184 + +id 342 +epd nr1kqr1b/pp2pppp/5n2/2pp4/P5b1/5P2/1PPPPRPP/NRBK1QNB w Bfb - +perft 1 18 +perft 2 626 +perft 3 12386 +perft 4 434138 +perft 5 9465555 +perft 6 335004239 + +id 343 +epd nbkrbqrn/1pppppp1/8/4P2p/pP6/P7/2PP1PPP/NBRKBQRN w GC - +perft 1 22 +perft 2 329 +perft 3 8475 +perft 4 148351 +perft 5 4160034 +perft 6 82875306 + +id 344 +epd nrkb1qrn/pp1pp1pp/8/5p1b/P1p4P/6N1/1PPPPPP1/NRKBBQR1 w GBgb - +perft 1 16 +perft 2 479 +perft 3 9037 +perft 4 275354 +perft 5 5862341 +perft 6 184959796 + +id 345 +epd 1rkq1brn/ppppp1pp/1n6/3b1p2/3N3P/5P2/PPPPP1P1/1RKQBBRN w GBgb - +perft 1 23 +perft 2 614 +perft 3 15324 +perft 4 418395 +perft 5 11090645 +perft 6 313526088 + +id 346 +epd nrk1brnb/pp1ppppp/2p5/3q4/5P2/PP6/1KPPP1PP/NR1QBRNB w fb - +perft 1 25 +perft 2 942 +perft 3 21765 +perft 4 792179 +perft 5 19318837 +perft 6 685549171 + +id 347 +epd nbrkqr1n/1pppp2p/p4pp1/2Bb4/5P2/6P1/PPPPP2P/NBRKQ1RN w Cfc - +perft 1 30 +perft 2 841 +perft 3 24775 +perft 4 677876 +perft 5 20145765 +perft 6 557578726 + +id 348 +epd n1kbqrbn/2p1pppp/1r6/pp1p4/P7/3P4/1PP1PPPP/NRKBQRBN w FBf - +perft 1 21 +perft 2 591 +perft 3 14101 +perft 4 394289 +perft 5 10295086 +perft 6 292131422 + +id 349 +epd nrkqrbb1/ppp1pppp/3p4/8/4P3/2Pn1P2/PP4PP/NRKQRBBN w EBeb - +perft 1 4 +perft 2 88 +perft 3 3090 +perft 4 73414 +perft 5 2640555 +perft 6 66958031 + +id 350 +epd nrkqrnbb/ppppp1p1/7p/1P3p2/3P4/2P5/P3PPPP/NRKQRNBB w EBeb - +perft 1 29 +perft 2 689 +perft 3 21091 +perft 4 508789 +perft 5 16226660 +perft 6 408570219 + +id 351 +epd bbnr1rqn/pp2pkpp/2pp1p2/8/4P1P1/8/PPPP1P1P/BBNRKRQN w FD - +perft 1 21 +perft 2 463 +perft 3 11135 +perft 4 256244 +perft 5 6826249 +perft 6 165025370 + +id 352 +epd bnrbk1qn/1pppprpp/8/p4p1P/6P1/3P4/PPP1PP2/BNRBKRQN w FCc - +perft 1 22 +perft 2 459 +perft 3 11447 +perft 4 268157 +perft 5 7371098 +perft 6 190583454 + +id 353 +epd 1nrkrbqn/p1pp1ppp/4p3/1p6/1PP5/6PB/P2PPPbP/BNRKR1QN w ECec - +perft 1 30 +perft 2 931 +perft 3 29012 +perft 4 887414 +perft 5 28412902 +perft 6 869228014 + +id 354 +epd b1rkr1nb/pppppqp1/n4B2/7p/8/1P4P1/P1PPPP1P/1NKRRQNB w ec - +perft 1 36 +perft 2 934 +perft 3 31790 +perft 4 930926 +perft 5 30392925 +perft 6 952871799 + +id 355 +epd nbbrkrqn/p1ppp1p1/8/1p3p1p/2P3PP/8/PP1PPPQ1/NBBRKR1N w FDfd - +perft 1 34 +perft 2 938 +perft 3 31848 +perft 4 921716 +perft 5 31185844 +perft 6 944483246 + +id 356 +epd 1rbbkrqn/ppp1pp2/1n1p2p1/7p/P3P1P1/3P4/1PP2P1P/NRBBKRQN w FBfb - +perft 1 26 +perft 2 646 +perft 3 18083 +perft 4 472744 +perft 5 14006203 +perft 6 384101783 + +id 357 +epd nrbkrbq1/Qpppp1pp/2n5/5p2/P4P2/6N1/1PPPP1PP/NRBKRB2 w EBeb - +perft 1 27 +perft 2 619 +perft 3 16713 +perft 4 421845 +perft 5 11718463 +perft 6 313794027 + +id 358 +epd 1rbkr1nb/pppp1qpp/1n6/4pp2/1PP1P3/8/PB1P1PPP/NR1KRQNB w EBeb - +perft 1 32 +perft 2 1029 +perft 3 32970 +perft 4 1080977 +perft 5 35483796 +perft 6 1181835398 + +id 359 +epd nbrk1rqn/p1ppp2p/1p6/5ppb/8/1N2P2P/PPPP1PP1/1BKRBRQN w fc - +perft 1 18 +perft 2 594 +perft 3 12350 +perft 4 408544 +perft 5 9329122 +perft 6 315021712 + +id 360 +epd nrkbbrqn/3pppp1/7p/ppp5/P7/1N5P/1PPPPPP1/1RKBBRQN w FBfb - +perft 1 19 +perft 2 417 +perft 3 9026 +perft 4 218513 +perft 5 5236331 +perft 6 137024458 + +id 361 +epd nrkr1bqn/ppp1pppp/3p4/1b6/7P/P7/1PPPPPP1/NRKRBBQN w DBdb - +perft 1 17 +perft 2 457 +perft 3 9083 +perft 4 243872 +perft 5 5503579 +perft 6 150091997 + +id 362 +epd nrkrbqnb/p4ppp/1p2p3/2pp4/6P1/2P2N2/PPNPPP1P/1RKRBQ1B w DBdb - +perft 1 27 +perft 2 755 +perft 3 21012 +perft 4 620093 +perft 5 17883987 +perft 6 547233320 + +id 363 +epd nbkrr1bn/ppB2ppp/4p3/2qp4/4P3/5P2/PPPP2PP/NBRKRQ1N w EC - +perft 1 37 +perft 2 1473 +perft 3 51939 +perft 4 1956521 +perft 5 68070015 +perft 6 2490912491 + +id 364 +epd n1kbrqbn/p1pp1pp1/4p2p/2B5/1r3P2/8/PPPPP1PP/NRKBRQ1N w EBe - +perft 1 30 +perft 2 1029 +perft 3 30874 +perft 4 1053163 +perft 5 32318550 +perft 6 1106487743 + +id 365 +epd nrkrqbbn/2pppp1p/8/pp6/1P1P2p1/P5P1/2P1PP1P/NRKRQBBN w DBdb - +perft 1 22 +perft 2 421 +perft 3 10034 +perft 4 221927 +perft 5 5754555 +perft 6 141245633 + +id 366 +epd nrkr1nbb/1ppp2pp/p3q3/4pp2/2P5/P3P3/1PKP1PPP/NR1RQNBB w db - +perft 1 22 +perft 2 619 +perft 3 13953 +perft 4 411392 +perft 5 9905109 +perft 6 301403003 + +id 367 +epd bbnrkrnq/1pp1p2p/6p1/p2p1p2/8/1P2P3/P1PP1PPP/BBNRKRNQ w FDfd - +perft 1 27 +perft 2 805 +perft 3 21915 +perft 4 688224 +perft 5 19133881 +perft 6 620749189 + +id 368 +epd bnrbkrn1/pp1ppp2/2p3pp/8/2Pq4/P4PP1/1P1PP2P/BNRBKRNQ w FCfc - +perft 1 20 +perft 2 770 +perft 3 16593 +perft 4 577980 +perft 5 13581691 +perft 6 456736500 + +id 369 +epd b1rkrbnq/1pp1pppp/2np4/p5N1/8/1P2P3/P1PP1PPP/BNRKRB1Q w ECec - +perft 1 37 +perft 2 740 +perft 3 27073 +perft 4 581744 +perft 5 21156664 +perft 6 485803600 + +id 370 +epd b1krrnqb/pp1ppp1p/n1p3p1/2N5/6P1/8/PPPPPP1P/B1RKRNQB w EC - +perft 1 34 +perft 2 850 +perft 3 28494 +perft 4 752350 +perft 5 25360295 +perft 6 698159474 + +id 371 +epd 1bbr1rnq/ppppkppp/8/3np3/4P3/3P4/PPP1KPPP/NBBRR1NQ w - - +perft 1 27 +perft 2 704 +perft 3 18290 +perft 4 480474 +perft 5 12817011 +perft 6 341026662 + +id 372 +epd nrbbk1nq/p1p1prpp/1p6/N2p1p2/P7/8/1PPPPPPP/R1BBKRNQ w Fb - +perft 1 23 +perft 2 552 +perft 3 13710 +perft 4 348593 +perft 5 9236564 +perft 6 248469879 + +id 373 +epd 1rbkrb1q/1pppp1pp/1n5n/p4p2/P3P3/1P6/2PPNPPP/NRBKRB1Q w EBeb - +perft 1 22 +perft 2 415 +perft 3 10198 +perft 4 217224 +perft 5 5735644 +perft 6 135295774 + +id 374 +epd nrbkr1qb/1pp1pppp/6n1/p2p4/2P1P3/1N4N1/PP1P1PPP/1RBKR1QB w EBeb - +perft 1 27 +perft 2 709 +perft 3 19126 +perft 4 506214 +perft 5 14192779 +perft 6 380516508 + +id 375 +epd nbrkbrnq/p3p1pp/1pp2p2/3p4/1PP5/4P3/P1KP1PPP/NBR1BRNQ w fc - +perft 1 24 +perft 2 715 +perft 3 18009 +perft 4 535054 +perft 5 14322279 +perft 6 427269976 + +id 376 +epd nrk1brnq/pp1p1pp1/7p/b1p1p3/1P6/6P1/P1PPPPQP/NRKBBRN1 w FBfb - +perft 1 29 +perft 2 675 +perft 3 20352 +perft 4 492124 +perft 5 15316285 +perft 6 389051744 + +id 377 +epd nrkr1bnq/1p2pppp/p2p4/1bp5/PP6/1R5N/2PPPPPP/N1KRBB1Q w Ddb - +perft 1 27 +perft 2 744 +perft 3 20494 +perft 4 571209 +perft 5 16188945 +perft 6 458900901 + +id 378 +epd nrk1b1qb/pppn1ppp/3rp3/3p4/2P3P1/3P4/PPN1PP1P/1RKRBNQB w DBb - +perft 1 35 +perft 2 941 +perft 3 33203 +perft 4 935791 +perft 5 33150360 +perft 6 968024386 + +id 379 +epd nb1rrnbq/ppkp1ppp/8/2p1p3/P7/1N2P3/1PPP1PPP/1BKRRNBQ w - - +perft 1 19 +perft 2 451 +perft 3 9655 +perft 4 235472 +perft 5 5506897 +perft 6 139436165 + +id 380 +epd nrkbrnbq/4pppp/1ppp4/p7/2P1P3/3P2N1/PP3PPP/NRKBR1BQ w EBeb - +perft 1 29 +perft 2 591 +perft 3 17132 +perft 4 384358 +perft 5 11245508 +perft 6 270967202 + +id 381 +epd nrkrnbbq/3p1ppp/1p6/p1p1p3/3P2P1/P4Q2/1PP1PP1P/NRKRNBB1 w DBdb - +perft 1 38 +perft 2 792 +perft 3 28597 +perft 4 640961 +perft 5 22654797 +perft 6 540864616 + +id 382 +epd nr1rnqbb/ppp1pp1p/3k2p1/3p4/1P5P/3P1N2/P1P1PPP1/NRKR1QBB w DB - +perft 1 25 +perft 2 758 +perft 3 18547 +perft 4 543643 +perft 5 13890077 +perft 6 402109399 + +id 383 +epd bbqrnnkr/1ppp1p1p/5p2/p5p1/P7/1P4P1/2PPPP1P/1BQRNNKR w HDhd - +perft 1 20 +perft 2 322 +perft 3 7224 +perft 4 145818 +perft 5 3588435 +perft 6 82754650 + +id 384 +epd bqrb2k1/pppppppr/5nnp/8/3P1P2/4P1N1/PPP3PP/BQRBN1KR w HCc - +perft 1 25 +perft 2 597 +perft 3 15872 +perft 4 397970 +perft 5 11162476 +perft 6 295682250 + +id 385 +epd bqrnn1kr/1pppbppp/8/4p3/1p6/2P1N2P/P2PPPP1/BQR1NBKR w HChc - +perft 1 34 +perft 2 921 +perft 3 31695 +perft 4 864023 +perft 5 30126510 +perft 6 850296236 + +id 386 +epd bqr1nkr1/pppppp2/2n3p1/7p/1P1b1P2/8/PQP1P1PP/B1RNNKRB w GCgc - +perft 1 23 +perft 2 788 +perft 3 21539 +perft 4 686795 +perft 5 20849374 +perft 6 645694580 + +id 387 +epd qbbrnn1r/1pppp1pk/p7/5p1p/P2P3P/3N4/1PP1PPP1/QBBR1NKR w HD - +perft 1 34 +perft 2 713 +perft 3 24475 +perft 4 562189 +perft 5 19494094 +perft 6 482645160 + +id 388 +epd qrbb2kr/p1pppppp/1p1n4/8/1P3n2/P7/Q1PPP1PP/1RBBNNKR w HBhb - +perft 1 28 +perft 2 977 +perft 3 26955 +perft 4 949925 +perft 5 27802999 +perft 6 992109168 + +id 389 +epd qrb2bkr/1pp1pppp/2np1n2/pN6/3P4/4B3/PPP1PPPP/QR2NBKR w HBhb - +perft 1 27 +perft 2 730 +perft 3 20534 +perft 4 585091 +perft 5 17005916 +perft 6 507008968 + +id 390 +epd qrbnnkrb/pp2pp1p/8/2pp2p1/7P/P1P5/QP1PPPP1/1RBNNKRB w GBgb - +perft 1 24 +perft 2 813 +perft 3 21142 +perft 4 707925 +perft 5 19615756 +perft 6 655850285 + +id 391 +epd 1brnb1kr/p1pppppp/1p6/8/4q2n/1P2P1P1/PNPP1P1P/QBR1BNKR w HChc - +perft 1 17 +perft 2 734 +perft 3 13462 +perft 4 530809 +perft 5 11032633 +perft 6 416356876 + +id 392 +epd 1rnbbnkr/1pp1pppp/1q1p4/p7/4P3/5PN1/PPPP1BPP/QRNB2KR w HBhb - +perft 1 26 +perft 2 809 +perft 3 21764 +perft 4 706677 +perft 5 20292750 +perft 6 675408811 + +id 393 +epd qrnnbb1Q/ppp1pk1p/3p2p1/5p2/PP6/5P2/2PPP1PP/1RNNBBKR w HB - +perft 1 37 +perft 2 751 +perft 3 27902 +perft 4 603931 +perft 5 22443036 +perft 6 515122176 + +id 394 +epd qrnnbkrb/p3p1pp/3p1p2/1pp5/PP2P3/8/2PP1PPP/QRNNBRKB w gb - +perft 1 30 +perft 2 906 +perft 3 27955 +perft 4 872526 +perft 5 27658191 +perft 6 890966633 + +id 395 +epd qbrnnkbr/1p2pp1p/p1p3p1/3p4/6P1/P1N4P/1PPPPP2/QBR1NKBR w HChc - +perft 1 26 +perft 2 701 +perft 3 18930 +perft 4 521377 +perft 5 14733245 +perft 6 416881799 + +id 396 +epd qr1b1kbr/1p1ppppp/1n1n4/p1p5/4P3/5NPP/PPPP1P2/QRNB1KBR w HBhb - +perft 1 26 +perft 2 649 +perft 3 17235 +perft 4 451997 +perft 5 12367604 +perft 6 342165821 + +id 397 +epd qrnnkb1r/1pppppp1/7p/p4b2/4P3/5P1P/PPPP2PR/QRNNKBB1 w Bhb - +perft 1 34 +perft 2 941 +perft 3 31720 +perft 4 901240 +perft 5 30307554 +perft 6 888709821 + +id 398 +epd qr1nkrbb/p2ppppp/1pp5/8/3Pn3/1NP3P1/PP2PP1P/QR1NKRBB w FBfb - +perft 1 19 +perft 2 505 +perft 3 11107 +perft 4 294251 +perft 5 7046501 +perft 6 190414579 + +id 399 +epd bbrqn1kr/1pppp1pp/4n3/5p2/p5P1/3P4/PPP1PPKP/BBRQNN1R w hc - +perft 1 24 +perft 2 573 +perft 3 12963 +perft 4 335845 +perft 5 8191054 +perft 6 227555387 + +id 400 +epd brqb1nkr/pppppp1p/8/4N1pn/5P2/6P1/PPPPP2P/BRQB1NKR w HBhb - +perft 1 26 +perft 2 550 +perft 3 14338 +perft 4 331666 +perft 5 8903754 +perft 6 223437427 + +id 401 +epd brqnn1kr/pp3ppp/2pbp3/3p4/8/2NPP3/PPP1BPPP/BRQ1N1KR w HBhb - +perft 1 27 +perft 2 780 +perft 3 20760 +perft 4 589328 +perft 5 16243731 +perft 6 463883447 + +id 402 +epd brq1nkrb/ppp2ppp/8/n2pp2P/P7/4P3/1PPP1PP1/BRQNNKRB w GBgb - +perft 1 17 +perft 2 426 +perft 3 8295 +perft 4 235162 +perft 5 5048497 +perft 6 153986034 + +id 403 +epd rbbqn1kr/pp2p1pp/6n1/2pp1p2/2P4P/P7/BP1PPPP1/R1BQNNKR w HAha - +perft 1 27 +perft 2 916 +perft 3 25798 +perft 4 890435 +perft 5 26302461 +perft 6 924181432 + +id 404 +epd 1qbbn1kr/1ppppppp/r3n3/8/p1P5/P7/1P1PPPPP/RQBBNNKR w HAh - +perft 1 29 +perft 2 817 +perft 3 24530 +perft 4 720277 +perft 5 22147642 +perft 6 670707652 + +id 405 +epd rqbnnbkr/ppp1ppp1/7p/3p4/PP6/7P/1NPPPPP1/RQB1NBKR w HAa - +perft 1 23 +perft 2 572 +perft 3 14509 +perft 4 381474 +perft 5 10416981 +perft 6 288064942 + +id 406 +epd r1bnnkrb/q1ppp1pp/p7/1p3pB1/2P1P3/3P4/PP3PPP/RQ1NNKRB w GAga - +perft 1 31 +perft 2 925 +perft 3 27776 +perft 4 860969 +perft 5 26316355 +perft 6 843078864 + +id 407 +epd rbqnb1kr/ppppp1pp/5p2/5N2/7P/1n3P2/PPPPP1P1/RBQNB1KR w HAha - +perft 1 32 +perft 2 864 +perft 3 27633 +perft 4 766551 +perft 5 24738875 +perft 6 707188107 + +id 408 +epd rqnbbn1r/ppppppp1/6k1/8/6Pp/2PN4/PP1PPPKP/RQ1BBN1R w - - +perft 1 27 +perft 2 566 +perft 3 15367 +perft 4 347059 +perft 5 9714509 +perft 6 234622128 + +id 409 +epd rqnnbbkr/p1p2pp1/1p1p3p/4p3/4NP2/6P1/PPPPP2P/RQN1BBKR w HAha - +perft 1 27 +perft 2 631 +perft 3 17923 +perft 4 452734 +perft 5 13307890 +perft 6 356279813 + +id 410 +epd 1qnnbrkb/rppp1ppp/p3p3/8/4P3/2PP1P2/PP4PP/RQNNBKRB w GA - +perft 1 24 +perft 2 479 +perft 3 12135 +perft 4 271469 +perft 5 7204345 +perft 6 175460841 + +id 411 +epd rbqnn1br/p1pppk1p/1p4p1/5p2/8/P1P2P2/1PBPP1PP/R1QNNKBR w HA - +perft 1 31 +perft 2 756 +perft 3 23877 +perft 4 625194 +perft 5 20036784 +perft 6 554292502 + +id 412 +epd rqnbnkbr/1ppppp2/p5p1/8/1P4p1/4PP2/P1PP3P/RQNBNKBR w HAha - +perft 1 24 +perft 2 715 +perft 3 18536 +perft 4 575589 +perft 5 16013189 +perft 6 515078271 + +id 413 +epd rq1nkbbr/1p2pppp/p2n4/2pp4/1P4P1/P2N4/2PPPP1P/RQ1NKBBR w HAha - +perft 1 27 +perft 2 694 +perft 3 19840 +perft 4 552904 +perft 5 16685687 +perft 6 494574415 + +id 414 +epd r1nnkrbb/pp1pppp1/2p3q1/7p/8/1PPP3P/P3PPP1/RQNNKRBB w FAfa - +perft 1 18 +perft 2 520 +perft 3 10808 +perft 4 329085 +perft 5 7508201 +perft 6 235103697 + +id 415 +epd bbrnqk1r/pppp3p/6p1/4pp2/3P2P1/8/PPP1PP1P/BBRN1NKR w HC - +perft 1 22 +perft 2 566 +perft 3 12965 +perft 4 362624 +perft 5 8721079 +perft 6 259069471 + +id 416 +epd brnb1nkr/pppqpp2/3p2pp/8/3PP3/1P6/PBP2PPP/1RNBQNKR w HBhb - +perft 1 32 +perft 2 859 +perft 3 28517 +perft 4 817464 +perft 5 27734108 +perft 6 829785474 + +id 417 +epd brnq1b1r/ppp1ppkp/3p1np1/8/8/5P1P/PPPPPKPR/BRNQNB2 w - - +perft 1 21 +perft 2 511 +perft 3 10951 +perft 4 273756 +perft 5 6372681 +perft 6 167139732 + +id 418 +epd brnq1rkb/1pppppp1/3n3p/p7/8/P4NP1/1PPPPPRP/BRNQ1K1B w B - +perft 1 25 +perft 2 548 +perft 3 14049 +perft 4 341208 +perft 5 9015901 +perft 6 235249649 + +id 419 +epd rbb1qnkr/p1ppp1pp/1p3p2/6n1/8/1PN1P2P/P1PP1PP1/RBB1QNKR w HAha - +perft 1 25 +perft 2 673 +perft 3 16412 +perft 4 467660 +perft 5 12099119 +perft 6 361714466 + +id 420 +epd rnbb1nkr/1ppp1ppp/4p3/p5q1/6P1/1PP5/PB1PPP1P/RN1BQNKR w HAha - +perft 1 19 +perft 2 663 +perft 3 14149 +perft 4 489653 +perft 5 11491355 +perft 6 399135495 + +id 421 +epd rnbqnbkr/1pp1p2p/3p1p2/p5p1/5PP1/2P5/PPNPP2P/RNBQ1BKR w HAha - +perft 1 24 +perft 2 647 +perft 3 16679 +perft 4 461931 +perft 5 12649636 +perft 6 361157611 + +id 422 +epd rnb2krb/pppqppnp/8/3p2p1/1P4P1/7P/P1PPPPB1/RNBQNKR1 w GAga - +perft 1 24 +perft 2 722 +perft 3 18749 +perft 4 605229 +perft 5 16609220 +perft 6 563558512 + +id 423 +epd rbnqb1kr/pppn1pp1/3p3p/4p3/1P6/P7/R1PPPPPP/1BNQBNKR w Hha - +perft 1 20 +perft 2 538 +perft 3 12277 +perft 4 345704 +perft 5 8687621 +perft 6 255304141 + +id 424 +epd rnqb1nkr/p1pbp1pp/8/1pPp1p2/P2P4/8/1P2PPPP/RNQBBNKR w HAha - +perft 1 35 +perft 2 764 +perft 3 26952 +perft 4 632796 +perft 5 22592380 +perft 6 564255328 + +id 425 +epd rnq1bbkr/1p1ppp1p/4n3/p1p3p1/P1PP4/8/RP2PPPP/1NQNBBKR w Hha - +perft 1 29 +perft 2 709 +perft 3 21296 +perft 4 570580 +perft 5 17597398 +perft 6 506140370 + +id 426 +epd 1nqnbkrb/1pppp2p/r7/p4pp1/3P4/8/PPPBPPPP/RNQNK1RB w g - +perft 1 27 +perft 2 1028 +perft 3 28534 +perft 4 1050834 +perft 5 30251988 +perft 6 1096869832 + +id 427 +epd rbnqnkbr/p1pp1p1p/8/1p2p3/3P2pP/2P5/PP2PPP1/RBNQNKBR w HAha - +perft 1 32 +perft 2 832 +perft 3 27120 +perft 4 750336 +perft 5 24945574 +perft 6 724171581 + +id 428 +epd rnq1nkbr/1p1p1ppp/2p1pb2/p7/7P/2P5/PPNPPPPB/RNQB1K1R w HAha - +perft 1 31 +perft 2 779 +perft 3 24010 +perft 4 638640 +perft 5 19919434 +perft 6 551494771 + +id 429 +epd rnqnk1br/p1ppp1bp/1p3p2/6p1/4N3/P5P1/1PPPPP1P/R1QNKBBR w HAha - +perft 1 25 +perft 2 717 +perft 3 19396 +perft 4 576577 +perft 5 16525239 +perft 6 507175842 + +id 430 +epd rnq1krbb/p1p1pppp/8/1p1p4/1n5B/2N2P2/PPPPP1PP/RNQ1KR1B w FAfa - +perft 1 28 +perft 2 867 +perft 3 24029 +perft 4 735686 +perft 5 21112751 +perft 6 654808184 + +id 431 +epd bbrnnqkr/1pp1pppp/3p4/p7/P3P3/7P/1PPP1PP1/BBRNNQKR w HChc - +perft 1 24 +perft 2 405 +perft 3 11025 +perft 4 210557 +perft 5 6196438 +perft 6 131401224 + +id 432 +epd brnbnqkr/p1ppp3/1p5p/5Pp1/5P2/3N4/PPPPP2P/BRNB1QKR w HBhb g6 +perft 1 25 +perft 2 785 +perft 3 21402 +perft 4 698331 +perft 5 20687969 +perft 6 695850727 + +id 433 +epd br1nqbkr/1ppppp2/pn6/6pp/2PP4/1N4P1/PP2PP1P/BR1NQBKR w HBhb - +perft 1 25 +perft 2 596 +perft 3 16220 +perft 4 421882 +perft 5 12185361 +perft 6 337805606 + +id 434 +epd 1rnnqkrb/p2ppp1p/1pp5/2N3p1/8/1P6/P1PPPPKP/BR1NQ1RB w gb - +perft 1 38 +perft 2 960 +perft 3 34831 +perft 4 913665 +perft 5 32490040 +perft 6 880403591 + +id 435 +epd rbbnnqkr/pp3pp1/2p1p3/3p3p/3P3P/1PP5/P3PPP1/RBBNNQKR w HAha - +perft 1 30 +perft 2 785 +perft 3 23079 +perft 4 656618 +perft 5 19885037 +perft 6 599219582 + +id 436 +epd rn1bnqkr/p1ppppp1/8/1p5p/P4P1P/3N4/1PPPP1b1/RNBB1QKR w HAha - +perft 1 27 +perft 2 752 +perft 3 21735 +perft 4 613194 +perft 5 18862234 +perft 6 547415271 + +id 437 +epd 1nbnqbkr/1p1p1ppp/r3p3/p1p5/P3P3/3Q4/1PPP1PPP/RNBN1BKR w HAh - +perft 1 33 +perft 2 721 +perft 3 24278 +perft 4 572535 +perft 5 19648535 +perft 6 496023732 + +id 438 +epd rnbnqkrb/2pppppp/1p6/p7/1PP5/4N2P/P2PPPP1/RNB1QKRB w GAg - +perft 1 23 +perft 2 570 +perft 3 14225 +perft 4 374196 +perft 5 10022614 +perft 6 279545007 + +id 439 +epd rbnnbq1r/ppppppkp/6p1/N7/4P3/P7/1PPP1PPP/RB1NBQKR w HA - +perft 1 27 +perft 2 620 +perft 3 18371 +perft 4 440594 +perft 5 13909432 +perft 6 349478320 + +id 440 +epd r1nbbqkr/pppppp1p/8/8/1n3Pp1/3N1QP1/PPPPP2P/RN1BB1KR w HAha - +perft 1 31 +perft 2 791 +perft 3 25431 +perft 4 682579 +perft 5 22408813 +perft 6 636779732 + +id 441 +epd rnq1bbkr/pp1p1ppp/2pnp3/8/7P/1QP5/PP1PPPPR/RNN1BBK1 w Aha - +perft 1 28 +perft 2 559 +perft 3 16838 +perft 4 390887 +perft 5 12242780 +perft 6 315431511 + +id 442 +epd rnnqbrkb/2ppppp1/1p1N4/p6p/4P3/8/PPPP1PPP/R1NQBKRB w GA - +perft 1 32 +perft 2 638 +perft 3 20591 +perft 4 438792 +perft 5 14395828 +perft 6 331782223 + +id 443 +epd rbnnq1br/pppp1kp1/4pp2/7p/PP6/2PP4/4PPPP/RBNNQKBR w HA - +perft 1 21 +perft 2 521 +perft 3 12201 +perft 4 320429 +perft 5 8239159 +perft 6 227346638 + +id 444 +epd rnnbqkbr/p2ppp2/7p/1pp3p1/2P2N2/8/PP1PPPPP/RN1BQKBR w HAha - +perft 1 25 +perft 2 528 +perft 3 13896 +perft 4 326094 +perft 5 9079829 +perft 6 232750602 + +id 445 +epd rnn1kbbr/ppppqp2/6p1/2N1p2p/P7/2P5/1P1PPPPP/RN1QKBBR w HAha - +perft 1 27 +perft 2 801 +perft 3 22088 +perft 4 707078 +perft 5 20334071 +perft 6 682580976 + +id 446 +epd rnnqkrbb/p1p1p1pp/1p3p2/8/3p2Q1/P1P1P3/1P1P1PPP/RNN1KRBB w FAfa - +perft 1 37 +perft 2 1014 +perft 3 34735 +perft 4 998999 +perft 5 32921537 +perft 6 988770109 + +id 447 +epd bbrnk1qr/1pppppp1/p4n1p/8/P2P2N1/8/1PP1PPPP/BBR1NKQR w HC - +perft 1 21 +perft 2 481 +perft 3 11213 +perft 4 279993 +perft 5 7015419 +perft 6 187564853 + +id 448 +epd brnbnkqr/1pp1p1p1/p2p1p2/7p/1P4PP/8/PBPPPP2/1RNBNKQR w HBhb - +perft 1 31 +perft 2 743 +perft 3 24260 +perft 4 660177 +perft 5 22391185 +perft 6 653721389 + +id 449 +epd br2kbqr/ppppp1pp/3n1p2/3P4/3n3P/3N4/PPP1PPP1/BR1NKBQR w HBhb - +perft 1 25 +perft 2 872 +perft 3 22039 +perft 4 748726 +perft 5 20281962 +perft 6 685749952 + +id 450 +epd br1nkqrb/ppppppp1/8/7p/4P3/n1P2PP1/PP1P3P/BRNNKQRB w GBgb - +perft 1 28 +perft 2 607 +perft 3 16934 +perft 4 396483 +perft 5 11607818 +perft 6 294181806 + +id 451 +epd rbbn1kqr/pp1pp1p1/2pn3p/5p2/5P2/1P1N4/PNPPP1PP/RBB2KQR w HAha - +perft 1 27 +perft 2 725 +perft 3 21543 +perft 4 616082 +perft 5 19239812 +perft 6 581716972 + +id 452 +epd rnbbnk1r/pp1ppp1p/6q1/2p5/PP4p1/4P3/2PP1PPP/RNBBNKQR w HAha - +perft 1 25 +perft 2 1072 +perft 3 26898 +perft 4 1088978 +perft 5 28469879 +perft 6 1122703887 + +id 453 +epd rnbnkbqr/1pp3pp/3p4/p3pp2/3P2P1/2N1N3/PPP1PP1P/R1B1KBQR w HAha - +perft 1 31 +perft 2 1028 +perft 3 32907 +perft 4 1095472 +perft 5 36025223 +perft 6 1211187800 + +id 454 +epd r1bnkqrb/1ppppppp/p3n3/8/6P1/4N3/PPPPPPRP/RNB1KQ1B w Aga - +perft 1 23 +perft 2 457 +perft 3 11416 +perft 4 250551 +perft 5 6666787 +perft 6 159759052 + +id 455 +epd rbn1bkqr/p1pp1pp1/1pn5/4p2p/7P/1PBP4/P1P1PPP1/RBNN1KQR w HAha - +perft 1 23 +perft 2 470 +perft 3 11649 +perft 4 264274 +perft 5 6963287 +perft 6 172833738 + +id 456 +epd rnnbbkqr/3ppppp/p7/1pp5/P6P/6P1/1PPPPP2/RNNBBKQR w HAha - +perft 1 26 +perft 2 569 +perft 3 15733 +perft 4 375556 +perft 5 11008114 +perft 6 284485303 + +id 457 +epd r1nk1bqr/1pppp1pp/2n5/p4p1b/5P2/1N4B1/PPPPP1PP/RN1K1BQR w HAha - +perft 1 25 +perft 2 824 +perft 3 21983 +perft 4 738366 +perft 5 20904119 +perft 6 716170771 + +id 458 +epd r1nkbqrb/p2pppp1/npp4p/8/4PP2/2N4P/PPPP2P1/R1NKBQRB w GAga - +perft 1 31 +perft 2 548 +perft 3 17480 +perft 4 349633 +perft 5 11469548 +perft 6 255067638 + +id 459 +epd rbnnkqbr/ppppp2p/5p2/6p1/2P1B3/P6P/1P1PPPP1/R1NNKQBR w HAha - +perft 1 31 +perft 2 809 +perft 3 24956 +perft 4 680747 +perft 5 21247414 +perft 6 606221516 + +id 460 +epd 1r1bkqbr/pppp1ppp/2nnp3/8/2P5/N4P2/PP1PP1PP/1RNBKQBR w Hh - +perft 1 28 +perft 2 810 +perft 3 22844 +perft 4 694599 +perft 5 20188622 +perft 6 636748147 + +id 461 +epd rn1kqbbr/p1pppp1p/1p4p1/1n6/1P2P3/4Q2P/P1PP1PP1/RNNK1BBR w HAha - +perft 1 39 +perft 2 848 +perft 3 30100 +perft 4 724426 +perft 5 25594662 +perft 6 659615710 + +id 462 +epd rn1kqrbb/pppppppp/8/8/2nP2P1/1P2P3/P1P2P1P/RNNKQRBB w FAfa - +perft 1 29 +perft 2 766 +perft 3 21701 +perft 4 567971 +perft 5 16944425 +perft 6 456898648 + +id 463 +epd b1rnnkrq/bpppppp1/7p/8/1p6/2B5/PNPPPPPP/1BR1NKRQ w GCgc - +perft 1 25 +perft 2 667 +perft 3 17253 +perft 4 472678 +perft 5 12865247 +perft 6 365621294 + +id 464 +epd brnb1krq/pppppppp/8/5P2/2P1n2P/8/PP1PP1P1/BRNBNKRQ w GBgb - +perft 1 23 +perft 2 620 +perft 3 14882 +perft 4 402561 +perft 5 10776855 +perft 6 300125003 + +id 465 +epd b1nnkbrq/pr1pppp1/1p5p/2p5/P2N1P2/8/1PPPP1PP/BR1NKBRQ w GBg - +perft 1 24 +perft 2 472 +perft 3 12181 +perft 4 267398 +perft 5 7370758 +perft 6 178605165 + +id 466 +epd br1nkrqb/p1p1p1pp/3n4/1p1p1p2/5N1P/4P3/PPPP1PP1/BR1NKRQB w FBfb - +perft 1 24 +perft 2 775 +perft 3 19398 +perft 4 624309 +perft 5 16429837 +perft 6 539767605 + +id 467 +epd rbbnnkrq/p2pp1pp/2p5/5p2/1pPP1B2/P7/1P2PPPP/RB1NNKRQ w GAga - +perft 1 34 +perft 2 921 +perft 3 30474 +perft 4 849933 +perft 5 28095833 +perft 6 806446436 + +id 468 +epd rnbbnkr1/1p1ppp1p/2p3p1/p7/2Pq4/1P1P4/P2BPPPP/RN1BNKRQ w GAga - +perft 1 26 +perft 2 1139 +perft 3 29847 +perft 4 1204863 +perft 5 32825932 +perft 6 1281760240 + +id 469 +epd 1rbnkbrq/pppppp2/n5pp/2P5/P7/4N3/1P1PPPPP/RNB1KBRQ w GAg - +perft 1 23 +perft 2 574 +perft 3 14146 +perft 4 391413 +perft 5 10203438 +perft 6 301874034 + +id 470 +epd 1nbnkr1b/rppppppq/p7/7p/1P5P/3P2P1/P1P1PP2/RNBNKRQB w FAf - +perft 1 33 +perft 2 823 +perft 3 26696 +perft 4 724828 +perft 5 23266182 +perft 6 672294132 + +id 471 +epd rbn1bkrq/ppppp3/4n2p/5pp1/1PN5/2P5/P2PPPPP/RBN1BKRQ w GAga - +perft 1 27 +perft 2 859 +perft 3 24090 +perft 4 796482 +perft 5 23075785 +perft 6 789152120 + +id 472 +epd r1nbbkrq/1ppp2pp/2n2p2/p3p3/5P2/1N4BP/PPPPP1P1/RN1B1KRQ w GAga - +perft 1 25 +perft 2 774 +perft 3 20141 +perft 4 618805 +perft 5 16718577 +perft 6 515864053 + +id 473 +epd rnnkbbrq/1pppp1p1/5p2/7p/p6P/3N1P2/PPPPP1PQ/RN1KBBR1 w GAga - +perft 1 29 +perft 2 673 +perft 3 20098 +perft 4 504715 +perft 5 15545590 +perft 6 416359581 + +id 474 +epd r1nkbrqb/pppp1p2/n3p1p1/7p/2P2P2/1P6/P2PPQPP/RNNKBR1B w FAfa - +perft 1 27 +perft 2 722 +perft 3 21397 +perft 4 593762 +perft 5 18742426 +perft 6 537750982 + +id 475 +epd rbnnkr1q/1ppp2pp/p4p2/P2bp3/4P2P/8/1PPP1PP1/RBNNKRBQ w FAfa - +perft 1 26 +perft 2 848 +perft 3 23387 +perft 4 741674 +perft 5 21591790 +perft 6 675163653 + +id 476 +epd rn1bkrb1/1ppppp1p/pn4p1/8/P2q3P/3P4/NPP1PPP1/RN1BKRBQ w FAfa - +perft 1 22 +perft 2 803 +perft 3 18322 +perft 4 632920 +perft 5 15847763 +perft 6 536419559 + +id 477 +epd rn1krbbq/pppp1npp/4pp2/8/4P2P/3P2P1/PPP2P2/RNNKRBBQ w EAea - +perft 1 29 +perft 2 810 +perft 3 23968 +perft 4 670500 +perft 5 20361517 +perft 6 575069358 + +id 478 +epd rnn1rqbb/ppkp1pp1/2p1p2p/2P5/8/3P1P2/PP2P1PP/RNNKRQBB w EA - +perft 1 22 +perft 2 506 +perft 3 11973 +perft 4 292344 +perft 5 7287368 +perft 6 189865944 + +id 479 +epd bbqr1knr/pppppp1p/8/4n1p1/2P1P3/6P1/PPQP1P1P/BB1RNKNR w HDhd - +perft 1 26 +perft 2 650 +perft 3 18253 +perft 4 481200 +perft 5 14301029 +perft 6 394943978 + +id 480 +epd bq1bnknr/pprppp1p/8/2p3p1/4PPP1/8/PPPP3P/BQRBNKNR w HCh - +perft 1 24 +perft 2 548 +perft 3 14021 +perft 4 347611 +perft 5 9374021 +perft 6 250988458 + +id 481 +epd bqrnkb1r/1p2pppp/p1pp3n/5Q2/2P4P/5N2/PP1PPPP1/B1RNKB1R w HChc - +perft 1 46 +perft 2 823 +perft 3 33347 +perft 4 673905 +perft 5 26130444 +perft 6 582880996 + +id 482 +epd bq1rknrb/pppppp1p/4n3/6p1/4P1P1/3P1P2/PPP4P/BQRNKNRB w GCg - +perft 1 23 +perft 2 618 +perft 3 14815 +perft 4 419474 +perft 5 10606831 +perft 6 315124518 + +id 483 +epd q1brnknr/pp1pp1p1/8/2p2p1p/5b2/P4N2/1PPPP1PP/QBBRK1NR w hd - +perft 1 22 +perft 2 675 +perft 3 15778 +perft 4 473994 +perft 5 12077228 +perft 6 368479752 + +id 484 +epd qrbbnknr/1p1ppp1p/p1p5/8/1P2P1p1/3P1B2/P1P2PPP/QRB1NKNR w HBhb - +perft 1 32 +perft 2 722 +perft 3 24049 +perft 4 569905 +perft 5 19584539 +perft 6 484814878 + +id 485 +epd qrb1kbnr/p3pppp/2n5/1ppp4/7P/3P1P2/PPP1P1PR/QRBNKBN1 w Bhb - +perft 1 26 +perft 2 831 +perft 3 22606 +perft 4 724505 +perft 5 20500804 +perft 6 662608969 + +id 486 +epd qrbnknrb/ppp1pp2/6p1/7p/PPNp4/8/2PPPPPP/QRB1KNRB w GBgb - +perft 1 31 +perft 2 840 +perft 3 26762 +perft 4 742772 +perft 5 24422614 +perft 6 701363800 + +id 487 +epd qbrnbknr/pp1pp1pp/8/2p2p2/3Q4/PP6/2PPPPPP/1BRNBKNR w HChc - +perft 1 38 +perft 2 1121 +perft 3 39472 +perft 4 1198438 +perft 5 41108769 +perft 6 1285503872 + +id 488 +epd qr1bbk1r/pppppp1p/1n6/5np1/4B3/1PP5/P2PPPPP/QRN1BKNR w HBhb - +perft 1 25 +perft 2 694 +perft 3 16938 +perft 4 472950 +perft 5 12164609 +perft 6 345122090 + +id 489 +epd qrnkbbnr/1p1pp2p/p7/2p1Npp1/6P1/7P/PPPPPP2/QR1KBBNR w HBhb - +perft 1 27 +perft 2 586 +perft 3 16348 +perft 4 393391 +perft 5 11409633 +perft 6 298054792 + +id 490 +epd qrnkbnrb/pp1p1p2/2p1p1pp/4N3/P4P2/8/1PPPP1PP/QR1KBNRB w GBgb - +perft 1 32 +perft 2 645 +perft 3 20737 +perft 4 460319 +perft 5 15037464 +perft 6 358531599 + +id 491 +epd qbrnknbr/1pppppp1/p6p/8/1P6/3PP3/PQP2PPP/1BRNKNBR w HChc - +perft 1 26 +perft 2 595 +perft 3 16755 +perft 4 415022 +perft 5 12214768 +perft 6 323518628 + +id 492 +epd qrnbk1br/1ppppp1p/p5p1/8/4Pn2/4K1P1/PPPP1P1P/QRNB1NBR w hb - +perft 1 24 +perft 2 609 +perft 3 13776 +perft 4 359415 +perft 5 8538539 +perft 6 230364479 + +id 493 +epd qrnk1bbr/1pnp1ppp/p1p1p3/8/3Q4/1P1N3P/P1PPPPP1/1RNK1BBR w HBhb - +perft 1 43 +perft 2 1106 +perft 3 42898 +perft 4 1123080 +perft 5 41695761 +perft 6 1113836402 + +id 494 +epd qrnknrb1/pppppp2/8/6pp/4P2P/3P1P2/PbP3P1/QRNKNRBB w FBfb - +perft 1 24 +perft 2 658 +perft 3 17965 +perft 4 488373 +perft 5 14457245 +perft 6 400971226 + +id 495 +epd bbrqnrk1/ppp2ppp/7n/3pp3/8/P4N1N/1PPPPPPP/BBRQ1RK1 w - - +perft 1 22 +perft 2 503 +perft 3 12078 +perft 4 310760 +perft 5 8080951 +perft 6 224960353 + +id 496 +epd brqbnk1r/1ppp1ppp/8/p3pn2/8/2PP1P2/PP2PKPP/BRQBN1NR w hb - +perft 1 25 +perft 2 745 +perft 3 19387 +perft 4 570459 +perft 5 15520298 +perft 6 460840861 + +id 497 +epd brqnkbnr/pp2pp1p/3p4/2p5/5p2/3P3P/PPP1PPP1/B1RNKBNR w Hhb - +perft 1 19 +perft 2 516 +perft 3 10755 +perft 4 312996 +perft 5 6995034 +perft 6 214340699 + +id 498 +epd brq1kn1b/1ppppprp/2n3p1/p7/P1N5/6P1/1PPPPP1P/BRQNK1RB w GBb - +perft 1 29 +perft 2 557 +perft 3 16739 +perft 4 352277 +perft 5 10840256 +perft 6 249999654 + +id 499 +epd rbbq1k1r/ppp1pppp/7n/1n1p4/5P2/P2P4/1PPBP1PP/RB1QNKNR w HAha - +perft 1 25 +perft 2 769 +perft 3 20110 +perft 4 638340 +perft 5 17438715 +perft 6 570893953 + +id 500 +epd r1bbnk1r/qpp1pppp/p6n/3p4/1P6/5N1P/P1PPPPP1/RQBBK1NR w ha - +perft 1 23 +perft 2 728 +perft 3 18209 +perft 4 587364 +perft 5 16053564 +perft 6 529082811 + +id 501 +epd rqbnkbnr/1pp2p1p/3p4/p3p1p1/8/2P2P2/PP1PPNPP/RQBNKB1R w HAha - +perft 1 26 +perft 2 772 +perft 3 21903 +perft 4 653704 +perft 5 19571559 +perft 6 593915677 + +id 502 +epd r1bnknrb/pqppp1p1/1p5p/5p2/7P/3P2N1/PPP1PPP1/RQBNK1RB w GAga - +perft 1 27 +perft 2 748 +perft 3 20291 +perft 4 597105 +perft 5 16324542 +perft 6 506453626 + +id 503 +epd rbqnbknr/pp1pppp1/8/2p5/3P3p/5N1P/PPP1PPPR/RBQNBK2 w Aha - +perft 1 30 +perft 2 859 +perft 3 26785 +perft 4 819631 +perft 5 26363334 +perft 6 842796987 + +id 504 +epd rqnbbrk1/ppppppp1/8/5n1p/3P3P/2B3P1/PPP1PP2/RQNB1KNR w HA - +perft 1 22 +perft 2 505 +perft 3 11452 +perft 4 283464 +perft 5 7055215 +perft 6 186760784 + +id 505 +epd rqnkbbnr/pp2p1p1/8/2pp1p1p/3PPP2/8/PPP1N1PP/RQNKBB1R w HAha - +perft 1 28 +perft 2 832 +perft 3 23142 +perft 4 722857 +perft 5 20429246 +perft 6 663183060 + +id 506 +epd rqnkbnr1/pppp2bp/6p1/4pp2/1P2P3/3NN3/P1PP1PPP/RQ1KB1RB w GAga - +perft 1 28 +perft 2 641 +perft 3 18835 +perft 4 459993 +perft 5 14038570 +perft 6 364210162 + +id 507 +epd rbq2kbr/pppppppp/2n5/P7/3P1n2/2P5/1P2PPPP/RBQNKNBR w HA - +perft 1 31 +perft 2 889 +perft 3 27028 +perft 4 766181 +perft 5 24299415 +perft 6 692180754 + +id 508 +epd rq1bkn1r/ppppp2p/3n4/5pp1/2b3P1/1N1P1P2/PPP1P2P/RQ1BKNBR w HAha - +perft 1 28 +perft 2 810 +perft 3 22667 +perft 4 657520 +perft 5 18719949 +perft 6 556282676 + +id 509 +epd r1nknbbr/p2ppp1p/1pp3p1/8/1P6/4P3/P1PPNPPq/R1QKNBBR w HAha - +perft 1 24 +perft 2 797 +perft 3 22144 +perft 4 719069 +perft 5 21862776 +perft 6 716521139 + +id 510 +epd rqnknrbb/ppp1p3/5ppp/2Np4/2P5/4P3/PP1P1PPP/RQNK1RBB w FAfa - +perft 1 34 +perft 2 686 +perft 3 23277 +perft 4 515541 +perft 5 17664543 +perft 6 423574794 + +id 511 +epd 1brnqknr/2p1pppp/p2p4/1P6/6P1/4Nb2/PP1PPP1P/BBR1QKNR w HChc - +perft 1 34 +perft 2 1019 +perft 3 32982 +perft 4 1003103 +perft 5 33322477 +perft 6 1043293394 + +id 512 +epd brn1qknr/1p1pppp1/pb5p/Q1p5/3P3P/8/PPP1PPPR/BRNB1KN1 w Bhb - +perft 1 32 +perft 2 642 +perft 3 20952 +perft 4 464895 +perft 5 15454749 +perft 6 371861782 + +id 513 +epd brnqkbnr/pppppp2/8/6pp/6P1/P2P1P2/1PP1P2P/BRNQKBNR w HBhb - +perft 1 20 +perft 2 441 +perft 3 9782 +perft 4 240220 +perft 5 5770284 +perft 6 153051835 + +id 514 +epd 2nqknrb/1rpppppp/5B2/pp6/1PP1b3/3P4/P3PPPP/1RNQKNRB w GBg - +perft 1 35 +perft 2 1042 +perft 3 36238 +perft 4 1101159 +perft 5 38505058 +perft 6 1202668717 + +id 515 +epd rb1nqknr/1pp1pppp/8/3p4/p2P4/6PN/PPPQPP1P/RBBN1K1R w HAha - +perft 1 29 +perft 2 692 +perft 3 21237 +perft 4 555018 +perft 5 17820605 +perft 6 497251206 + +id 516 +epd rnbbqknr/pppp4/5p2/4p1pp/P7/2N2PP1/1PPPP2P/R1BBQKNR w HAha - +perft 1 23 +perft 2 595 +perft 3 14651 +perft 4 415772 +perft 5 10881112 +perft 6 329010121 + +id 517 +epd rn1qkbnr/p1p1pp1p/bp4p1/3p4/1P6/4P3/P1PP1PPP/RNBQKBNR w HAha - +perft 1 30 +perft 2 794 +perft 3 24319 +perft 4 690811 +perft 5 21657601 +perft 6 647745807 + +id 518 +epd r1bqk1rb/pppnpppp/5n2/3p4/2P3PP/2N5/PP1PPP2/R1BQKNRB w GAga - +perft 1 32 +perft 2 821 +perft 3 27121 +perft 4 733155 +perft 5 24923473 +perft 6 710765657 + +id 519 +epd rbnqbknr/1p1ppp1p/6p1/p1p5/7P/3P4/PPP1PPP1/RBNQBKNR w HAha - +perft 1 24 +perft 2 720 +perft 3 18842 +perft 4 575027 +perft 5 15992882 +perft 6 501093456 + +id 520 +epd r1qbbk1r/pp1ppppp/n1p5/5n2/B1P3P1/8/PP1PPP1P/RNQ1BKNR w HAha - +perft 1 27 +perft 2 831 +perft 3 22293 +perft 4 698986 +perft 5 19948650 +perft 6 637973209 + +id 521 +epd rnqkbb1r/p1pppppp/8/8/1p4n1/PP4PP/2PPPP2/RNQKBBNR w HAha - +perft 1 18 +perft 2 463 +perft 3 9519 +perft 4 256152 +perft 5 6065231 +perft 6 172734380 + +id 522 +epd rnqk1nrb/pppbpp2/7p/3p2p1/4B3/2N1N1P1/PPPPPP1P/R1QKB1R1 w GAga - +perft 1 34 +perft 2 1171 +perft 3 38128 +perft 4 1318217 +perft 5 42109356 +perft 6 1465473753 + +id 523 +epd rbnqknbr/1pp1ppp1/3p4/7p/p2P2PP/2P5/PP2PP2/RBNQKNBR w HAha - +perft 1 32 +perft 2 867 +perft 3 28342 +perft 4 798722 +perft 5 26632459 +perft 6 781067145 + +id 524 +epd rn1bknbr/pq2pppp/1p6/2pp4/P7/1P1P4/2PNPPPP/RNQBK1BR w HAha - +perft 1 24 +perft 2 627 +perft 3 16652 +perft 4 462942 +perft 5 13200921 +perft 6 385193532 + +id 525 +epd r1qk1bbr/ppp1pp1p/2np1n2/6p1/2PP4/3BP3/PP3PPP/RNQKN1BR w HAha - +perft 1 31 +perft 2 992 +perft 3 30213 +perft 4 986631 +perft 5 30397368 +perft 6 1011631987 + +id 526 +epd r1qknrbb/pppp1p2/2n3p1/4p2p/8/QPP5/P1NPPPPP/RN1K1RBB w FAfa - +perft 1 30 +perft 2 702 +perft 3 21563 +perft 4 532939 +perft 5 16813114 +perft 6 438096194 + +id 527 +epd bbkr1qnr/2pppppp/2n5/pp6/8/PPN5/1BPPPPPP/1BR1KQNR w HC - +perft 1 25 +perft 2 573 +perft 3 15183 +perft 4 380910 +perft 5 10554668 +perft 6 283975400 + +id 528 +epd 1rnbkqnr/1bpppppp/1p6/7P/p2P4/5P2/PPP1P1P1/BRNBKQNR w HBhb - +perft 1 21 +perft 2 503 +perft 3 11790 +perft 4 301084 +perft 5 7679979 +perft 6 207799378 + +id 529 +epd brnkqbnr/2p1pppp/1p6/3p4/1pP5/P6P/3PPPP1/BRNKQBNR w HBhb - +perft 1 28 +perft 2 743 +perft 3 21054 +perft 4 587192 +perft 5 17354516 +perft 6 507176753 + +id 530 +epd br1kqnrb/npp1pppp/8/3p4/p4N2/PP6/2PPPPPP/BR1KQNRB w GBgb - +perft 1 31 +perft 2 808 +perft 3 25585 +perft 4 698475 +perft 5 22376575 +perft 6 640362920 + +id 531 +epd rbbnkq1r/pppppp1p/7n/6p1/P5P1/2P2N2/1P1PPP1P/RBBNKQ1R w HAha - +perft 1 29 +perft 2 580 +perft 3 17585 +perft 4 404831 +perft 5 12730970 +perft 6 325226128 + +id 532 +epd rnbbk1nr/pp2qppp/2ppp3/8/3P4/P1N4N/1PP1PPPP/R1BBKQ1R w HAha - +perft 1 29 +perft 2 838 +perft 3 24197 +perft 4 721884 +perft 5 21100580 +perft 6 646624429 + +id 533 +epd rnbk1b1r/ppppn1pp/4pp2/7q/7P/P5PB/1PPPPP2/RNBKQ1NR w HAha - +perft 1 20 +perft 2 729 +perft 3 16633 +perft 4 576199 +perft 5 14507076 +perft 6 498621813 + +id 534 +epd r2kqnrb/pbppppp1/np5p/8/4Q1P1/3P4/PPP1PP1P/RNBK1NRB w GAga - +perft 1 47 +perft 2 1219 +perft 3 55009 +perft 4 1486353 +perft 5 65239153 +perft 6 1834391369 + +id 535 +epd rbnkbq1r/p1p2ppp/1p2pn2/3p4/P3P3/3P4/1PP1KPPP/RBN1BQNR w ha - +perft 1 29 +perft 2 923 +perft 3 27179 +perft 4 883866 +perft 5 26202752 +perft 6 868565895 + +id 536 +epd rk1bb1nr/ppppqppp/n7/1N2p3/6P1/7N/PPPPPP1P/R1KBBQ1R w HA - +perft 1 27 +perft 2 703 +perft 3 19478 +perft 4 559525 +perft 5 16049807 +perft 6 492966455 + +id 537 +epd rnkqbbnr/p1ppp2p/1p4p1/8/1B3p1P/2NP4/PPP1PPP1/R1KQ1BNR w HAha - +perft 1 29 +perft 2 610 +perft 3 18855 +perft 4 438277 +perft 5 14020041 +perft 6 355083962 + +id 538 +epd rnkqb1rb/pp1p1ppp/4p3/2P3n1/8/1PP5/P3PPPP/RNKQBNRB w GAga - +perft 1 29 +perft 2 675 +perft 3 20699 +perft 4 535821 +perft 5 17000613 +perft 6 476598337 + +id 539 +epd rb1kqnbr/pp1pp1p1/1np2p2/7p/P1P3PP/8/1P1PPP2/RBNKQNBR w HAha - +perft 1 31 +perft 2 1077 +perft 3 33661 +perft 4 1183381 +perft 5 37415304 +perft 6 1328374620 + +id 540 +epd rnkbq1br/ppp2ppp/3p4/Q3p1n1/5P2/3P2P1/PPP1P2P/RNKB1NBR w HAha - +perft 1 41 +perft 2 1201 +perft 3 46472 +perft 4 1420367 +perft 5 52991625 +perft 6 1675608008 + +id 541 +epd rn1qnbbr/pp2pppp/2ppk3/8/2PP4/3Q1N2/PP2PPPP/RNK2BBR w HA - +perft 1 34 +perft 2 666 +perft 3 22474 +perft 4 472299 +perft 5 15860369 +perft 6 353831792 + +id 542 +epd rnkqnr1b/ppppp1pp/5p2/8/Q1P2P2/8/PP1P2PP/RbK1NRBB w FAfa - +perft 1 36 +perft 2 876 +perft 3 31987 +perft 4 788580 +perft 5 29022529 +perft 6 736717252 + +id 543 +epd bbrn1nqr/ppp1k1pp/5p2/3pp3/7P/3PN3/PPP1PPP1/BBRK1NQR w - - +perft 1 24 +perft 2 583 +perft 3 15063 +perft 4 383532 +perft 5 10522064 +perft 6 280707118 + +id 544 +epd brnbkn1r/1pppp1p1/4q3/p4p1p/7P/1N3P2/PPPPP1PQ/BR1BKN1R w HBhb - +perft 1 27 +perft 2 935 +perft 3 26120 +perft 4 885699 +perft 5 26000648 +perft 6 873063158 + +id 545 +epd br1knbqr/pp2p1pp/1n6/2pp1p2/6P1/2P4B/PP1PPPQP/BRNKN2R w HBhb - +perft 1 27 +perft 2 681 +perft 3 19202 +perft 4 510687 +perft 5 14954779 +perft 6 415624943 + +id 546 +epd brnk1qrb/p1ppppp1/1p5p/8/P3n3/1N4P1/1PPPPPRP/BR1KNQ1B w Bgb - +perft 1 22 +perft 2 638 +perft 3 13991 +perft 4 412346 +perft 5 9760752 +perft 6 293499724 + +id 547 +epd rbbnknqr/pppp3p/5pp1/8/1P1pP3/7P/P1P2PP1/RBBNKNQR w HAha - +perft 1 29 +perft 2 756 +perft 3 21616 +perft 4 614074 +perft 5 17602252 +perft 6 528140595 + +id 548 +epd 1nbbknqr/rpp1ppp1/1Q1p3p/p7/2P2PP1/8/PP1PP2P/RNBBKN1R w HAh - +perft 1 37 +perft 2 977 +perft 3 34977 +perft 4 944867 +perft 5 33695089 +perft 6 940198007 + +id 549 +epd rnb2bqr/ppkpppp1/3n3p/2p5/6PP/2N2P2/PPPPP3/R1BKNBQR w HA - +perft 1 30 +perft 2 647 +perft 3 20365 +perft 4 467780 +perft 5 15115531 +perft 6 369257622 + +id 550 +epd rn1k1qrb/p1pppppp/bp6/8/4n3/P4BPP/1PPPPP2/RNBKNQR1 w GAga - +perft 1 22 +perft 2 670 +perft 3 14998 +perft 4 451517 +perft 5 11199653 +perft 6 339919682 + +id 551 +epd rb2bnqr/nppkpppp/3p4/p7/1P6/P2N2P1/2PPPP1P/RB1KBNQR w HA - +perft 1 22 +perft 2 479 +perft 3 11475 +perft 4 264739 +perft 5 6831555 +perft 6 167329117 + +id 552 +epd r1kbb1qr/2pppppp/np2n3/p7/2P3P1/8/PP1PPPQP/RNKBBN1R w HAha - +perft 1 32 +perft 2 723 +perft 3 23953 +perft 4 581832 +perft 5 19472074 +perft 6 504622114 + +id 553 +epd rnknbb1r/p1ppp1pp/8/1p1P1p1q/8/P1P5/1P2PPPP/RNKNBBQR w HAha - +perft 1 19 +perft 2 607 +perft 3 12733 +perft 4 417451 +perft 5 9753617 +perft 6 325177085 + +id 554 +epd rnkn1qrb/pp1bp1pp/2p5/1N1p1p2/8/2P5/PPKPPPPP/R2NBQRB w ga - +perft 1 27 +perft 2 533 +perft 3 14549 +perft 4 330747 +perft 5 9206957 +perft 6 232664675 + +id 555 +epd r1nknqbr/pp2p1pp/2p2p2/3p4/6P1/PP1P4/2P1PP1b/RBNKNQBR w HAha - +perft 1 20 +perft 2 582 +perft 3 13777 +perft 4 409166 +perft 5 10708639 +perft 6 326565393 + +id 556 +epd rnkb1qbr/p1pp1p1p/1p2pn2/1Q4p1/4P3/N4P2/PPPP2PP/R1KBN1BR w HAha - +perft 1 40 +perft 2 1038 +perft 3 39356 +perft 4 1051441 +perft 5 39145902 +perft 6 1079612614 + +id 557 +epd rn2qbbr/1pkppp1p/p3n1p1/8/8/2P2P2/PP1PP1PP/RNKN1BBR w HA - +perft 1 24 +perft 2 605 +perft 3 14888 +perft 4 385964 +perft 5 9687507 +perft 6 260874068 + +id 558 +epd rn1nqrbb/p1kppp1p/8/1pp3p1/1P6/2N1P3/P1PP1PPP/RK1NQRBB w - - +perft 1 21 +perft 2 540 +perft 3 12489 +perft 4 337997 +perft 5 8436136 +perft 6 237525904 + +id 559 +epd bbrnknrq/1pp3pp/p2p1p2/4p3/P7/1P2N3/2PPPPPP/BBRN1RKQ w gc - +perft 1 24 +perft 2 527 +perft 3 13900 +perft 4 326175 +perft 5 9139962 +perft 6 226253685 + +id 560 +epd brnb1nrq/pppp1kpp/4p3/8/5p1P/P1P3P1/1P1PPP2/BRNBKNRQ w GB - +perft 1 29 +perft 2 773 +perft 3 23904 +perft 4 638768 +perft 5 20503775 +perft 6 560338709 + +id 561 +epd br1k1brq/ppppp2p/1n1n1pp1/8/P1P5/3P2P1/1P2PP1P/BRNKNBRQ w GBgb - +perft 1 28 +perft 2 811 +perft 3 23550 +perft 4 664880 +perft 5 19913758 +perft 6 565143976 + +id 562 +epd 1r1knrqb/n1pppppp/p1b5/1p6/8/3N1P2/PPPPP1PP/BRNK1RQB w fb - +perft 1 29 +perft 2 753 +perft 3 23210 +perft 4 620019 +perft 5 20044474 +perft 6 558383603 + +id 563 +epd rbbnk1rq/pppppppp/8/3Pn3/8/4P1P1/PPP2P1P/RBBNKNRQ w GAga - +perft 1 22 +perft 2 551 +perft 3 12619 +perft 4 324608 +perft 5 8204171 +perft 6 217689974 + +id 564 +epd rnbbk1rq/2pppp1p/p3n1p1/1p6/P3N3/8/1PPPPPPP/RNBB1KRQ w ga - +perft 1 26 +perft 2 742 +perft 3 20061 +perft 4 599527 +perft 5 16787080 +perft 6 525678162 + +id 565 +epd rnbkn1rq/ppppppb1/6p1/7p/2B2P2/1P2P3/P1PP2PP/RNBKN1RQ w GAga - +perft 1 28 +perft 2 799 +perft 3 23210 +perft 4 689436 +perft 5 20755098 +perft 6 639632905 + +id 566 +epd rn1knrqb/p2pppp1/b1p5/1p5p/2P2P2/1P6/P2PP1PP/RNBKNRQB w FAfa - +perft 1 30 +perft 2 579 +perft 3 18481 +perft 4 397545 +perft 5 13257198 +perft 6 311282465 + +id 567 +epd rbnkbnrq/pp2p1Np/2p2p2/8/3p4/8/PPPPPPPP/RBNKBR1Q w Aga - +perft 1 23 +perft 2 670 +perft 3 16435 +perft 4 501883 +perft 5 13012378 +perft 6 411860744 + +id 568 +epd rk1bbnrq/ppp1pppp/n7/3p4/5P2/3P2NP/PPP1P1P1/RNKBB1RQ w GA - +perft 1 26 +perft 2 597 +perft 3 16238 +perft 4 402506 +perft 5 11269462 +perft 6 296701249 + +id 569 +epd r1knbbrq/pppp2p1/2n1p2p/5p2/4P3/P1PP4/1P3PPP/RNKNBBRQ w GAga - +perft 1 20 +perft 2 596 +perft 3 13091 +perft 4 399069 +perft 5 9416862 +perft 6 293659781 + +id 570 +epd rnknbrqb/p1p1pp1p/3p4/1p1N2p1/8/N7/PPPPPPPP/1RK1BRQB w Ffa - +perft 1 26 +perft 2 724 +perft 3 18942 +perft 4 552040 +perft 5 15257204 +perft 6 461293885 + +id 571 +epd rbnknrb1/1p1ppp1p/p1p3p1/8/1P3P2/1R6/PqPPP1PP/RBNKN1BQ w Afa - +perft 1 31 +perft 2 1183 +perft 3 34723 +perft 4 1289502 +perft 5 38722152 +perft 6 1421492227 + +id 572 +epd rnkbnrbq/2p1ppp1/p7/1p1p3p/3P4/1P4P1/P1P1PP1P/RNKBNRBQ w FAfa - +perft 1 24 +perft 2 506 +perft 3 12748 +perft 4 301464 +perft 5 8086100 +perft 6 207129256 + +id 573 +epd r1knrbbq/pp1ppppp/2p1n3/8/2P3P1/P7/1PKPPP1P/RN1NRBBQ w ea - +perft 1 28 +perft 2 570 +perft 3 16037 +perft 4 352471 +perft 5 10278695 +perft 6 242592363 + +id 574 +epd rnknrq1b/ppp1p1p1/4b3/3p1p1p/6P1/P4P2/1PPPPQ1P/RNKNR1BB w EAea - +perft 1 30 +perft 2 739 +perft 3 23124 +perft 4 594962 +perft 5 19252739 +perft 6 521629794 + +id 575 +epd bbqr1krn/pppp1p1p/5n2/4p1p1/3P4/P3QP2/1PP1P1PP/BB1RNKRN w GDgd - +perft 1 31 +perft 2 799 +perft 3 25627 +perft 4 674913 +perft 5 22172123 +perft 6 609277274 + +id 576 +epd bq1b1krn/pp1ppppp/3n4/2r5/3p3N/6N1/PPP1PPPP/BQRB1KR1 w GCg - +perft 1 21 +perft 2 798 +perft 3 18571 +perft 4 688429 +perft 5 17546069 +perft 6 647165916 + +id 577 +epd bqrnkbrn/2pp1pp1/p7/1p2p2p/1P6/4N3/P1PPPPPP/BQR1KBRN w GCgc - +perft 1 27 +perft 2 783 +perft 3 22327 +perft 4 670798 +perft 5 20059741 +perft 6 624462073 + +id 578 +epd bqr1krnb/1np1pppp/8/pp1p4/8/2P2N2/PP1PPPPP/BQRNKR1B w FCfc - +perft 1 28 +perft 2 636 +perft 3 18874 +perft 4 461104 +perft 5 14237097 +perft 6 372181570 + +id 579 +epd qbb1rkrn/1ppppppp/p7/7n/8/P2P4/1PP1PPPP/QBBRNKRN w Gg - +perft 1 25 +perft 2 547 +perft 3 13837 +perft 4 332918 +perft 5 8849383 +perft 6 229112926 + +id 580 +epd 1rbbnkrn/p1p1pp1p/2q5/1p1p2p1/8/2P3P1/PP1PPP1P/QRBBNKRN w GBgb - +perft 1 24 +perft 2 1010 +perft 3 24370 +perft 4 983770 +perft 5 24328258 +perft 6 961371180 + +id 581 +epd qrb1kbrn/ppp1p2p/4npp1/3p4/8/1PP4P/PR1PPPP1/Q1BNKBRN w Ggb - +perft 1 18 +perft 2 451 +perft 3 9291 +perft 4 247310 +perft 5 5568106 +perft 6 155744022 + +id 582 +epd qr2krnb/p1p1pppp/b1np4/1p6/3NP3/7P/PPPP1PP1/QRBNKR1B w FBfb - +perft 1 25 +perft 2 667 +perft 3 17081 +perft 4 476030 +perft 5 12458875 +perft 6 361495148 + +id 583 +epd qbrnbkrn/ppp3pp/3p4/5p2/2P1pP2/6PP/PP1PP3/QBRNBKRN w GCgc - +perft 1 24 +perft 2 650 +perft 3 16835 +perft 4 445263 +perft 5 12187382 +perft 6 326834539 + +id 584 +epd qrnb1krn/ppp1p1pp/5p2/2Np4/b2P4/2P5/PP2PPPP/QR1BBKRN w GBgb - +perft 1 27 +perft 2 641 +perft 3 17490 +perft 4 432041 +perft 5 12103076 +perft 6 310695797 + +id 585 +epd qrnkbbrn/pp2pp2/8/2pp2pp/6PP/3P4/PPPKPP2/QRN1BBRN w gb - +perft 1 22 +perft 2 554 +perft 3 13116 +perft 4 357404 +perft 5 9014737 +perft 6 258925091 + +id 586 +epd qrnkbrnb/p1p1ppp1/1p6/3p4/3P3p/5N1P/PPP1PPP1/QRNKBR1B w FBfb - +perft 1 24 +perft 2 529 +perft 3 13205 +perft 4 318722 +perft 5 8295874 +perft 6 213856651 + +id 587 +epd qbr1krbn/1pppp1pp/p7/5pn1/2PP4/8/PPB1PPPP/Q1RNKRBN w FCfc - +perft 1 26 +perft 2 831 +perft 3 21651 +perft 4 696830 +perft 5 18961456 +perft 6 621884383 + +id 588 +epd 1rnbkrbn/1qp1pppp/3p4/pp6/4P3/1NP4P/PP1P1PP1/QR1BKRBN w FBfb - +perft 1 24 +perft 2 597 +perft 3 15089 +perft 4 404761 +perft 5 10832084 +perft 6 307793179 + +id 589 +epd q1rkrbbn/ppp1pppp/8/3p4/1PnP4/P7/1RP1PPPP/Q1NKRBBN w Ee - +perft 1 20 +perft 2 520 +perft 3 10769 +perft 4 278067 +perft 5 6452205 +perft 6 170268300 + +id 590 +epd qrnkrn1b/ppppp1pp/4b3/7P/6p1/P7/1PPPPP2/QRNKRNBB w EBeb - +perft 1 26 +perft 2 566 +perft 3 15623 +perft 4 381312 +perft 5 10940750 +perft 6 287987207 + +id 591 +epd bbr1nkrn/ppp1pppp/3q4/3p4/8/P7/1PPPPPPP/BBRQNRKN w gc - +perft 1 19 +perft 2 661 +perft 3 13895 +perft 4 460396 +perft 5 10870247 +perft 6 356399665 + +id 592 +epd brqbnkrn/pp1pp2p/5pp1/2p5/4P3/P2P1N2/1PP2PPP/BRQB1KRN w GBgb - +perft 1 27 +perft 2 679 +perft 3 19916 +perft 4 527306 +perft 5 16391730 +perft 6 455940859 + +id 593 +epd 2qnkbrn/p1pppppp/8/1r6/1p2bP2/7N/PPPPP1PP/BR1QKBRN w GBg - +perft 1 18 +perft 2 774 +perft 3 15713 +perft 4 635461 +perft 5 14371755 +perft 6 559579332 + +id 594 +epd r1qnkr1b/p1pppppp/7n/1p6/8/1P3b1N/PRPPPPPP/B1QNK1RB w f - +perft 1 21 +perft 2 677 +perft 3 15437 +perft 4 501520 +perft 5 12463801 +perft 6 410795298 + +id 595 +epd rbbqn1rn/pppp1pp1/3k4/4p2Q/2PPP3/8/PP3PPP/RBB1NKRN w GA - +perft 1 40 +perft 2 742 +perft 3 28757 +perft 4 579833 +perft 5 21852196 +perft 6 471452088 + +id 596 +epd rqbbnkrn/3pppp1/p1p4p/1p6/5P2/P2N4/1PPPP1PP/RQBBK1RN w ga - +perft 1 23 +perft 2 665 +perft 3 16400 +perft 4 492544 +perft 5 12794736 +perft 6 396640086 + +id 597 +epd r2nkbrn/pp2pppp/8/2ppqb2/2P3P1/5P2/PP1PPN1P/RQB1KBRN w GAga - +perft 1 28 +perft 2 1108 +perft 3 31164 +perft 4 1194581 +perft 5 34780853 +perft 6 1292405738 + +id 598 +epd rqbnk1nb/p1pppr1p/5p2/1p4p1/1PP1P3/8/P2P1PPP/RQBNKRNB w FAa - +perft 1 26 +perft 2 650 +perft 3 18208 +perft 4 491403 +perft 5 14565370 +perft 6 416833400 + +id 599 +epd rbqnb1rn/p1pp1kpp/1p2pp2/8/4P2P/P5P1/1PPP1P2/RBQNBKRN w GA - +perft 1 20 +perft 2 437 +perft 3 9423 +perft 4 222154 +perft 5 5282124 +perft 6 132309824 + +id 600 +epd rqnbbkrn/p1p1pppp/3p4/1p5B/8/1P1NP3/P1PP1PPP/RQ2BKRN w GAga - +perft 1 30 +perft 2 606 +perft 3 18382 +perft 4 422491 +perft 5 12989786 +perft 6 326601372 + +id 601 +epd rqnkbbr1/ppppp1pp/5p2/7n/8/2PNP2P/PP1P1PP1/RQ1KBBRN w GAga - +perft 1 23 +perft 2 482 +perft 3 12506 +perft 4 297869 +perft 5 8430874 +perft 6 217797292 + +id 602 +epd r1nkbrnb/2ppppp1/1q6/pp5p/1P6/P3P3/2PPKPPP/RQN1BRNB w fa - +perft 1 25 +perft 2 827 +perft 3 21518 +perft 4 701071 +perft 5 19290675 +perft 6 632892337 + +id 603 +epd rbqnkrbn/p1ppppp1/7p/1p6/7P/2N1P3/PPPP1PPB/RBQ1KR1N w FAfa - +perft 1 30 +perft 2 627 +perft 3 18566 +perft 4 440217 +perft 5 12976682 +perft 6 337377291 + +id 604 +epd r1nbkrbn/p1qp1ppp/8/1pp1p3/2P1P3/6P1/PP1PBP1P/RQN1KRBN w FAfa - +perft 1 22 +perft 2 616 +perft 3 14503 +perft 4 431199 +perft 5 10850952 +perft 6 335943324 + +id 605 +epd rqnkr1bn/ppp1ppb1/3p2pp/8/P7/2P2P2/1PKPP1PP/RQN1RBBN w ea - +perft 1 31 +perft 2 679 +perft 3 21365 +perft 4 493500 +perft 5 15661072 +perft 6 379844460 + +id 606 +epd r2krnbb/qppp1ppp/1n6/p3p3/PP6/4N3/N1PPPPPP/RQ1KR1BB w EAea - +perft 1 24 +perft 2 645 +perft 3 17054 +perft 4 487028 +perft 5 13837270 +perft 6 416239106 + +id 607 +epd bbr1qk1n/1ppppp1p/2n5/p7/P7/1P2P3/2PP1PrP/1BRNQKRN w GCc - +perft 1 18 +perft 2 520 +perft 3 10680 +perft 4 304462 +perft 5 7215306 +perft 6 207612575 + +id 608 +epd brnbq1rn/2ppppkp/p5p1/1p6/8/1BP3P1/PP1PPP1P/BRN1QRKN w - - +perft 1 21 +perft 2 625 +perft 3 13989 +perft 4 419667 +perft 5 9929336 +perft 6 300902534 + +id 609 +epd brn1kbrn/pp2p1pp/3p4/q1p2p2/2P4P/6P1/PP1PPP2/BRNQKBRN w GBgb - +perft 1 18 +perft 2 477 +perft 3 10205 +perft 4 273925 +perft 5 6720181 +perft 6 187205941 + +id 610 +epd brn1krnb/p3pppp/1qpp4/1p6/2P3P1/1P6/P2PPP1P/BRNQKRNB w FBfb - +perft 1 30 +perft 2 835 +perft 3 24761 +perft 4 716151 +perft 5 21806428 +perft 6 654487872 + +id 611 +epd r1b1qkrn/1p1ppppp/p1p1n3/8/4P3/1PN5/P1PPQPPb/RBB2KRN w GAga - +perft 1 28 +perft 2 825 +perft 3 24536 +perft 4 716585 +perft 5 22079005 +perft 6 647939781 + +id 612 +epd r1bbqk1n/p1pppprp/n7/1p4p1/5P2/2N3N1/PPPPP1PP/1RBBQKR1 w Ga - +perft 1 25 +perft 2 545 +perft 3 14657 +perft 4 358854 +perft 5 10271111 +perft 6 273864588 + +id 613 +epd rnbqkbrn/p1pp1pp1/4p3/7p/2p4P/2P5/PP1PPPP1/R1BQKBRN w GAga - +perft 1 17 +perft 2 445 +perft 3 9076 +perft 4 255098 +perft 5 5918310 +perft 6 174733195 + +id 614 +epd rnbqkrnb/1p1pp1p1/2p4p/p4p2/3P2P1/7N/PPPBPP1P/RN1QKR1B w FAfa - +perft 1 34 +perft 2 746 +perft 3 25319 +perft 4 623133 +perft 5 21285553 +perft 6 569141201 + +id 615 +epd rbnqbkr1/1ppppp2/p5n1/6pp/4P3/1N6/PPPP1PPP/RBQ1BRKN w ga - +perft 1 18 +perft 2 466 +perft 3 9683 +perft 4 260864 +perft 5 6051500 +perft 6 170135726 + +id 616 +epd rnqb1krn/ppppp1p1/7p/7b/P1P2pPP/8/1P1PPP2/RNQBBKRN w GAga - +perft 1 24 +perft 2 575 +perft 3 15400 +perft 4 385825 +perft 5 11039042 +perft 6 291243811 + +id 617 +epd rnqkbbr1/p1pp1ppp/4p3/1p6/P3P2n/5P2/1PPP1NPP/RNQKBBR1 w GAga - +perft 1 27 +perft 2 803 +perft 3 22883 +perft 4 694449 +perft 5 20666099 +perft 6 638696065 + +id 618 +epd rn1kbrnb/1qppp1pp/1p6/p4p2/1B1P4/1P5N/P1P1PPPP/RNQK1R1B w FAfa - +perft 1 37 +perft 2 1209 +perft 3 43015 +perft 4 1425600 +perft 5 49748034 +perft 6 1671593862 + +id 619 +epd rbnqkrbn/Bppp1p2/p5pp/4p3/5P2/6PP/PPPPP3/RBNQKR1N w FAfa - +perft 1 29 +perft 2 720 +perft 3 20434 +perft 4 534148 +perft 5 15384362 +perft 6 421343249 + +id 620 +epd rnqbkr1n/1p1ppbpp/3p1p2/p7/8/1P6/P1PPPPPP/R1QBKRBN w FAfa - +perft 1 20 +perft 2 657 +perft 3 14424 +perft 4 492678 +perft 5 11843134 +perft 6 413965054 + +id 621 +epd rnqkrb1n/ppppp3/6p1/5p1p/2b2P2/P1N5/1PPPP1PP/RQ1KRBBN w EAea - +perft 1 28 +perft 2 749 +perft 3 20684 +perft 4 543151 +perft 5 15379233 +perft 6 417191461 + +id 622 +epd rnqk1nbb/1pp2ppp/3pr3/p3p3/3P1P2/2N3N1/PPP1P1PP/R1QKR1BB w EAa - +perft 1 29 +perft 2 883 +perft 3 26412 +perft 4 815098 +perft 5 25144295 +perft 6 789705382 + +id 623 +epd bbr1kqrn/p1p1ppp1/1p2n2p/3p4/1P1P4/2N5/P1P1PPPP/BBR1KQRN w GCgc - +perft 1 22 +perft 2 485 +perft 3 11475 +perft 4 271271 +perft 5 6825123 +perft 6 171793012 + +id 624 +epd brnbkq1n/ppp1ppr1/7p/3p2p1/2P3PP/8/PPBPPP2/BRN1KQRN w GBb - +perft 1 30 +perft 2 634 +perft 3 19017 +perft 4 442537 +perft 5 13674310 +perft 6 345386924 + +id 625 +epd brnkqbr1/1pppp1pp/5p2/p7/P1P1P2n/8/1P1P1PP1/BRNKQBRN w GBgb - +perft 1 21 +perft 2 504 +perft 3 11672 +perft 4 305184 +perft 5 7778289 +perft 6 217596497 + +id 626 +epd b1rkqrnb/p1ppp1pp/1p1n4/5p2/5P2/PN5P/1PPPP1P1/BR1KQRNB w FBf - +perft 1 23 +perft 2 688 +perft 3 17259 +perft 4 531592 +perft 5 14228372 +perft 6 451842354 + +id 627 +epd 1bbnkqrn/rppppp2/p5p1/7p/7P/P1P1P3/1P1P1PP1/RBBNKQRN w GAg - +perft 1 25 +perft 2 450 +perft 3 12391 +perft 4 263946 +perft 5 7752404 +perft 6 185393913 + +id 628 +epd rnbbkqr1/1pppppp1/7p/p3n3/PP5P/8/1BPPPPP1/RN1BKQRN w GAga - +perft 1 23 +perft 2 543 +perft 3 12224 +perft 4 305812 +perft 5 7549008 +perft 6 199883770 + +id 629 +epd r1bkqbrn/ppppp1pp/8/5p2/3nPP2/1P4N1/P1PP2PP/RNBKQBR1 w GAga - +perft 1 27 +perft 2 751 +perft 3 21158 +perft 4 600417 +perft 5 17989920 +perft 6 527273615 + +id 630 +epd rnbkqr1b/1p1pp1pp/p4p1n/2p5/1P5P/N4P2/P1PPP1P1/R1BKQRNB w FAfa - +perft 1 21 +perft 2 498 +perft 3 11738 +perft 4 302278 +perft 5 7808375 +perft 6 216224115 + +id 631 +epd rbnkbqrn/p1p3pp/1p1p4/B3pp2/3P2P1/6N1/PPP1PP1P/RBNK1QR1 w GAga - +perft 1 34 +perft 2 977 +perft 3 33464 +perft 4 961128 +perft 5 33318567 +perft 6 978991050 + +id 632 +epd r1kbbqrn/ppp3pp/2np1p2/1P2p3/3P1P2/8/P1P1P1PP/RNKBBQRN w GAga - +perft 1 32 +perft 2 920 +perft 3 28916 +perft 4 844881 +perft 5 26763259 +perft 6 797524786 + +id 633 +epd rk1qbbrn/p2npppp/1p6/2p4Q/8/4P3/PPPP1PPP/RNK1B1RN w GA - +perft 1 35 +perft 2 657 +perft 3 22359 +perft 4 495406 +perft 5 16662477 +perft 6 419496845 + +id 634 +epd rnk1brnb/pp1p1pp1/8/q1p1p2p/5P2/NP6/P1PPP1PP/R1KQBRNB w FAfa - +perft 1 26 +perft 2 774 +perft 3 20215 +perft 4 610661 +perft 5 16987110 +perft 6 523437649 + +id 635 +epd rb1kqrbn/npp1ppp1/p7/3P3p/2PP4/8/PP3PPP/RBNKQRBN w FAfa - +perft 1 35 +perft 2 775 +perft 3 27395 +perft 4 661118 +perft 5 23983464 +perft 6 625669222 + +id 636 +epd rnkb1rbn/pp1p2pp/8/2p1pp1q/P6P/1PN5/2PPPPP1/R1KBQRBN w FAfa - +perft 1 22 +perft 2 899 +perft 3 21188 +perft 4 850597 +perft 5 21518343 +perft 6 857951339 + +id 637 +epd rnkqrbbn/1pppp1p1/8/p2N1p1p/2P4P/8/PP1PPPP1/R1KQRBBN w EAea - +perft 1 29 +perft 2 585 +perft 3 17571 +perft 4 393221 +perft 5 12238776 +perft 6 299752383 + +id 638 +epd rnk1r1bb/pp1ppppp/1q4n1/2p5/5P1P/3PP3/PPP3P1/RNKQRNBB w EAea - +perft 1 27 +perft 2 884 +perft 3 24613 +perft 4 811915 +perft 5 23698701 +perft 6 790239502 + +id 639 +epd bbrnkrqn/1ppp1p2/6pp/p3p3/5PP1/2PB4/PP1PP2P/B1RNKRQN w FCfc - +perft 1 37 +perft 2 693 +perft 3 25425 +perft 4 550527 +perft 5 20138432 +perft 6 481498664 + +id 640 +epd b1rbkrqn/ppp2ppp/1n2p3/3p4/6P1/2PP4/PP2PP1P/BRNBKRQN w FBf - +perft 1 21 +perft 2 463 +perft 3 10610 +perft 4 253204 +perft 5 6307276 +perft 6 159025909 + +id 641 +epd brnkrb1n/1pp1p1pp/3p4/p1Nq1p2/2P5/8/PP1PPPPP/BRK1RBQN w eb - +perft 1 27 +perft 2 725 +perft 3 17842 +perft 4 496072 +perft 5 12604078 +perft 6 362747791 + +id 642 +epd brn1r1nb/ppppkppp/4p3/8/2PP1P2/8/PP1KP1PP/BRN1RQNB w - - +perft 1 25 +perft 2 623 +perft 3 16874 +perft 4 426659 +perft 5 12290985 +perft 6 317097424 + +id 643 +epd rbb1krqn/1pp1pp1p/p3n1p1/3pP3/8/1PN5/P1PP1PPP/RBB1KRQN w FAfa d6 +perft 1 23 +perft 2 529 +perft 3 12641 +perft 4 310277 +perft 5 7861413 +perft 6 202594556 + +id 644 +epd r1bbkrqn/p1pppppp/8/4n3/1p5P/P2P2P1/1PP1PP2/RNBBKRQN w FAfa - +perft 1 23 +perft 2 571 +perft 3 13133 +perft 4 346793 +perft 5 8699448 +perft 6 243460643 + +id 645 +epd rnbkrbqn/p1pp1ppp/4p3/1p6/8/BPN3P1/P1PPPP1P/R2KRBQN w EAea - +perft 1 29 +perft 2 692 +perft 3 20014 +perft 4 500375 +perft 5 14904192 +perft 6 386694739 + +id 646 +epd rnbkrqn1/pppppp2/8/1Q2b1pp/P3P3/5P2/1PPP2PP/RNBKR1NB w EAea - +perft 1 37 +perft 2 1001 +perft 3 36440 +perft 4 987842 +perft 5 35626426 +perft 6 993747544 + +id 647 +epd rbnkbrqn/p1pppp2/7p/1p4pP/3P1P2/8/PPP1P1P1/RBNKBRQN w FAfa - +perft 1 30 +perft 2 564 +perft 3 17143 +perft 4 381364 +perft 5 11859538 +perft 6 293703269 + +id 648 +epd 1nkbbrqn/3ppppp/r1p5/pp6/8/4PP2/PPPPN1PP/RNKBBRQ1 w FAf - +perft 1 26 +perft 2 546 +perft 3 14641 +perft 4 344592 +perft 5 9556962 +perft 6 245137199 + +id 649 +epd rnkrbbq1/pppppnp1/7p/8/1B1Q1p2/3P1P2/PPP1P1PP/RNKR1B1N w DAda - +perft 1 43 +perft 2 887 +perft 3 36240 +perft 4 846858 +perft 5 33185346 +perft 6 851927292 + +id 650 +epd 1rkrbqnb/pppppp2/2n3p1/7p/3P3P/P4N2/1PP1PPP1/RNKRBQ1B w DAd - +perft 1 26 +perft 2 622 +perft 3 16049 +perft 4 403921 +perft 5 10786140 +perft 6 285233838 + +id 651 +epd rbnkr1bn/pp1pqp1p/2p1p3/6p1/3P4/7P/PPP1PPP1/RBNKRQBN w EAea - +perft 1 19 +perft 2 566 +perft 3 12257 +perft 4 381197 +perft 5 9107175 +perft 6 293397389 + +id 652 +epd r1kbrqb1/pppp2pp/2n1p1n1/5p1B/4PP2/P7/1PPP2PP/RNK1RQBN w EAea - +perft 1 39 +perft 2 1359 +perft 3 53626 +perft 4 1876028 +perft 5 73871486 +perft 6 2633945690 + +id 653 +epd rnkrqbbn/p1p3pp/1p1ppp2/8/1P6/3P2P1/PKP1PP1P/RN1RQBBN w da - +perft 1 26 +perft 2 776 +perft 3 20735 +perft 4 611907 +perft 5 16884013 +perft 6 503561996 + +id 654 +epd rnkrqnbb/ppp2p1p/3p4/4p1p1/3P3P/N1Q5/PPP1PPP1/R1KR1NBB w DAda - +perft 1 40 +perft 2 1175 +perft 3 45637 +perft 4 1375884 +perft 5 52620163 +perft 6 1633655838 + +id 655 +epd bbrnkrn1/p1pppp2/1p6/6pp/3q4/1P3QP1/P1PPPP1P/BBRNKRN1 w FCfc - +perft 1 34 +perft 2 1398 +perft 3 45749 +perft 4 1712950 +perft 5 57268492 +perft 6 2059942014 + +id 656 +epd br1bkrnq/1p2pppp/pnp5/3p4/P1P5/5P2/1P1PPKPP/BRNB1RNQ w fb - +perft 1 24 +perft 2 501 +perft 3 12237 +perft 4 284936 +perft 5 7049659 +perft 6 177940764 + +id 657 +epd brnkrbn1/pppppp1q/B6p/6p1/8/1P2PP2/P1PP2PP/BRNKR1NQ w EBeb - +perft 1 34 +perft 2 815 +perft 3 25868 +perft 4 700970 +perft 5 22006883 +perft 6 639803952 + +id 658 +epd br1krnqb/pppppp1p/1n4p1/8/8/P2NN3/2PPPPPP/BR1K1RQB w Beb - +perft 1 37 +perft 2 1029 +perft 3 36748 +perft 4 1025712 +perft 5 36214583 +perft 6 1026195877 + +id 659 +epd rbbnkr1q/p1p2ppp/1p1ppn2/8/1PP4P/8/P2PPPP1/RBBNKRNQ w FAfa - +perft 1 28 +perft 2 755 +perft 3 22623 +perft 4 605106 +perft 5 18972778 +perft 6 513486101 + +id 660 +epd r1b1krnq/pp2pppp/1bn5/2pp4/4N3/5P2/PPPPPRPP/R1BBK1NQ w Afa - +perft 1 24 +perft 2 705 +perft 3 17427 +perft 4 532521 +perft 5 13532966 +perft 6 426443376 + +id 661 +epd 1nbkrbn1/rpppppqp/p7/6p1/4P3/3P2P1/PPP1KP1P/RNB1RBNQ w e - +perft 1 31 +perft 2 800 +perft 3 24748 +perft 4 693366 +perft 5 21193292 +perft 6 625757852 + +id 662 +epd r1bkrnqb/pp3ppp/n1ppp3/8/1P5P/P7/R1PPPPP1/1NBKRNQB w Eea - +perft 1 21 +perft 2 482 +perft 3 11417 +perft 4 275339 +perft 5 7112890 +perft 6 180378139 + +id 663 +epd rbnkbrnq/ppp1p2p/5p2/3p2p1/1B1P4/1N4P1/PPP1PP1P/RB1K1RNQ w FAfa - +perft 1 33 +perft 2 780 +perft 3 25532 +perft 4 628945 +perft 5 20756770 +perft 6 535497008 + +id 664 +epd rnk1brnq/pp1ppppp/2p5/b7/8/1P2P2P/P1PP1PPQ/RNKBBRN1 w FAfa - +perft 1 29 +perft 2 648 +perft 3 19043 +perft 4 449637 +perft 5 13722785 +perft 6 341389148 + +id 665 +epd rnkrbbnq/p1p3pp/5p2/1p1pp3/P7/1PN2P2/2PPP1PP/R1KRBBNQ w DAda - +perft 1 26 +perft 2 827 +perft 3 21865 +perft 4 683167 +perft 5 18916370 +perft 6 589161126 + +id 666 +epd r1krbnqb/p1pp1ppp/2n1p3/8/1p4P1/PPP5/3PPP1P/RNKRBNQB w DAda - +perft 1 25 +perft 2 540 +perft 3 14709 +perft 4 331332 +perft 5 9491817 +perft 6 225389422 + +id 667 +epd rbnkrnbq/ppp1pp2/3p2p1/2N5/P6p/2P5/1P1PPPPP/RB1KRNBQ w EAea - +perft 1 32 +perft 2 790 +perft 3 25107 +perft 4 661207 +perft 5 20906017 +perft 6 578332225 + +id 668 +epd rnkbrn1q/1ppppppb/8/p4N1p/8/P1N5/1PPPPPPP/R1KBR1BQ w EAea - +perft 1 31 +perft 2 691 +perft 3 20813 +perft 4 510665 +perft 5 15308408 +perft 6 404129987 + +id 669 +epd rnkrnbbq/p1p2ppp/3pp3/1p6/6P1/4PQ1B/PPPP1P1P/RNKRN1B1 w DAda - +perft 1 29 +perft 2 558 +perft 3 16800 +perft 4 352887 +perft 5 10825379 +perft 6 246965507 + +id 670 +epd rnkrnqbb/pp2p1p1/3p3p/2p2p2/5P2/1P1N4/P1PPPQPP/RNKR2BB w DAda - +perft 1 29 +perft 2 762 +perft 3 23210 +perft 4 644936 +perft 5 20522675 +perft 6 596067005 + +id 671 +epd bb1rknnr/ppqppppp/8/2p5/3P1N2/1P6/P1P1PPPP/BBQRKN1R w HDhd - +perft 1 33 +perft 2 963 +perft 3 32279 +perft 4 1000890 +perft 5 34552118 +perft 6 1124738493 + +id 672 +epd bqrbknnr/ppp1p2p/8/3p1p2/5p2/P3N2P/1PPPP1P1/BQRBK1NR w HChc - +perft 1 20 +perft 2 398 +perft 3 9009 +perft 4 194859 +perft 5 4834319 +perft 6 113660536 + +id 673 +epd b1rk1bnr/qpp1pppp/p4n2/3p4/3PPP2/7N/PPP3PP/BQRKNB1R w HChc - +perft 1 25 +perft 2 648 +perft 3 16587 +perft 4 455720 +perft 5 12200870 +perft 6 351766307 + +id 674 +epd bqkrnnrb/pppp2p1/4pp2/4P2p/6P1/7P/PPPP1P2/BQRKNNRB w GC - +perft 1 30 +perft 2 493 +perft 3 15118 +perft 4 280726 +perft 5 8786998 +perft 6 181492621 + +id 675 +epd q1brknnr/1p1ppppp/p7/2p5/8/1PPP4/P2RPPPP/QBB1KNNR w Hhd - +perft 1 25 +perft 2 501 +perft 3 13206 +perft 4 290463 +perft 5 7982978 +perft 6 192717198 + +id 676 +epd qrb1k1nr/ppppb1pp/6n1/4ppN1/3P4/4N3/PPP1PPPP/QRBBK2R w HBhb - +perft 1 31 +perft 2 872 +perft 3 26191 +perft 4 739276 +perft 5 22493014 +perft 6 646855304 + +id 677 +epd 1rbknbnr/1ppp1pp1/q6p/p3p3/5P2/2PPB3/PP2P1PP/QR1KNBNR w HBhb - +perft 1 28 +perft 2 1020 +perft 3 28147 +perft 4 984000 +perft 5 27484692 +perft 6 947786800 + +id 678 +epd qrbk2rb/1ppp1ppp/5nn1/p3p3/1N6/P7/1PPPPPPP/QRB1KNRB w gb - +perft 1 23 +perft 2 592 +perft 3 14398 +perft 4 395716 +perft 5 10098215 +perft 6 293988585 + +id 679 +epd qbrk1nnr/1pp1pppp/2b5/p2p4/P2P2P1/8/1PP1PP1P/QBKRBNNR w hc - +perft 1 26 +perft 2 654 +perft 3 18103 +perft 4 471653 +perft 5 13740891 +perft 6 373081138 + +id 680 +epd qrkbbnnr/ppp2p1p/4p3/3p2p1/P7/2PP4/1P2PPPP/QRKBBNNR w HBhb - +perft 1 25 +perft 2 626 +perft 3 16616 +perft 4 431634 +perft 5 12079406 +perft 6 324006164 + +id 681 +epd qr1kbbnr/ppp1pp1p/4n1p1/2Pp4/6P1/4N3/PP1PPP1P/QRK1BBNR w HB d6 +perft 1 26 +perft 2 699 +perft 3 18068 +perft 4 497152 +perft 5 13353359 +perft 6 375702908 + +id 682 +epd qrk1b1rb/p1pppppp/3nnQ2/1p6/1P3P2/3P4/P1P1P1PP/1RKNBNRB w GBgb - +perft 1 43 +perft 2 1369 +perft 3 55463 +perft 4 1831200 +perft 5 71514365 +perft 6 2427477375 + +id 683 +epd qbrk1nbr/pppp3p/5n2/4ppp1/3P1P2/4N3/PPP1P1PP/QBKRN1BR w hc - +perft 1 25 +perft 2 752 +perft 3 20165 +perft 4 615263 +perft 5 17493373 +perft 6 543180234 + +id 684 +epd qrkb1nbr/1pppppQp/3n4/p7/5p2/1P1N4/P1PPP1PP/1RKB1NBR w HBhb - +perft 1 45 +perft 2 946 +perft 3 40100 +perft 4 966903 +perft 5 39736157 +perft 6 1051910977 + +id 685 +epd qrk1nbbr/ppp1p1p1/4n2p/3p1p2/1P5P/3P2P1/P1P1PP2/QRKNNBBR w HBhb - +perft 1 32 +perft 2 770 +perft 3 25367 +perft 4 646977 +perft 5 21717615 +perft 6 577979364 + +id 686 +epd qrkn1rbb/pp2pppp/2p5/3p4/P2Qn1P1/1P6/2PPPP1P/1RKNNRBB w FBfb - +perft 1 38 +perft 2 943 +perft 3 35335 +perft 4 868165 +perft 5 31909835 +perft 6 798405123 + +id 687 +epd bbrqknnr/ppp4p/3pp3/5pp1/4PP2/5Q2/PPPP2PP/BBR1KNNR w HChc - +perft 1 36 +perft 2 843 +perft 3 29974 +perft 4 758528 +perft 5 26828059 +perft 6 723306114 + +id 688 +epd 1rqbkn1r/p1p1pppp/1p5n/P2p4/3Pb1P1/8/1PP1PP1P/BRQBKNNR w HBhb - +perft 1 23 +perft 2 778 +perft 3 19482 +perft 4 649789 +perft 5 17337683 +perft 6 579112676 + +id 689 +epd br1knbnr/1qp1pppp/pp1p4/8/8/PP6/2PPPPPP/BRQKNBNR w HBhb - +perft 1 26 +perft 2 697 +perft 3 18835 +perft 4 546622 +perft 5 15280079 +perft 6 473071890 + +id 690 +epd brqk2rb/ppppp1pp/4np2/8/2n5/3P1Q2/PP2PPPP/BR1KNNRB w GBgb - +perft 1 32 +perft 2 948 +perft 3 30434 +perft 4 885713 +perft 5 29821322 +perft 6 874251866 + +id 691 +epd r1bqknnr/pp1pp1p1/5p1p/2p1b2N/2P5/8/PPQPPPPP/RBB1K1NR w HAha - +perft 1 31 +perft 2 785 +perft 3 25549 +perft 4 659952 +perft 5 22244193 +perft 6 592797491 + +id 692 +epd rqbbknnr/ppppp2p/5pp1/8/8/1P3PP1/PQPPP2P/R1BBKNNR w HAha - +perft 1 23 +perft 2 391 +perft 3 10163 +perft 4 198450 +perft 5 5576671 +perft 6 121267576 + +id 693 +epd rqbknbnr/1pp1p2p/p7/3p1pp1/7N/1PP5/P2PPPPP/RQBK1BNR w HAha - +perft 1 27 +perft 2 676 +perft 3 19606 +perft 4 522428 +perft 5 15955388 +perft 6 448477218 + +id 694 +epd rqb1nnrb/2ppkppp/1p2p3/p7/2PPP3/1P6/P4PPP/RQBKNNRB w GA - +perft 1 31 +perft 2 727 +perft 3 22895 +perft 4 570647 +perft 5 18361051 +perft 6 483248153 + +id 695 +epd rb1kbn1r/p1ppppp1/qp5n/7p/P7/RPP5/3PPPPP/1BQKBNNR w Hha - +perft 1 29 +perft 2 837 +perft 3 23815 +perft 4 730083 +perft 5 21279560 +perft 6 682863811 + +id 696 +epd rqkbb1nr/p1p2ppp/1p1p2n1/3Np3/4P3/5N2/PPPP1PPP/RQKBB2R w HAha - +perft 1 28 +perft 2 717 +perft 3 20663 +perft 4 550987 +perft 5 16347343 +perft 6 453153783 + +id 697 +epd rqknbbr1/p1pppp1p/1p3np1/8/4P3/2P2P1P/PP1P2P1/RQKNBBNR w HAa - +perft 1 27 +perft 2 650 +perft 3 18231 +perft 4 475303 +perft 5 13847463 +perft 6 383256006 + +id 698 +epd r1k1bnrb/1qpppppp/1p2n3/p7/1P5P/6P1/P1PPPP2/RQKNBNR1 w GAga - +perft 1 24 +perft 2 806 +perft 3 20693 +perft 4 713220 +perft 5 19382263 +perft 6 686009788 + +id 699 +epd rb1knnbr/1pp1ppp1/p2p3p/5q2/3B2P1/3P1P2/PPP1P2P/RBQKNN1R w HAha - +perft 1 34 +perft 2 1360 +perft 3 44096 +perft 4 1605706 +perft 5 51973672 +perft 6 1837704407 + +id 700 +epd rqkb1nbr/p1p1ppp1/1p3n1p/2Qp4/8/2P5/PP1PPPPP/R1KBNNBR w HAha - +perft 1 39 +perft 2 983 +perft 3 38218 +perft 4 940989 +perft 5 36347815 +perft 6 918801645 + +id 701 +epd rqknnbbr/2pppp2/pp5p/6p1/1P1P4/4PP2/P1P3PP/RQKNNBBR w HAha - +perft 1 26 +perft 2 628 +perft 3 17638 +perft 4 464924 +perft 5 13787303 +perft 6 386125234 + +id 702 +epd rqkn1rbb/1pp1pppp/p7/3p4/3Pn3/2P1PP2/PP4PP/RQKNNRBB w FAfa - +perft 1 20 +perft 2 527 +perft 3 12216 +perft 4 321533 +perft 5 8082183 +perft 6 219311659 + +id 703 +epd bbrkqn1r/1pppppp1/5n2/p7/1PP2P1p/7N/P2PP1PP/BBRKQN1R w HChc - +perft 1 36 +perft 2 963 +perft 3 35291 +perft 4 973839 +perft 5 35907489 +perft 6 1034223364 + +id 704 +epd brkbqn1r/p2ppppp/7n/1p6/P1p3PP/8/1PPPPP1N/BRKBQ1NR w HBhb - +perft 1 18 +perft 2 583 +perft 3 11790 +perft 4 394603 +perft 5 8858385 +perft 6 304339862 + +id 705 +epd brkq1bnr/pp1ppp1p/8/2p2np1/P7/8/1PPPPPPP/BRKQNBNR w HBhb - +perft 1 19 +perft 2 552 +perft 3 11811 +perft 4 354260 +perft 5 8432183 +perft 6 262293169 + +id 706 +epd brkqnnrb/1ppppppp/8/8/p3P3/5N2/PPPP1PPP/BRKQ1NRB w GBgb - +perft 1 21 +perft 2 397 +perft 3 9653 +perft 4 204350 +perft 5 5489836 +perft 6 128389738 + +id 707 +epd rbbkq1nr/1p2pppp/p1p3nB/3p4/1Q1P4/6N1/PPP1PPPP/RB1K2NR w HAha - +perft 1 40 +perft 2 1132 +perft 3 43404 +perft 4 1260470 +perft 5 47425783 +perft 6 1415578783 + +id 708 +epd rkbbq1nr/1pppp1p1/4np2/p6p/8/PP3P2/1KPPP1PP/R1BBQNNR w ha - +perft 1 24 +perft 2 596 +perft 3 15220 +perft 4 402121 +perft 5 10822049 +perft 6 302056813 + +id 709 +epd r1bqn1nr/pkpppp1p/1p4pb/8/PN6/R7/1PPPPPPP/1KBQ1BNR w H - +perft 1 33 +perft 2 794 +perft 3 25450 +perft 4 649150 +perft 5 20919309 +perft 6 561073410 + +id 710 +epd rkb1nnrb/1pppq1pp/p4p2/4p3/5P2/1P1PB3/P1P1P1PP/RK1QNNRB w GAga - +perft 1 26 +perft 2 625 +perft 3 17050 +perft 4 442036 +perft 5 12515042 +perft 6 342967558 + +id 711 +epd rbkqbn1r/pppp1p1p/2n1p1p1/8/8/1P1PP1N1/P1P2PPP/RBKQB1NR w HAha - +perft 1 30 +perft 2 660 +perft 3 20308 +perft 4 492714 +perft 5 15348335 +perft 6 403323883 + +id 712 +epd rkqbb1n1/pppppppr/8/6np/5P2/8/PPPPP1PP/RKQBBNNR w HAa - +perft 1 23 +perft 2 500 +perft 3 12154 +perft 4 292936 +perft 5 7519117 +perft 6 196524441 + +id 713 +epd rkqnbbnr/ppppppp1/8/7p/3N4/6PP/PPPPPP2/RKQNBB1R w HAa - +perft 1 24 +perft 2 484 +perft 3 12495 +perft 4 284570 +perft 5 7775173 +perft 6 193947530 + +id 714 +epd rkqnb1rb/p1p1pppp/1p1p4/2n5/3P4/2P1N1N1/PP2PPPP/RKQ1B1RB w GAga - +perft 1 28 +perft 2 1020 +perft 3 29124 +perft 4 1027904 +perft 5 30515456 +perft 6 1073711823 + +id 715 +epd rbk1nnbr/1ppq1ppp/p2p4/4p3/P3B2P/2P5/1P1PPPP1/R1KQNNBR w HAha - +perft 1 38 +perft 2 998 +perft 3 37265 +perft 4 1047592 +perft 5 38552638 +perft 6 1139322479 + +id 716 +epd r1qbn1br/k1pppppp/6n1/pp6/5P1P/P7/1PPPP1PB/RKQBNN1R w HA - +perft 1 22 +perft 2 549 +perft 3 12867 +perft 4 348574 +perft 5 8725809 +perft 6 251613569 + +id 717 +epd rkqnn1br/pppp3p/4p1pb/5p2/P2P4/7P/1PP1PPPB/RKQNNB1R w HAha - +perft 1 32 +perft 2 659 +perft 3 21249 +perft 4 469701 +perft 5 15434721 +perft 6 365761521 + +id 718 +epd rk1nnrbb/p1p1pppp/1p6/3p1q2/P3P3/2NN4/1PPP1PPP/RKQ2RBB w FAfa - +perft 1 29 +perft 2 989 +perft 3 29087 +perft 4 980477 +perft 5 29643404 +perft 6 998848556 + +id 719 +epd bbrk1q1r/ppppppp1/3n4/7p/3Pn3/6PN/PPP1PPNP/BBRK1Q1R w HChc - +perft 1 23 +perft 2 712 +perft 3 16551 +perft 4 516177 +perft 5 12995202 +perft 6 411077508 + +id 720 +epd brkbnq1r/p1ppp2p/5ppn/1p6/5P2/1P1P2P1/P1P1P2P/BRKBNQNR w HBhb - +perft 1 28 +perft 2 856 +perft 3 24984 +perft 4 780503 +perft 5 23529352 +perft 6 754501112 + +id 721 +epd br1k1bnr/ppppp1pp/4np2/1B2P2q/3P4/8/PPP2PPP/BRKNQ1NR w HB - +perft 1 36 +perft 2 1214 +perft 3 40615 +perft 4 1328331 +perft 5 45096834 +perft 6 1470987023 + +id 722 +epd brk1qnrb/pnppp1p1/1p6/5p1p/8/5PPP/PPPPP1R1/BRKNQN1B w Bgb - +perft 1 22 +perft 2 551 +perft 3 13111 +perft 4 353317 +perft 5 9040545 +perft 6 259643605 + +id 723 +epd rbbkn1nr/1ppp2pp/p3p3/2q2p2/3P4/6P1/PPPBPP1P/RB1KNQNR w HAha - +perft 1 31 +perft 2 1060 +perft 3 31332 +perft 4 1015099 +perft 5 30314172 +perft 6 976268967 + +id 724 +epd rkbbn1nr/ppppp1pp/8/6N1/5p2/1q6/P1PPPPPP/RKBBN1QR w HAha - +perft 1 3 +perft 2 72 +perft 3 1919 +perft 4 50827 +perft 5 1400832 +perft 6 39654253 + +id 725 +epd rkb2bnr/pp2pppp/2p1n3/3p4/q2P4/5NP1/PPP1PP1P/RKBNQBR1 w Aha - +perft 1 29 +perft 2 861 +perft 3 24504 +perft 4 763454 +perft 5 22763215 +perft 6 731511256 + +id 726 +epd rkbq1nrb/ppppppp1/7p/8/1P1n4/P4P1P/2PPP1P1/RKBNQNRB w GAga - +perft 1 25 +perft 2 672 +perft 3 17631 +perft 4 473864 +perft 5 12954224 +perft 6 361237536 + +id 727 +epd rbknb1nr/ppp1qp1p/6p1/3pp3/3P3P/2B1P3/PPP2PP1/RBKN1QNR w HAha - +perft 1 27 +perft 2 857 +perft 3 24688 +perft 4 792538 +perft 5 23790033 +perft 6 768247869 + +id 728 +epd rknbbq1r/p1pppppp/1p2N3/8/3n4/2P5/PP1PPPPP/RK1BBQNR w HAha - +perft 1 29 +perft 2 763 +perft 3 22138 +perft 4 574054 +perft 5 16926075 +perft 6 447896703 + +id 729 +epd r1nqbbnr/1pppp1pp/1k6/p4p2/8/4P3/PPPP1PPP/RKN1BBNR w HA - +perft 1 26 +perft 2 658 +perft 3 17302 +perft 4 464039 +perft 5 12380488 +perft 6 349047256 + +id 730 +epd rkn2qrb/ppp1pppp/6n1/1b1p4/1P6/4PPB1/P1PP2PP/RKNQ1NRB w GAga - +perft 1 23 +perft 2 574 +perft 3 14070 +perft 4 370324 +perft 5 9501401 +perft 6 263870337 + +id 731 +epd rbkn2br/ppppp1p1/4np1p/1P5q/8/2P1N3/P2PPPPP/RBK1QNBR w HAha - +perft 1 29 +perft 2 992 +perft 3 29506 +perft 4 999564 +perft 5 30148787 +perft 6 1045942540 + +id 732 +epd 1knbqnbr/1ppppp1p/r5p1/p7/7P/2PN2P1/PP1PPP2/RK1BQNBR w HAh - +perft 1 26 +perft 2 698 +perft 3 19395 +perft 4 512023 +perft 5 14848229 +perft 6 402599313 + +id 733 +epd rk1qnbbr/pnpppp1p/6p1/1p6/3P4/1P6/P1P1PPPP/RKNQNBBR w HAha - +perft 1 20 +perft 2 480 +perft 3 11159 +perft 4 287539 +perft 5 7425917 +perft 6 203194521 + +id 734 +epd rknqnrbb/pp1p2p1/5p1p/2p1p3/2P1P3/P2P4/1P3PPP/RKNQNRBB w FAfa - +perft 1 26 +perft 2 679 +perft 3 18116 +perft 4 494953 +perft 5 13790137 +perft 6 392629571 + +id 735 +epd bbrk2qr/pp1p1ppp/3n2n1/2p1p3/3P1P2/6N1/PPP1P1PP/BBRKN1QR w HChc - +perft 1 26 +perft 2 790 +perft 3 21521 +perft 4 673269 +perft 5 19259490 +perft 6 617563700 + +id 736 +epd b1krnnqr/1p1ppppp/p1p5/b6B/P7/4P1N1/1PPP1PPP/BRK1N1QR w HB - +perft 1 26 +perft 2 625 +perft 3 16451 +perft 4 415452 +perft 5 11490615 +perft 6 304805107 + +id 737 +epd 1rknnbqr/3ppppp/p7/1pp5/4b2P/P4P2/1PPPP1PR/BRKNNBQ1 w Bhb - +perft 1 24 +perft 2 757 +perft 3 19746 +perft 4 618777 +perft 5 17275100 +perft 6 544309489 + +id 738 +epd br1nn1rb/pppkpqpp/3p1p2/8/PP6/4N3/1KPPPPPP/BR2NQRB w - - +perft 1 24 +perft 2 682 +perft 3 17129 +perft 4 482711 +perft 5 13057308 +perft 6 375033550 + +id 739 +epd rbbkn1qr/pppp2p1/6np/4pp2/7N/7P/PPPPPPPR/RBBK1NQ1 w Aha - +perft 1 22 +perft 2 586 +perft 3 14158 +perft 4 409891 +perft 5 10607781 +perft 6 324452612 + +id 740 +epd rk1bn1qr/pppbpppp/4n3/4p3/4P3/5P2/PPPP2PP/RKBB1NQR w HAha - +perft 1 22 +perft 2 530 +perft 3 13440 +perft 4 348004 +perft 5 9514787 +perft 6 259898748 + +id 741 +epd rkbnnbqr/1ppp1ppp/p7/4p3/8/QP3P2/P1PPP1PP/RKBNNB1R w HAha - +perft 1 29 +perft 2 705 +perft 3 21511 +perft 4 551042 +perft 5 17524731 +perft 6 472356665 + +id 742 +epd 1kbnnqrb/1pp1p1pp/r4p2/p2p4/N4P2/3P4/PPP1P1PP/RKB1NQRB w GAg - +perft 1 21 +perft 2 623 +perft 3 14979 +perft 4 437554 +perft 5 11601134 +perft 6 343214006 + +id 743 +epd rbknbn1r/pppp1p1p/4p1q1/8/P1P3Pp/8/1P1PPP2/RBKNBNQR w HAha - +perft 1 30 +perft 2 813 +perft 3 24959 +perft 4 708454 +perft 5 23379040 +perft 6 692576573 + +id 744 +epd rk1bb1qr/2pppppp/p2nn3/1p4P1/6QP/8/PPPPPP2/RKNBBN1R w HAha - +perft 1 36 +perft 2 857 +perft 3 30124 +perft 4 757524 +perft 5 26485812 +perft 6 696999449 + +id 745 +epd rkn1bbqr/p2ppppp/2p1n3/1p6/4PP2/6PP/PPPP4/RKNNBBQR w HAha - +perft 1 33 +perft 2 687 +perft 3 22744 +perft 4 511018 +perft 5 17101732 +perft 6 412778368 + +id 746 +epd rkn1bqrb/pnp1pppp/3p4/8/Pp6/1N2NP2/1PPPP1PP/RK2BQRB w GAga - +perft 1 28 +perft 2 591 +perft 3 17174 +perft 4 406025 +perft 5 12182448 +perft 6 312575205 + +id 747 +epd rbk1n1br/ppp1ppqp/2n5/2Np2p1/8/2P5/PPBPPPPP/R1KN1QBR w HAha - +perft 1 35 +perft 2 930 +perft 3 30663 +perft 4 844433 +perft 5 27160490 +perft 6 780616047 + +id 748 +epd rknbn1br/1ppp1ppp/p3p3/8/1q6/2P2N1P/P2PPPP1/RKNB1QBR w HAha - +perft 1 4 +perft 2 157 +perft 3 3697 +perft 4 138102 +perft 5 3454704 +perft 6 125373395 + +id 749 +epd rkn1qbbr/pp3ppp/4n3/2ppp3/4P1P1/P2P4/1PP2P1P/RKNNQBBR w HAha - +perft 1 28 +perft 2 840 +perft 3 24437 +perft 4 771328 +perft 5 23200961 +perft 6 756489357 + +id 750 +epd rkn1qrbb/pp1ppp2/2p1n1p1/7p/2P2P1P/6P1/PP1PP3/RKNNQRBB w FAfa - +perft 1 32 +perft 2 867 +perft 3 27595 +perft 4 757836 +perft 5 24485663 +perft 6 688115847 + +id 751 +epd b1rknnrq/bpppp1p1/p6p/5p1P/6P1/4N3/PPPPPP2/BBRKN1RQ w GCgc - +perft 1 33 +perft 2 851 +perft 3 28888 +perft 4 763967 +perft 5 26686205 +perft 6 731944177 + +id 752 +epd brkb1nr1/pppppp2/3n2pp/3B4/1P6/4P3/PqPP1PPP/BRK1NNRQ w GBgb - +perft 1 4 +perft 2 98 +perft 3 2965 +perft 4 76143 +perft 5 2352530 +perft 6 64251468 + +id 753 +epd brk1nbrq/1ppppn1p/6p1/p4p2/P5P1/5R2/1PPPPP1P/BRKNNB1Q w Bgb - +perft 1 29 +perft 2 922 +perft 3 27709 +perft 4 879527 +perft 5 27463717 +perft 6 888881062 + +id 754 +epd brkn1rqb/1p1ppppp/3n4/p1p5/1P3P2/8/PNPPP1PP/BR1KNRQB w fb - +perft 1 29 +perft 2 633 +perft 3 19399 +perft 4 469818 +perft 5 15076198 +perft 6 396737074 + +id 755 +epd rb1k1nrq/pbp1pppp/1p1p1n2/8/5P2/4NN1P/PPPPP1P1/RBBK2RQ w GAga - +perft 1 28 +perft 2 841 +perft 3 24056 +perft 4 710751 +perft 5 20772996 +perft 6 613798447 + +id 756 +epd rkbbnnrq/p1pp3p/4p1p1/1p3p2/P6P/1P6/1BPPPPP1/RK1BNNRQ w GAga - +perft 1 33 +perft 2 957 +perft 3 30668 +perft 4 907217 +perft 5 29735654 +perft 6 903933626 + +id 757 +epd rk2nbrq/p1ppppp1/bpn5/7p/6P1/2N2P2/PPPPP1QP/RKB1NBR1 w GAga - +perft 1 24 +perft 2 687 +perft 3 18206 +perft 4 544627 +perft 5 15518417 +perft 6 484217179 + +id 758 +epd rkbn1r1b/pp1pppnp/6q1/2p3p1/5P1P/4N3/PPPPP1P1/RKB1NRQB w FAfa - +perft 1 23 +perft 2 831 +perft 3 21254 +perft 4 754622 +perft 5 21126103 +perft 6 744755212 + +id 759 +epd rbknb1rq/ppp1p1p1/3pnp1p/8/6PP/2PP4/PP2PP2/RBKNBNRQ w GAga - +perft 1 31 +perft 2 838 +perft 3 26800 +perft 4 736910 +perft 5 24008129 +perft 6 677776408 + +id 760 +epd rknbb1rq/p1pn1ppp/4p3/1p1p4/2P5/1P2N1P1/P2PPP1P/RKNBB1RQ w GAga - +perft 1 29 +perft 2 830 +perft 3 24798 +perft 4 721630 +perft 5 22243832 +perft 6 660040360 + +id 761 +epd rk1nbbrq/pp1p1ppp/3n4/P3p3/2p4P/8/1PPPPPP1/RKNNBBRQ w GAga - +perft 1 24 +perft 2 484 +perft 3 12776 +perft 4 297419 +perft 5 8379748 +perft 6 214004367 + +id 762 +epd rknnbr1b/ppp2pqp/3p4/4p1p1/7P/3P1P2/PPP1P1P1/RKNNBRQB w FAfa - +perft 1 32 +perft 2 838 +perft 3 26408 +perft 4 740701 +perft 5 23472124 +perft 6 699211365 + +id 763 +epd rb1k1rbq/ppppN1pp/2nn4/5p2/7P/8/PPPPPPP1/RBK1NRBQ w FA - +perft 1 27 +perft 2 800 +perft 3 22785 +perft 4 701742 +perft 5 20804424 +perft 6 660917073 + +id 764 +epd r1nbnrbq/kppppp1p/6p1/8/p1PP1P2/4P3/PP4PP/RKNBNRBQ w FA - +perft 1 28 +perft 2 757 +perft 3 21198 +perft 4 602699 +perft 5 17180857 +perft 6 507618340 + +id 765 +epd rkn1rbbq/p1pppppp/2n5/1pP5/8/1N2P3/PP1P1PPP/RK1NRBBQ w EAea - +perft 1 22 +perft 2 483 +perft 3 11890 +perft 4 283679 +perft 5 7497674 +perft 6 191130942 + +id 766 +epd rknnrqbb/2pppppp/8/p7/Np3P2/3P4/PPP1P1PP/RKN1RQBB w EAea - +perft 1 25 +perft 2 536 +perft 3 14456 +perft 4 339180 +perft 5 9694947 +perft 6 245669668 + +id 767 +epd bb1rknrn/1qppppp1/1p4B1/p6N/8/2P5/PP1PPPPP/B1QRK1RN w GDgd - +perft 1 32 +perft 2 715 +perft 3 22421 +perft 4 575008 +perft 5 17860156 +perft 6 502410909 + +id 768 +epd b1rbknrn/qpp1ppp1/p6p/3p4/2P5/1P1P1P2/P3P1PP/BQRBKNRN w GCgc - +perft 1 30 +perft 2 818 +perft 3 24421 +perft 4 688711 +perft 5 20981488 +perft 6 611986786 + +id 769 +epd bqkrnbrn/1pp1pp1p/p7/1B1p2p1/4P3/7P/PPPP1PP1/BQKRN1RN w - - +perft 1 28 +perft 2 676 +perft 3 18366 +perft 4 478054 +perft 5 13126287 +perft 6 363765666 + +id 770 +epd bqrknrnb/1p2ppp1/p1pp3p/8/3P1P2/1PP5/P3P1PP/BQRKNRNB w FCfc - +perft 1 31 +perft 2 646 +perft 3 20686 +perft 4 455607 +perft 5 14984618 +perft 6 349082278 + +id 771 +epd qbbrkn1r/pppppp1p/8/6p1/2P1Pn1P/6N1/PP1P1PP1/QBBRKNR1 w GDd - +perft 1 20 +perft 2 532 +perft 3 11581 +perft 4 303586 +perft 5 7512432 +perft 6 202967948 + +id 772 +epd 1rbbknr1/p1ppp1pp/1pq2pn1/8/3P4/P3P3/QPP2PPP/1RBBKNRN w GBgb - +perft 1 31 +perft 2 1002 +perft 3 30581 +perft 4 999607 +perft 5 30642468 +perft 6 1009228283 + +id 773 +epd qrbkn1rn/pppp1ppp/8/6b1/P1P1Pp2/8/1P1P2PP/QRBKNBRN w GBgb - +perft 1 22 +perft 2 505 +perft 3 12447 +perft 4 304863 +perft 5 8192621 +perft 6 214730959 + +id 774 +epd qrbk1rnb/p2ppp1p/5n2/1pp3p1/8/7P/PPPPPPPN/QRBKR1NB w Bfb - +perft 1 20 +perft 2 619 +perft 3 13448 +perft 4 449630 +perft 5 10571176 +perft 6 369603424 + +id 775 +epd qbrkb1r1/ppp2ppp/3pn1n1/P3p3/4P3/3P4/1PP2PPP/QBRKBNRN w GCgc - +perft 1 26 +perft 2 755 +perft 3 20596 +perft 4 604483 +perft 5 17164382 +perft 6 510878835 + +id 776 +epd qrkbb1r1/ppp1pnpp/3p2n1/5p2/1P3P2/2Q3N1/P1PPP1PP/1RKBB1RN w GBgb - +perft 1 35 +perft 2 918 +perft 3 32244 +perft 4 870888 +perft 5 30933394 +perft 6 867833733 + +id 777 +epd qrknbbrn/ppp1ppp1/8/7p/2Bp4/4PPP1/PPPP3P/QRKNB1RN w GBgb - +perft 1 27 +perft 2 593 +perft 3 16168 +perft 4 376808 +perft 5 10422676 +perft 6 258348640 + +id 778 +epd qrk1brnb/ppppp3/4n2p/5pp1/2PP4/2N4P/PP2PPP1/QRK1BRNB w FBfb - +perft 1 24 +perft 2 672 +perft 3 17447 +perft 4 506189 +perft 5 13765777 +perft 6 414930519 + +id 779 +epd qbrknrb1/p2ppppp/2p3n1/8/p4P2/6PP/1PPPP3/QBRKNRBN w FCfc - +perft 1 29 +perft 2 759 +perft 3 23235 +perft 4 634493 +perft 5 20416668 +perft 6 584870558 + +id 780 +epd 1rkb1rbn/p1pp1ppp/3np3/1p6/4qP2/3NB3/PPPPPRPP/QRKB3N w Bfb - +perft 1 22 +perft 2 923 +perft 3 22585 +perft 4 914106 +perft 5 24049880 +perft 6 957218571 + +id 781 +epd 1rknrbbn/p1pp1p1p/8/1p2p1p1/4qPP1/2P5/PP1PP1BP/QRKNR1BN w EBeb - +perft 1 28 +perft 2 1309 +perft 3 36355 +perft 4 1568968 +perft 5 44576409 +perft 6 1846382333 + +id 782 +epd qrk1rn1b/ppppp2p/4n3/3b1pp1/4P2P/5BP1/PPPP1P2/QRKNRNB1 w EBeb - +perft 1 26 +perft 2 839 +perft 3 22189 +perft 4 726354 +perft 5 19978260 +perft 6 661207281 + +id 783 +epd bbrqk1rn/pp1ppppp/8/2p5/2P1P3/5n1P/PPBP1PP1/B1RQKNRN w GCgc - +perft 1 3 +perft 2 95 +perft 3 2690 +perft 4 85038 +perft 5 2518864 +perft 6 80775549 + +id 784 +epd brqbk2n/pppppprp/8/6p1/1P3n2/5P2/P1PPP1PP/R1QBKNRN w Gb - +perft 1 22 +perft 2 593 +perft 3 13255 +perft 4 362760 +perft 5 8922397 +perft 6 253271592 + +id 785 +epd brqknbr1/pp3ppp/3p2n1/2p1p3/2P5/5P2/PPKPP1PP/BRQ1NBRN w gb - +perft 1 21 +perft 2 590 +perft 3 13190 +perft 4 397355 +perft 5 9581695 +perft 6 304103516 + +id 786 +epd 1rqknrnb/2pp1ppp/p3p3/1p6/P2P4/5bP1/1PP1PP1P/BRQKNRNB w FBfb - +perft 1 24 +perft 2 737 +perft 3 20052 +perft 4 598439 +perft 5 17948681 +perft 6 536330341 + +id 787 +epd rbb1k1rn/p1pqpppp/6n1/1p1p4/5P2/3PP3/PPP1K1PP/RBBQ1NRN w ga - +perft 1 24 +perft 2 694 +perft 3 16773 +perft 4 513782 +perft 5 13094823 +perft 6 419402704 + +id 788 +epd rqbbknr1/1ppp2pp/p5n1/4pp2/P7/1PP5/1Q1PPPPP/R1BBKNRN w GAga - +perft 1 24 +perft 2 600 +perft 3 15347 +perft 4 408207 +perft 5 11029596 +perft 6 308553169 + +id 789 +epd rqbknbrn/2pppppp/6Q1/pp6/8/2P5/PP1PPPPP/R1BKNBRN w GAga - +perft 1 40 +perft 2 949 +perft 3 34100 +perft 4 889887 +perft 5 31296485 +perft 6 881529007 + +id 790 +epd rqbknr1b/pp1ppp2/2p2n1p/6p1/8/3P1PPP/PPP1P3/RQBKNRNB w FAfa - +perft 1 20 +perft 2 560 +perft 3 12275 +perft 4 373921 +perft 5 8687544 +perft 6 277906201 + +id 791 +epd rbqkbnrn/p3pppp/1p6/3p4/P1p3P1/1P6/1QPPPP1P/RB1KBNRN w GAga - +perft 1 30 +perft 2 1155 +perft 3 35865 +perft 4 1351455 +perft 5 43092716 +perft 6 1614019629 + +id 792 +epd rqkbb1rn/p1p1pppn/1p1p4/7p/4PP2/7P/PPPPB1P1/RQK1BNRN w GAga - +perft 1 30 +perft 2 701 +perft 3 20804 +perft 4 515942 +perft 5 15450970 +perft 6 401499189 + +id 793 +epd rqknbbrn/1p2pp1p/3p2p1/p1p5/P2P4/1P6/1KP1PPPP/RQ1NBBRN w ga - +perft 1 28 +perft 2 756 +perft 3 21655 +perft 4 610320 +perft 5 17989811 +perft 6 525585996 + +id 794 +epd rqknbrnb/1pp3pp/5p2/p2pp3/P7/3PPN2/1PP2PPP/RQKNBR1B w FAfa - +perft 1 26 +perft 2 731 +perft 3 19509 +perft 4 550395 +perft 5 15209404 +perft 6 439767476 + +id 795 +epd rbqkr1bn/p1pppp1p/1p1n4/6p1/7P/3P1PP1/PPP1P3/RBQKNRBN w FAa - +perft 1 27 +perft 2 586 +perft 3 16282 +perft 4 381604 +perft 5 10905865 +perft 6 274364342 + +id 796 +epd rqk1nrb1/ppbp1ppp/4p1n1/2p5/7P/1PP5/P2PPPP1/RQKBNRBN w FAfa - +perft 1 27 +perft 2 749 +perft 3 21480 +perft 4 602318 +perft 5 18084787 +perft 6 520547029 + +id 797 +epd rqknrbbn/pp1p1ppp/4p3/2p5/3P2P1/7P/PPP1PP2/RQKNRBBN w EAa - +perft 1 20 +perft 2 533 +perft 3 11829 +perft 4 336248 +perft 5 8230417 +perft 6 245871540 + +id 798 +epd rqknrnbb/pp1ppp1p/2p3p1/8/8/1P2P1NP/P1PP1PP1/RQKNR1BB w EAea - +perft 1 22 +perft 2 633 +perft 3 14480 +perft 4 441877 +perft 5 10827868 +perft 6 343525739 + +id 799 +epd 1brkq1rn/2pppppp/1p2n3/p2bN3/8/7P/PPPPPPP1/BBRKQ1RN w GCgc - +perft 1 27 +perft 2 748 +perft 3 20134 +perft 4 580054 +perft 5 16010135 +perft 6 475206624 + +id 800 +epd brkbqnrn/2pp1ppp/8/1p2p3/Pp2N3/8/2PPPPPP/BRKBQNR1 w GBgb - +perft 1 30 +perft 2 827 +perft 3 25308 +perft 4 757837 +perft 5 23746165 +perft 6 751690068 + +id 801 +epd brk1nbrn/pp1ppppp/2p5/7P/5P2/q2P4/PPP1P1P1/BRKQNBRN w GBgb - +perft 1 15 +perft 2 471 +perft 3 8716 +perft 4 276424 +perft 5 5960901 +perft 6 190316951 + +id 802 +epd brkqnrnb/1p1pp1p1/p4p2/2p4p/8/P2PP3/1PP1QPPP/BRK1NRNB w FBfb - +perft 1 24 +perft 2 479 +perft 3 12584 +perft 4 280081 +perft 5 7830230 +perft 6 190419716 + +id 803 +epd rbbkqnrn/2ppp2p/pp3p2/6p1/P6P/8/RPPPPPP1/1BBKQNRN w Gga - +perft 1 21 +perft 2 523 +perft 3 12125 +perft 4 328733 +perft 5 8322614 +perft 6 242240658 + +id 804 +epd rkbbqr1n/1ppppppn/7p/p7/4P3/2P2P2/PP1PB1PP/RKB1QNRN w GAa - +perft 1 27 +perft 2 563 +perft 3 16026 +perft 4 372148 +perft 5 11105151 +perft 6 283211800 + +id 805 +epd rkbqnbrn/ppppp3/8/5ppp/2P3P1/7P/PPQPPP2/RKB1NBRN w GAga - +perft 1 28 +perft 2 639 +perft 3 19250 +perft 4 469250 +perft 5 14872172 +perft 6 384663405 + +id 806 +epd rkb1nrnb/pppp1pp1/5q1p/8/P3p3/4R1P1/1PPPPP1P/1KBQNRNB w Ffa - +perft 1 28 +perft 2 873 +perft 3 23690 +perft 4 720814 +perft 5 20209424 +perft 6 625281937 + +id 807 +epd rbkqb1rn/1p1ppppp/4n3/p1p5/8/3PBP2/PPP1P1PP/RBKQ1NRN w GAga - +perft 1 26 +perft 2 798 +perft 3 21416 +perft 4 667496 +perft 5 18475618 +perft 6 591681956 + +id 808 +epd rk1qbnrn/1p1ppppp/1b6/p1p5/P7/2P3NP/1P1PPPP1/RKQBB1RN w GAga - +perft 1 22 +perft 2 506 +perft 3 12313 +perft 4 301029 +perft 5 7891676 +perft 6 205739580 + +id 809 +epd rk1nbbrn/ppp1ppp1/8/3p3p/1P1P2q1/5PB1/P1P1P1PP/RKQN1BRN w GAga - +perft 1 31 +perft 2 956 +perft 3 29219 +perft 4 903799 +perft 5 27827461 +perft 6 876341492 + +id 810 +epd rkqnbr1b/pp1pppp1/7p/2p2n2/P2P4/7N/RPP1PPPP/1KQNBR1B w Ffa - +perft 1 31 +perft 2 750 +perft 3 24267 +perft 4 646252 +perft 5 21639104 +perft 6 617064197 + +id 811 +epd rbkq1rbn/2p1pppp/pp3n2/3p4/5P2/3N2N1/PPPPP1PP/RBKQR1B1 w Afa - +perft 1 26 +perft 2 647 +perft 3 18027 +perft 4 465119 +perft 5 13643783 +perft 6 369702807 + +id 812 +epd rkqbr1bn/p2ppppp/1pp2n2/8/5P2/3P1N2/PPP1PRPP/RKQB2BN w Aa - +perft 1 24 +perft 2 574 +perft 3 14593 +perft 4 371597 +perft 5 10066892 +perft 6 271121237 + +id 813 +epd rk1qrbbn/p1ppp1pp/1p2n3/5p2/1P6/K3N3/P1PPPPPP/R1Q1RBBN w ea - +perft 1 25 +perft 2 548 +perft 3 14069 +perft 4 340734 +perft 5 9043111 +perft 6 235545764 + +id 814 +epd rkqnrnbb/pp1pp3/2p5/5ppp/8/PP4NP/2PPPPP1/RKQNR1BB w EAea - +perft 1 23 +perft 2 727 +perft 3 18228 +perft 4 566572 +perft 5 15078056 +perft 6 471296844 + +id 815 +epd bbrknq1r/ppppppp1/8/7p/5n2/3P4/PPP1PNPP/BBKRNQR1 w c - +perft 1 21 +perft 2 610 +perft 3 13300 +perft 4 394705 +perft 5 9605845 +perft 6 293532398 + +id 816 +epd brkbnqr1/2pppnpp/pp3p2/8/4PPPP/8/PPPP4/BRKBNQRN w GBgb - +perft 1 30 +perft 2 757 +perft 3 23908 +perft 4 621332 +perft 5 20360394 +perft 6 548380577 + +id 817 +epd brk1qb1n/ppppppr1/2n3pp/8/2P3P1/2N5/PP1PPP1P/BR1KQBRN w b - +perft 1 26 +perft 2 570 +perft 3 15537 +perft 4 352883 +perft 5 10081351 +perft 6 242864559 + +id 818 +epd brknq1nb/pp2prpp/8/2pP1p2/6P1/2N5/PPPP1P1P/BRK1QRNB w FBb - +perft 1 33 +perft 2 830 +perft 3 27897 +perft 4 764915 +perft 5 26262884 +perft 6 765831403 + +id 819 +epd rbbk1qrn/ppp1p1pp/5p2/3p1n2/7N/P7/1PPPPPPP/RBB1KQRN w ga - +perft 1 21 +perft 2 562 +perft 3 13060 +perft 4 378883 +perft 5 9520963 +perft 6 290579255 + +id 820 +epd rk1b1qrn/ppp1pppp/5n2/3pN3/P6P/7b/1PPPPPP1/RKBB1QRN w GAga - +perft 1 28 +perft 2 677 +perft 3 19235 +perft 4 488740 +perft 5 14354779 +perft 6 383207197 + +id 821 +epd rkbnqbrn/pp1ppp1p/2p5/6p1/P7/4P3/KPPPQPPP/R1BN1BRN w - - +perft 1 28 +perft 2 585 +perft 3 17443 +perft 4 401483 +perft 5 12574541 +perft 6 310495538 + +id 822 +epd rk1nqrnb/pbpppp2/1p4p1/7p/P7/5NP1/1PPPPPBP/RKBNQR2 w FAfa - +perft 1 26 +perft 2 774 +perft 3 21626 +perft 4 645200 +perft 5 19093408 +perft 6 576325868 + +id 823 +epd rbknb1rn/p1pp2pp/1p6/4pp2/1q3P1B/2N5/PPPPPNPP/RBK2QR1 w GAga - +perft 1 31 +perft 2 1206 +perft 3 36940 +perft 4 1374158 +perft 5 42849564 +perft 6 1555711209 + +id 824 +epd rk1bbqrn/pp1pp1pp/3n4/5p2/3p4/1PP5/PK2PPPP/R1NBBQRN w ga - +perft 1 21 +perft 2 629 +perft 3 14059 +perft 4 429667 +perft 5 10587910 +perft 6 332632033 + +id 825 +epd rknqbbr1/p1pp1pp1/1p4n1/4p2p/4P1P1/6RB/PPPP1P1P/RKNQB2N w Aga - +perft 1 27 +perft 2 753 +perft 3 20918 +perft 4 593155 +perft 5 17318772 +perft 6 507563675 + +id 826 +epd rknqbr1b/pppp1ppp/4p2n/8/1P3P2/4P3/P1PPN1PP/RKNQBR1B w FAfa - +perft 1 26 +perft 2 623 +perft 3 17177 +perft 4 460663 +perft 5 13389799 +perft 6 383508368 + +id 827 +epd r2kqrbn/bppppppp/2n5/p4B2/5P2/2P5/PP1PP1PP/1RKNQRBN w F - +perft 1 39 +perft 2 1026 +perft 3 37800 +perft 4 1011922 +perft 5 35946987 +perft 6 992756232 + +id 828 +epd rk1bqrb1/ppppppp1/1n6/7p/2P2P1n/4P1Q1/PP1P2PP/RKNB1RBN w FAfa - +perft 1 35 +perft 2 760 +perft 3 25817 +perft 4 610557 +perft 5 21014787 +perft 6 536852043 + +id 829 +epd rkq1rb1n/ppppp1pp/1n6/5p2/PPb2P2/8/1KPPP1PP/R1NQRBBN w ea - +perft 1 27 +perft 2 754 +perft 3 21009 +perft 4 568788 +perft 5 16461795 +perft 6 448313956 + +id 830 +epd rknqr2b/pppnp1pp/3p4/3b1p2/8/1N1P2N1/PPP1PPPP/RKQ1R1BB w EAea - +perft 1 27 +perft 2 803 +perft 3 23708 +perft 4 700453 +perft 5 21875031 +perft 6 654754840 + +id 831 +epd bbrknrqn/ppppp1pB/8/2P2p1p/8/5N2/PP1PPPPP/B1RK1RQN w FCfc - +perft 1 30 +perft 2 799 +perft 3 23923 +perft 4 671112 +perft 5 20532790 +perft 6 603059376 + +id 832 +epd brkbnrq1/1pppp1p1/6np/p4p2/4P3/1PP5/P1KP1PPP/BR1BNRQN w fb - +perft 1 27 +perft 2 726 +perft 3 19329 +perft 4 555622 +perft 5 15156662 +perft 6 457601127 + +id 833 +epd brknrbq1/1p1p1ppp/p3p1n1/2p5/8/1P1BPP2/P1PP2PP/BRKNR1QN w EBeb - +perft 1 36 +perft 2 786 +perft 3 27868 +perft 4 655019 +perft 5 22852433 +perft 6 577223409 + +id 834 +epd brknrqnb/p2ppp1p/2p5/1p6/3P2p1/P1P1N3/1P2PPPP/BRK1RQNB w EBeb - +perft 1 23 +perft 2 649 +perft 3 15169 +perft 4 440504 +perft 5 10687843 +perft 6 320881984 + +id 835 +epd rbbk1rqn/1ppppppp/3n4/p7/2P5/3N4/PP1PPPPP/RBB1KRQN w fa - +perft 1 20 +perft 2 478 +perft 3 11094 +perft 4 275250 +perft 5 7094988 +perft 6 185488058 + +id 836 +epd rkbbnrqn/p2p1ppp/1p2p3/8/P1p1P3/1BP5/1P1P1PPP/RKB1NRQN w FAfa - +perft 1 22 +perft 2 570 +perft 3 13295 +perft 4 346811 +perft 5 8671852 +perft 6 229898448 + +id 837 +epd rkb1rb1n/ppppppqp/8/2n3p1/2P1P1P1/8/PP1P1P1P/RKBNRBQN w EAea - +perft 1 23 +perft 2 663 +perft 3 16212 +perft 4 490748 +perft 5 12900485 +perft 6 404944553 + +id 838 +epd rkb1rqnb/pppp3p/2n3p1/4pp2/P2P3P/2P5/1P2PPP1/RKBNRQNB w EAea - +perft 1 25 +perft 2 845 +perft 3 22188 +perft 4 741972 +perft 5 20276176 +perft 6 683290790 + +id 839 +epd rbk1brqn/ppp1pppp/8/3p4/7P/1P4P1/2PPPP2/RBKNBRQN w FAfa - +perft 1 24 +perft 2 526 +perft 3 13862 +perft 4 322175 +perft 5 9054028 +perft 6 222704171 + +id 840 +epd rknbbrqn/pp3pp1/4p3/2pp3p/2P5/8/PPBPPPPP/RKN1BRQN w FAfa - +perft 1 26 +perft 2 756 +perft 3 19280 +perft 4 559186 +perft 5 14697705 +perft 6 433719427 + +id 841 +epd 1knrbbqn/rp1p1ppp/p3p3/2p5/8/5P1P/PPPPP1P1/RKNRBBQN w DAd - +perft 1 26 +perft 2 539 +perft 3 15194 +perft 4 345070 +perft 5 10223443 +perft 6 248715580 + +id 842 +epd rknr1qnb/ppp1p1pp/3p2b1/8/4p3/1P3P1P/P1PP2P1/RKNRBQNB w DAda - +perft 1 25 +perft 2 701 +perft 3 18969 +perft 4 561369 +perft 5 16047041 +perft 6 496340789 + +id 843 +epd rbk1r1bn/ppppp1pp/4n3/5p2/1P3P2/4N2P/PqPPP1P1/RBK1RQBN w EAea - +perft 1 2 +perft 2 60 +perft 3 1319 +perft 4 41765 +perft 5 1017864 +perft 6 33183408 + +id 844 +epd r1nbrqbn/k1ppp1pp/1p6/p4p2/2P5/6PQ/PP1PPP1P/RKNBR1BN w EA - +perft 1 27 +perft 2 699 +perft 3 20436 +perft 4 561765 +perft 5 17192121 +perft 6 499247248 + +id 845 +epd rknrqbbn/1pp1pp2/p5p1/3p3p/6P1/PN5P/1PPPPP2/RK1RQBBN w DAda - +perft 1 23 +perft 2 611 +perft 3 15515 +perft 4 435927 +perft 5 11917036 +perft 6 352885930 + +id 846 +epd rknrqn1b/p1pp1ppb/8/1p2p1Qp/3P4/3N4/PPP1PPPP/RK1R1NBB w DAda - +perft 1 45 +perft 2 1170 +perft 3 48283 +perft 4 1320341 +perft 5 52213677 +perft 6 1500007485 + +id 847 +epd bbkrnrnq/p2p1ppp/2p1p3/1p6/1P2Q3/6P1/P1PPPP1P/BBKRNRN1 w - - +perft 1 41 +perft 2 1035 +perft 3 39895 +perft 4 1035610 +perft 5 38555608 +perft 6 1037686769 + +id 848 +epd brkbnr2/1ppppp1p/7n/p5N1/P2q4/8/1PPPPPPP/BRKBNRQ1 w FBfb - +perft 1 22 +perft 2 869 +perft 3 19234 +perft 4 679754 +perft 5 16453359 +perft 6 567287944 + +id 849 +epd brknrbnq/p1ppppp1/1p6/7p/2PP4/5P2/PPK1P1PP/BR1NRBNQ w eb - +perft 1 23 +perft 2 641 +perft 3 14748 +perft 4 422240 +perft 5 10192718 +perft 6 302864305 + +id 850 +epd brk1r1qb/pp1ppnpp/2p2pn1/8/6N1/2N3P1/PPPPPP1P/BRK1R1QB w EBeb - +perft 1 32 +perft 2 863 +perft 3 28379 +perft 4 773191 +perft 5 25848794 +perft 6 720443112 + +id 851 +epd rbbk1rnq/pppp1pp1/4p2p/8/3P2n1/4BN1P/PPP1PPP1/RB1K1RNQ w FAfa - +perft 1 26 +perft 2 628 +perft 3 16151 +perft 4 411995 +perft 5 11237919 +perft 6 300314373 + +id 852 +epd rkbbnr1q/p1pppppp/5n2/1p5B/PP6/4P3/2PP1PPP/RKB1NRNQ w FAfa - +perft 1 30 +perft 2 692 +perft 3 21036 +perft 4 519283 +perft 5 16025428 +perft 6 420887328 + +id 853 +epd rkb1rbnq/1pppp1pp/5p2/p7/5n1P/1PN3P1/P1PPPP2/RKB1RBNQ w EAea - +perft 1 32 +perft 2 825 +perft 3 27130 +perft 4 697251 +perft 5 23593363 +perft 6 622249676 + +id 854 +epd rkbnrnqb/1ppp1p1p/p5p1/4p3/4P3/2N2P2/PPPP2PP/RKBR1NQB w Aea - +perft 1 24 +perft 2 487 +perft 3 13300 +perft 4 301989 +perft 5 8782713 +perft 6 215787079 + +id 855 +epd rbknbr1q/pppp2pp/4p3/5p1n/1P2P2N/8/P1PP1PPP/RBKNBR1Q w FAfa - +perft 1 23 +perft 2 571 +perft 3 13799 +perft 4 365272 +perft 5 9224232 +perft 6 257288920 + +id 856 +epd rknbb1nq/pppppr2/5pp1/7p/8/1N4P1/PPPPPP1P/RK1BBRNQ w FAa - +perft 1 26 +perft 2 548 +perft 3 15618 +perft 4 350173 +perft 5 10587626 +perft 6 253006082 + +id 857 +epd rknr1bnq/p2pp1pp/1p3p2/2p4b/6PP/2P2N2/PP1PPP2/RKNRBB1Q w DAda - +perft 1 25 +perft 2 502 +perft 3 13150 +perft 4 279098 +perft 5 7824941 +perft 6 175766730 + +id 858 +epd rknrb1qb/ppp1pppp/3p4/8/4P1nP/2P5/PPKP1PP1/R1NRBNQB w da - +perft 1 23 +perft 2 643 +perft 3 14849 +perft 4 426616 +perft 5 10507328 +perft 6 312096061 + +id 859 +epd rbk1rnbq/pppp1npp/4p3/5p2/4P1P1/7P/PPPP1P1N/RBKNR1BQ w EAea - +perft 1 24 +perft 2 591 +perft 3 15178 +perft 4 376988 +perft 5 10251465 +perft 6 263574861 + +id 860 +epd rknbrnb1/p1pppp1p/1p6/3N2p1/P3q1P1/8/1PPPPP1P/RKNBR1BQ w EAea - +perft 1 28 +perft 2 948 +perft 3 27343 +perft 4 864588 +perft 5 26241141 +perft 6 812343987 + +id 861 +epd rknrn1b1/ppppppqp/8/6p1/2P5/2P1BP2/PP2P1PP/RKNRNB1Q w DAda - +perft 1 31 +perft 2 807 +perft 3 24360 +perft 4 672973 +perft 5 20455205 +perft 6 588518645 + +id 862 +epd 1k1rnqbb/npppppp1/r7/p2B3p/5P2/1N4P1/PPPPP2P/RK1RNQB1 w DAd - +perft 1 40 +perft 2 1122 +perft 3 44297 +perft 4 1249989 +perft 5 48711073 +perft 6 1412437357 + +id 863 +epd bbqr1rkn/pp1ppppp/8/2p5/1P2P1n1/7N/P1PP1P1P/BBQRKR1N w FD - +perft 1 26 +perft 2 841 +perft 3 22986 +perft 4 746711 +perft 5 21328001 +perft 6 705170410 + +id 864 +epd bqkr1rnn/1ppp1ppp/p4b2/4p3/P7/3PP2N/1PP2PPP/BQRBKR1N w FC - +perft 1 24 +perft 2 500 +perft 3 12802 +perft 4 293824 +perft 5 7928916 +perft 6 197806842 + +id 865 +epd bqrkrbnn/1pp1ppp1/8/p6p/3p4/P3P2P/QPPP1PP1/B1RKRBNN w ECec - +perft 1 31 +perft 2 592 +perft 3 18585 +perft 4 396423 +perft 5 12607528 +perft 6 298629240 + +id 866 +epd bqkrrnnb/2p1pppp/p7/1P1p4/8/2R3P1/PP1PPP1P/BQ1KRNNB w E - +perft 1 42 +perft 2 1124 +perft 3 45187 +perft 4 1276664 +perft 5 50052573 +perft 6 1483524894 + +id 867 +epd qbbrkrn1/p1pppn1p/8/1p3Pp1/2P5/8/PP1PPP1P/QBBRKRNN w FDfd - +perft 1 21 +perft 2 577 +perft 3 13244 +perft 4 392131 +perft 5 9683808 +perft 6 300294295 + +id 868 +epd qrbbkrnn/pp1p2pp/4p3/5p2/2p2P1P/2P5/PP1PP1P1/QRBBKRNN w FBfb - +perft 1 21 +perft 2 571 +perft 3 12736 +perft 4 345681 +perft 5 8239872 +perft 6 228837930 + +id 869 +epd qrbkrbn1/1pp1pppp/p2p4/8/5PPn/2P5/PP1PP3/QRBKRBNN w EBeb - +perft 1 18 +perft 2 466 +perft 3 9443 +perft 4 257776 +perft 5 5679073 +perft 6 162883949 + +id 870 +epd qrb1rnnb/pp1p1ppp/2pk4/4p3/1P2P3/1R6/P1PP1PPP/Q1BKRNNB w E - +perft 1 37 +perft 2 760 +perft 3 26863 +perft 4 562201 +perft 5 19486022 +perft 6 421740856 + +id 871 +epd qbrkbrn1/p1pppp1p/6n1/1p4p1/1P6/5P2/P1PPPBPP/QBRK1RNN w FCfc - +perft 1 33 +perft 2 824 +perft 3 27385 +perft 4 750924 +perft 5 25176664 +perft 6 734656217 + +id 872 +epd qrkbbr2/2pppppp/5nn1/pp1Q4/P7/3P4/1PP1PPPP/1RKBBRNN w FBfb - +perft 1 42 +perft 2 1147 +perft 3 44012 +perft 4 1311247 +perft 5 48216013 +perft 6 1522548864 + +id 873 +epd qrkrbbnn/pp2pp2/2pp2pp/1B6/P7/4P3/1PPP1PPP/QRKRB1NN w DBdb - +perft 1 26 +perft 2 464 +perft 3 12653 +perft 4 242892 +perft 5 6928220 +perft 6 142507795 + +id 874 +epd qrkrbnnb/p1pp1pp1/1p5p/4p3/1P6/6PN/PKPPPP1P/QR1RBN1B w db - +perft 1 29 +perft 2 705 +perft 3 20000 +perft 4 529810 +perft 5 15055365 +perft 6 419552571 + +id 875 +epd qbrkr1bn/p1p1pp1p/1p1p2n1/6p1/3P1P2/4P3/PPP3PP/QBKRRNBN w ec - +perft 1 23 +perft 2 613 +perft 3 14835 +perft 4 426484 +perft 5 10747407 +perft 6 323905533 + +id 876 +epd qrk1rnb1/p1pp1ppp/1p2Bbn1/8/4P3/6P1/PPPP1P1P/QRK1RNBN w EBeb - +perft 1 28 +perft 2 927 +perft 3 24887 +perft 4 846839 +perft 5 23063284 +perft 6 807913585 + +id 877 +epd 1qkrnbbn/1rpppppp/pp6/5N2/P4P2/8/1PPPP1PP/QRKRNBB1 w DBd - +perft 1 30 +perft 2 542 +perft 3 16646 +perft 4 345172 +perft 5 10976745 +perft 6 251694423 + +id 878 +epd qrkr2bb/pppppppp/8/1n2n3/1N5P/1P6/P1PPPPP1/QRKR1NBB w DBdb - +perft 1 28 +perft 2 719 +perft 3 21048 +perft 4 562015 +perft 5 17351761 +perft 6 479400272 + +id 879 +epd bbrqkrnn/3ppppp/8/ppp5/6P1/4P2N/PPPPKP1P/BBRQ1R1N w fc - +perft 1 21 +perft 2 704 +perft 3 16119 +perft 4 546215 +perft 5 13676371 +perft 6 470796854 + +id 880 +epd brqbkrnn/1pp2p1p/3pp1p1/p5N1/8/1P6/P1PPPPPP/BRQBK1RN w Bfb - +perft 1 34 +perft 2 688 +perft 3 22827 +perft 4 505618 +perft 5 16639723 +perft 6 402140795 + +id 881 +epd br1krb1n/2qppppp/pp3n2/8/1P4P1/8/P1PPPP1P/1RQKRBNN w EBeb - +perft 1 24 +perft 2 945 +perft 3 23943 +perft 4 926427 +perft 5 25019636 +perft 6 959651619 + +id 882 +epd brqkr1nb/2ppp1pp/1p2np2/p7/2P1PN2/8/PP1P1PPP/BRQKRN1B w EBeb - +perft 1 28 +perft 2 675 +perft 3 19728 +perft 4 504128 +perft 5 15516491 +perft 6 417396563 + +id 883 +epd rbbqkrnn/3pppp1/p7/1pp4p/2P1P2P/8/PP1P1PP1/RBBQKRNN w FAfa - +perft 1 26 +perft 2 671 +perft 3 18164 +perft 4 496806 +perft 5 14072641 +perft 6 404960259 + +id 884 +epd rqbbkr1n/pp1p1p1p/4pn2/2p3p1/4P1P1/3P3P/PPP2P2/RQBBKRNN w FAfa - +perft 1 22 +perft 2 633 +perft 3 14629 +perft 4 441809 +perft 5 10776416 +perft 6 335689685 + +id 885 +epd rqbkrbnn/p1ppp3/1p3pp1/7p/3P4/P1P5/1PQ1PPPP/R1BKRBNN w EAea - +perft 1 32 +perft 2 607 +perft 3 20339 +perft 4 454319 +perft 5 15586203 +perft 6 383515709 + +id 886 +epd rqbkrnn1/pp2ppbp/3p4/2p3p1/2P5/1P3N1P/P2PPPP1/RQBKRN1B w EAea - +perft 1 29 +perft 2 943 +perft 3 28732 +perft 4 908740 +perft 5 28761841 +perft 6 907579129 + +id 887 +epd rbqkb1nn/1ppppr1p/p5p1/5p2/1P6/2P4P/P1KPPPP1/RBQ1BRNN w a - +perft 1 22 +perft 2 441 +perft 3 10403 +perft 4 231273 +perft 5 5784206 +perft 6 140934555 + +id 888 +epd rqkb1rnn/1pp1pp1p/p5p1/1b1p4/3P4/P5P1/RPP1PP1P/1QKBBRNN w Ffa - +perft 1 21 +perft 2 505 +perft 3 11592 +perft 4 290897 +perft 5 7147063 +perft 6 188559137 + +id 889 +epd rq1rbbnn/pkp1ppp1/3p3p/1p2N1P1/8/8/PPPPPP1P/RQKRBB1N w DA - +perft 1 27 +perft 2 608 +perft 3 16419 +perft 4 387751 +perft 5 10808908 +perft 6 268393274 + +id 890 +epd rqkrb2b/p2ppppp/2p3nn/1p6/5P2/PP1P4/2P1P1PP/RQKRBNNB w DAda - +perft 1 30 +perft 2 749 +perft 3 21563 +perft 4 581531 +perft 5 16916813 +perft 6 485406712 + +id 891 +epd rbqkr1bn/pp1ppp2/2p1n2p/6p1/8/4BPNP/PPPPP1P1/RBQKRN2 w EAea - +perft 1 23 +perft 2 600 +perft 3 15082 +perft 4 410057 +perft 5 11041820 +perft 6 314327867 + +id 892 +epd rqkbrnb1/2ppp1pp/pp3pn1/8/5P2/B2P4/PPP1P1PP/RQKBRN1N w EAea - +perft 1 22 +perft 2 569 +perft 3 13541 +perft 4 371471 +perft 5 9395816 +perft 6 269460607 + +id 893 +epd rqkrnbb1/p1p1pppp/1p4n1/3p4/7P/P3P3/1PPPBPP1/RQKRN1BN w DAda - +perft 1 27 +perft 2 579 +perft 3 15565 +perft 4 373079 +perft 5 10238486 +perft 6 266047417 + +id 894 +epd rqkrn1bb/p1ppp1pp/4n3/1p6/6p1/4N3/PPPPPPPP/RQKR2BB w DAda - +perft 1 20 +perft 2 462 +perft 3 10234 +perft 4 274162 +perft 5 6563859 +perft 6 193376359 + +id 895 +epd bbrkqr2/pppp1ppp/6nn/8/2P1p3/3PP2N/PP3PPP/BBRKQR1N w FCfc - +perft 1 28 +perft 2 724 +perft 3 21688 +perft 4 619064 +perft 5 19318355 +perft 6 593204629 + +id 896 +epd brk1qrnn/1pppbppp/4p3/8/1p6/P1P4P/3PPPP1/BRKBQRNN w FBfb - +perft 1 24 +perft 2 662 +perft 3 16920 +perft 4 468215 +perft 5 12610387 +perft 6 355969349 + +id 897 +epd 1r1qrbnn/p1pkpppp/1p1p4/8/3P1PP1/P4b2/1PP1P2P/BRKQRBNN w EB - +perft 1 22 +perft 2 696 +perft 3 17021 +perft 4 510247 +perft 5 13697382 +perft 6 401903030 + +id 898 +epd 1rkqrnnb/p1p1p1pp/1p1p4/3b1p1N/4P3/5N2/PPPP1PPP/BRKQR2B w EBeb - +perft 1 29 +perft 2 887 +perft 3 27035 +perft 4 816176 +perft 5 26051242 +perft 6 791718847 + +id 899 +epd rbbkq1rn/pppppppp/7n/8/P7/3P3P/1PPKPPP1/RBB1QRNN w a - +perft 1 22 +perft 2 417 +perft 3 9900 +perft 4 216855 +perft 5 5505063 +perft 6 134818483 + +id 900 +epd rkbbqr1n/1p1pppp1/2p2n2/p4NBp/8/3P4/PPP1PPPP/RK1BQRN1 w FAfa - +perft 1 37 +perft 2 832 +perft 3 30533 +perft 4 728154 +perft 5 26676373 +perft 6 673756141 + +id 901 +epd rkbqrb1n/3pBppp/ppp2n2/8/8/P2P4/1PP1PPPP/RK1QRBNN w EAea - +perft 1 28 +perft 2 685 +perft 3 19718 +perft 4 543069 +perft 5 16033316 +perft 6 482288814 + +id 902 +epd rkb1rn1b/ppppqppp/4p3/8/1P2n1P1/5Q2/P1PP1P1P/RKB1RNNB w EAea - +perft 1 37 +perft 2 1158 +perft 3 40114 +perft 4 1234768 +perft 5 44672979 +perft 6 1389312729 + +id 903 +epd r1kqbrnn/pp1pp1p1/7p/2P2p2/5b2/3P4/P1P1P1PP/RBKQBRNN w FAfa - +perft 1 5 +perft 2 161 +perft 3 4745 +perft 4 154885 +perft 5 4734999 +perft 6 157499039 + +id 904 +epd rkqbbr1n/ppp1ppp1/8/Q2p3p/4n3/3P1P2/PPP1P1PP/RK1BBRNN w FAfa - +perft 1 38 +perft 2 1144 +perft 3 40433 +perft 4 1236877 +perft 5 43832975 +perft 6 1366087771 + +id 905 +epd rkqrbbn1/p1ppppp1/Bp5p/8/P6n/2P1P3/1P1P1PPP/RKQRB1NN w DAda - +perft 1 28 +perft 2 551 +perft 3 15488 +perft 4 350861 +perft 5 9944107 +perft 6 251179183 + +id 906 +epd rkqrb1nb/1ppp1ppp/p7/4p3/5n2/3P2N1/PPPQPPPP/RK1RB1NB w DAda - +perft 1 26 +perft 2 690 +perft 3 19877 +perft 4 513628 +perft 5 15965907 +perft 6 418191735 + +id 907 +epd rbkqrnbn/pppp1p2/4p1p1/7p/7P/P2P4/BPP1PPP1/R1KQRNBN w EAea - +perft 1 27 +perft 2 515 +perft 3 13992 +perft 4 309727 +perft 5 8792550 +perft 6 218658292 + +id 908 +epd rkqbrnbn/pp1ppp2/8/2p3p1/P1P4p/5P2/1PKPP1PP/R1QBRNBN w ea - +perft 1 27 +perft 2 627 +perft 3 16843 +perft 4 431101 +perft 5 11978698 +perft 6 328434174 + +id 909 +epd rkqrnbbn/1p2pp1p/3p2p1/p1p5/P5PP/3N4/1PPPPP2/RKQR1BBN w DAda - +perft 1 23 +perft 2 624 +perft 3 15512 +perft 4 451860 +perft 5 11960861 +perft 6 367311176 + +id 910 +epd rk2rnbb/ppqppppp/2pn4/8/1P3P2/6P1/P1PPP1NP/RKQR1NBB w DAa - +perft 1 27 +perft 2 727 +perft 3 20206 +perft 4 581003 +perft 5 16633696 +perft 6 505212747 + +id 911 +epd b1krrqnn/pp1ppp1p/2p3p1/8/P3Pb1P/1P6/2PP1PP1/BBRKRQNN w EC - +perft 1 32 +perft 2 943 +perft 3 30759 +perft 4 865229 +perft 5 28672582 +perft 6 800922511 + +id 912 +epd 1rkbrqnn/p1pp1ppp/1p6/8/P2Pp3/8/1PPKPPQP/BR1BR1NN w eb - +perft 1 28 +perft 2 916 +perft 3 24892 +perft 4 817624 +perft 5 22840279 +perft 6 759318058 + +id 913 +epd brkrqb1n/1pppp1pp/p7/3n1p2/P5P1/3PP3/1PP2P1P/BRKRQBNN w DBdb - +perft 1 27 +perft 2 669 +perft 3 18682 +perft 4 484259 +perft 5 13956472 +perft 6 380267099 + +id 914 +epd brkrqnnb/3pppp1/1p6/p1p4p/2P3P1/6N1/PP1PPP1P/BRKRQ1NB w DBdb - +perft 1 29 +perft 2 699 +perft 3 20042 +perft 4 512639 +perft 5 15093909 +perft 6 406594531 + +id 915 +epd r1bkrq1n/pp2pppp/3b1n2/2pp2B1/6P1/3P1P2/PPP1P2P/RB1KRQNN w EAea - +perft 1 27 +perft 2 835 +perft 3 22848 +perft 4 713550 +perft 5 19867800 +perft 6 631209313 + +id 916 +epd rk1brq1n/p1p1pppp/3p1n2/1p3b2/4P3/2NQ4/PPPP1PPP/RKBBR2N w EAea - +perft 1 36 +perft 2 1004 +perft 3 35774 +perft 4 979608 +perft 5 35143142 +perft 6 966310885 + +id 917 +epd rkbrqbnn/1p2ppp1/B1p5/p2p3p/4P2P/8/PPPP1PP1/RKBRQ1NN w DAda - +perft 1 27 +perft 2 748 +perft 3 21005 +perft 4 597819 +perft 5 17597073 +perft 6 515304215 + +id 918 +epd rkbrqn1b/pp1pp1pp/2p2p2/5n2/8/2P2P2/PP1PP1PP/RKBRQ1NB w DAda - +perft 1 20 +perft 2 479 +perft 3 10485 +perft 4 266446 +perft 5 6253775 +perft 6 167767913 + +id 919 +epd rbkrbnn1/ppppp1pp/5q2/5p2/5P2/P3P2N/1PPP2PP/RBKRBQ1N w DAda - +perft 1 28 +perft 2 947 +perft 3 26900 +perft 4 876068 +perft 5 26007841 +perft 6 838704143 + +id 920 +epd rkr1bqnn/1ppp1p1p/p5p1/4p3/3PP2b/2P2P2/PP4PP/RKRBBQNN w CAca - +perft 1 31 +perft 2 1004 +perft 3 32006 +perft 4 1006830 +perft 5 32688124 +perft 6 1024529879 + +id 921 +epd rkrqbbnn/pppp3p/8/4ppp1/1PP4P/8/P2PPPP1/RKRQBBNN w CAca - +perft 1 24 +perft 2 717 +perft 3 18834 +perft 4 564137 +perft 5 15844525 +perft 6 484884485 + +id 922 +epd rkrqbn1b/pppp2pp/8/4pp2/1P1P2n1/5N2/P1P1PP1P/RKRQBN1B w CAca - +perft 1 25 +perft 2 718 +perft 3 19654 +perft 4 587666 +perft 5 17257753 +perft 6 537354146 + +id 923 +epd rbkrqnbn/p1p1ppp1/1p1p4/8/3PP2p/2PB4/PP3PPP/R1KRQNBN w DAda - +perft 1 30 +perft 2 754 +perft 3 23298 +perft 4 611322 +perft 5 19338246 +perft 6 532603566 + +id 924 +epd 1krbqnbn/1p2pppp/r1pp4/p7/8/1P1P2PP/P1P1PP2/RKRBQNBN w CAc - +perft 1 21 +perft 2 566 +perft 3 13519 +perft 4 375128 +perft 5 9700847 +perft 6 279864836 + +id 925 +epd rkrq1b2/pppppppb/3n2np/2N5/4P3/7P/PPPP1PP1/RKRQ1BBN w CAca - +perft 1 33 +perft 2 654 +perft 3 21708 +perft 4 479678 +perft 5 15990307 +perft 6 382218272 + +id 926 +epd rkr1nnbb/ppp2p1p/3p1qp1/4p3/P5P1/3PN3/1PP1PP1P/RKRQN1BB w CAca - +perft 1 28 +perft 2 715 +perft 3 20361 +perft 4 555328 +perft 5 16303092 +perft 6 468666425 + +id 927 +epd bbrkrnqn/1p1ppppp/8/8/p2pP3/PP6/2P2PPP/BBRKRNQN w ECec - +perft 1 24 +perft 2 757 +perft 3 19067 +perft 4 603231 +perft 5 15957628 +perft 6 509307623 + +id 928 +epd brkbrnqn/ppp2p2/4p3/P2p2pp/6P1/5P2/1PPPP2P/BRKBRNQN w EBeb - +perft 1 25 +perft 2 548 +perft 3 14563 +perft 4 348259 +perft 5 9688526 +perft 6 247750144 + +id 929 +epd brkr1bqn/1pppppp1/3n3p/1p6/P7/4P1P1/1PPP1P1P/BRKRN1QN w DBdb - +perft 1 19 +perft 2 359 +perft 3 7430 +perft 4 157099 +perft 5 3521652 +perft 6 81787718 + +id 930 +epd brkr1qnb/pppp2pp/2B1p3/5p2/2n5/6PP/PPPPPPN1/BRKR1QN1 w DBdb - +perft 1 27 +perft 2 854 +perft 3 23303 +perft 4 741626 +perft 5 20558538 +perft 6 667089231 + +id 931 +epd rbbkrnqn/p1p1p1pp/8/1p1p4/1P1Pp3/6N1/P1P2PPP/RBBKRNQ1 w EAea - +perft 1 28 +perft 2 723 +perft 3 19844 +perft 4 514440 +perft 5 14621108 +perft 6 397454100 + +id 932 +epd rkbbrn1n/pppppp2/5q1p/6p1/3P3P/4P3/PPP2PP1/RKBBRNQN w EAea - +perft 1 25 +perft 2 741 +perft 3 19224 +perft 4 585198 +perft 5 15605840 +perft 6 485037906 + +id 933 +epd rkbr1bq1/ppnppppp/6n1/2p5/2P1N2P/8/PP1PPPP1/RKBRNBQ1 w DAda - +perft 1 24 +perft 2 547 +perft 3 14359 +perft 4 339497 +perft 5 9410221 +perft 6 234041078 + +id 934 +epd 1kbrnqnb/r1ppppp1/8/pp5p/8/1P1NP3/P1PP1PPP/RKB1RQNB w Ad - +perft 1 26 +perft 2 618 +perft 3 17305 +perft 4 442643 +perft 5 13112297 +perft 6 357030697 + +id 935 +epd rbkrb1qn/1pp1ppp1/3pn2p/pP6/8/4N1P1/P1PPPP1P/RBKRB1QN w DAda - +perft 1 21 +perft 2 544 +perft 3 12492 +perft 4 338832 +perft 5 8381483 +perft 6 236013157 + +id 936 +epd rkrbbnqn/ppppp3/5p2/6pp/5PBP/4P3/PPPP2P1/RKR1BNQN w CAca - +perft 1 30 +perft 2 891 +perft 3 25435 +perft 4 764356 +perft 5 21894752 +perft 6 669256602 + +id 937 +epd rkr1bb1n/ppppp1pp/5p2/4n3/3QP3/5P2/RPPP2PP/1KRNBB1N w Cca - +perft 1 45 +perft 2 1172 +perft 3 51766 +perft 4 1332060 +perft 5 57856784 +perft 6 1501852662 + +id 938 +epd rkr1bqnb/pp1ppppp/8/2pN4/1P6/5N2/P1PPnPPP/RKR1BQ1B w CAca - +perft 1 28 +perft 2 730 +perft 3 20511 +perft 4 559167 +perft 5 16323242 +perft 6 463032124 + +id 939 +epd rbkrnqb1/2ppppp1/p5np/1p6/8/3N4/PPPPPPPP/RBKRQNB1 w DAda - +perft 1 20 +perft 2 417 +perft 3 9159 +perft 4 217390 +perft 5 5180716 +perft 6 133936564 + +id 940 +epd rkrbnqb1/p1pppnpp/5p2/1p6/2P5/1P1P1N2/P3PPPP/RKRB1QBN w CAca - +perft 1 25 +perft 2 546 +perft 3 14039 +perft 4 330316 +perft 5 8813781 +perft 6 222026485 + +id 941 +epd rkr1qbbn/ppppppp1/4n3/7p/8/P7/KPPPPPPP/R1RNQBBN w ca - +perft 1 22 +perft 2 484 +perft 3 11458 +perft 4 267495 +perft 5 6633319 +perft 6 163291279 + +id 942 +epd rkrnqnb1/1ppppp2/p5p1/7p/8/P1bPP3/1PP1QPPP/RKRN1NBB w CAca - +perft 1 22 +perft 2 636 +perft 3 15526 +perft 4 441001 +perft 5 11614241 +perft 6 331083405 + +id 943 +epd b2krn1q/p1rppppp/1Q3n2/2p1b3/1P4P1/8/P1PPPP1P/BBRKRNN1 w ECe - +perft 1 36 +perft 2 1192 +perft 3 42945 +perft 4 1406795 +perft 5 50382104 +perft 6 1650202838 + +id 944 +epd brkbrnn1/pp1pppp1/7q/2p5/6Pp/4P1NP/PPPP1P2/BRKBR1NQ w EBeb - +perft 1 30 +perft 2 978 +perft 3 29593 +perft 4 942398 +perft 5 29205057 +perft 6 936568065 + +id 945 +epd brkrnb1q/pp1p1ppp/2p1p3/5n2/1P6/5N1N/P1PPPPPP/BRKR1B1Q w DBdb - +perft 1 31 +perft 2 897 +perft 3 27830 +perft 4 810187 +perft 5 25423729 +perft 6 755334868 + +id 946 +epd brkr1nqb/pp1p1pp1/2pn3p/P3p3/4P3/6P1/1PPP1P1P/BRKRNNQB w DBdb - +perft 1 19 +perft 2 382 +perft 3 8052 +perft 4 182292 +perft 5 4232274 +perft 6 103537333 + +id 947 +epd r1bkrn1q/ppbppppp/5n2/2p5/3P4/P6N/1PP1PPPP/RBBKRNQ1 w EAea - +perft 1 27 +perft 2 822 +perft 3 22551 +perft 4 678880 +perft 5 19115128 +perft 6 578210135 + +id 948 +epd rkbbrnnq/pp2pppp/8/2pp4/P1P5/1P3P2/3PP1PP/RKBBRNNQ w EAea - +perft 1 23 +perft 2 643 +perft 3 15410 +perft 4 442070 +perft 5 11170489 +perft 6 329615708 + +id 949 +epd rkbr1b1q/p1pppppp/1p1n4/7n/5QP1/3N4/PPPPPP1P/RKBR1BN1 w DAda - +perft 1 37 +perft 2 943 +perft 3 34382 +perft 4 880474 +perft 5 31568111 +perft 6 842265141 + +id 950 +epd rkbr1nqb/pppp2np/8/4ppp1/1P6/6N1/P1PPPPPP/RKBRN1QB w DAda - +perft 1 23 +perft 2 574 +perft 3 13260 +perft 4 362306 +perft 5 9020291 +perft 6 261247606 + +id 951 +epd rbkr1nnq/p1p1pp1p/1p4p1/3p4/b3P3/4N3/PPPPNPPP/RBKRB1Q1 w DAda - +perft 1 26 +perft 2 900 +perft 3 23414 +perft 4 805006 +perft 5 21653203 +perft 6 745802405 + +id 952 +epd rkrbb1nq/p2pppp1/1p4n1/2p4p/3N4/4P1P1/PPPP1P1P/RKRBBN1Q w CAca - +perft 1 32 +perft 2 697 +perft 3 22231 +perft 4 531121 +perft 5 17150175 +perft 6 441578567 + +id 953 +epd rkrnbb1q/pp2pp1p/6pn/2pp4/2B1P2P/8/PPPP1PP1/RKRNB1NQ w CAca - +perft 1 28 +perft 2 854 +perft 3 23853 +perft 4 755990 +perft 5 21823412 +perft 6 712787248 + +id 954 +epd rk2bnqb/pprpppp1/4n2p/2p5/P7/3P2NP/1PP1PPP1/RKRNB1QB w CAa - +perft 1 26 +perft 2 596 +perft 3 16251 +perft 4 414862 +perft 5 11758184 +perft 6 323043654 + +id 955 +epd r1krnnbq/pp1ppp1p/6p1/2p5/2P5/P3P3/Rb1P1PPP/1BKRNNBQ w Dda - +perft 1 2 +perft 2 61 +perft 3 1312 +perft 4 40072 +perft 5 937188 +perft 6 28753562 + +id 956 +epd 1krbnnbq/1pp1p1pp/r7/p2p1p2/3PP3/2P3P1/PP3P1P/RKRBNNBQ w CAc - +perft 1 30 +perft 2 953 +perft 3 28033 +perft 4 860530 +perft 5 25531358 +perft 6 787205262 + +id 957 +epd rkr1nbbq/2ppp1pp/1pn5/p4p2/P6P/3P4/1PP1PPPB/RKRNNB1Q w CAca - +perft 1 24 +perft 2 645 +perft 3 15689 +perft 4 446423 +perft 5 11484012 +perft 6 341262639 + +id 958 +epd rkrnnqbb/p1ppp2p/Qp6/4Pp2/5p2/8/PPPP2PP/RKRNN1BB w CAca - +perft 1 35 +perft 2 929 +perft 3 32020 +perft 4 896130 +perft 5 31272517 +perft 6 915268405 + +id 959 +epd bbq1nr1r/pppppk1p/2n2p2/6p1/P4P2/4P1P1/1PPP3P/BBQNNRKR w HF - +perft 1 23 +perft 2 589 +perft 3 14744 +perft 4 387556 +perft 5 10316716 +perft 6 280056112 diff --git a/examples/perft/crazyhouse.perft b/examples/perft/crazyhouse.perft index 42ccd68a6..ef07eac04 100644 --- a/examples/perft/crazyhouse.perft +++ b/examples/perft/crazyhouse.perft @@ -18,7 +18,7 @@ perft 3 58057 perft 4 2083382 id zh-promoted -epd 4k3/1Q~6/8/8/4b3/8/Kpp5/8/ b - - 0 1 +epd 4k3/1Q~6/8/8/4b3/8/Kpp5/8/ b - - perft 1 20 perft 2 360 perft 3 5445 diff --git a/examples/perft/tricky.perft b/examples/perft/tricky.perft index b3cca9fd2..2510a9aea 100644 --- a/examples/perft/tricky.perft +++ b/examples/perft/tricky.perft @@ -124,8 +124,26 @@ perft 1 6 perft 2 121 perft 3 711 +id align-ep-pinned +epd 1b1k4/8/8/1rPpK3/8/8/8/8 w - d6 +perft 1 5 +perft 2 100 +perft 3 555 + id ep-unrelated-check epd rnbqk1nr/bb3p1p/1q2r3/2pPp3/3P4/7P/1PP1NpPP/R1BQKBNR w KQkq c6 perft 1 2 perft 2 92 perft 3 2528 + +# +# Impossible castling rights +# + +id asymmetrical-and-king-on-h +epd r2r3k/p7/3p4/8/8/P6P/8/R3K2R b KQq - +perft 1 14 +perft 2 206 +perft 3 3672 +perft 4 64639 +perft 5 1320962 diff --git a/fuzz/engine.py b/fuzz/engine.py index a08d08bf3..ad0cb9fe9 100644 --- a/fuzz/engine.py +++ b/fuzz/engine.py @@ -7,7 +7,6 @@ logging.getLogger("chess.engine").setLevel(logging.CRITICAL) -asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) @PythonFuzz diff --git a/release.py b/release.py index 099dd4a98..4057ab785 100755 --- a/release.py +++ b/release.py @@ -97,9 +97,9 @@ def tag_and_push(): def pypi(): print("--- PYPI ---------------------------------------------------------") system("rm -rf build") - system("python3 setup.py sdist bdist_wheel") + system("python3 setup.py sdist") system("twine check dist/*") - system("twine upload --skip-existing --sign dist/*") + system("twine upload --skip-existing dist/*") def github_release(tagname): diff --git a/setup.py b/setup.py index bc6a8af51..5815947cd 100755 --- a/setup.py +++ b/setup.py @@ -1,21 +1,5 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# -# This file is part of the python-chess library. -# Copyright (C) 2012-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . import os import platform @@ -34,8 +18,13 @@ end of 2018. Consider upgrading to Python 3. """)) -if sys.version_info < (3, 7): - raise ImportError("Since version 1.0.0, python-chess requires Python 3.7 or later.") +if sys.version_info < (3, 8): + raise ImportError(textwrap.dedent("""\ + You are trying to install python-chess. + + Since version 1.11.0, python-chess requires Python 3.8 or later. + Since version 1.0.0, python-chess requires Python 3.7 or later. + """)) import chess @@ -81,7 +70,7 @@ def read_description(): package_data={ "chess": ["py.typed"], }, - python_requires=">=3.7", + python_requires=">=3.8", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -89,11 +78,13 @@ def read_description(): "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Games/Entertainment :: Board Games", "Topic :: Games/Entertainment :: Turn Based Strategy", "Topic :: Software Development :: Libraries :: Python Modules", diff --git a/test.py b/test.py index 4c8db196e..228c62f3f 100755 --- a/test.py +++ b/test.py @@ -1,20 +1,4 @@ #!/usr/bin/env python3 -# -# This file is part of the python-chess library. -# Copyright (C) 2012-2021 Niklas Fiekas -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . import asyncio import copy @@ -93,6 +77,31 @@ def test_parse_square(self): with self.assertRaises(ValueError): self.assertEqual(chess.parse_square("a0")) + def test_square_distance(self): + self.assertEqual(chess.square_distance(chess.A1, chess.A1), 0) + self.assertEqual(chess.square_distance(chess.A1, chess.H8), 7) + self.assertEqual(chess.square_distance(chess.E1, chess.E8), 7) + self.assertEqual(chess.square_distance(chess.A4, chess.H4), 7) + self.assertEqual(chess.square_distance(chess.D4, chess.E5), 1) + + def test_square_manhattan_distance(self): + self.assertEqual(chess.square_manhattan_distance(chess.A1, chess.A1), 0) + self.assertEqual(chess.square_manhattan_distance(chess.A1, chess.H8), 14) + self.assertEqual(chess.square_manhattan_distance(chess.E1, chess.E8), 7) + self.assertEqual(chess.square_manhattan_distance(chess.A4, chess.H4), 7) + self.assertEqual(chess.square_manhattan_distance(chess.D4, chess.E5), 2) + + def test_square_knight_distance(self): + self.assertEqual(chess.square_knight_distance(chess.A1, chess.A1), 0) + self.assertEqual(chess.square_knight_distance(chess.A1, chess.H8), 6) + self.assertEqual(chess.square_knight_distance(chess.G1, chess.F3), 1) + self.assertEqual(chess.square_knight_distance(chess.E1, chess.E8), 5) + self.assertEqual(chess.square_knight_distance(chess.A4, chess.H4), 5) + self.assertEqual(chess.square_knight_distance(chess.A1, chess.B1), 3) + self.assertEqual(chess.square_knight_distance(chess.A1, chess.C3), 4) + self.assertEqual(chess.square_knight_distance(chess.A1, chess.B2), 4) + self.assertEqual(chess.square_knight_distance(chess.C1, chess.B2), 2) + class MoveTestCase(unittest.TestCase): @@ -579,6 +588,7 @@ def _check(board, white, black): _check(chess.variant.CrazyhouseBoard("8/8/8/8/3k4/3N~4/3K4/8 w - - 0 1"), False, False) _check(chess.variant.HordeBoard("8/5k2/8/8/8/4NN2/8/8 w - - 0 1"), True, False) + _check(chess.variant.HordeBoard("8/1b5r/1P6/1Pk3q1/1PP5/r1P5/P1P5/2P5 b - - 0 52"), False, False) def test_promotion_with_check(self): board = chess.Board("8/6P1/2p5/1Pqk4/6P1/2P1RKP1/4P1P1/8 w - - 0 1") @@ -693,6 +703,11 @@ def test_san(self): self.assertEqual(board.san(chess.Move.from_uci("h4g6")), "Nh4g6") self.assertEqual(board.fen(), fen) + # Test a bug where shakmaty used overly specific disambiguation. + fen = "8/2KN1p2/5p2/3N1B1k/5PNp/7P/7P/8 w - -" + board = chess.Board(fen) + self.assertEqual(board.san(chess.Move.from_uci("d5f6")), "N5xf6#") + # Do not disambiguate illegal alternatives. fen = "8/8/8/R2nkn2/8/8/2K5/8 b - - 0 1" board = chess.Board(fen) @@ -1133,6 +1148,11 @@ def test_eret_epd(self): self.assertEqual(ops["id"], "ERET 001 - Entlastung") self.assertEqual(ops["bm"], [chess.Move.from_uci("f1f4")]) + def test_set_fen_as_epd(self): + board = chess.Board() + with self.assertRaises(ValueError): + board.set_epd(board.fen()) # Move numbers are not valid opcodes + def test_null_moves(self): self.assertEqual(str(chess.Move.null()), "0000") self.assertEqual(chess.Move.null().uci(), "0000") @@ -1200,7 +1220,7 @@ def test_clear(self): self.assertFalse(board.ep_square) self.assertFalse(board.piece_at(chess.E1)) - self.assertEqual(chess.popcount(board.occupied), 0) + self.assertEqual(board.piece_count(), 0) def test_threefold_repetition(self): board = chess.Board() @@ -1700,6 +1720,10 @@ def test_impossible_check_due_to_en_passant(self): self.assertFalse(board.has_legal_en_passant()) self.assertEqual(len(list(board.legal_moves)), 2) + def test_multiple_kings(self): + board = chess.Board("KKKK1kkk/8/8/8/8/8/8/8 w - - 0 1") + self.assertEqual(board.king(chess.WHITE), None) + class LegalMoveGeneratorTestCase(unittest.TestCase): @@ -2068,29 +2092,29 @@ class PgnTestCase(unittest.TestCase): def test_exporter(self): game = chess.pgn.Game() - game.comment = "Test game:" + game.comments = ["Test game:"] game.headers["Result"] = "*" game.headers["VeryLongHeader"] = "This is a very long header, much wider than the 80 columns that PGNs are formatted with by default" e4 = game.add_variation(game.board().parse_san("e4")) - e4.comment = "Scandinavian Defense:" + e4.comments = ["Scandinavian Defense:"] e4_d5 = e4.add_variation(e4.board().parse_san("d5")) e4_h5 = e4.add_variation(e4.board().parse_san("h5")) e4_h5.nags.add(chess.pgn.NAG_MISTAKE) - e4_h5.starting_comment = "This" - e4_h5.comment = "is nonsense" + e4_h5.starting_comments = ["This"] + e4_h5.comments = ["is nonsense"] e4_e5 = e4.add_variation(e4.board().parse_san("e5")) e4_e5_Qf3 = e4_e5.add_variation(e4_e5.board().parse_san("Qf3")) e4_e5_Qf3.nags.add(chess.pgn.NAG_MISTAKE) e4_c5 = e4.add_variation(e4.board().parse_san("c5")) - e4_c5.comment = "Sicilian" + e4_c5.comments = ["Sicilian"] e4_d5_exd5 = e4_d5.add_main_variation(e4_d5.board().parse_san("exd5")) - e4_d5_exd5.comment = "Best" + e4_d5_exd5.comments = ["Best", "and the end of this {example}"] # Test string exporter with various options. exporter = chess.pgn.StringExporter(headers=False, comments=False, variations=False) @@ -2115,7 +2139,7 @@ def test_exporter(self): { Test game: } 1. e4 { Scandinavian Defense: } 1... d5 ( { This } 1... h5 $2 { is nonsense } ) ( 1... e5 2. Qf3 $2 ) ( 1... c5 { Sicilian } ) 2. exd5 - { Best } *""") + { Best } { and the end of this example } *""") self.assertEqual(str(exporter), pgn) # Test file exporter. @@ -2205,6 +2229,14 @@ def test_read_game(self): self.assertEqual(sixth_game.headers["White"], "Deep Blue (Computer)") self.assertEqual(sixth_game.headers["Result"], "1-0") + def test_read_game_with_multicomment_move(self): + pgn = io.StringIO("1. e4 {A common opening} 1... e5 {A common response} {An uncommon comment}") + game = chess.pgn.read_game(pgn) + first_move = game.variation(0) + self.assertEqual(first_move.comments, ["A common opening"]) + second_move = first_move.variation(0) + self.assertEqual(second_move.comments, ["A common response", "An uncommon comment"]) + def test_comment_at_eol(self): pgn = io.StringIO(textwrap.dedent("""\ 1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5 4. c3 Nf6 5. d3 d6 6. Nbd2 a6 $6 (6... Bb6 $5 { @@ -2219,7 +2251,7 @@ def test_comment_at_eol(self): # Make sure the comment for the second variation is there. self.assertIn(5, node[1].nags) - self.assertEqual(node[1].comment, "\n/\\ Ne7, c6") + self.assertEqual(node[1].comments, ["\n/\\ Ne7, c6"]) def test_promotion_without_equals(self): # Example game from https://github.com/rozim/ChessData as originally @@ -2307,12 +2339,12 @@ def test_variation_stack(self): def test_game_starting_comment(self): pgn = io.StringIO("{ Game starting comment } 1. d3") game = chess.pgn.read_game(pgn) - self.assertEqual(game.comment, "Game starting comment") + self.assertEqual(game.comments, ["Game starting comment"]) self.assertEqual(game[0].san(), "d3") pgn = io.StringIO("{ Empty game, but has a comment }") game = chess.pgn.read_game(pgn) - self.assertEqual(game.comment, "Empty game, but has a comment") + self.assertEqual(game.comments, ["Empty game, but has a comment"]) def test_game_starting_variation(self): pgn = io.StringIO(textwrap.dedent("""\ @@ -2320,17 +2352,17 @@ def test_game_starting_variation(self): """)) game = chess.pgn.read_game(pgn) - self.assertEqual(game.comment, "Start of game") + self.assertEqual(game.comments, ["Start of game"]) node = game[0] self.assertEqual(node.move, chess.Move.from_uci("e2e4")) - self.assertFalse(node.comment) - self.assertFalse(node.starting_comment) + self.assertFalse(node.comments) + self.assertFalse(node.starting_comments) node = game[1] self.assertEqual(node.move, chess.Move.from_uci("d2d4")) - self.assertFalse(node.comment) - self.assertEqual(node.starting_comment, "Start of variation") + self.assertFalse(node.comments) + self.assertEqual(node.starting_comments, ["Start of variation"]) def test_annotation_symbols(self): pgn = io.StringIO("1. b4?! g6 2. Bb2 Nc6? 3. Bxh8!!") @@ -2483,6 +2515,53 @@ def test_read_headers(self): self.assertEqual(first_drawn_game.headers["Site"], "03") self.assertEqual(first_drawn_game[0].move, chess.Move.from_uci("d2d3")) + def test_parse_time_control(self): + with open("data/pgn/nepomniachtchi-liren-game1.pgn") as pgn: + game = chess.pgn.read_game(pgn) + tc = game.time_control() + + self.assertEqual(tc, chess.pgn.parse_time_control(game.headers["TimeControl"])) + + self.assertEqual(tc.type, chess.pgn.TimeControlType.STANDARD) + self.assertEqual(len(tc.parts), 3) + + tcp1, tcp2, tcp3 = tc.parts + + self.assertEqual(tcp1, chess.pgn.TimeControlPart(40, 7200)) + self.assertEqual(tcp2, chess.pgn.TimeControlPart(20, 3600)) + self.assertEqual(tcp3, chess.pgn.TimeControlPart(0, 900, 30)) + + self.assertEqual(chess.pgn.TimeControlType.BULLET, chess.pgn.parse_time_control("60").type) + self.assertEqual(chess.pgn.TimeControlType.BULLET, chess.pgn.parse_time_control("60+1").type) + + self.assertEqual(chess.pgn.TimeControlType.BLITZ, chess.pgn.parse_time_control("60+2").type) + self.assertEqual(chess.pgn.TimeControlType.BLITZ, chess.pgn.parse_time_control("300").type) + self.assertEqual(chess.pgn.TimeControlType.BLITZ, chess.pgn.parse_time_control("300+3").type) + + self.assertEqual(chess.pgn.TimeControlType.RAPID, chess.pgn.parse_time_control("300+10").type) + self.assertEqual(chess.pgn.TimeControlType.RAPID, chess.pgn.parse_time_control("1800").type) + self.assertEqual(chess.pgn.TimeControlType.RAPID, chess.pgn.parse_time_control("1800+10").type) + + self.assertEqual(chess.pgn.TimeControlType.STANDARD, chess.pgn.parse_time_control("1800+30").type) + self.assertEqual(chess.pgn.TimeControlType.STANDARD, chess.pgn.parse_time_control("5400").type) + self.assertEqual(chess.pgn.TimeControlType.STANDARD, chess.pgn.parse_time_control("5400+30").type) + + with self.assertRaises(ValueError): + chess.pgn.parse_time_control("300+a") + + with self.assertRaises(ValueError): + chess.pgn.parse_time_control("300+ad") + + with self.assertRaises(ValueError): + chess.pgn.parse_time_control("600:20/180") + + with self.assertRaises(ValueError): + chess.pgn.parse_time_control("abc") + + with self.assertRaises(ValueError): + chess.pgn.parse_time_control("40/abc") + + def test_visit_board(self): class TraceVisitor(chess.pgn.BaseVisitor): def __init__(self): @@ -2628,12 +2707,12 @@ def test_add_line(self): tail = game.add_line(moves, starting_comment="start", comment="end", nags=(17, 42)) self.assertEqual(tail.parent.move, chess.Move.from_uci("g1f3")) - self.assertEqual(tail.parent.starting_comment, "start") - self.assertEqual(tail.parent.comment, "") + self.assertEqual(tail.parent.starting_comments, ["start"]) + self.assertEqual(tail.parent.comments, []) self.assertEqual(len(tail.parent.nags), 0) self.assertEqual(tail.move, chess.Move.from_uci("d7d5")) - self.assertEqual(tail.comment, "end") + self.assertEqual(tail.comments, ["end"]) self.assertIn(42, tail.nags) def test_mainline(self): @@ -2763,27 +2842,27 @@ def test_recursion(self): def test_annotations(self): game = chess.pgn.Game() - game.comment = "foo [%bar] baz" + game.comments = ["foo [%bar] baz"] self.assertTrue(game.clock() is None) clock = 12345 game.set_clock(clock) - self.assertEqual(game.comment, "foo [%bar] baz [%clk 3:25:45]") + self.assertEqual(game.comments, ["foo [%bar] baz", "[%clk 3:25:45]"]) self.assertEqual(game.clock(), clock) self.assertTrue(game.eval() is None) game.set_eval(chess.engine.PovScore(chess.engine.Cp(-80), chess.WHITE)) - self.assertEqual(game.comment, "foo [%bar] baz [%clk 3:25:45] [%eval -0.80]") + self.assertEqual(game.comments, ["foo [%bar] baz", "[%clk 3:25:45]", "[%eval -0.80]"]) self.assertEqual(game.eval().white().score(), -80) self.assertEqual(game.eval_depth(), None) game.set_eval(chess.engine.PovScore(chess.engine.Mate(1), chess.WHITE), 5) - self.assertEqual(game.comment, "foo [%bar] baz [%clk 3:25:45] [%eval #1,5]") + self.assertEqual(game.comments, ["foo [%bar] baz", "[%clk 3:25:45]", "[%eval #1,5]"]) self.assertEqual(game.eval().white().mate(), 1) self.assertEqual(game.eval_depth(), 5) self.assertEqual(game.arrows(), []) game.set_arrows([(chess.A1, chess.A1), chess.svg.Arrow(chess.A1, chess.H1, color="red"), chess.svg.Arrow(chess.B1, chess.B8)]) - self.assertEqual(game.comment, "[%csl Ga1][%cal Ra1h1,Gb1b8] foo [%bar] baz [%clk 3:25:45] [%eval #1,5]") + self.assertEqual(game.comments, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]", "[%eval #1,5]"]) arrows = game.arrows() self.assertEqual(len(arrows), 3) self.assertEqual(arrows[0].color, "green") @@ -2793,43 +2872,49 @@ def test_annotations(self): self.assertTrue(game.emt() is None) emt = 321 game.set_emt(emt) - self.assertEqual(game.comment, "[%csl Ga1][%cal Ra1h1,Gb1b8] foo [%bar] baz [%clk 3:25:45] [%eval #1,5] [%emt 0:05:21]") + self.assertEqual(game.comments, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]", "[%eval #1,5]", "[%emt 0:05:21]"]) self.assertEqual(game.emt(), emt) game.set_eval(None) - self.assertEqual(game.comment, "[%csl Ga1][%cal Ra1h1,Gb1b8] foo [%bar] baz [%clk 3:25:45] [%emt 0:05:21]") + self.assertEqual(game.comments, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]", "[%emt 0:05:21]"]) game.set_emt(None) - self.assertEqual(game.comment, "[%csl Ga1][%cal Ra1h1,Gb1b8] foo [%bar] baz [%clk 3:25:45]") + self.assertEqual(game.comments, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]"]) game.set_clock(None) game.set_arrows([]) - self.assertEqual(game.comment, "foo [%bar] baz") + self.assertEqual(game.comments, ["foo [%bar] baz"]) + + def test_eval(self): + game = chess.pgn.Game() + for cp in range(199, 220): + game.set_eval(chess.engine.PovScore(chess.engine.Cp(cp), chess.WHITE)) + self.assertEqual(game.eval().white().cp, cp) def test_float_emt(self): game = chess.pgn.Game() - game.comment = "[%emt 0:00:01.234]" + game.comments = ["[%emt 0:00:01.234]"] self.assertEqual(game.emt(), 1.234) game.set_emt(6.54321) - self.assertEqual(game.comment, "[%emt 0:00:06.543]") + self.assertEqual(game.comments, ["[%emt 0:00:06.543]"]) self.assertEqual(game.emt(), 6.543) game.set_emt(-70) - self.assertEqual(game.comment, "[%emt 0:00:00]") # Clamped + self.assertEqual(game.comments, ["[%emt 0:00:00]"]) # Clamped self.assertEqual(game.emt(), 0) def test_float_clk(self): game = chess.pgn.Game() - game.comment = "[%clk 0:00:01.234]" + game.comments = ["[%clk 0:00:01.234]"] self.assertEqual(game.clock(), 1.234) game.set_clock(6.54321) - self.assertEqual(game.comment, "[%clk 0:00:06.543]") + self.assertEqual(game.comments, ["[%clk 0:00:06.543]"]) self.assertEqual(game.clock(), 6.543) game.set_clock(-70) - self.assertEqual(game.comment, "[%clk 0:00:00]") # Clamped + self.assertEqual(game.comments, ["[%clk 0:00:00]"]) # Clamped self.assertEqual(game.clock(), 0) def test_node_turn(self): @@ -2874,6 +2959,18 @@ def end_variation(self): game = chess.pgn.read_game(io.StringIO(pgn)).accept(BlackVariationsOnly()) self.assertEqual(game.accept(chess.pgn.StringExporter(headers=False)), expected_pgn) + def test_utf8_bom(self): + not_utf8_sig = "utf-8" + with open("data/pgn/utf8-bom.pgn", encoding=not_utf8_sig) as pgn: + game = chess.pgn.read_game(pgn) + self.assertEqual(game.headers["Event"], "A") + + game = chess.pgn.read_game(pgn) + self.assertEqual(game.headers["Event"], "B") + + game = chess.pgn.read_game(pgn) + self.assertEqual(game, None) + @unittest.skipIf(sys.platform == "win32" and (3, 8, 0) <= sys.version_info < (3, 8, 1), "https://bugs.python.org/issue34679") class EngineTestCase(unittest.TestCase): @@ -2932,9 +3029,10 @@ def test_score_ordering(self): self.assertEqual(i >= j, a >= b) self.assertEqual(i < j, a.score(mate_score=100000) < b.score(mate_score=100000)) - self.assertTrue(not (i < j) or a.wdl().expectation() <= b.wdl().expectation()) - self.assertTrue(not (i < j) or a.wdl().winning_chance() <= b.wdl().winning_chance()) - self.assertTrue(not (i < j) or a.wdl().losing_chance() >= b.wdl().losing_chance()) + for model in ["sf12", "sf14", "sf15", "sf15.1", "sf16", "sf16.1"]: + self.assertTrue(not (i < j) or a.wdl(model=model).expectation() <= b.wdl(model=model).expectation()) + self.assertTrue(not (i < j) or a.wdl(model=model).winning_chance() <= b.wdl(model=model).winning_chance()) + self.assertTrue(not (i < j) or a.wdl(model=model).losing_chance() >= b.wdl(model=model).losing_chance()) def test_score(self): # Negation. @@ -2969,6 +3067,9 @@ def test_wdl_model(self): self.assertEqual(chess.engine.Cp(131).wdl(model="sf12", ply=25), chess.engine.Wdl(524, 467, 9)) self.assertEqual(chess.engine.Cp(146).wdl(model="sf14", ply=25), chess.engine.Wdl(601, 398, 1)) self.assertEqual(chess.engine.Cp(40).wdl(model="sf15", ply=25), chess.engine.Wdl(58, 937, 5)) + self.assertEqual(chess.engine.Cp(100).wdl(model="sf15.1", ply=64), chess.engine.Wdl(497, 503, 0)) + self.assertEqual(chess.engine.Cp(-52).wdl(model="sf16", ply=63), chess.engine.Wdl(0, 932, 68)) + self.assertEqual(chess.engine.Cp(51).wdl(model="sf16.1", ply=158), chess.engine.Wdl(36, 964, 0)) @catchAndSkip(FileNotFoundError, "need stockfish") def test_sf_forced_mates(self): @@ -2982,7 +3083,7 @@ def test_sf_forced_mates(self): for epd in epds: operations = board.set_epd(epd) - result = engine.play(board, chess.engine.Limit(mate=5), game=object()) + result = engine.play(board, chess.engine.Limit(mate=3), game=object()) self.assertIn(result.move, operations["bm"], operations["id"]) @catchAndSkip(FileNotFoundError, "need stockfish") @@ -2996,7 +3097,7 @@ def test_sf_options(self): def test_sf_analysis(self): with chess.engine.SimpleEngine.popen_uci("stockfish", setpgrp=True, debug=True) as engine: board = chess.Board("8/6K1/1p1B1RB1/8/2Q5/2n1kP1N/3b4/4n3 w - - 0 1") - limit = chess.engine.Limit(depth=28) + limit = chess.engine.Limit(depth=40) analysis = engine.analysis(board, limit) with analysis: for info in iter(analysis.next, None): @@ -3045,6 +3146,26 @@ def test_sf_quit(self): with self.assertRaises(chess.engine.EngineTerminatedError), engine: engine.ping() + @catchAndSkip(FileNotFoundError, "need fairy-stockfish") + def test_fairy_sf_initialize(self): + with chess.engine.SimpleEngine.popen_uci("fairy-stockfish", setpgrp=True, debug=True): + pass + + def test_uci_option_parse(self): + async def main(): + protocol = chess.engine.UciProtocol() + mock = chess.engine.MockTransport(protocol) + + mock.expect("uci", ["option name UCI_Variant type combo default chess var bughouse var chess var mini var minishogi var threekings", "uciok"]) + await protocol.initialize() + mock.assert_done() + + mock.expect("isready", ["readyok"]) + await protocol.ping() + mock.assert_done() + + asyncio.run(main()) + @catchAndSkip(FileNotFoundError, "need crafty") def test_crafty_play_to_mate(self): logging.disable(logging.WARNING) @@ -3095,7 +3216,6 @@ async def main(): await protocol.ping() mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_uci_debug(self): @@ -3111,7 +3231,6 @@ async def main(): protocol.debug(False) mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_uci_go(self): @@ -3154,7 +3273,6 @@ async def main(): self.assertEqual(result.ponder, None) mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_iota_log(self): @@ -3177,7 +3295,6 @@ async def main(): await protocol.play(board, chess.engine.Limit(time=5.0)) mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_uci_analyse_mode(self): @@ -3224,7 +3341,6 @@ async def main(): self.assertEqual(best.ponder, chess.Move.from_uci("e7e5")) mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_uci_play_after_analyse(self): @@ -3252,7 +3368,6 @@ async def main(): mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_uci_ponderhit(self): @@ -3264,13 +3379,18 @@ async def main(): mock.expect("uci", [ "option name Hash type spin default 16 min 1 max 33554432", "option name Ponder type check default false", + "option name UCI_Opponent type string", "uciok", ]) await protocol.initialize() + primary_opponent = chess.engine.Opponent("Eliza", None, 3500, True) + await protocol.send_opponent_information(opponent=primary_opponent) + # First search. mock.expect("setoption name Ponder value true") mock.expect("ucinewgame") + mock.expect("setoption name UCI_Opponent value none 3500 computer Eliza") mock.expect("isready", ["readyok"]) mock.expect("position startpos") mock.expect("go movetime 1000", ["bestmove d2d4 ponder g8f6"]) @@ -3334,9 +3454,35 @@ async def main(): mock.expect("go ponder movetime 5000") await protocol.play(board, chess.engine.Limit(time=5), ponder=True) + # Ponderhit prevented by new opponent, which starts a new game. + board.push(chess.Move.from_uci("c4d5")) + board.push(chess.Move.from_uci("e6d5")) + mock.expect("stop", ["bestmove c1g5 ponder h7h6"]) + mock.expect("ucinewgame") + mock.expect("setoption name UCI_Opponent value GM 3000 human Guy Chapman") + mock.expect("isready", ["readyok"]) + mock.expect("position startpos moves d2d4 g8f6 c2c4 e7e6 b1c3 f8b4 d1c2 d7d5 c4d5 e6d5") + mock.expect("go movetime 5000", ["bestmove c1g5 ponder h7h6"]) + mock.expect("position startpos moves d2d4 g8f6 c2c4 e7e6 b1c3 f8b4 d1c2 d7d5 c4d5 e6d5 c1g5 h7h6") + mock.expect("go ponder movetime 5000") + opponent = chess.engine.Opponent("Guy Chapman", "GM", 3000, False) + await protocol.play(board, chess.engine.Limit(time=5), ponder=True, opponent=opponent) + + # Ponderhit prevented by restoration of previous opponent, which again starts a new game. + board.push(chess.Move.from_uci("c1g5")) + board.push(chess.Move.from_uci("h7h6")) + mock.expect("stop", ["bestmove g5h4 ponder b8c6"]) + mock.expect("ucinewgame") + mock.expect("setoption name UCI_Opponent value none 3500 computer Eliza") + mock.expect("isready", ["readyok"]) + mock.expect("position startpos moves d2d4 g8f6 c2c4 e7e6 b1c3 f8b4 d1c2 d7d5 c4d5 e6d5 c1g5 h7h6") + mock.expect("go movetime 5000", ["bestmove g5h4 ponder b8c6"]) + mock.expect("position startpos moves d2d4 g8f6 c2c4 e7e6 b1c3 f8b4 d1c2 d7d5 c4d5 e6d5 c1g5 h7h6 g5h4 b8c6") + mock.expect("go ponder movetime 5000") + await protocol.play(board, chess.engine.Limit(time=5), ponder=True) + mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_uci_info(self): @@ -3366,14 +3512,15 @@ def test_uci_info(self): self.assertEqual(info["seldepth"], 8) self.assertEqual(info["score"], chess.engine.PovScore(chess.engine.Mate(+3), chess.WHITE)) - # Info: tbhits, cpuload, hashfull, time, nodes, nps. - info = chess.engine._parse_uci_info("tbhits 123 cpuload 456 hashfull 789 time 987 nodes 654 nps 321", board) + # Info: tbhits, cpuload, hashfull, time, nodes, nps, movesleft. + info = chess.engine._parse_uci_info("tbhits 123 cpuload 456 hashfull 789 movesleft 42 time 987 nodes 654 nps 321", board) self.assertEqual(info["tbhits"], 123) self.assertEqual(info["cpuload"], 456) self.assertEqual(info["hashfull"], 789) self.assertEqual(info["time"], 0.987) self.assertEqual(info["nodes"], 654) self.assertEqual(info["nps"], 321) + self.assertEqual(info["movesleft"], 42) # Hakkapeliitta double spaces. info = chess.engine._parse_uci_info("depth 10 seldepth 9 score cp 22 time 17 nodes 48299 nps 2683000 tbhits 0", board) @@ -3398,7 +3545,50 @@ def test_uci_info(self): # WDL (activated with UCI_ShowWDL). info = chess.engine._parse_uci_info("depth 1 seldepth 2 time 16 nodes 1 score cp 72 wdl 249 747 4 hashfull 0 nps 400 tbhits 0 multipv 1", board) - self.assertEqual(info["wdl"], (249, 747, 4)) + self.assertEqual(info["wdl"].white(), chess.engine.Wdl(249, 747, 4)) + + def test_uci_result(self): + async def main(): + protocol = chess.engine.UciProtocol() + mock = chess.engine.MockTransport(protocol) + + mock.expect("uci", ["uciok"]) + await protocol.initialize() + mock.assert_done() + + limit = chess.engine.Limit(time=5) + checkmate_board = chess.Board("k7/7R/6R1/8/8/8/8/K7 w - - 0 1") + + mock.expect("ucinewgame") + mock.expect("isready", ["readyok"]) + mock.expect("position fen k7/7R/6R1/8/8/8/8/K7 w - - 0 1") + mock.expect("go movetime 5000", ["bestmove g6g8"]) + result = await protocol.play(checkmate_board, limit, game="checkmate") + self.assertEqual(result.move, checkmate_board.parse_uci("g6g8")) + checkmate_board.push(result.move) + self.assertTrue(checkmate_board.is_checkmate()) + await protocol.send_game_result(checkmate_board) + mock.assert_done() + + asyncio.run(main()) + + def test_uci_output_after_command(self): + async def main(): + protocol = chess.engine.UciProtocol() + mock = chess.engine.MockTransport(protocol) + + mock.expect("uci", [ + "Arasan v24.0.0-10-g367aa9f Copyright 1994-2023 by Jon Dart.", + "All rights reserved.", + "id name Arasan v24.0.0-10-g367aa9f", + "uciok", + "info string out of do_all_pending, list size=0" + ]) + await protocol.initialize() + + mock.assert_done() + + asyncio.run(main()) def test_hiarcs_bestmove(self): async def main(): @@ -3422,7 +3612,6 @@ async def main(): self.assertEqual(result.info["string"], "keep double space") mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_xboard_options(self): @@ -3485,7 +3674,6 @@ async def main(): await protocol.configure({"buttonvar": None}) mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_xboard_replay(self): @@ -3547,7 +3735,152 @@ async def main(): self.assertEqual(result.move, board.parse_san("d4")) mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) + asyncio.run(main()) + + def test_xboard_opponent(self): + async def main(): + protocol = chess.engine.XBoardProtocol() + mock = chess.engine.MockTransport(protocol) + + mock.expect("xboard") + mock.expect("protover 2", ["feature ping=1 setboard=1 name=1 done=1"]) + await protocol.initialize() + mock.assert_done() + + limit = chess.engine.Limit(time=5) + board = chess.Board() + opponent = chess.engine.Opponent("Turk", "Mechanical", 2100, True) + await protocol.send_opponent_information(opponent=opponent, engine_rating=3600) + + mock.expect("new") + mock.expect("name Mechanical Turk") + mock.expect("rating 3600 2100") + mock.expect("computer") + mock.expect("force") + mock.expect("st 5") + mock.expect("nopost") + mock.expect("easy") + mock.expect("go", ["move e2e4"]) + mock.expect_ping() + result = await protocol.play(board, limit, game="game") + self.assertEqual(result.move, board.parse_san("e4")) + mock.assert_done() + + new_opponent = chess.engine.Opponent("Turochamp", None, 800, True) + board.push(result.move) + mock.expect("new") + mock.expect("name Turochamp") + mock.expect("rating 3600 800") + mock.expect("computer") + mock.expect("force") + mock.expect("e2e4") + mock.expect("st 5") + mock.expect("nopost") + mock.expect("easy") + mock.expect("go", ["move e7e5"]) + mock.expect_ping() + result = await protocol.play(board, limit, game="game", opponent=new_opponent) + self.assertEqual(result.move, board.parse_san("e5")) + mock.assert_done() + + bad_opponent = chess.engine.Opponent("New\nLine", "GM", 1, False) + with self.assertRaises(chess.engine.EngineError): + await protocol.send_opponent_information(opponent=bad_opponent) + mock.assert_done() + + with self.assertRaises(chess.engine.EngineError): + result = await protocol.play(board, limit, game="bad game", opponent=bad_opponent) + mock.assert_done() + + asyncio.run(main()) + + def test_xboard_result(self): + async def main(): + protocol = chess.engine.XBoardProtocol() + mock = chess.engine.MockTransport(protocol) + + mock.expect("xboard") + mock.expect("protover 2", ["feature ping=1 setboard=1 done=1"]) + await protocol.initialize() + mock.assert_done() + + limit = chess.engine.Limit(time=5) + checkmate_board = chess.Board("k7/7R/6R1/8/8/8/8/K7 w - - 0 1") + + mock.expect("new") + mock.expect("force") + mock.expect("setboard k7/7R/6R1/8/8/8/8/K7 w - - 0 1") + mock.expect("st 5") + mock.expect("nopost") + mock.expect("easy") + mock.expect("go", ["move g6g8"]) + mock.expect_ping() + mock.expect("force") + mock.expect("result 1-0 {White mates}") + result = await protocol.play(checkmate_board, limit, game="checkmate") + self.assertEqual(result.move, checkmate_board.parse_uci("g6g8")) + checkmate_board.push(result.move) + self.assertTrue(checkmate_board.is_checkmate()) + await protocol.send_game_result(checkmate_board) + mock.assert_done() + + unfinished_board = chess.Board() + mock.expect("new") + mock.expect("force") + mock.expect("st 5") + mock.expect("nopost") + mock.expect("easy") + mock.expect("go", ["move e2e4"]) + mock.expect_ping() + mock.expect("force") + mock.expect("result *") + result = await protocol.play(unfinished_board, limit, game="unfinished") + self.assertEqual(result.move, unfinished_board.parse_uci("e2e4")) + unfinished_board.push(result.move) + await protocol.send_game_result(unfinished_board, game_complete=False) + mock.assert_done() + + timeout_board = chess.Board() + mock.expect("new") + mock.expect("force") + mock.expect("st 5") + mock.expect("nopost") + mock.expect("easy") + mock.expect("go", ["move e2e4"]) + mock.expect_ping() + mock.expect("force") + mock.expect("result 0-1 {Time forfeiture}") + result = await protocol.play(timeout_board, limit, game="timeout") + self.assertEqual(result.move, timeout_board.parse_uci("e2e4")) + timeout_board.push(result.move) + await protocol.send_game_result(timeout_board, chess.BLACK, "Time forfeiture") + mock.assert_done() + + error_board = chess.Board() + mock.expect("new") + mock.expect("force") + mock.expect("st 5") + mock.expect("nopost") + mock.expect("easy") + mock.expect("go", ["move e2e4"]) + mock.expect_ping() + result = await protocol.play(error_board, limit, game="error") + self.assertEqual(result.move, error_board.parse_uci("e2e4")) + error_board.push(result.move) + for c in "\n\r{}": + with self.assertRaises(chess.engine.EngineError): + await protocol.send_game_result(error_board, chess.BLACK, f"Time{c}forfeiture") + mock.assert_done() + + material_board = chess.Board("k7/8/8/8/8/8/8/K7 b - - 0 1") + self.assertTrue(material_board.is_insufficient_material()) + mock.expect("new") + mock.expect("force") + mock.expect("setboard k7/8/8/8/8/8/8/K7 b - - 0 1") + mock.expect("result 1/2-1/2 {Insufficient material}") + await protocol.send_game_result(material_board) + mock.assert_done() + asyncio.run(main()) def test_xboard_analyse(self): @@ -3585,7 +3918,6 @@ async def main(): self.assertEqual(info["pv"], [chess.Move.from_uci(move) for move in ["f7f6", "e2e4", "e7e6"]]) mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) def test_xboard_level(self): @@ -3599,7 +3931,9 @@ async def main(): mock.assert_done() limit = chess.engine.Limit(black_clock=65, white_clock=100, - black_inc=4, white_inc=8) + black_inc=4, white_inc=8, + clock_id="xboard level") + board = chess.Board() mock.expect("new") mock.expect("force") mock.expect("level 0 1:40 8") @@ -3609,11 +3943,25 @@ async def main(): mock.expect("easy") mock.expect("go", ["move e2e4"]) mock.expect_ping() - result = await protocol.play(chess.Board(), limit) + result = await protocol.play(board, limit) self.assertEqual(result.move, chess.Move.from_uci("e2e4")) mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) + board.push(result.move) + board.push_uci("e7e5") + + mock.expect("force") + mock.expect("e7e5") + mock.expect("time 10000") + mock.expect("otim 6500") + mock.expect("nopost") + mock.expect("easy") + mock.expect("go", ["move d2d4"]) + mock.expect_ping() + result = await protocol.play(board, limit) + self.assertEqual(result.move, chess.Move.from_uci("d2d4")) + mock.assert_done() + asyncio.run(main()) def test_xboard_error(self): @@ -3632,7 +3980,6 @@ async def main(): mock.assert_done() - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) @catchAndSkip(FileNotFoundError, "need /bin/bash") @@ -3644,7 +3991,6 @@ async def main(): self.assertNotEqual(results[0], None) self.assertNotEqual(results[1], None) - asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy()) asyncio.run(main()) @catchAndSkip(FileNotFoundError, "need /bin/bash") @@ -4006,6 +4352,21 @@ def test_two_ep(self): board = chess.Board("K7/8/8/6k1/5pPp/8/8/8 b - g3 0 61") self.assertEqual(self.tablebase.probe_dtm(board), 17) + @catchAndSkip(chess.gaviota.MissingTableError, "need KPvKP.gtb.cp4") + def test_ep_is_best(self): + board = chess.Board("8/8/7k/8/1pP5/7K/8/8 b - c3 0 1") + self.assertEqual(self.tablebase.probe_dtm(board), 19) + + @catchAndSkip(chess.gaviota.MissingTableError, "need KQPvKP.gtb.cp4") + def test_ep_is_mate(self): + # The resulting mate. + board = chess.Board("5Q2/7k/6P1/5K2/8/8/8/8 b - - 0 1") + self.assertEqual(self.tablebase.probe_dtm(board), 0) + + # Ep leads to the previously tested mate position. + board = chess.Board("5Q2/7k/8/5KpP/8/8/8/8 w - g6 0 1") + self.assertEqual(self.tablebase.probe_dtm(board), 1) + class SvgTestCase(unittest.TestCase): diff --git a/tox.ini b/tox.ini index 3eeb764ba..493a46feb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,17 @@ [tox] -envlist = py37,py38,py39,py310 +envlist = py38,py39,py310,py311,py312,py313,py314 [testenv] passenv = LD_LIBRARY_PATH whitelist_externals = stockfish crafty + fairy-stockfish commands = python test.py --verbose python -m doctest README.rst --verbose -[testenv:{py37,py38,py39}] +[testenv:{py38,py39}] passenv = LD_LIBRARY_PATH whitelist_externals = stockfish @@ -20,11 +21,18 @@ commands = [flake8] ignore = - E126 E131 # Allow over-indent and unaligned indent - E241 # Allow indenting array elements - E302 E305 # Allow grouping functions - E741 # Allow "ambiguous" variable names - W504 # Allow binary operators before EOL (end of line) - E704 E301 # Allow "def overloaded(): ..." - E722 # Bare except (with or without reraise) + # Allow over-indent and unaligned indent + E126 E131 + # Allow indenting array elements + E241 + # Allow grouping functions + E302 E305 + # Allow "ambiguous" variable names + E741 + # Allow binary operators before EOL (end of line) + W504 + # Allow "def overloaded(): ..." + E704 E301 + # Bare except (with or without reraise) + E722 max-line-length = 160