/* 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;
}