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 ofscid_movespecwith 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
Enumerating Legal Moves
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);
}
}