#include #include #include #include "uci/internals/response.h" // Tokenize a UCI message into a command. Supports double-quoted strings so // that multi-word values (e.g. FEN positions) survive tokenization. void load_from_message(uci_cmd_t* cmd, const char* message) { char token[MAX_TOKEN_SIZE]; int i = 0, k = 0; bool quoted = false; while (message[i] != 0 && message[i] != '\n' && message[i] != '\r') { if (message[i] == '"') { quoted = !quoted; i++; continue; } if (message[i] == ' ' && !quoted) { if (k == 0) { i++; continue; } i++; token[k] = 0; uci_cmd_add(cmd, token); k = 0; continue; } token[k++] = message[i++]; } token[k] = 0; uci_cmd_add(cmd, token); } #include "uci/state.h" #include "fen.h" void handle_uci(uci_state_t *state, uci_cmd_t cmd) { if (state->current_state == UCI_STATE_INITIAL) { return handle_initial(state, cmd); } if (state->current_state == UCI_STATE_IDLE) { return handle_idle(state, cmd); } if (state->current_state == UCI_STATE_SYNC) { return handle_sync(state, cmd); } if (state->current_state == UCI_STATE_PING) { return handle_ping(state, cmd); } if (state->current_state == UCI_STATE_ACTIVE) { return handle_active(state, cmd); } return handle_halt(state, cmd); } void handle_initial(uci_state_t *state, uci_cmd_t cmd) { if (cmd.empty || strcmp(cmd.root, "uci") != 0) return; // TIMEOUT NOTICE: THE FOLLOWING BLOCK SHOULDN'T TAKE MORE THAN 5 SECONDS printf("id name %s\n", state->name); printf("id author %s\n", state->author); int settings_count = sizeof(state->option_settings)/sizeof(option_setting_t); for (int i = 0; i < settings_count; i++) { print_setting(state->option_settings[i]); } // --- // flush around the handshake so piped GUIs see responses immediately fflush(stdout); printf("uciok\n"); fflush(stdout); state->current_state = UCI_STATE_IDLE; } void handle_idle(uci_state_t *state, uci_cmd_t cmd) { if (cmd.empty) return; if (strcmp(cmd.root, "debug") == 0) { state->debug = strcmp(uci_cmd_get_arg(cmd, 0), "on") == 0; } else if (strcmp(cmd.root, "setoption") == 0) { char buffer[2048] = {0}; char name[64] = {0}; char *cursor = name; int limit = 64; int i = 1; if (i < cmd.args_count && strcmp(uci_cmd_get_arg(cmd, i), "name") == 0) i++; for (; i < cmd.args_count; i++) { assert((long)cursor - (long)(limit == 64 ? name : buffer) < limit); char *arg = uci_cmd_get_arg(cmd, i); if (strcmp(arg, "value") == 0) { if (cursor > name && *(cursor - 1) == ' ') cursor--; *cursor = 0; cursor = buffer; limit = 2048; continue; } while (*arg) *(cursor++) = *(arg++); *(cursor++) = ' '; } if (cursor > name && *(cursor - 1) == ' ') cursor--; if (cursor > buffer && *(cursor - 1) == ' ') cursor--; *cursor = 0; apply_option(state, name, buffer); } else if (strcmp(cmd.root, "ucinewgame") == 0) { } else if (strcmp(cmd.root, "position") == 0) { int k = 0; char* mode = uci_cmd_get_arg(cmd, k++); if (strcmp(mode, "startpos") == 0) { struct fen_load fen = load_fen( "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" ); assert(fen.failed == 0); state->position = fen.position; } else if (strcmp(mode, "fen") == 0) { struct fen_load fen = load_fen(uci_cmd_get_arg(cmd, k++)); if (fen.failed) { printf("info invalid fen\n"); return; } state->position = fen.position; } else { assert(0 && "Invalid argument for position command"); } if (cmd.args_count <= k) return; assert(strcmp(uci_cmd_get_arg(cmd, k++), "moves") == 0); for (; k < cmd.args_count; k++) { char* move = uci_cmd_get_arg(cmd, k); add_ipc_move( state->moves, (ipc_move_t) { (move[0] - 'a') * 8 + (move[1] - '1'), (move[2] - 'a') * 8 + (move[3] - '1') } ); } } else if (strcmp(cmd.root, "stop") == 0) { } else if (strcmp(cmd.root, "quit") == 0) { state->signal = UCI_SIGNAL_QUIT; } else if (strcmp(cmd.root, "isready") == 0) { state->current_state = UCI_STATE_SYNC; } else if (strcmp(cmd.root, "go") == 0) { go_args_t info = (go_args_t) {0}; for (int i = 0; i < cmd.args_count; i++) { if (strcmp(uci_cmd_get_arg(cmd, i), "searchmoves") == 0) { ipc_moves_init(&info.searchmoves); for (i++; i < cmd.args_count; i++) { char* move_str = uci_cmd_get_arg(cmd, i); if (move_str[1] < '0' || move_str[1] > '9') break; add_ipc_move(&info.searchmoves, (ipc_move_t) { (move_str[0] - 'a') * 8 + (move_str[1] - '1'), (move_str[2] - 'a') * 8 + (move_str[3] - '1') }); } i--; // it was harsh on my eyes, that's why i did this formatting } else if (strcmp(uci_cmd_get_arg(cmd, i), "ponder") == 0) { info.ponder = true; } else if (strcmp(uci_cmd_get_arg(cmd, i), "wtime") == 0) { info.wtime = atoi(uci_cmd_get_arg(cmd, ++i)); } else if (strcmp(uci_cmd_get_arg(cmd, i), "btime") == 0) { info.btime = atoi(uci_cmd_get_arg(cmd, ++i)); } else if (strcmp(uci_cmd_get_arg(cmd, i), "winc") == 0) { info.winc = atoi(uci_cmd_get_arg(cmd, ++i)); } else if (strcmp(uci_cmd_get_arg(cmd, i), "binc") == 0) { info.binc = atoi(uci_cmd_get_arg(cmd, ++i)); } else if (strcmp(uci_cmd_get_arg(cmd, i), "movestogo") == 0) { info.movestogo = atoi(uci_cmd_get_arg(cmd, ++i)); } else if (strcmp(uci_cmd_get_arg(cmd, i), "depth") == 0) { info.depth = atoi(uci_cmd_get_arg(cmd, ++i)); } else if (strcmp(uci_cmd_get_arg(cmd, i), "nodes") == 0) { info.nodes = atoi(uci_cmd_get_arg(cmd, ++i)); } else if (strcmp(uci_cmd_get_arg(cmd, i), "mate") == 0) { info.mate = atoi(uci_cmd_get_arg(cmd, ++i)); } else if (strcmp(uci_cmd_get_arg(cmd, i), "movetime") == 0) { info.movetime = atoi(uci_cmd_get_arg(cmd, ++i)); } else if (strcmp(uci_cmd_get_arg(cmd, i), "infinite") == 0) { info.infinite = true; } else if (strcmp(uci_cmd_get_arg(cmd, i), "perft") == 0) { info.perft = atoi(uci_cmd_get_arg(cmd, ++i)); } } memcpy(&state->go_args, &info, sizeof(info)); state->signal = UCI_SIGNAL_GO; state->current_state = UCI_STATE_ACTIVE; } else { printf("info violation\n"); } } void handle_sync(uci_state_t *state, uci_cmd_t cmd) { // TIMEOUT NOTICE: THE FOLLOWING BLOCK SHOULDN'T TAKE MORE THAN 5 SECONDS if (cmd.empty || strcmp(cmd.root, "isready") != 0) { // commands sent while a sync is pending must still be handled. return handle_idle(state, cmd); } printf("readyok\n"); state->current_state = UCI_STATE_IDLE; } void handle_ping(uci_state_t *state, uci_cmd_t cmd) { // TIMEOUT NOTICE: THE FOLLOWING BLOCK SHOULDN'T TAKE MORE THAN 1 SECONDS // if (TODO) { // printf("bestmove\n"); // current_state = UCI_STATE_IDLE; // } // --- printf("readyok\n"); state->current_state = UCI_STATE_ACTIVE; } void handle_active(uci_state_t *state, uci_cmd_t cmd) { if (cmd.empty) { return; } if (strcmp(cmd.root, "isready") == 0) { state->current_state = UCI_STATE_PING; } else if (strcmp(cmd.root, "stop") == 0) { state->current_state = UCI_STATE_HALT; } else { printf("info violation\n"); } } void handle_halt(uci_state_t *state, uci_cmd_t cmd) { // TIMEOUT NOTICE: THE FOLLOWING BLOCK SHOULDN'T TAKE MORE THAN 1 SECONDS state->signal = UCI_SIGNAL_STOP; // --- state->current_state = UCI_STATE_IDLE; } #ifdef TEST_MOD #include #include static uci_cmd_t __make_cmd(const char *line) { uci_cmd_t cmd; uci_cmd_init(&cmd); load_from_message(&cmd, line); return cmd; } static void __free_cmd(uci_cmd_t cmd) { uci_cmd_deinit(cmd); } static char *__capture_uci( void (*handler)(uci_state_t *, uci_cmd_t), uci_state_t *state, const char *line, char *out, int out_size ) { int pipefd[2]; if (pipe(pipefd) != 0) { out[0] = 0; return out; } int saved = dup(STDOUT_FILENO); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); uci_cmd_t cmd = __make_cmd(line); handler(state, cmd); __free_cmd(cmd); fflush(stdout); dup2(saved, STDOUT_FILENO); close(saved); int n = read(pipefd[0], out, out_size - 1); close(pipefd[0]); out[n > 0 ? n : 0] = 0; return out; } // ---- UCI handshake ---- bool test_uci_handshake_id_and_uciok() { uci_state_t state; uci_state_init(&state); char out[2048]; __capture_uci(handle_initial, &state, "uci", out, sizeof(out)); bool ok = true; ok &= strstr(out, "id name Gacrux") != NULL; ok &= strstr(out, "id author") != NULL; ok &= strstr(out, "uciok") != NULL; ok &= state.current_state == UCI_STATE_IDLE; uci_state_deinit(&state); return ok; } bool test_uci_handshake_prints_all_options() { uci_state_t state; uci_state_init(&state); char out[4096]; __capture_uci(handle_initial, &state, "uci", out, sizeof(out)); bool ok = true; ok &= strstr(out, "option name Threads type spin") != NULL; ok &= strstr(out, "option name Hash type spin") != NULL; ok &= strstr(out, "option name Clear Hash type button") != NULL; ok &= strstr(out, "option name UCI_ShowCurrLine type check") != NULL; ok &= strstr(out, "option name UCI_LimitStrength type check") != NULL; ok &= strstr(out, "option name UCI_Elo type spin") != NULL; ok &= strstr(out, "option name UCI_AnalyseMode type check") != NULL; ok &= strstr(out, "option name UCI_Opponent type string") != NULL; ok &= strstr(out, "option name UCI_EngineAbout type string") != NULL; uci_state_deinit(&state); return ok; } // ---- isready ---- bool test_isready_returns_readyok() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_sync, &state, "isready", out, sizeof(out)); bool ok = true; ok &= strstr(out, "readyok") != NULL; ok &= state.current_state == UCI_STATE_IDLE; uci_state_deinit(&state); return ok; } bool test_isready_transitions_to_idle() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_SYNC; char out[256]; __capture_uci(handle_uci, &state, "isready", out, sizeof(out)); bool ok = true; ok &= strstr(out, "readyok") != NULL; ok &= state.current_state == UCI_STATE_IDLE; uci_state_deinit(&state); return ok; } // ---- debug ---- bool test_debug_on_sets_flag() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "debug on", out, sizeof(out)); bool ok = state.debug == true; uci_state_deinit(&state); return ok; } bool test_debug_off_clears_flag() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; state.debug = true; char out[256]; __capture_uci(handle_idle, &state, "debug off", out, sizeof(out)); bool ok = state.debug == false; uci_state_deinit(&state); return ok; } // ---- setoption ---- bool test_setoption_spin() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "setoption name Threads value 4", out, sizeof(out)); bool ok = state.threads == 4; uci_state_deinit(&state); return ok; } bool test_setoption_spin_clamps_too_low() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "setoption name Threads value 0", out, sizeof(out)); bool ok = state.threads == 1; uci_state_deinit(&state); return ok; } bool test_setoption_spin_clamps_too_high() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "setoption name Threads value 99", out, sizeof(out)); bool ok = state.threads == 1; uci_state_deinit(&state); return ok; } bool test_setoption_check_true() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "setoption name UCI_AnalyseMode value true", out, sizeof(out)); bool ok = state.uci_analysemode == true; uci_state_deinit(&state); return ok; } bool test_setoption_check_false() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; state.uci_analysemode = true; char out[256]; __capture_uci(handle_idle, &state, "setoption name UCI_AnalyseMode value false", out, sizeof(out)); bool ok = state.uci_analysemode == false; uci_state_deinit(&state); return ok; } bool test_setoption_button() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "setoption name Clear Hash", out, sizeof(out)); bool ok = state.clear_hash == true; uci_state_deinit(&state); return ok; } bool test_setoption_string() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "setoption name UCI_Opponent value Magnus", out, sizeof(out)); bool ok = true; ok &= state.uci_opponent.length == 6; ok &= memcmp(state.uci_opponent.data, "Magnus", 6) == 0; uci_state_deinit(&state); return ok; } bool test_setoption_hash_changes_value() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "setoption name Hash value 128", out, sizeof(out)); bool ok = state.hash == 128; uci_state_deinit(&state); return ok; } // ---- position ---- bool test_position_startpos_loads() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; ipc_moves_t moves; ipc_moves_init(&moves); state.moves = &moves; char out[256]; __capture_uci(handle_idle, &state, "position startpos", out, sizeof(out)); bool ok = true; ok &= state.position.bitboards[WHITE_PAWN] != 0; ok &= state.position.turn == WHITE_TURN; ipc_moves_deinit(&moves); uci_state_deinit(&state); return ok; } bool test_position_fen_loads() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; ipc_moves_t moves; ipc_moves_init(&moves); state.moves = &moves; char out[256]; __capture_uci(handle_idle, &state, "position fen \"k7/8/KQ6/8/8/8/8/8 w - - 0 1\"", out, sizeof(out)); bool ok = state.position.bitboards[WHITE_QUEEN] != 0; ipc_moves_deinit(&moves); uci_state_deinit(&state); return ok; } bool test_position_startpos_moves() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; ipc_moves_t moves; ipc_moves_init(&moves); state.moves = &moves; char out[256]; __capture_uci(handle_idle, &state, "position startpos moves e2e4", out, sizeof(out)); bool ok = state.moves->count == 1; if (ok) { ok &= state.moves->data[0].from == 33; ok &= state.moves->data[0].to == 35; } ipc_moves_deinit(&moves); uci_state_deinit(&state); return ok; } // ---- go ---- bool test_go_depth_sets_args() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go depth 10", out, sizeof(out)); bool ok = true; ok &= state.go_args.depth == 10; ok &= state.current_state == UCI_STATE_ACTIVE; ok &= state.signal == UCI_SIGNAL_GO; uci_state_deinit(&state); return ok; } bool test_go_wtime_btime_sets_args() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go wtime 60000 btime 60000 winc 1000 binc 1000", out, sizeof(out)); bool ok = true; ok &= state.go_args.wtime == 60000; ok &= state.go_args.btime == 60000; ok &= state.go_args.winc == 1000; ok &= state.go_args.binc == 1000; uci_state_deinit(&state); return ok; } bool test_go_nodes_sets_args() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go nodes 50000", out, sizeof(out)); bool ok = state.go_args.nodes == 50000; uci_state_deinit(&state); return ok; } bool test_go_movetime_sets_args() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go movetime 5000", out, sizeof(out)); bool ok = state.go_args.movetime == 5000; uci_state_deinit(&state); return ok; } bool test_go_movestogo_sets_args() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go movestogo 30", out, sizeof(out)); bool ok = state.go_args.movestogo == 30; uci_state_deinit(&state); return ok; } bool test_go_mate_sets_args() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go mate 3", out, sizeof(out)); bool ok = state.go_args.mate == 3; uci_state_deinit(&state); return ok; } bool test_go_infinite_sets_flag() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go infinite", out, sizeof(out)); bool ok = state.go_args.infinite == true; uci_state_deinit(&state); return ok; } bool test_go_ponder_sets_flag() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go ponder", out, sizeof(out)); bool ok = state.go_args.ponder == true; uci_state_deinit(&state); return ok; } bool test_go_combined_args() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go depth 6 wtime 30000 btime 30000 winc 500 movestogo 20", out, sizeof(out)); bool ok = true; ok &= state.go_args.depth == 6; ok &= state.go_args.wtime == 30000; ok &= state.go_args.btime == 30000; ok &= state.go_args.winc == 500; ok &= state.go_args.movestogo == 20; uci_state_deinit(&state); return ok; } bool test_go_perft_sets_args() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "go perft 5", out, sizeof(out)); bool ok = state.go_args.perft == 5; uci_state_deinit(&state); return ok; } // ---- state machine transitions ---- bool test_active_stop_transitions_to_idle() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_ACTIVE; char out[256]; __capture_uci(handle_active, &state, "stop", out, sizeof(out)); bool ok = true; ok &= state.current_state == UCI_STATE_HALT; uci_state_deinit(&state); return ok; } bool test_halt_sets_stop_signal() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_HALT; char out[256]; __capture_uci(handle_halt, &state, "", out, sizeof(out)); bool ok = true; ok &= state.signal == UCI_SIGNAL_STOP; ok &= state.current_state == UCI_STATE_IDLE; uci_state_deinit(&state); return ok; } bool test_active_isready_transitions_to_ping() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_ACTIVE; char out[256]; __capture_uci(handle_active, &state, "isready", out, sizeof(out)); bool ok = state.current_state == UCI_STATE_PING; uci_state_deinit(&state); return ok; } bool test_quit_sets_signal() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "quit", out, sizeof(out)); bool ok = state.signal == UCI_SIGNAL_QUIT; uci_state_deinit(&state); return ok; } bool test_new_game_accepted() { uci_state_t state; uci_state_init(&state); state.current_state = UCI_STATE_IDLE; char out[256]; __capture_uci(handle_idle, &state, "ucinewgame", out, sizeof(out)); bool ok = state.current_state == UCI_STATE_IDLE; uci_state_deinit(&state); return ok; } bool test_full_handshake_sequence() { uci_state_t state; uci_state_init(&state); ipc_moves_t moves; ipc_moves_init(&moves); state.moves = &moves; char out[2048]; __capture_uci(handle_uci, &state, "uci", out, sizeof(out)); bool ok = state.current_state == UCI_STATE_IDLE; ok &= strstr(out, "uciok") != NULL; __capture_uci(handle_idle, &state, "isready", out, sizeof(out)); ok &= state.current_state == UCI_STATE_SYNC; __capture_uci(handle_uci, &state, "isready", out, sizeof(out)); ok &= state.current_state == UCI_STATE_IDLE; ok &= strstr(out, "readyok") != NULL; __capture_uci(handle_idle, &state, "setoption name Threads value 2", out, sizeof(out)); ok &= state.threads == 2; __capture_uci(handle_idle, &state, "position startpos", out, sizeof(out)); ok &= state.position.bitboards[WHITE_PAWN] != 0; __capture_uci(handle_idle, &state, "go depth 5", out, sizeof(out)); ok &= state.current_state == UCI_STATE_ACTIVE; ok &= state.go_args.depth == 5; __capture_uci(handle_active, &state, "stop", out, sizeof(out)); ok &= state.current_state == UCI_STATE_HALT; __capture_uci(handle_halt, &state, "", out, sizeof(out)); ok &= state.current_state == UCI_STATE_IDLE; ipc_moves_deinit(&moves); uci_state_deinit(&state); return ok; } #endif