Skip to content

Core API Reference

This module covers game tree representation, immutable cursors, board position analysis, and tournament draw adjudication.


Game

libscid.Game

Game(position: Position | None = None)

Chess game entity encapsulating metadata tags and hierarchical movetext.

A Game represents a complete chess contest, including the Seven Tag Roster (STR) metadata (Event, Site, Date, Round, White, Black, Result), supplemental PGN header tags, the initial board starting position, and the hierarchical movetext tree (mainline moves, alternative variations, NAG annotations, and commentary).

Game tree traversal and editing are performed by creating a Cursor via create_cursor().

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn(
...     '[Event "World Championship"]\n'
...     '[White "Kasparov, Garry"]\n'
...     '[Black "Karpov, Anatoly"]\n'
...     '[Result "1-0"]\n\n'
...     '1. e4 e5 2. Nf3 Nc6 3. Bb5 1-0'
... )
>>> game.get_tag("White")
'Kasparov, Garry'
>>> game.mainline_move_count
5
>>> cursor = game.create_cursor()
>>> cursor.next().previous_move_san
'e4'

Initialise a blank chess game.

Initialises standard default PGN tags (Event, Site, Date, Round, White, Black, Result). If a custom starting position is supplied, a FEN tag is automatically attached.

Parameters:

Name Type Description Default
position Position | None

Optional custom starting Position. If omitted or None, the standard chess starting board setup is used.

None

Examples:

>>> import libscid
>>> game = libscid.Game()
>>> game.get_tag("Result")
'*'
>>> pos = libscid.Position.from_fen("4k3/8/8/8/8/8/8/4K2R w K - 0 1")
>>> custom_game = libscid.Game(pos)
>>> custom_game.get_tag("FEN")
'4k3/8/8/8/8/8/8/4K2R w K - 0 1'
Source code in src/libscid/_game.py
def __init__(self, position: Position | None = None):
    """Initialise a blank chess game.

    Initialises standard default PGN tags (`Event`, `Site`, `Date`,
    `Round`, `White`, `Black`, `Result`). If a custom starting `position`
    is supplied, a `FEN` tag is automatically attached.

    Args:
        position: Optional custom starting [`Position`][libscid.Position].
            If omitted or None, the standard chess starting board setup is
            used.

    Examples:
        >>> import libscid
        >>> game = libscid.Game()
        >>> game.get_tag("Result")
        '*'
        >>> pos = libscid.Position.from_fen("4k3/8/8/8/8/8/8/4K2R w K - 0 1")
        >>> custom_game = libscid.Game(pos)
        >>> custom_game.get_tag("FEN")
        '4k3/8/8/8/8/8/8/4K2R w K - 0 1'
    """
    if position is None:
        self._native = load_library()
        self._handle = self._native.create_blank_game()
    else:
        self._native = position._native
        self._handle = self._native.create_blank_game(position._handle)
    self._finalizer = weakref.finalize(self, self._native.free_game, self._handle)

Attributes

mainline_move_count property

mainline_move_count: int

Total number of halfmoves (ply) in the mainline of the game.

start_position property

start_position: Position

Initial board state at the start of the game.

end_position property

end_position: Position

Final board state at the conclusion of the mainline.

Methods:

from_pgn classmethod

from_pgn(
    pgn: str | bytes, position: Position | None = None
) -> Game

Create a game by parsing PGN text.

Parses PGN header tags, move notation, nested variations, comments, and Numeric Annotation Glyphs (NAGs) from the supplied string buffer.

Parameters:

Name Type Description Default
pgn str | bytes

PGN text string or bytes to parse.

required
position Position | None

Optional custom starting Position context for games starting from a non-standard setup.

None

Returns:

Type Description
Game

A newly allocated Game instance.

Raises:

Type Description
LibScidError

If the PGN syntax is invalid, illegal moves are encountered, or parsing fails.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. d4 d5 2. c4 e6 *")
>>> game.mainline_move_count
4
Source code in src/libscid/_game.py
@classmethod
def from_pgn(cls, pgn: str | bytes, position: Position | None = None) -> Game:
    """Create a game by parsing PGN text.

    Parses PGN header tags, move notation, nested variations, comments,
    and Numeric Annotation Glyphs (NAGs) from the supplied string buffer.

    Args:
        pgn: PGN text string or bytes to parse.
        position: Optional custom starting [`Position`][libscid.Position]
            context for games starting from a non-standard setup.

    Returns:
        A newly allocated [`Game`][libscid.Game] instance.

    Raises:
        LibScidError: If the PGN syntax is invalid, illegal moves are
            encountered, or parsing fails.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. d4 d5 2. c4 e6 *")
        >>> game.mainline_move_count
        4
    """
    if position is None:
        native = load_library()
        return cls._from_handle(native, native.create_game_from_pgn(pgn))

    native = position._native
    return cls._from_handle(
        native, native.create_game_from_pgn(pgn, position._handle)
    )

get_tag

get_tag(name: str | bytes) -> str

Retrieve the value of a PGN header tag by name.

Queries Seven Tag Roster (STR) headers, well-known supplemental tags (e.g. "ECO", "FEN"), and custom tags.

Parameters:

Name Type Description Default
name str | bytes

PGN tag header name (e.g. "White", "ECO", "Event").

required

Returns:

Type Description
str

The tag value string, or an empty string if the tag is absent.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn('[White "Tal, Mikhail"] 1. e4 *')
>>> game.get_tag("White")
'Tal, Mikhail'
>>> game.get_tag("Annotator")
''
Source code in src/libscid/_game.py
def get_tag(self, name: str | bytes) -> str:
    """Retrieve the value of a PGN header tag by name.

    Queries Seven Tag Roster (STR) headers, well-known supplemental tags
    (e.g. "ECO", "FEN"), and custom tags.

    Args:
        name: PGN tag header name (e.g. "White", "ECO", "Event").

    Returns:
        The tag value string, or an empty string if the tag is absent.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn('[White "Tal, Mikhail"] 1. e4 *')
        >>> game.get_tag("White")
        'Tal, Mikhail'
        >>> game.get_tag("Annotator")
        ''
    """
    return self._native.game_get_tag(self._handle, name)

set_tag

set_tag(name: str | bytes, value: str | bytes) -> None

Set or update the value of a PGN header tag.

Parameters:

Name Type Description Default
name str | bytes

PGN tag header name.

required
value str | bytes

Value string to assign to the tag.

required

Raises:

Type Description
LibScidError

If the tag value is invalid (e.g. malformed "Result").

Examples:

>>> import libscid
>>> game = libscid.Game()
>>> game.set_tag("White", "Spassky, Boris")
>>> game.set_tag("WhiteElo", "2660")
>>> game.get_tag("WhiteElo")
'2660'
Source code in src/libscid/_game.py
def set_tag(self, name: str | bytes, value: str | bytes) -> None:
    """Set or update the value of a PGN header tag.

    Args:
        name: PGN tag header name.
        value: Value string to assign to the tag.

    Raises:
        LibScidError: If the tag value is invalid (e.g. malformed "Result").

    Examples:
        >>> import libscid
        >>> game = libscid.Game()
        >>> game.set_tag("White", "Spassky, Boris")
        >>> game.set_tag("WhiteElo", "2660")
        >>> game.get_tag("WhiteElo")
        '2660'
    """
    self._native.game_set_tag(self._handle, name, value)

