How to Mutate and Merge Move Trees
This guide explains how to structurally alter chess game trees using the libscid C ABI: truncating variations at a specific ply, deleting unwanted sub-variations, and merging moves from an external source game into an existing target game.
1. Overview of Symbols
scid_game_cursor_truncate: Removes all moves following the cursor along the current variation, cutting the tree short at the active ply.scid_game_cursor_variation_delete: Deletes a specific sub-variation branching from the current position.scid_game_merge_moves: Merges moves, variations, comments, and NAGs from a source game into a target game starting at the active cursor position.
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
contains(
const char* text,
const char* needle)
{
return strstr(text, needle) != NULL;
}
int
main(void)
{
const char* start_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
const char* pgn = "[Event \"Mutation\"]\n"
"[Result \"*\"]\n"
"\n"
"1. e4 e5 (1... c5) *\n";
scid_game* game = NULL;
scid_game* source_game = NULL;
scid_game_cursor* cursor = NULL;
scid_game_cursor* next_cursor = NULL;
scid_game_cursor* source_cursor = NULL;
scid_game_cursor* source_next_cursor = NULL;
scid_position* position = NULL;
scid_movespec move;
char output[4096];
int changed = 0;
int moved = 0;
size_t output_size = 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, NULL, 0, NULL),
"scid_game_create") ||
!check(scid_game_cursor_create(game, &cursor), "scid_game_cursor_create"))
{
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 1;
}
if (!check(scid_game_cursor_next(cursor, &moved, &next_cursor), "scid_game_cursor_next") ||
!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_comment_set(game, cursor, "King pawn"),
"scid_game_cursor_comment_set") ||
!check(scid_game_cursor_nag_add(game, cursor, 1, &changed), "scid_game_cursor_nag_add") ||
!changed ||
!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_position_get(cursor, position), "scid_game_cursor_position_get") ||
!check(
scid_movespec_create_from_san(position, "e6", &move),
"scid_movespec_create_from_san") ||
!check(scid_game_create_blank(position, &source_game), "scid_game_create_blank") ||
!check(scid_game_cursor_create(source_game, &source_cursor), "scid_game_cursor_create") ||
!check(
scid_game_cursor_comment_set(source_game, source_cursor, "French branch"),
"scid_game_cursor_comment_set") ||
!check(
scid_game_cursor_move_add(source_game, source_cursor, move, &source_next_cursor),
"scid_game_cursor_move_add") ||
!take_cursor(&source_cursor, &source_next_cursor) ||
!check(
scid_game_merge_moves(
game, cursor, source_game, SCID_GAME_MERGE_MOVES_INSERT_VARIATION, &next_cursor),
"scid_game_merge_moves") ||
!take_cursor(&cursor, &next_cursor))
{
scid_game_cursor_free(next_cursor);
scid_game_cursor_free(source_next_cursor);
scid_game_cursor_free(source_cursor);
scid_game_free(source_game);
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 1;
}
scid_game_cursor_free(source_cursor);
source_cursor = NULL;
scid_game_cursor_free(source_next_cursor);
source_next_cursor = NULL;
scid_game_free(source_game);
source_game = NULL;
next_cursor = NULL;
if (!check(
scid_game_cursor_variation_promote_to_first(game, cursor, &changed, &next_cursor),
"scid_game_cursor_variation_promote_to_first") ||
!changed || !take_cursor(&cursor, &next_cursor))
{
scid_game_cursor_free(next_cursor);
scid_game_cursor_free(source_next_cursor);
scid_game_cursor_free(source_cursor);
scid_game_free(source_game);
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) ||
!check(
scid_game_to_pgn(game, NULL, output, sizeof(output), &output_size), "scid_game_to_pgn"))
{
scid_game_cursor_free(next_cursor);
scid_game_cursor_free(source_next_cursor);
scid_game_cursor_free(source_cursor);
scid_game_free(source_game);
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 1;
}
next_cursor = NULL;
printf("%.*s", (int)output_size, output);
if (!contains(output, "e4 $1 {King pawn}") || !contains(output, "{French branch}") ||
!contains(output, "e6") || !contains(output, "c5"))
{
scid_position_free(position);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 1;
}
scid_position_free(position);
scid_game_cursor_free(source_next_cursor);
scid_game_cursor_free(source_cursor);
scid_game_free(source_game);
scid_game_cursor_free(cursor);
scid_game_free(game);
return 0;
}
3. Key Concepts and Patterns
Truncating Plies
To slice off moves after a given board position, position a cursor at the desired terminal move and invoke scid_game_cursor_truncate:
scid_game_cursor* cursor = NULL;
scid_game_cursor_create(game, &cursor);
/* Navigate to ply 2 */
scid_game_cursor* next_cursor = NULL;
scid_game_cursor_next(cursor, &next_cursor);
take_cursor(&cursor, &next_cursor);
/* Truncate everything after this move */
scid_game_cursor_truncate(cursor);
Merging Independent Games
The scid_game_merge_moves function combines variation trees, grafting alternative lines of play as variations if the mainline diverges: