/* DreamChess ** ** DreamChess is the legal property of its developers, whose names are too ** numerous to list here. Please refer to the AUTHORS.txt file distributed ** with this source distribution. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ #include #include #include #include #include "commands.h" #include "dreamer.h" #include "e_comm.h" #include "git_rev.h" #include "history.h" #include "move.h" #include "repetition.h" #include "san.h" #include "search.h" #include "timer.h" #include "transposition.h" static int is_coord_move(char *ms) { int len = strlen(ms); /* Check format. */ if ((len < 4) || (len > 5)) return 0; if ((ms[0] < 'a') || (ms[0] > 'h') || (ms[1] < '1') || (ms[1] > '8') || (ms[2] < 'a') || (ms[2] > 'h') || (ms[3] < '1') || (ms[3] > '8')) return 0; if (len == 5) switch (ms[4]) { case 'q': case 'r': case 'n': case 'b': return 1; default: return 0; } return 1; } static int convert_piece(int san_piece) { switch (san_piece) { case SAN_KING: return KING; case SAN_QUEEN: return QUEEN; case SAN_ROOK: return ROOK; case SAN_KNIGHT: return KNIGHT; case SAN_BISHOP: return BISHOP; case SAN_PAWN: return PAWN; } /* We should never get here */ assert(0); } static int san_piece(int piece) { switch (piece) { case KING: return SAN_KING; case QUEEN: return SAN_QUEEN; case ROOK: return SAN_ROOK; case KNIGHT: return SAN_KNIGHT; case BISHOP: return SAN_BISHOP; case PAWN: return SAN_PAWN; } /* We should never get here */ assert(0); } static move_t get_san_move(board_t *board, int ply, san_move_t *san) { move_t move; int piece; bitboard_t en_passant = board->en_passant; int castle_flags = board->castle_flags; int fifty_moves = board->fifty_moves; int found = 0; move_t found_move; if (san->type == SAN_QUEENSIDE_CASTLE) { san->source_file = 4; san->source_rank = (board->current_player == SIDE_WHITE ? 0 : 7); san->destination = (board->current_player == SIDE_WHITE ? 2 : 58); piece = KING + board->current_player; } else if (san->type == SAN_KINGSIDE_CASTLE) { san->source_file = 4; san->source_rank = (board->current_player == SIDE_WHITE ? 0 : 7); san->destination = (board->current_player == SIDE_WHITE ? 6 : 62); piece = KING + board->current_player; } else piece = convert_piece(san->piece) + board->current_player; compute_legal_moves(board, ply); /* Look for move in list. */ while ((move = move_next(board, ply)) != NO_MOVE) { int move_piece; if (MOVE_GET(move, DEST) != san->destination) continue; if (board->current_player == SIDE_WHITE) move_piece = find_white_piece(board, MOVE_GET(move, SOURCE)); else move_piece = find_black_piece(board, MOVE_GET(move, SOURCE)); if (move_piece != piece) continue; if (san->source_file != SAN_NOT_SPECIFIED) if (san->source_file != MOVE_GET(move, SOURCE) % 8) continue; if (san->source_rank != SAN_NOT_SPECIFIED) if (san->source_rank != MOVE_GET(move, SOURCE) / 8) continue; if (san->type == SAN_CAPTURE) { /* TODO verify en passant capture? */ if (!(move & (CAPTURE_MOVE_EN_PASSANT | CAPTURE_MOVE))) continue; } if ((move & PROMOTION_MOVE_QUEEN) && (san->promotion_piece != SAN_QUEEN)) continue; if ((move & PROMOTION_MOVE_ROOK) && (san->promotion_piece != SAN_ROOK)) continue; if ((move & PROMOTION_MOVE_BISHOP) && (san->promotion_piece != SAN_BISHOP)) continue; if ((move & PROMOTION_MOVE_KNIGHT) && (san->promotion_piece != SAN_KNIGHT)) continue; if (!(move & MOVE_PROMOTION_MASK) && (san->promotion_piece != SAN_NOT_SPECIFIED)) continue; /* TODO verify check and checkmate flags? */ execute_move(board, move); board->current_player = OPPONENT(board->current_player); if (!is_check(board, ply + 1)) { found++; found_move = move; } board->current_player = OPPONENT(board->current_player); unmake_move(board, move, en_passant, castle_flags, fifty_moves); } if (found != 1) return NO_MOVE; return found_move; } static move_t get_coord_move(board_t *board, int ply, char *ms) { int source, dest; move_t move; source = (ms[0] - 'a') + 8 * (ms[1] - '1'); dest = (ms[2] - 'a') + 8 * (ms[3] - '1'); compute_legal_moves(board, ply); /* Look for move in list. */ while ((move = move_next(board, ply)) != NO_MOVE) { if ((MOVE_GET(move, SOURCE) == source) && (MOVE_GET(move, DEST) == dest)) { bitboard_t en_passant = board->en_passant; int castle_flags = board->castle_flags; int fifty_moves = board->fifty_moves; /* Move found. */ execute_move(board, move); board->current_player = OPPONENT(board->current_player); if (!is_check(board, ply + 1)) { board->current_player = OPPONENT(board->current_player); unmake_move(board, move, en_passant, castle_flags, fifty_moves); break; } board->current_player = OPPONENT(board->current_player); unmake_move(board, move, en_passant, castle_flags, fifty_moves); } } if (move != NO_MOVE) { if (move & MOVE_PROMOTION_MASK) { /* Set correct promotion piece. */ move &= ~MOVE_PROMOTION_MASK; if (strlen(ms) == 5) switch (ms[4]) { case 'q': move |= PROMOTION_MOVE_QUEEN; break; case 'r': move |= PROMOTION_MOVE_ROOK; break; case 'n': move |= PROMOTION_MOVE_KNIGHT; break; case 'b': move |= PROMOTION_MOVE_BISHOP; } else { /* No promotion piece specified. */ return NO_MOVE; } return move; } if (strlen(ms) == 4) return move; } return NO_MOVE; } char *coord_move_str(move_t move) { char *ret = malloc(6); ret[5] = '\0'; ret[0] = 'a' + MOVE_GET(move, SOURCE) % 8; ret[1] = '1' + MOVE_GET(move, SOURCE) / 8; ret[2] = 'a' + MOVE_GET(move, DEST) % 8; ret[3] = '1' + MOVE_GET(move, DEST) / 8; switch (move & MOVE_PROMOTION_MASK) { case PROMOTION_MOVE_QUEEN: ret[4] = 'q'; break; case PROMOTION_MOVE_BISHOP: ret[4] = 'b'; break; case PROMOTION_MOVE_KNIGHT: ret[4] = 'n'; break; case PROMOTION_MOVE_ROOK: ret[4] = 'r'; break; default: ret[4] = '\0'; } return ret; } char *san_move_str(board_t *board, int ply, move_t move) { san_move_t san_move; int state; int move_piece; bitboard_t en_passant = board->en_passant; int castle_flags = board->castle_flags; int fifty_moves = board->fifty_moves; execute_move(board, move); state = check_game_state(board, ply); unmake_move(board, move, en_passant, castle_flags, fifty_moves); switch (state) { case STATE_CHECK: san_move.state = SAN_STATE_CHECK; break; case STATE_MATE: san_move.state = SAN_STATE_CHECKMATE; break; default: san_move.state = SAN_STATE_NORMAL; } switch (MOVE_GET(move, TYPE)) { case CASTLING_MOVE_QUEENSIDE: san_move.type = SAN_QUEENSIDE_CASTLE; return san_string(&san_move); case CASTLING_MOVE_KINGSIDE: san_move.type = SAN_KINGSIDE_CASTLE; return san_string(&san_move); case CAPTURE_MOVE: case CAPTURE_MOVE_EN_PASSANT: san_move.type = SAN_CAPTURE; break; default: san_move.type = SAN_NORMAL; } if (board->current_player == SIDE_WHITE) move_piece = find_white_piece(board, MOVE_GET(move, SOURCE)) & PIECE_MASK; else move_piece = find_black_piece(board, MOVE_GET(move, SOURCE)) & PIECE_MASK; san_move.piece = san_piece(move_piece); if (MOVE_GET(move, TYPE) & MOVE_PROMOTION_MASK) san_move.promotion_piece = san_piece(MOVE_GET(move, CAPTURED) & PIECE_MASK); else san_move.promotion_piece = SAN_NOT_SPECIFIED; san_move.source_file = SAN_NOT_SPECIFIED; san_move.source_rank = SAN_NOT_SPECIFIED; san_move.destination = MOVE_GET(move, DEST); if (san_move.piece == SAN_PAWN) { if (MOVE_GET(move, SOURCE) % 8 != MOVE_GET(move, DEST) % 8) san_move.source_file = MOVE_GET(move, SOURCE) % 8; } else { move_t u_move; u_move = get_san_move(board, ply, &san_move); if (u_move == NO_MOVE) { san_move.source_file = MOVE_GET(move, SOURCE) % 8; u_move = get_san_move(board, ply, &san_move); if (u_move == NO_MOVE) { san_move.source_file = SAN_NOT_SPECIFIED; san_move.source_rank = MOVE_GET(move, SOURCE) / 8; u_move = get_san_move(board, ply, &san_move); if (u_move == NO_MOVE) { san_move.source_file = MOVE_GET(move, SOURCE) % 8; u_move = get_san_move(board, ply, &san_move); if (!u_move) { char *move_s = coord_move_str(move); e_comm_send("failed to convert move %s to SAN notation", move_s); free(move_s); return NULL; } } } } } return san_string(&san_move); } static void error(char *type, char *command) { e_comm_send("Error (%s): %s\n", type, command); } #define NOT_NOW(c) error("command not legal now", c) #define UNKNOWN(c) error("unknown command", c) #define BADPARAM(c) error("invalid or missing parameter(s)", c) static int parse_time_control(state_t *state, char *s) { struct time_control t; char *end; char *semi; errno = 0; t.mps = strtol(s, &end, 10); if (errno || *end != ' ') return 1; semi = strchr(end + 1, ':'); if (semi) { *semi = ' '; t.base = strtol(end + 1, &end, 10) * 60 * 100; *semi = ':'; if (errno || end != semi) return 1; t.base += strtol(end + 1, &end, 10) * 100; if (errno || *end != ' ') return 1; } else { t.base = strtol(end + 1, &end, 10) * 60 * 100; if (errno || *end != ' ') return 1; } t.inc = strtol(end + 1, &end, 10) * 100; if (errno || *end != 0) return 1; /* Time control with both mps and inc is invalid */ if (t.mps != 0 && t.inc != 0) return 1; state->time = t; timer_set(&state->engine_time, t.base); return 0; } static int command_always(state_t *state, char *command) { if (!strcmp(command, "post")) { set_option(OPTION_POST, 1); return 1; } if (!strcmp(command, "nopost")) { set_option(OPTION_POST, 0); return 1; } if (!strncmp(command, "time ", 5)) { int time; char *end; errno = 0; time = strtol(command + 5, &end, 10); if (errno || *end != 0) BADPARAM(command); else timer_set(&state->engine_time, time); return 1; } if (!strncmp(command, "otim ", 5)) return 1; return 0; } int parse_move(board_t *board, int ply, char *command, move_t *move) { san_move_t *san; san = san_parse(command); if (san) { *move = get_san_move(board, ply, san); free(san); return 0; } else if (is_coord_move(command)) { *move = get_coord_move(board, ply, command); return 0; } return 1; } int command_usermove(state_t *state, char *command) { move_t move; if (!parse_move(&state->board, 0, command, &move)) { if (move == NO_MOVE) { e_comm_send("Illegal move: %s\n", command); return 0; } if (my_turn(state)) { NOT_NOW(command); return 0; } if (state->mode == MODE_WHITE || state->mode == MODE_BLACK) timer_start(&state->engine_time); do_move(state, move); check_game_end(state); if (state->mode == MODE_IDLE) state->mode = (state->board.current_player == SIDE_WHITE ? MODE_WHITE : MODE_BLACK); if (state->ponder_my_move != NO_MOVE) { /* We already have a possible answer to this move from pondering. */ if (move == state->ponder_opp_move && MOVE_IS_REGULAR(state->ponder_my_move)) { /* User made the expected move. */ send_move(state, state->ponder_my_move); } state->ponder_my_move = NO_MOVE; } return 0; } return 1; } void command_handle(state_t *state, char *command) { if (command_always(state, command)) return; if (!strcmp(command, "xboard")) { /* xboard mode is default. */ return; } if (!strncmp(command, "protover ", 9)) { char *endptr; errno = 0; strtol(command + 9, &endptr, 10); if (errno || (*endptr != 0)) BADPARAM(command); e_comm_send("feature myname=\"Dreamer %s\"\n", g_version); e_comm_send("feature setboard=1\n"); e_comm_send("feature colors=0\n"); e_comm_send("feature done=1\n"); return; } if (!strncmp(command, "level ", 6)) { if (parse_time_control(state, command + 6)) BADPARAM(command); return; } if (!strncmp(command, "accepted ", 9)) { if (!strcmp(command + 9, "setboard") || !strcmp(command + 9, "done") || !strcmp(command + 9, "myname") || !strcmp(command + 9, "colors")) return; BADPARAM(command); return; } if (!strcmp(command, "new")) { setup_board(&state->board); forget_history(); clear_table(); pv_clear(); repetition_init(&state->board); state->done = 0; state->mode = MODE_BLACK; state->flags = 0; state->depth = MAX_DEPTH; if (state->undo_data != NULL) free(state->undo_data); state->undo_data = NULL; state->moves = 0; timer_init(&state->engine_time, 1); timer_set(&state->engine_time, state->time.base * 60 * 100); timer_init(&state->move_time, 1); state->hint = NO_MOVE; state->ponder_opp_move = NO_MOVE; state->ponder_my_move = NO_MOVE; state->ponder_actual_move = NO_MOVE; return; } if (!strcmp(command, "quit")) { state->mode = MODE_QUIT; return; } if (!strcmp(command, "force")) { state->mode = MODE_FORCE; return; } if (!strcmp(command, "white")) { if (state->board.current_player != SIDE_WHITE) { /* FIXME, we should support this, but right now it would cause ** severe problems with the undo system. */ NOT_NOW(command); return; } state->mode = MODE_BLACK; return; } if (!strcmp(command, "black")) { if (state->board.current_player != SIDE_BLACK) { /* FIXME, see above. */ NOT_NOW(command); return; } state->mode = MODE_WHITE; return; } if (!strcmp(command, "playother")) { if (state->mode != MODE_FORCE) { NOT_NOW(command); return; } if (state->board.current_player == SIDE_WHITE) state->mode = MODE_BLACK; else state->mode = MODE_WHITE; return; } if (!strncmp(command, "sd", 2)) { char *number = strchr(command, ' '); char *end; if (number && (*(number + 1) != '\0')) { long int val = strtol(number + 1, &end, 10); if ((*end == '\0') && (val > 0)) state->depth = val; else BADPARAM(command); } else BADPARAM(command); return; } if (!strcmp(command, "go")) { if (state->board.current_player == SIDE_WHITE) state->mode = MODE_WHITE; else state->mode = MODE_BLACK; return; } if (!strcmp(command, "remove")) { switch (state->mode) { case MODE_WHITE: if (state->board.current_player == SIDE_WHITE) { NOT_NOW(command); return; } break; case MODE_BLACK: if (state->board.current_player == SIDE_BLACK) { NOT_NOW(command); return; } break; case MODE_FORCE: break; default: NOT_NOW(command); return; } if (state->moves < 2) NOT_NOW(command); undo_move(state); undo_move(state); state->done = 0; return; } if (!strcmp(command, "?")) { NOT_NOW(command); return; } if (!strncmp(command, "setboard ", 9)) { board_t board; if (state->mode != MODE_FORCE) { NOT_NOW(command); return; } if (setup_board_fen(&board, command + 9)) { BADPARAM(command); return; } state->board = board; forget_history(); clear_table(); repetition_init(&state->board); state->done = 0; return; } if (!strcmp(command, "noquiesce")) { set_option(OPTION_QUIESCE, 0); return; } if (!strcmp(command, "easy")) { set_option(OPTION_PONDER, 0); state->ponder_my_move = NO_MOVE; return; } if (!strcmp(command, "hard")) { set_option(OPTION_PONDER, 1); if (state->moves != 0) state->flags |= FLAG_PONDER; return; } if (!strcmp(command, "hint")) { if (state->hint != NO_MOVE) { char *str = coord_move_str(state->hint); e_comm_send("Hint: %s\n", str); free(str); } return; } if (!command_usermove(state, command)) return; UNKNOWN(command); } int command_check_abort(state_t *state, int ply, char *command) { if (!strcmp(command, "?")) { if (my_turn(state)) return 1; } if (command_always(state, command)) return 0; if (!strcmp(command, "new")) { state->flags = FLAG_NEW_GAME | FLAG_IGNORE_MOVE; return 1; } if (!strcmp(command, "quit") || !strcmp(command, "force")) { state->flags = FLAG_IGNORE_MOVE; command_handle(state, command); return 1; } if (!strcmp(command, "easy")) { if (state->flags & FLAG_PONDER) { state->flags = FLAG_IGNORE_MOVE; state->ponder_actual_move = NO_MOVE; } command_handle(state, command); return 1; } if (!strcmp(command, "hint")) { if (state->flags & FLAG_PONDER) { char *str = coord_move_str(state->ponder_opp_move); e_comm_send("Hint: %s\n", str); free(str); return 0; } } if (state->flags & FLAG_PONDER) { move_t move; if (!parse_move(&state->root_board, ply, command, &move)) { if (move == NO_MOVE) { e_comm_send("Illegal move: %s\n", command); return 0; } /* Start our timer */ timer_start(&state->engine_time); if (move == state->ponder_opp_move) { /* User made the expected move. */ state->flags = 0; return 0; } else { /* User made a different move, abort and restart search. */ state->flags = FLAG_IGNORE_MOVE; state->ponder_actual_move = move; return 1; } } } NOT_NOW(command); return 0; }