remove_tag

remove_tag(name: str | bytes) -> bool

Remove a supplemental PGN header tag by name.

Standard mandatory Seven Tag Roster (STR) tags and the FEN tag cannot be removed.

Parameters:

Name Type Description Default
name str | bytes

PGN tag header name to remove.

required

Returns:

Type Description
bool

True if the tag was present and successfully removed; False if the tag was not found or is non-removable.

Examples:

>>> import libscid
>>> game = libscid.Game()
>>> game.set_tag("Annotator", "Fischer")
>>> game.remove_tag("Annotator")
True
>>> game.remove_tag("White")
False
Source code in src/libscid/_game.py
def remove_tag(self, name: str | bytes) -> bool:
    """Remove a supplemental PGN header tag by name.

    Standard mandatory Seven Tag Roster (STR) tags and the `FEN` tag
    cannot be removed.

    Args:
        name: PGN tag header name to remove.

    Returns:
        True if the tag was present and successfully removed; False if the
            tag was not found or is non-removable.

    Examples:
        >>> import libscid
        >>> game = libscid.Game()
        >>> game.set_tag("Annotator", "Fischer")
        >>> game.remove_tag("Annotator")
        True
        >>> game.remove_tag("White")
        False
    """
    return self._native.game_remove_tag(self._handle, name)

get_tags

get_tags() -> tuple[tuple[str, str], ...]

Retrieve all PGN header tags present in the game.

Returns:

Type Description
tuple[tuple[str, str], ...]

A tuple of (name, value) string pairs representing all tags.

Examples:

>>> import libscid
>>> game = libscid.Game()
>>> dict(game.get_tags())["Result"]
'*'
Source code in src/libscid/_game.py
def get_tags(self) -> tuple[tuple[str, str], ...]:
    """Retrieve all PGN header tags present in the game.

    Returns:
        A tuple of `(name, value)` string pairs representing all tags.

    Examples:
        >>> import libscid
        >>> game = libscid.Game()
        >>> dict(game.get_tags())["Result"]
        '*'
    """
    return self._native.game_get_tags(self._handle)

create_cursor

create_cursor() -> Cursor

Create a new cursor initialised at the game's starting position.

Returns:

Type Description
Cursor

A new Cursor positioned at the start of the game.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 *")
>>> cursor = game.create_cursor()
>>> cursor.next().previous_move_san
'e4'
Source code in src/libscid/_game.py
def create_cursor(self) -> Cursor:
    """Create a new cursor initialised at the game's starting position.

    Returns:
        A new [`Cursor`][libscid.Cursor] positioned at the start of the
            game.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 *")
        >>> cursor = game.create_cursor()
        >>> cursor.next().previous_move_san
        'e4'
    """
    return Cursor._from_handle(
        self._native, self, self._native.game_create_cursor(self._handle)
    )

iter_movetext

iter_movetext(
    *, variations: bool = True
) -> Iterator[MovetextEvent]

Iterate over hierarchical movetext events from the game start.

Parameters:

Name Type Description Default
variations bool

Whether to recursively traverse nested variation branches. Defaults to True.

True

Returns:

Type Description
Iterator[MovetextEvent]

An iterator yielding MovetextEvent instances.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
>>> moves = [
...     e.san for e in game.iter_movetext()
...     if isinstance(e, libscid.MovetextMove)
... ]
>>> moves
['e4', 'd4', 'd5', 'e5']
Source code in src/libscid/_game.py
def iter_movetext(self, *, variations: bool = True) -> Iterator[MovetextEvent]:
    """Iterate over hierarchical movetext events from the game start.

    Args:
        variations: Whether to recursively traverse nested variation branches.
            Defaults to True.

    Returns:
        An iterator yielding [`MovetextEvent`][libscid.MovetextEvent]
            instances.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
        >>> moves = [
        ...     e.san for e in game.iter_movetext()
        ...     if isinstance(e, libscid.MovetextMove)
        ... ]
        >>> moves
        ['e4', 'd4', 'd5', 'e5']
    """
    return self.create_cursor().iter_movetext(variations=variations)

to_pgn

to_pgn(options: PgnOptions | None = None) -> str

Serialise the game to a standard PGN-formatted string.

Parameters:

Name Type Description Default
options PgnOptions | None

Optional PgnOptions specifying formatting controls such as line wrapping, NAG notation, comments, and variation inclusion.

None

Returns:

Type Description
str

PGN-formatted string representing the game.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 $1 {King pawn} (1. c4) e5 *")
>>> options = libscid.PgnOptions(
...     symbolic_nags=True,
...     variations=False,
... )
>>> pgn = game.to_pgn(options)
>>> "1.e4 ! {King pawn} 1...e5" in pgn
True
Source code in src/libscid/_game.py
def to_pgn(self, options: PgnOptions | None = None) -> str:
    """Serialise the game to a standard PGN-formatted string.

    Args:
        options: Optional [`PgnOptions`][libscid.PgnOptions] specifying
            formatting controls such as line wrapping, NAG notation,
            comments, and variation inclusion.

    Returns:
        PGN-formatted string representing the game.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 $1 {King pawn} (1. c4) e5 *")
        >>> options = libscid.PgnOptions(
        ...     symbolic_nags=True,
        ...     variations=False,
        ... )
        >>> pgn = game.to_pgn(options)
        >>> "1.e4 ! {King pawn} 1...e5" in pgn
        True
    """
    return self._native.game_to_pgn(self._handle, options)

Cursor

libscid.Cursor

Cursor()

Navigational node within a hierarchical chess game movetext tree.

A Cursor points to a specific ply within a Game, providing access to the board Position, incoming and departing moves, commentary, NAG annotations, and variation branching.

Navigation methods (such as next(), previous(), and enter_variation()) return newly allocated cursor instances representing target locations, maintaining immutable value semantics during traversal. Tree mutation operations (such as append_move() and set_comment()) modify the underlying game.

Direct instantiation of Cursor is disallowed; instances must be obtained via Game.create_cursor() or navigation methods.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 *")
>>> cursor = game.create_cursor()
>>> cursor.is_line_start
True
>>> move1 = cursor.next()
>>> move1.previous_move_san
'e4'
>>> move1.position.side_to_move
'black'

Disallow direct cursor instantiation.

Raises:

Type Description
TypeError

Always raised if instantiated directly.

Source code in src/libscid/_cursor.py
def __init__(self):
    """Disallow direct cursor instantiation.

    Raises:
        TypeError: Always raised if instantiated directly.
    """
    raise TypeError("Cursor objects are returned by libscid APIs")

Attributes

arbiter property

arbiter: Arbiter

Retrieve an arbiter to evaluate tournament rules at this position.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 *")
>>> cursor = game.create_cursor().to_game_end()
>>> cursor.arbiter.can_claim_fifty_move_rule
False

previous_move_san property

previous_move_san: str | None

Standard Algebraic Notation of the incoming move, or None at line start.

next_move_san property

next_move_san: str | None

Standard Algebraic Notation of the upcoming move, or None at line end.

