How to Navigate Variation Trees with Cursors
This guide demonstrates how to traverse branching game trees using scid_game_cursor handles in the libscid C ABI, stepping forward and backward through plies, entering sub-variations, and inspecting moves, comments, and NAGs at each position.
1. Overview of Symbols
scid_game_cursor_create: Spawns a cursor at the root position of a game.scid_game_cursor_next: Steps forward along the active variation, producing a new cursor pointing to the next move.scid_game_cursor_previous: Steps backward toward the root position.scid_game_cursor_variation_count_get: Returns the number of sub-variations diverging from the current position.scid_game_cursor_variation_enter: Enters a specified sub-variation at a given 0-based index.scid_game_cursor_variation_leave: Ascends out of the current sub-variation back to the parent variation.scid_game_cursor_next_move_san_get: Inspects the Standard Algebraic Notation (SAN) string of the upcoming move without stepping forward.scid_game_cursor_next_movespec_get: Retrieves the low-levelscid_movespecdescriptor of the upcoming move.
2. Complete Recipe
#include <scid/scid.h>
#include <stdio.h>
#include <string.h>
static int
check(
scid_error error,
const char* call)
{
if (error == SCID_OK)
{
return 1;
}
fprintf(stderr, "%s failed with scid_error %hu\n", call, error);
return 0;
}
static int
take_cursor(
scid_game_cursor** cursor,
scid_game_cursor** next_cursor)
{
if (next_cursor == NULL || *next_cursor == NULL)
{
return 0;
}
scid_game_cursor_free(*cursor);
*cursor = *next_cursor;
*next_cursor = NULL;
return 1;
}
static int
read_text(
scid_error error,
const char* call,
const char* label,
const char* text,
size_t text_size)
{
if (!check(error, call))
{
return 0;
}
printf("%s%.*s\n", label, (int)text_size, text);
return 1;
}
static int
text_equals(
const char* text,
size_t text_size,
const char* expected)
{
return text_size == strlen(expected) && strncmp(text, expected, text_size) == 0;
}
static int
print_next_move(scid_game_cursor* cursor)
{
char san[64];
char uci[16];
char comment[256];
scid_movespec move;
scid_nag nag = 0;
size_t san_size = 0;
size_t uci_size = 0;
size_t comment_size = 0;
size_t nag_count = 0;
size_t variation_count = 0;
size_t ply = 0;
if (!check(scid_game_cursor_ply_get(cursor, &ply), "scid_game_cursor_ply_get") ||
!check(
scid_game_cursor_next_movespec_get(cursor, &move),
"scid_game_cursor_next_movespec_get") ||
!read_text(
scid_movespec_to_uci(move, uci, sizeof(uci), &uci_size), "scid_movespec_to_uci",
"next uci: ", uci, uci_size) ||
!read_text(
scid_game_cursor_next_move_san_get(cursor, san, sizeof(san), &san_size),
"scid_game_cursor_next_move_san_get", "next san: ", san, san_size) ||
!read_text(
scid_game_cursor_next_move_comment_get(cursor, comment, sizeof(comment), &comment_size),
"scid_game_cursor_next_move_comment_get", "next comment: ", comment, comment_size) ||
!check(
scid_game_cursor_next_move_nag_count_get(cursor, &nag_count),
"scid_game_cursor_next_move_nag_count_get") ||
!check(
scid_game_cursor_variation_count_get(cursor, &variation_count),
"scid_game_cursor_variation_count_get"))
{
return 0;
}
if (!text_equals(san, san_size, "e4") || !text_equals(uci, uci_size, "e2e4") ||
!text_equals(comment, comment_size, "King pawn") || ply != 0 || nag_count != 1 ||
variation_count != 1)
{
return 0;
}
printf("ply before move: %zu\n", ply);
printf("next nag count: %zu\n", nag_count);
printf("next variation count: %zu\n", variation_count);
if (nag_count > 0)
{
if (!check(
scid_game_cursor_next_move_nag_at_get(cursor, 0, &nag),
"scid_game_cursor_next_move_nag_at_get"))
{
return 0;
}
printf("first next nag: %u\n", (unsigned)nag);
}
return 1;
}
int
main(void)
{
const char* start_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
const char* pgn = "[Event \"Navigation\"]\n"
"[Result \"*\"]\n"
"\n"
"{Before game} 1. e4 $1 {King pawn} "
"({Queen pawn line} 1. d4 {Queen pawn} d5) e5 2. Nf3 Nc6 *\n";
scid_game* game = NULL;
scid_game_cursor* cursor = NULL;
scid_game_cursor* next_cursor = NULL;
scid_position* position = NULL;
char diagnostic[1024];
char text[256];
scid_movespec move;
int moved = 0;
size_t diagnostic_size = 0;
size_t text_size = 0;
size_t depth = 0;
size_t variation_count = 0;
if (!check(
scid_position_create_from_fen(start_fen, &position), "scid_position_create_from_fen") ||
!check(
scid_game_create(
position, pgn, strlen(pgn), &game, diagnostic, sizeof(diagnostic),
&diagnostic_size),
"scid_game_create") ||
!check(scid_game_cursor_create(game, &cursor), "scid_game_cursor_create"))
{
fprintf(stderr, "%.*s\n", (int)diagnostic_size, diagnostic);
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 1;
}
if (!read_text(
scid_game_cursor_comment_get(cursor, text, sizeof(text), &text_size),
"scid_game_cursor_comment_get", "initial comment: ", text, text_size) ||
!text_equals(text, text_size, "Before game") || !print_next_move(cursor) ||
!check(
scid_game_cursor_variation_count_get(cursor, &variation_count),
"scid_game_cursor_variation_count_get") ||
variation_count != 1)
{
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 1;
}
if (!check(
scid_game_cursor_variation_enter(cursor, 0, &moved, &next_cursor),
"scid_game_cursor_variation_enter") ||
!moved || !take_cursor(&cursor, &next_cursor) ||
!check(
scid_game_cursor_variation_depth_get(cursor, &depth),
"scid_game_cursor_variation_depth_get") ||
depth != 1 ||
!read_text(
scid_game_cursor_comment_get(cursor, text, sizeof(text), &text_size),
"scid_game_cursor_comment_get", "variation comment: ", text, text_size) ||
!text_equals(text, text_size, "Queen pawn line") ||
!read_text(
scid_game_cursor_next_move_san_get(cursor, text, sizeof(text), &text_size),
"scid_game_cursor_next_move_san_get", "variation first move: ", text, text_size) ||
!text_equals(text, text_size, "d4"))
{
scid_game_cursor_free(next_cursor);
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 1;
}
next_cursor = NULL;
if (!check(
scid_game_cursor_variation_exit(cursor, &moved, &next_cursor),
"scid_game_cursor_variation_exit") ||
!moved || !take_cursor(&cursor, &next_cursor))
{
scid_game_cursor_free(next_cursor);
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 1;
}
next_cursor = NULL;
if (!check(scid_game_cursor_next(cursor, &moved, &next_cursor), "scid_game_cursor_next") ||
!moved || !take_cursor(&cursor, &next_cursor) ||
!read_text(
scid_game_cursor_previous_move_san_get(cursor, text, sizeof(text), &text_size),
"scid_game_cursor_previous_move_san_get", "previous san: ", text, text_size) ||
!text_equals(text, text_size, "e4") ||
!check(
scid_game_cursor_previous_movespec_get(cursor, &move),
"scid_game_cursor_previous_movespec_get") ||
!read_text(
scid_movespec_to_uci(move, text, sizeof(text), &text_size), "scid_movespec_to_uci",
"previous uci: ", text, text_size) ||
!text_equals(text, text_size, "e2e4") ||
!read_text(
scid_game_cursor_next_move_san_get(cursor, text, sizeof(text), &text_size),
"scid_game_cursor_next_move_san_get", "next san after e4: ", text, text_size) ||
!text_equals(text, text_size, "e5") ||
!check(
scid_game_cursor_next_movespec_get(cursor, &move),
"scid_game_cursor_next_movespec_get") ||
!read_text(
scid_movespec_to_uci(move, text, sizeof(text), &text_size), "scid_movespec_to_uci",
"next uci after e4: ", text, text_size) ||
!text_equals(text, text_size, "e7e5"))
{
scid_game_cursor_free(next_cursor);
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 1;
}
next_cursor = NULL;
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 0;
}
3. Key Concepts and Patterns
Cursor Immutability and Variation Traversal
Navigation cursors in libscid never mutate internal state in place. Every navigational call (scid_game_cursor_next, scid_game_cursor_variation_enter, etc.) accepts an output pointer scid_game_cursor** out_cursor to return a newly allocated cursor:
scid_game_cursor* cursor = NULL;
scid_game_cursor_create(game, &cursor);
/* Inspect variations available at current position */
size_t variation_count = 0;
scid_game_cursor_variation_count_get(cursor, &variation_count);
if (variation_count > 0)
{
scid_game_cursor* var_cursor = NULL;
/* Enter variation 0 */
scid_game_cursor_variation_enter(cursor, 0, &var_cursor);
/* ... navigate within variation ... */
scid_game_cursor_free(var_cursor);
}
scid_game_cursor_free(cursor);