Skip to content

How to Navigate and Edit Move Trees

This guide demonstrates how to traverse chess move trees, create and prune variations, and attach commentary or Numeric Annotation Glyphs (NAGs) using immutable cursors.


1. Navigating Mainline Moves

A Cursor points to a specific node within a Game tree. Navigation methods return new cursor instances:

import libscid

game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 *")

# Initialise cursor at root:
cursor = game.create_cursor()
assert cursor.is_line_start

# Step forward along the mainline:
c1 = cursor.next()
print(f"Move 1: {c1.previous_move_san}")  # 'e4'

c2 = c1.next()
print(f"Move 2: {c2.previous_move_san}")  # 'e5'

# Step backward:
c_back = c2.previous()
assert c_back.previous_move_san == "e4"

# Fast-forward to the end of the line:
c_end = cursor.to_game_end()
assert c_end.is_line_end

2. Appending Moves Programmatically

To build a game programmatically, append moves in Standard Algebraic Notation (SAN):

import libscid

game = libscid.Game()
cursor = game.create_cursor()

# Chain moves sequentially:
cursor = cursor.append_move("d4")
cursor = cursor.append_move("Nf6")
cursor = cursor.append_move("c4")
cursor = cursor.append_move("e6")

print(game.to_pgn())

3. Creating and Entering Variations

When alternative moves are explored, branch into sub-lines using add_variation:

import libscid

game = libscid.Game.from_pgn("1. e4 e5 2. Nf3 Nc6 *")

# Rewind to after 1. e4:
c_after_e4 = game.create_cursor().next()

# Add Sicilian variation 1... c5 branching off after 1. e4:
var_cursor = c_after_e4.add_variation("c5")
var_cursor = var_cursor.append_move("Nf3")
var_cursor = var_cursor.append_move("d6")

# Inspect variation metadata:
print(f"Variation depth: {var_cursor.variation_depth}")      # 1
print(f"Is variation line: {var_cursor.is_variation_line}")  # True

# Return to parent mainline:
parent_cursor = var_cursor.exit_variation()
print(f"Parent move: {parent_cursor.previous_move_san}")     # 'e5'

4. Traversing Existing Variations

When inspecting a game containing branching lines, use variation_count and enter_variation:

import libscid

game = libscid.Game.from_pgn("1. e4 e5 (1... c5 2. Nf3) (1... e6 2. d4) 2. Nf3 *")
cursor = game.create_cursor().next()  # Position after 1. e4

print(f"Available variations: {cursor.variation_count}")  # 2

# Enter first variation (1... c5):
var0 = cursor.enter_variation(0)
print(f"Branch 0 first move: {var0.previous_move_san}")   # 'c5'

# Enter second variation (1... e6):
var1 = cursor.enter_variation(1)
print(f"Branch 1 first move: {var1.previous_move_san}")   # 'e6'

5. Attaching Commentary and NAG Annotations

Decorate moves with textual commentary or standard evaluation symbols:

import libscid

game = libscid.Game()
cursor = game.create_cursor()

# Play move and attach comments:
cursor = cursor.append_move("e4")
cursor.comment = "The open game."
cursor.preceding_comment = "Round 1 opening preparation."

# Attach Numeric Annotation Glyphs (NAGs):
cursor.add_nag(libscid.Nag.GOOD_MOVE)  # $1 (!)

# Read back annotations:
print(f"Comment:           {cursor.comment}")
print(f"Preceding comment: {cursor.preceding_comment}")
print(f"Assigned NAGs:     {cursor.previous_move_nags}")

# Output PGN with annotations:
print(game.to_pgn())

6. Pruning and Promoting Variations

Adjust tree structures using variation manipulation methods:

# Promote a variation to become the primary mainline line:
var_cursor.promote_variation()

# Delete a variation line completely:
c_after_e4.delete_variation(0)