previous_move_uci property

previous_move_uci: str | None

Universal Chess Interface notation of the incoming move, or None at start.

next_move_uci property

next_move_uci: str | None

Universal Chess Interface notation of the upcoming move, or None at end.

previous_move_nags property

previous_move_nags: tuple[Nag, ...] | None

NAG annotations attached to the incoming move, or None at line start.

next_move_nags property

next_move_nags: tuple[Nag, ...] | None

NAG annotations attached to the upcoming move, or None at line end.

comment property

comment: str | None

Commentary text attached to the incoming move, or None at line start.

preceding_comment property

preceding_comment: str | None

Introductory commentary preceding the first move, or None if not at start.

variation_count property

variation_count: int

Number of alternative variations branching from the upcoming move.

variation_depth property

variation_depth: int

Variation nesting depth (0 for mainline, 1 for sub-variation, etc.).

variation_index property

variation_index: int

Sibling index among alternative variations at the parent fork.

is_main_line property

is_main_line: bool

True if the cursor is positioned on the game's mainline.

is_variation_line property

is_variation_line: bool

True if the cursor is positioned on a sub-variation branch.

is_line_start property

is_line_start: bool

True if the cursor is at the beginning of the current line.

is_line_end property

is_line_end: bool

True if the cursor is at the terminal end of the current line.

position property

position: Position

Board state snapshot at this cursor position.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 *")
>>> cursor = game.create_cursor().next()
>>> cursor.position.side_to_move
'black'
>>> cursor.position.get_piece_at("e4")
'P'

Methods:

clone

clone() -> Cursor

Duplicate this cursor at its current position.

Returns:

Type Description
Cursor

A new Cursor pointing to the same node.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 *")
>>> cursor = game.create_cursor().next()
>>> cloned = cursor.clone()
>>> cloned.previous_move_san
'e4'
Source code in src/libscid/_cursor.py
def clone(self) -> Cursor:
    """Duplicate this cursor at its current position.

    Returns:
        A new [`Cursor`][libscid.Cursor] pointing to the same node.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 *")
        >>> cursor = game.create_cursor().next()
        >>> cloned = cursor.clone()
        >>> cloned.previous_move_san
        'e4'
    """
    return self._from_handle(
        self._native,
        self._game,
        self._native.game_clone_cursor(self._game._handle, self._handle),
    )

next

next() -> Cursor | None

Advance the cursor to the next move in the current line.

Returns:

Type Description
Cursor | None

A new Cursor advanced by one ply, or None if the cursor is already at the end of the line.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 *")
>>> cursor = game.create_cursor()
>>> cursor.next().previous_move_san
'e4'
>>> cursor.to_game_end().next() is None
True
Source code in src/libscid/_cursor.py
def next(self) -> Cursor | None:
    """Advance the cursor to the next move in the current line.

    Returns:
        A new [`Cursor`][libscid.Cursor] advanced by one ply, or None if
            the cursor is already at the end of the line.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 *")
        >>> cursor = game.create_cursor()
        >>> cursor.next().previous_move_san
        'e4'
        >>> cursor.to_game_end().next() is None
        True
    """
    return self._from_optional_handle(self._native.cursor_next(self._handle))

previous

previous() -> Cursor | None

Step the cursor backward to the preceding move in the current line.

Returns:

Type Description
Cursor | None

A new Cursor moved back by one ply, or None if the cursor is already at the start of the line.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 *")
>>> cursor = game.create_cursor().to_game_end()
>>> cursor.previous().previous_move_san
'e4'
>>> game.create_cursor().previous() is None
True
Source code in src/libscid/_cursor.py
def previous(self) -> Cursor | None:
    """Step the cursor backward to the preceding move in the current line.

    Returns:
        A new [`Cursor`][libscid.Cursor] moved back by one ply, or None if
            the cursor is already at the start of the line.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 *")
        >>> cursor = game.create_cursor().to_game_end()
        >>> cursor.previous().previous_move_san
        'e4'
        >>> game.create_cursor().previous() is None
        True
    """
    return self._from_optional_handle(self._native.cursor_previous(self._handle))

to_game_start

to_game_start() -> Cursor

Move the cursor directly to the beginning of the mainline.

Returns:

Type Description
Cursor

A new Cursor at the initial starting position of the game.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 *")
>>> cursor = game.create_cursor().to_game_end()
>>> cursor.to_game_start().is_line_start
True
Source code in src/libscid/_cursor.py
def to_game_start(self) -> Cursor:
    """Move the cursor directly to the beginning of the mainline.

    Returns:
        A new [`Cursor`][libscid.Cursor] at the initial starting position
            of the game.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 *")
        >>> cursor = game.create_cursor().to_game_end()
        >>> cursor.to_game_start().is_line_start
        True
    """
    return self._from_handle(
        self._native,
        self._game,
        self._native.cursor_to_game_start(self._handle),
    )

to_game_end

to_game_end() -> Cursor

Move the cursor directly to the terminal position of the mainline.

Returns:

Type Description
Cursor

A new Cursor at the end of the mainline.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 *")
>>> cursor = game.create_cursor()
>>> cursor.to_game_end().is_line_end
True
Source code in src/libscid/_cursor.py
def to_game_end(self) -> Cursor:
    """Move the cursor directly to the terminal position of the mainline.

    Returns:
        A new [`Cursor`][libscid.Cursor] at the end of the mainline.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 *")
        >>> cursor = game.create_cursor()
        >>> cursor.to_game_end().is_line_end
        True
    """
    return self._from_handle(
        self._native,
        self._game,
        self._native.cursor_to_game_end(self._handle),
    )

to_main_line_offset

to_main_line_offset(offset: int) -> Cursor | None

Move the cursor to a specific 0-based ply offset on the mainline.

Parameters:

Name Type Description Default
offset int

Zero-based ply offset from the start of the mainline.

required

Returns:

Type Description
Cursor | None

A new Cursor at the target ply offset, or None if the offset exceeds the mainline ply count.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 *")
>>> cursor = game.create_cursor()
>>> ply2 = cursor.to_main_line_offset(2)
>>> ply2.previous_move_san
'e5'
>>> cursor.to_main_line_offset(10) is None
True
Source code in src/libscid/_cursor.py
def to_main_line_offset(self, offset: int) -> Cursor | None:
    """Move the cursor to a specific 0-based ply offset on the mainline.

    Args:
        offset: Zero-based ply offset from the start of the mainline.

    Returns:
        A new [`Cursor`][libscid.Cursor] at the target ply offset, or None
            if the offset exceeds the mainline ply count.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 *")
        >>> cursor = game.create_cursor()
        >>> ply2 = cursor.to_main_line_offset(2)
        >>> ply2.previous_move_san
        'e5'
        >>> cursor.to_main_line_offset(10) is None
        True
    """
    return self._from_optional_handle(
        self._native.cursor_to_main_line_offset(self._handle, offset)
    )

enter_variation

enter_variation(index: int) -> Cursor | None

Descend into a child variation branching from the upcoming move.

Parameters:

Name Type Description Default
index int

Zero-based index of the variation branch to enter.

required

Returns:

Type Description
Cursor | None

