Skip to content

How to Apply Moves and Enumerate Legal Moves

This guide shows how to interact with chess board positions directly using the libscid C ABI: generating legal moves, converting moves between UCI and SAN representations, applying moves to update board states, and querying check and checkmate conditions.


1. Overview of Symbols

  • scid_position_create_from_fen: Initialises a board position from a FEN string.
  • scid_position_legal_moves: Populates a caller-allocated array of scid_movespec with all legal moves available in the position.
  • SCID_MAX_LEGAL_MOVES: Compile-time constant specifying the maximum number of legal moves possible in any chess position (256).
  • scid_movespec_to_san: Formats a low-level move descriptor into Standard Algebraic Notation (SAN) based on the board context.
  • scid_movespec_to_uci: Formats a move into Universal Chess Interface (UCI) notation (e.g. e2e4, e7e8q).
  • scid_position_apply_san: Applies a SAN move directly to the position, mutating its state.
  • scid_position_is_check: Tests whether the side to move is currently in check.
  • scid_position_is_checkmate: Tests whether the current position is checkmate.
  • scid_position_to_fen: Formats the current position back into a FEN string.

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
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_legal_moves(scid_position* position)
{
    scid_movespec moves[SCID_MAX_LEGAL_MOVES];
    char          san[32];
    char          uci[8];
    size_t        move_count = 0;
    size_t        san_size = 0;
    size_t        uci_size = 0;
    size_t        i = 0;

    if (!check(
            scid_position_legal_moves(position, moves, SCID_MAX_LEGAL_MOVES, &move_count),
            "scid_position_legal_moves"))
    {
        return 0;
    }

    printf("legal moves: %zu\n", move_count);
    for (i = 0; i < move_count; ++i)
    {
        if (!check(
                scid_movespec_to_san(position, moves[i], san, sizeof(san), &san_size),
                "scid_movespec_to_san") ||
            !check(
                scid_movespec_to_uci(moves[i], uci, sizeof(uci), &uci_size),
                "scid_movespec_to_uci"))
        {
            return 0;
        }

        printf("  %.*s (%.*s)\n", (int)san_size, san, (int)uci_size, uci);
    }

    return 1;
}


int
main(void)
{
    const char*    standard_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
    const char*    expected_fen = "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/"
                                  "RNBQKBNR w KQkq - 0 2";
    scid_position* position = NULL;
    scid_position* next_position = NULL;
    scid_movespec  move;
    scid_colour    side_to_move = SCID_BLACK;
    scid_piece     piece = SCID_PIECE_NONE;
    char           text[256];
    size_t         text_size = 0;
    unsigned       number = 0;

    if (!check(
            scid_position_create_from_fen(standard_fen, &position),
            "scid_position_create_from_fen") ||
        !check(
            scid_movespec_create_from_san(position, "e4", &move),
            "scid_movespec_create_from_san") ||
        !check(
            scid_movespec_to_uci(move, text, sizeof(text), &text_size), "scid_movespec_to_uci") ||
        !text_equals(text, text_size, "e2e4") ||
        !check(
            scid_position_create_with_san(position, "e4", &next_position),
            "scid_position_create_with_san"))
    {
        scid_position_free(next_position);
        scid_position_free(position);
        return 1;
    }
    scid_position_free(position);
    position = next_position;
    next_position = NULL;

    if (!check(
            scid_position_create_with_uci(position, "c7c5", &next_position),
            "scid_position_create_with_uci"))
    {
        scid_position_free(next_position);
        scid_position_free(position);
        return 1;
    }
    scid_position_free(position);
    position = next_position;
    next_position = NULL;

    if (!check(
            scid_position_to_fen(position, text, sizeof(text), &text_size), "scid_position_to_fen"))
    {
        scid_position_free(next_position);
        scid_position_free(position);
        return 1;
    }

    printf("fen: %.*s\n", (int)text_size, text);
    if (!text_equals(text, text_size, expected_fen))
    {
        scid_position_free(position);
        return 1;
    }


    if (!check(
            scid_position_side_to_move_get(position, &side_to_move),
            "scid_position_side_to_move_get") ||
        side_to_move != SCID_WHITE ||
        !check(
            scid_position_fullmove_number_get(position, &number),
            "scid_position_fullmove_number_get") ||
        number != 2 ||
        !check(
            scid_position_halfmove_clock_get(position, &number),
            "scid_position_halfmove_clock_get") ||
        number != 0 || !check(scid_square_from_string("e4", &move.to), "scid_square_from_string") ||
        !check(
            scid_position_piece_at_get(position, move.to, &piece), "scid_position_piece_at_get") ||
        piece != SCID_PIECE_WHITE_PAWN ||
        !check(
            scid_square_to_string(move.to, text, sizeof(text), &text_size),
            "scid_square_to_string") ||
        !text_equals(text, text_size, "e4"))
    {
        scid_position_free(next_position);
        scid_position_free(position);
        return 1;
    }

    printf("side to move: white\n");
    printf("piece on %.*s: white pawn\n", (int)text_size, text);

    if (!print_legal_moves(position))
    {
        scid_position_free(next_position);
        scid_position_free(position);
        return 1;
    }

    scid_position_free(next_position);
    scid_position_free(position);
    return 0;
}

3. Key Concepts and Patterns

Legal move generation uses caller-allocated arrays with SCID_MAX_LEGAL_MOVES capacity:

scid_movespec moves[SCID_MAX_LEGAL_MOVES];
size_t move_count = 0;

if (scid_position_legal_moves(position, moves, SCID_MAX_LEGAL_MOVES, &move_count) == SCID_OK)
{
    for (size_t i = 0; i < move_count; ++i)
    {
        char san[32];
        char uci[8];
        size_t san_size = 0;
        size_t uci_size = 0;

        scid_movespec_to_san(position, moves[i], san, sizeof(san), &san_size);
        scid_movespec_to_uci(moves[i], uci, sizeof(uci), &uci_size);

        printf("Move %zu: %.*s (UCI: %.*s)\n", i, (int)san_size, san, (int)uci_size, uci);
    }
}

Mutating Board State and Detecting Check

scid_position_apply_san(position, "Qxf7#");

int is_check = 0;
int is_checkmate = 0;
scid_position_is_check(position, &is_check);
scid_position_is_checkmate(position, &is_checkmate);