A new Cursor at the start of the specified variation branch, or None if index is out of bounds or no variations exist.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
>>> cursor = game.create_cursor()
>>> var_cursor = cursor.enter_variation(0)
>>> var_cursor.next().previous_move_san
'd4'
>>> var_cursor.variation_depth
1
Source code in src/libscid/_cursor.py
def enter_variation(self, index: int) -> Cursor | None:
    """Descend into a child variation branching from the upcoming move.

    Args:
        index: Zero-based index of the variation branch to enter.

    Returns:
        A new [`Cursor`][libscid.Cursor] at the start of the specified
            variation branch, or None if `index` is out of bounds or no
            variations exist.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
        >>> cursor = game.create_cursor()
        >>> var_cursor = cursor.enter_variation(0)
        >>> var_cursor.next().previous_move_san
        'd4'
        >>> var_cursor.variation_depth
        1
    """
    return self._from_optional_handle(
        self._native.cursor_enter_variation(self._handle, index)
    )

exit_variation

exit_variation() -> Cursor | None

Ascend from the current variation back to its parent line.

Returns:

Type Description
Cursor | None

A new Cursor positioned on the parent line where this variation branched, or None if the cursor is already on the mainline.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
>>> var_cursor = game.create_cursor().enter_variation(0)
>>> parent = var_cursor.exit_variation()
>>> parent.variation_depth
0
Source code in src/libscid/_cursor.py
def exit_variation(self) -> Cursor | None:
    """Ascend from the current variation back to its parent line.

    Returns:
        A new [`Cursor`][libscid.Cursor] positioned on the parent line
            where this variation branched, or None if the cursor is already
            on the mainline.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
        >>> var_cursor = game.create_cursor().enter_variation(0)
        >>> parent = var_cursor.exit_variation()
        >>> parent.variation_depth
        0
    """
    return self._from_optional_handle(
        self._native.cursor_exit_variation(self._handle)
    )

append_move

append_move(san: str | bytes) -> Cursor

Append a move in Standard Algebraic Notation to the end of the line.

The cursor must currently be located at the end of the line (is_line_end must be True).

Parameters:

Name Type Description Default
san str | bytes

Standard Algebraic Notation move string (e.g. "e4", "Nf3").

required

Returns:

Type Description
Cursor

A new Cursor positioned after the newly appended move.

Raises:

Type Description
ValueError

If the cursor is not at the end of the line.

LibScidError

If the move is illegal or cannot be parsed.

Examples:

>>> import libscid
>>> game = libscid.Game()
>>> cursor = game.create_cursor()
>>> cursor = cursor.append_move("e4")
>>> cursor.previous_move_san
'e4'
>>> cursor = cursor.append_move("e5")
>>> cursor.previous_move_san
'e5'
Source code in src/libscid/_cursor.py
def append_move(self, san: str | bytes) -> Cursor:
    """Append a move in Standard Algebraic Notation to the end of the line.

    The cursor must currently be located at the end of the line
    ([`is_line_end`][libscid.Cursor.is_line_end] must be True).

    Args:
        san: Standard Algebraic Notation move string (e.g. "e4", "Nf3").

    Returns:
        A new [`Cursor`][libscid.Cursor] positioned after the newly appended
            move.

    Raises:
        ValueError: If the cursor is not at the end of the line.
        LibScidError: If the move is illegal or cannot be parsed.

    Examples:
        >>> import libscid
        >>> game = libscid.Game()
        >>> cursor = game.create_cursor()
        >>> cursor = cursor.append_move("e4")
        >>> cursor.previous_move_san
        'e4'
        >>> cursor = cursor.append_move("e5")
        >>> cursor.previous_move_san
        'e5'
    """
    self._require_line_end("append_move")
    return self._from_handle(
        self._native,
        self._game,
        self._native.cursor_append_move(self._game._handle, self._handle, san),
    )

append_game

append_game(source_game: Any) -> Cursor

Append all moves and variations from another game onto this line.

The cursor must currently be located at the end of the line (is_line_end must be True).

Parameters:

Name Type Description Default
source_game Any

The Game whose moves to append.

required

Returns:

Type Description
Cursor

A new Cursor positioned at the end of the appended moves.

Raises:

Type Description
ValueError

If the cursor is not at the end of the line.

LibScidError

If the positions are incompatible or appending fails.

Examples:

>>> import libscid
>>> game1 = libscid.Game.from_pgn("1. e4 e5 *")
>>> game2 = libscid.Game.from_pgn(
...     "2. Nf3 Nc6 *", position=game1.end_position
... )
>>> cursor = game1.create_cursor().to_game_end()
>>> cursor = cursor.append_game(game2)
>>> game1.mainline_move_count
4
Source code in src/libscid/_cursor.py
def append_game(self, source_game: Any) -> Cursor:
    """Append all moves and variations from another game onto this line.

    The cursor must currently be located at the end of the line
    ([`is_line_end`][libscid.Cursor.is_line_end] must be True).

    Args:
        source_game: The [`Game`][libscid.Game] whose moves to append.

    Returns:
        A new [`Cursor`][libscid.Cursor] positioned at the end of the
            appended moves.

    Raises:
        ValueError: If the cursor is not at the end of the line.
        LibScidError: If the positions are incompatible or appending fails.

    Examples:
        >>> import libscid
        >>> game1 = libscid.Game.from_pgn("1. e4 e5 *")
        >>> game2 = libscid.Game.from_pgn(
        ...     "2. Nf3 Nc6 *", position=game1.end_position
        ... )
        >>> cursor = game1.create_cursor().to_game_end()
        >>> cursor = cursor.append_game(game2)
        >>> game1.mainline_move_count
        4
    """
    self._require_line_end("append_game")
    return self._from_handle(
        self._native,
        self._game,
        self._native.cursor_append_game(
            self._game._handle, self._handle, source_game._handle
        ),
    )

add_variation

add_variation(
    preceding_comment: str | bytes = "",
) -> Cursor | None

Add a new variation branch departing from the upcoming move.

Parameters:

Name Type Description Default
preceding_comment str | bytes

Optional introductory commentary text to attach to the start of the new variation branch.

''

Returns:

Type Description
Cursor | None

A new Cursor at the start of the newly created variation, or None if the cursor is at the line end or the variation could not be created.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 *")
>>> cursor = game.create_cursor()
>>> var_cursor = cursor.add_variation("Alternative opening")
>>> var_cursor = var_cursor.append_move("d4")
>>> var_cursor.previous_move_san
'd4'
Source code in src/libscid/_cursor.py
def add_variation(self, preceding_comment: str | bytes = "") -> Cursor | None:
    """Add a new variation branch departing from the upcoming move.

    Args:
        preceding_comment: Optional introductory commentary text to attach
            to the start of the new variation branch.

    Returns:
        A new [`Cursor`][libscid.Cursor] at the start of the newly created
            variation, or None if the cursor is at the line end or the variation
            could not be created.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 *")
        >>> cursor = game.create_cursor()
        >>> var_cursor = cursor.add_variation("Alternative opening")
        >>> var_cursor = var_cursor.append_move("d4")
        >>> var_cursor.previous_move_san
        'd4'
    """
    return self._from_optional_handle(
        self._native.cursor_add_variation(
            self._game._handle, self._handle, preceding_comment
        )
    )

remove_variation

remove_variation() -> Cursor | None

Delete the current variation branch from the game tree.

Returns:

Type Description
Cursor | None

A new Cursor on the parent line where the variation branched, or None if the cursor is on the mainline.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
>>> var_cursor = game.create_cursor().enter_variation(0)
>>> parent = var_cursor.remove_variation()
>>> game.create_cursor().variation_count
0
Source code in src/libscid/_cursor.py
def remove_variation(self) -> Cursor | None:
    """Delete the current variation branch from the game tree.

    Returns:
        A new [`Cursor`][libscid.Cursor] on the parent line where the
            variation branched, or None if the cursor is on the mainline.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
        >>> var_cursor = game.create_cursor().enter_variation(0)
        >>> parent = var_cursor.remove_variation()
        >>> game.create_cursor().variation_count
        0
    """
    return self._from_optional_handle(
        self._native.cursor_remove_variation(self._game._handle, self._handle)
    )

promote_variation_to_first

promote_variation_to_first() -> Cursor | None

Promote the current variation to become the first alternative sibling.

Returns:

Type Description
Cursor | None

A new Cursor on the promoted variation, or None if the cursor is on the mainline.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 (1. d4) (1. c4) 1... e5 *")
>>> var2 = game.create_cursor().enter_variation(1)
>>> var2.next().previous_move_san
'c4'
>>> promoted = var2.promote_variation_to_first()
>>> game.create_cursor().enter_variation(0).next().previous_move_san
'c4'
Source code in src/libscid/_cursor.py
def promote_variation_to_first(self) -> Cursor | None:
    """Promote the current variation to become the first alternative sibling.

    Returns:
        A new [`Cursor`][libscid.Cursor] on the promoted variation, or None
            if the cursor is on the mainline.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 (1. d4) (1. c4) 1... e5 *")
        >>> var2 = game.create_cursor().enter_variation(1)
        >>> var2.next().previous_move_san
        'c4'
        >>> promoted = var2.promote_variation_to_first()
        >>> game.create_cursor().enter_variation(0).next().previous_move_san
        'c4'
    """
    return self._from_optional_handle(
        self._native.cursor_promote_variation_to_first(
            self._game._handle, self._handle
        )
    )

promote_variation_to_mainline

promote_variation_to_mainline() -> Cursor | None

Promote the current variation to become the new mainline of the game.

Swaps the current variation with the existing mainline from the branching point onward.

Returns:

Type Description
Cursor | None

A new Cursor on the new mainline, or None if the cursor is already on the mainline.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
>>> var = game.create_cursor().enter_variation(0)
>>> new_main = var.promote_variation_to_mainline()
>>> game.create_cursor().next().previous_move_san
'd4'
Source code in src/libscid/_cursor.py
def promote_variation_to_mainline(self) -> Cursor | None:
    """Promote the current variation to become the new mainline of the game.

    Swaps the current variation with the existing mainline from the branching
    point onward.

    Returns:
        A new [`Cursor`][libscid.Cursor] on the new mainline, or None if the
            cursor is already on the mainline.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
        >>> var = game.create_cursor().enter_variation(0)
        >>> new_main = var.promote_variation_to_mainline()
        >>> game.create_cursor().next().previous_move_san
        'd4'
    """
    return self._from_optional_handle(
        self._native.cursor_promote_variation_to_mainline(
            self._game._handle, self._handle
        )
    )

truncate

truncate() -> Cursor

Remove all subsequent moves in the current line from this point.

Returns:

Type Description
Cursor

A new Cursor at the newly truncated line end.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 *")
>>> cursor = game.create_cursor().to_main_line_offset(2)
>>> cursor.previous_move_san
'e5'
>>> truncated = cursor.truncate()
>>> game.mainline_move_count
2
Source code in src/libscid/_cursor.py
def truncate(self) -> Cursor:
    """Remove all subsequent moves in the current line from this point.

    Returns:
        A new [`Cursor`][libscid.Cursor] at the newly truncated line end.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 *")
        >>> cursor = game.create_cursor().to_main_line_offset(2)
        >>> cursor.previous_move_san
        'e5'
        >>> truncated = cursor.truncate()
        >>> game.mainline_move_count
        2
    """
    return self._from_handle(
        self._native,
        self._game,
        self._native.cursor_truncate(self._game._handle, self._handle),
    )

truncate_before

truncate_before() -> Cursor

Remove all preceding moves in the current line up to this point.

Returns:

Type Description
Cursor

A new Cursor at the beginning of the truncated line.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 *")
>>> cursor = game.create_cursor().to_main_line_offset(2)
>>> start = cursor.truncate_before()
>>> game.mainline_move_count
2
>>> start.next().previous_move_san
'Nf3'
Source code in src/libscid/_cursor.py
def truncate_before(self) -> Cursor:
    """Remove all preceding moves in the current line up to this point.

    Returns:
        A new [`Cursor`][libscid.Cursor] at the beginning of the truncated
            line.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 *")
        >>> cursor = game.create_cursor().to_main_line_offset(2)
        >>> start = cursor.truncate_before()
        >>> game.mainline_move_count
        2
        >>> start.next().previous_move_san
        'Nf3'
    """
    return self._from_handle(
        self._native,
        self._game,
        self._native.cursor_truncate_before(self._game._handle, self._handle),
    )

iter_movetext

iter_movetext(
    *, variations: bool = True
) -> Iterator[MovetextEvent]

Iterate over movetext events starting from this cursor position.

Parameters:

Name Type Description Default
variations bool

Whether to recursively traverse variation branches. Defaults to True.

True

Returns:

Type Description
Iterator[MovetextEvent]

An iterator yielding MovetextEvent instances.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
>>> moves = [
...     e.san for e in game.create_cursor().iter_movetext()
...     if isinstance(e, libscid.MovetextMove)
... ]
>>> moves
['e4', 'd4', 'd5', 'e5']
Source code in src/libscid/_cursor.py
def iter_movetext(self, *, variations: bool = True) -> Iterator[MovetextEvent]:
    """Iterate over movetext events starting from this cursor position.

    Args:
        variations: Whether to recursively traverse variation branches.
            Defaults to True.

    Returns:
        An iterator yielding [`MovetextEvent`][libscid.MovetextEvent]
            instances.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 (1. d4 d5) 1... e5 *")
        >>> moves = [
        ...     e.san for e in game.create_cursor().iter_movetext()
        ...     if isinstance(e, libscid.MovetextMove)
        ... ]
        >>> moves
        ['e4', 'd4', 'd5', 'e5']
    """
    from ._domain_support._movetext_iteration import iter_movetext

    return iter_movetext(self, variations=variations)

set_comment

set_comment(comment: str | bytes) -> None

Set commentary text at the current cursor node.

Parameters:

Name Type Description Default
comment str | bytes

Commentary text to attach.

required

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 *")
>>> cursor = game.create_cursor().next()
>>> cursor.set_comment("King Pawn opening")
>>> cursor.comment
'King Pawn opening'
Source code in src/libscid/_cursor.py
def set_comment(self, comment: str | bytes) -> None:
    """Set commentary text at the current cursor node.

    Args:
        comment: Commentary text to attach.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 *")
        >>> cursor = game.create_cursor().next()
        >>> cursor.set_comment("King Pawn opening")
        >>> cursor.comment
        'King Pawn opening'
    """
    self._native.cursor_set_comment(self._game._handle, self._handle, comment)

remove_comment

remove_comment() -> None

Clear commentary text at the current cursor node.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 {King pawn} e5 *")
>>> cursor = game.create_cursor().next()
>>> cursor.remove_comment()
>>> cursor.comment == ""
True
Source code in src/libscid/_cursor.py
def remove_comment(self) -> None:
    """Clear commentary text at the current cursor node.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 {King pawn} e5 *")
        >>> cursor = game.create_cursor().next()
        >>> cursor.remove_comment()
        >>> cursor.comment == ""
        True
    """
    self.set_comment("")

add_nag

add_nag(nag: Nag) -> bool

Attach a Numeric Annotation Glyph to the incoming move.

Parameters:

Name Type Description Default
nag Nag

The Nag glyph to add.

required

Returns:

Type Description
bool

True if the NAG was attached successfully; otherwise False.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 *")
>>> cursor = game.create_cursor().next()
>>> cursor.add_nag(libscid.Nag("!"))
True
>>> [nag.symbol for nag in cursor.previous_move_nags]
['!']
Source code in src/libscid/_cursor.py
def add_nag(self, nag: Nag) -> bool:
    """Attach a Numeric Annotation Glyph to the incoming move.

    Args:
        nag: The [`Nag`][libscid.Nag] glyph to add.

    Returns:
        True if the NAG was attached successfully; otherwise False.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 *")
        >>> cursor = game.create_cursor().next()
        >>> cursor.add_nag(libscid.Nag("!"))
        True
        >>> [nag.symbol for nag in cursor.previous_move_nags]
        ['!']
    """
    return self._native.cursor_add_nag(self._game._handle, self._handle, nag.code)

remove_move_nag

remove_move_nag() -> bool

Remove move evaluation NAGs ($1-$9) from the incoming move.

Returns:

Type Description
bool

True if a move NAG was found and removed; otherwise False.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 $1 $14 e5 *")
>>> cursor = game.create_cursor().next()
>>> cursor.remove_move_nag()
True
>>> [nag.text for nag in cursor.previous_move_nags]
['$14']
Source code in src/libscid/_cursor.py
def remove_move_nag(self) -> bool:
    """Remove move evaluation NAGs ($1-$9) from the incoming move.

    Returns:
        True if a move NAG was found and removed; otherwise False.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 $1 $14 e5 *")
        >>> cursor = game.create_cursor().next()
        >>> cursor.remove_move_nag()
        True
        >>> [nag.text for nag in cursor.previous_move_nags]
        ['$14']
    """
    return self._native.cursor_remove_nag(
        self._game._handle, self._handle, move_nag=True
    )

remove_position_nag

remove_position_nag() -> bool

Remove positional evaluation NAGs ($10-$255) from the incoming move.

Returns:

Type Description
bool

True if a positional NAG was found and removed; otherwise False.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 $1 $14 e5 *")
>>> cursor = game.create_cursor().next()
>>> cursor.remove_position_nag()
True
>>> [nag.text for nag in cursor.previous_move_nags]
['$1']
Source code in src/libscid/_cursor.py
def remove_position_nag(self) -> bool:
    """Remove positional evaluation NAGs ($10-$255) from the incoming move.

    Returns:
        True if a positional NAG was found and removed; otherwise False.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 $1 $14 e5 *")
        >>> cursor = game.create_cursor().next()
        >>> cursor.remove_position_nag()
        True
        >>> [nag.text for nag in cursor.previous_move_nags]
        ['$1']
    """
    return self._native.cursor_remove_nag(
        self._game._handle, self._handle, move_nag=False
    )

remove_nags

remove_nags() -> None

Remove all Numeric Annotation Glyphs from the incoming move.

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 $1 $14 e5 *")
>>> cursor = game.create_cursor().next()
>>> cursor.remove_nags()
>>> cursor.previous_move_nags
()
Source code in src/libscid/_cursor.py
def remove_nags(self) -> None:
    """Remove all Numeric Annotation Glyphs from the incoming move.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 $1 $14 e5 *")
        >>> cursor = game.create_cursor().next()
        >>> cursor.remove_nags()
        >>> cursor.previous_move_nags
        ()
    """
    self._native.cursor_remove_nags(self._game._handle, self._handle)

Position

libscid.Position

Position()

Represents a chess board state and rule validation context.

Encapsulates piece placement across all 64 squares, active side to move, castling availability rights, en passant target square, halfmove clock (fifty-move rule), and fullmove counter. Provides methods to query legal moves, evaluate check, checkmate, and stalemate states, calculate move metadata flags, and execute moves in-place.

Examples:

>>> import libscid
>>> fen = (
...     "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
... )
>>> pos = libscid.Position.from_fen(fen)
>>> pos.side_to_move
'black'
>>> pos.is_check
False
>>> pos.get_piece_at("e4")
'P'
>>> pos.apply_san("e5")
>>> pos.side_to_move
'white'

Disallows direct construction.

Raises:

Type Description
TypeError

Position objects cannot be instantiated directly and must be obtained via factory classmethods or domain handles.

Source code in src/libscid/_position.py
def __init__(self):
    """Disallows direct construction.

    Raises:
        TypeError: Position objects cannot be instantiated directly and
            must be obtained via factory classmethods or domain handles.
    """
    raise TypeError("Position objects are returned by libscid APIs")

Attributes

fen property

fen: str

Full 6-field Forsyth–Edwards Notation (FEN) string of the board state.

Returns:

Type Description
str

The complete FEN string including piece placement, side to move, castling availability, en passant target square, halfmove clock, and fullmove number.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
... )
>>> pos.fen
'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1'

side_to_move property

side_to_move: Literal['white', 'black']

The active player colour whose turn it is to move.

Returns:

Type Description
Literal['white', 'black']

"white" if White is to move, or "black" if Black is to move.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
... )
>>> pos.side_to_move
'white'

fullmove_number property

fullmove_number: int

The 1-based fullmove counter.

Starts at 1 and increments after each move made by Black.

Returns:

Type Description
int

The positive integer fullmove number.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
... )
>>> pos.fullmove_number
1

halfmove_clock property

halfmove_clock: int

The halfmove clock (ply count) for the fifty-move draw rule.

Counts the number of halfmoves since the last pawn advance or piece capture.

Returns:

Type Description
int

The non-negative integer halfmove clock.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/8/5N2/PPPPPPPP/RNBQKB1R b KQkq - 1 1"
... )
>>> pos.halfmove_clock
1

is_check property

is_check: bool

Whether the king of the player to move is currently in check.

Returns:

Type Description
bool

True if the active player is in check; False otherwise.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen("4k3/8/8/8/8/8/4R3/4K3 b - - 0 1")
>>> pos.is_check
True

is_checkmate property

is_checkmate: bool

Whether the position is in checkmate.

A position is checkmate when the active player's king is in check and has no legal moves.

Returns:

Type Description
bool

True if the active player is checkmated; False otherwise.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen("R3k3/8/4K3/8/8/8/8/8 b - - 0 1")
>>> pos.is_checkmate
True

is_stalemate property

is_stalemate: bool

Whether the position is in stalemate.

A position is stalemate when the active player is not in check and has no legal moves available.

Returns:

Type Description
bool

True if the position is stalemated; False otherwise.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen("k7/2Q5/1K6/8/8/8/8/8 b - - 0 1")
>>> pos.is_stalemate
True

legal_moves property

legal_moves: tuple[str, ...]

All strictly legal moves available in the current position.

Returns:

Type Description
tuple[str, ...]

Tuple of legal moves formatted as coordinate UCI strings (e.g. ("e2e4", "g1f3")).

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
... )
>>> len(pos.legal_moves)
20
>>> "e2e4" in pos.legal_moves
True

Methods:

from_fen classmethod

from_fen(fen: str | bytes) -> Position

Creates a new board position initialised from a FEN string.

Parameters:

Name Type Description Default
fen str | bytes

Forsyth–Edwards Notation (FEN) string or UTF-8 encoded bytes.

required

Returns:

Type Description
Position

A new Position instance initialised to the board state specified by fen.

Raises:

Type Description
LibScidError

If fen is malformed or describes an illegal board setup.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
... )
>>> pos.side_to_move
'black'
>>> pos.get_piece_at("e4")
'P'
Source code in src/libscid/_position.py
@classmethod
def from_fen(cls, fen: str | bytes) -> Position:
    """Creates a new board position initialised from a FEN string.

    Args:
        fen: Forsyth–Edwards Notation (FEN) string or UTF-8 encoded bytes.

    Returns:
        A new `Position` instance initialised to the board state specified by `fen`.

    Raises:
        LibScidError: If `fen` is malformed or describes an illegal board setup.

    Examples:
        >>> import libscid
        >>> pos = libscid.Position.from_fen(
        ...     "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
        ... )
        >>> pos.side_to_move
        'black'
        >>> pos.get_piece_at("e4")
        'P'
    """
    native = load_library()
    return cls._from_handle(native, native.create_position_from_fen(fen))

get_piece_at

get_piece_at(square: str | bytes) -> str | None

Retrieves the piece residing on a specified board square.

Parameters:

Name Type Description Default
square str | bytes

Algebraic coordinate string or UTF-8 bytes (e.g. "e4", "a1", b"h8").

required

Returns:

Type Description
str | None

Single-character piece letter ("K", "Q", "R", "B", "N", "P" for White; "k", "q", "r", "b", "n", "p" for Black), or None if the square is empty.

Raises:

Type Description
LibScidError

If square is not a valid coordinate string.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
... )
>>> pos.get_piece_at("e1")
'K'
>>> pos.get_piece_at("e8")
'k'
>>> pos.get_piece_at("e4") is None
True
Source code in src/libscid/_position.py
def get_piece_at(self, square: str | bytes) -> str | None:
    """Retrieves the piece residing on a specified board square.

    Args:
        square: Algebraic coordinate string or UTF-8 bytes (e.g. `"e4"`,
            `"a1"`, `b"h8"`).

    Returns:
        Single-character piece letter (`"K"`, `"Q"`, `"R"`, `"B"`, `"N"`, `"P"`
            for White; `"k"`, `"q"`, `"r"`, `"b"`, `"n"`, `"p"` for Black), or
            `None` if the square is empty.

    Raises:
        LibScidError: If `square` is not a valid coordinate string.

    Examples:
        >>> import libscid
        >>> pos = libscid.Position.from_fen(
        ...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
        ... )
        >>> pos.get_piece_at("e1")
        'K'
        >>> pos.get_piece_at("e8")
        'k'
        >>> pos.get_piece_at("e4") is None
        True
    """
    return self._native.position_piece_at(self._handle, square)

get_move_metadata

get_move_metadata(move: str | bytes) -> MoveMetadata

Calculates structural and rule characteristics for a move.

Evaluates check, checkmate, castling, and pawn promotion properties for the given move text against the current position.

Parameters:

Name Type Description Default
move str | bytes

Standard Algebraic Notation (SAN) or coordinate UCI move string or bytes (e.g. "Nf3", "e2e4", "b7b8q").

required

Returns:

Type Description
MoveMetadata

A MoveMetadata bitmask flag containing applicable attributes.

Raises:

Type Description
LibScidError

If move is illegal or ambiguous in the current position.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "r1bqkb1r/pppp1ppp/2n5/4p3/2B1n3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 5"
... )
>>> flags = pos.get_move_metadata("O-O")
>>> bool(flags & libscid.MoveMetadata.CASTLING)
True
>>> mate_pos = libscid.Position.from_fen(
...     "r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR"
...     " w KQkq - 4 4"
... )
>>> mate_flags = mate_pos.get_move_metadata("Qxf7#")
>>> bool(mate_flags & libscid.MoveMetadata.CHECKMATE)
True
Source code in src/libscid/_position.py
def get_move_metadata(self, move: str | bytes) -> MoveMetadata:
    """Calculates structural and rule characteristics for a move.

    Evaluates check, checkmate, castling, and pawn promotion properties
    for the given move text against the current position.

    Args:
        move: Standard Algebraic Notation (SAN) or coordinate UCI move string
            or bytes (e.g. `"Nf3"`, `"e2e4"`, `"b7b8q"`).

    Returns:
        A `MoveMetadata` bitmask flag containing applicable attributes.

    Raises:
        LibScidError: If `move` is illegal or ambiguous in the current position.

    Examples:
        >>> import libscid
        >>> pos = libscid.Position.from_fen(
        ...     "r1bqkb1r/pppp1ppp/2n5/4p3/2B1n3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 5"
        ... )
        >>> flags = pos.get_move_metadata("O-O")
        >>> bool(flags & libscid.MoveMetadata.CASTLING)
        True
        >>> mate_pos = libscid.Position.from_fen(
        ...     "r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR"
        ...     " w KQkq - 4 4"
        ... )
        >>> mate_flags = mate_pos.get_move_metadata("Qxf7#")
        >>> bool(mate_flags & libscid.MoveMetadata.CHECKMATE)
        True
    """
    movespec, san = self._native.position_move_metadata(self._handle, move)
    metadata = MoveMetadata.NONE
    if san.endswith("+") or san.endswith("#"):
        metadata |= MoveMetadata.CHECK
    if san.endswith("#"):
        metadata |= MoveMetadata.CHECKMATE
    if movespec.is_castling:
        metadata |= MoveMetadata.CASTLING
    if movespec.promotion != 0:
        metadata |= MoveMetadata.PROMOTION
    return metadata

to_san

to_san(move: str | bytes) -> str

Converts and normalises a move string to canonical SAN against this position.

Does not mutate the board state. Accepts standard SAN, permissive move notations, or coordinate UCI strings.

Parameters:

Name Type Description Default
move str | bytes

Move notation string or bytes to normalise (e.g. "e2e4", "OO", "b8Q").

required

Returns:

Type Description
str

Canonical Standard Algebraic Notation string (e.g. "e4", "O-O", "b8=Q+").

Raises:

Type Description
LibScidError

If move is illegal or ambiguous in the current position.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
... )
>>> pos.to_san("e2e4")
'e4'
>>> pos.to_san("g1f3")
'Nf3'
Source code in src/libscid/_position.py
def to_san(self, move: str | bytes) -> str:
    """Converts and normalises a move string to canonical SAN against this position.

    Does not mutate the board state. Accepts standard SAN, permissive move
    notations, or coordinate UCI strings.

    Args:
        move: Move notation string or bytes to normalise (e.g. `"e2e4"`,
            `"OO"`, `"b8Q"`).

    Returns:
        Canonical Standard Algebraic Notation string (e.g. `"e4"`, `"O-O"`,
            `"b8=Q+"`).

    Raises:
        LibScidError: If `move` is illegal or ambiguous in the current position.

    Examples:
        >>> import libscid
        >>> pos = libscid.Position.from_fen(
        ...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
        ... )
        >>> pos.to_san("e2e4")
        'e4'
        >>> pos.to_san("g1f3")
        'Nf3'
    """
    return self._native.position_to_san(self._handle, move)

apply_san

apply_san(san: str | bytes) -> None

Applies a Standard Algebraic Notation (SAN) move in-place to this position.

Updates piece positions, active colour, castling rights, en passant state, halfmove clock, and fullmove number.

Parameters:

Name Type Description Default
san str | bytes

Standard Algebraic Notation move string or bytes (e.g. "e4", "Nf3", "O-O").

required

Raises:

Type Description
LibScidError

If san is illegal or ambiguous in the current position.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
... )
>>> pos.apply_san("e4")
>>> pos.get_piece_at("e4")
'P'
>>> pos.side_to_move
'black'
Source code in src/libscid/_position.py
def apply_san(self, san: str | bytes) -> None:
    """Applies a Standard Algebraic Notation (SAN) move in-place to this position.

    Updates piece positions, active colour, castling rights, en passant
    state, halfmove clock, and fullmove number.

    Args:
        san: Standard Algebraic Notation move string or bytes (e.g. `"e4"`,
            `"Nf3"`, `"O-O"`).

    Raises:
        LibScidError: If `san` is illegal or ambiguous in the current position.

    Examples:
        >>> import libscid
        >>> pos = libscid.Position.from_fen(
        ...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
        ... )
        >>> pos.apply_san("e4")
        >>> pos.get_piece_at("e4")
        'P'
        >>> pos.side_to_move
        'black'
    """
    self._native.position_apply_san(self._handle, san)

apply_uci

apply_uci(uci: str | bytes) -> None

Applies a coordinate UCI move in-place to this position.

Updates piece positions, active colour, castling rights, en passant state, halfmove clock, and fullmove number.

Parameters:

Name Type Description Default
uci str | bytes

Coordinate UCI move string or bytes (e.g. "e2e4", "a7a8q").

required

Raises:

Type Description
LibScidError

If uci is illegal in the current position.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
... )
>>> pos.apply_uci("e2e4")
>>> pos.get_piece_at("e4")
'P'
>>> pos.side_to_move
'black'
Source code in src/libscid/_position.py
def apply_uci(self, uci: str | bytes) -> None:
    """Applies a coordinate UCI move in-place to this position.

    Updates piece positions, active colour, castling rights, en passant
    state, halfmove clock, and fullmove number.

    Args:
        uci: Coordinate UCI move string or bytes (e.g. `"e2e4"`, `"a7a8q"`).

    Raises:
        LibScidError: If `uci` is illegal in the current position.

    Examples:
        >>> import libscid
        >>> pos = libscid.Position.from_fen(
        ...     "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
        ... )
        >>> pos.apply_uci("e2e4")
        >>> pos.get_piece_at("e4")
        'P'
        >>> pos.side_to_move
        'black'
    """
    self._native.position_apply_uci(self._handle, uci)

Arbiter

libscid.Arbiter

Arbiter(cursor: Cursor)

Evaluates tournament rules and draw claim conditions for a cursor position.

An Arbiter instance is typically accessed via the Cursor.arbiter property. It evaluates claimable draw conditions defined by the FIDE Laws of Chess at the cursor's current position, including the fifty-move rule and threefold repetition.

Examples:

>>> import libscid
>>> pgn = "1. Nf3 Nf6 2. Ng1 Ng8 3. Nf3 Nf6 4. Ng1 Ng8 *"
>>> cursor = libscid.Game.from_pgn(pgn).create_cursor().to_game_end()
>>> cursor.arbiter.can_claim_threefold_repetition
True
>>> cursor.arbiter.can_claim_fifty_move_rule
False

Initialise an arbiter bound to a specific cursor position.

Parameters:

Name Type Description Default
cursor Cursor

The cursor navigating the game tree to inspect.

required

Examples:

>>> import libscid
>>> game = libscid.Game.from_pgn("1. e4 e5 *")
>>> cursor = game.create_cursor().to_game_end()
>>> arbiter = cursor.arbiter
>>> arbiter.can_claim_fifty_move_rule
False
Source code in src/libscid/_arbiter.py
def __init__(self, cursor: Cursor):
    """Initialise an arbiter bound to a specific cursor position.

    Args:
        cursor: The cursor navigating the game tree to inspect.

    Examples:
        >>> import libscid
        >>> game = libscid.Game.from_pgn("1. e4 e5 *")
        >>> cursor = game.create_cursor().to_game_end()
        >>> arbiter = cursor.arbiter
        >>> arbiter.can_claim_fifty_move_rule
        False
    """
    self._cursor = cursor

Attributes

can_claim_fifty_move_rule property

can_claim_fifty_move_rule: bool

Check whether a draw can be claimed under the fifty-move rule.

According to the FIDE Laws of Chess, a player may claim a draw if the last 50 consecutive full moves (100 halfmoves / ply) have been completed by each player without any piece capture and without any pawn advance.

Returns:

Type Description
bool

True if the halfmove clock is at least 100; otherwise False.

Examples:

>>> import libscid
>>> pos = libscid.Position.from_fen(
...     "8/8/8/8/8/4k3/8/4K3 w - - 100 51"
... )
>>> game = libscid.Game(position=pos)
>>> game.create_cursor().arbiter.can_claim_fifty_move_rule
True
>>> normal_game = libscid.Game.from_pgn("1. e4 e5 *")
>>> normal_game.create_cursor().arbiter.can_claim_fifty_move_rule
False

can_claim_threefold_repetition property

can_claim_threefold_repetition: bool

Check whether a draw can be claimed under the threefold repetition rule.

According to the FIDE Laws of Chess, a player may claim a draw if the exact same board position has occurred at least three times along the path from the game start to this cursor node. Two positions are identical if the side to move, piece placements, castling rights, and en passant target squares are identical.

Returns:

Type Description
bool

True if the current board state has occurred 3 or more times along the line of play; otherwise False.

Examples:

>>> import libscid
>>> pgn = "1. Nf3 Nf6 2. Ng1 Ng8 3. Nf3 Nf6 4. Ng1 Ng8 *"
>>> cursor = libscid.Game.from_pgn(pgn).create_cursor().to_game_end()
>>> cursor.arbiter.can_claim_threefold_repetition
True
>>> non_rep = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 *")
>>> non_rep.create_cursor().arbiter.can_claim_threefold_repetition
False