/* a 3D chess set Andrew.Tridgell@anu.edu.au January 1997 */ #include "includes.h" #include "trackball.h" #include "knightcap.h" static int window_size = 500; #define SLEEP_TIME 100000 /* microseconds */ #define REFRESH_TIME 20 /* in seconds */ #define SEEK_WAIT_TIME 120 /* in seconds */ #ifndef MULL_WAIT_TIME #define MULL_WAIT_TIME 0 #endif struct state *state; int seeking=0; int seek_answering=0; int demo_mode = 0; static int opponent; static int always_think; static int no_display; static int ascii_display; static int use_mulling = USE_PBRAIN; static int display_position=0; static int brain_inserting=0; static int ics_mode; static int move_pid; int display_pid; static int match_remaining; static int match_total; static int def_hash_table_size = DEFAULT_HASH_TABLE_SIZE; static float match_points; int color_display=1; int auto_exit=0; int bad_eval_fd; char noplay_list[5000]; char opponents_list[5000]; extern int need_redraw; #if LEARN_EVAL int learning=1; #else int learning=0; #endif static float default_move_time = DEFAULT_MOVE_TIME; char *ics_name = "KnightCap"; char *ics_master = NULL; char *coeffs_file = NULL; char *bad_eval_file = "bad_eval.dat"; char *tb_path = "../EGTB"; char play_target[100]; #ifndef SHM_R #define SHM_R 0400 #endif #ifndef SHM_W #define SHM_W 0200 #endif void sig_wake_up(int dum) { signal(SIGUSR1, sig_wake_up); } void wake_up(void) { kill(display_pid, SIGUSR1); } /* setup a standard chess starting position */ void reset_board(void) { setup_board(&state->position); state->computer = 0; state->moved = 0; state->always_think = always_think; state->use_mulling = use_mulling; state->need_reset = 1; state->moves_mulled = 0; if (state->always_think) lprintf(0,"Thinking on opponents time\n"); timer_reset(); state->analysed = 0; state->first_book_move = 1; estimate_game_stage(&state->position, 1); #if LEARN_EVAL learning=1; state->won = UNKNOWN; #endif #if STORE_LEAF_POS state->stored_move_num = 0; memset(state->predicted_move, 0, MAX_GAME_MOVES*sizeof(state->predicted_move[0])); #endif } static void display_move(Move *move) { print_move(&state->position, move); do_move(&state->position, &state->position, move); redraw(); } int player_moved(Square from, Square to) { Move move; move.from = from; move.to = to; move.v = INFINITY; if (!legal_move(&state->position, &move)) { lprintf(0, "Illegal move %s\n", short_movestr(&state->position, &move)); return 0; } state->game_record[state->position.move_num] = move; if (state->display_position) dump_ppn(&state->position); display_move(&move); return 1; } static void about(void) { lprintf(0,"\nThe author of KnightCap is Andrew Tridgell. He can be contacted\n" \ "via email Andrew.Tridgell@anu.edu.au.\n" \ "\n" \ "The ftp site for KnightCap is ftp://samba.anu.edu.au/pub/KnightCap/\n" \ "\n" \ "KnightCap is free software distributed under the GNU Public License\n" \ "\n" \ ); } static void help(void) { lprintf(0,"\n" \ "go start searching\n" \ "black computer plays black\n" \ "white computer plays white\n" \ "new reset the board\n" \ "time update computers time in hundredths of a second\n" \ "otim update oppenents time in hundredths of a second\n" \ "mtim update computers time in minutes\n" \ "level x xboard compat command\n" \ "load load a saved game\n" \ "save save a game\n" \ "quit quit!\n" \ "demo <1|0> enable/disable demo mode\n" \ "seek <1|0> enable/disable seeks (ics)\n" \ "answer <1|0> enable/disable seek answering (ics)\n" \ "ponder <1|0> enable/disable thinking on oppenents time\n" \ "robot <1|0> enable/disable ICS robot\n" \ "autoplay <1|0> enable/disable ICS autoplay\n" \ "mtime set time per move in seconds\n" \ "undo take back half moves\n" \ "redo replay half moves\n" \ "eval static evaluation debug\n" \ "ppn display the ppn of the current position\n" \ "ppnset set the current position to a ppn\n" \ "espeed measure eval fn speed\n" \ "testfin load a .fin test file and test each position\n" \ "print print the current board\n" \ "about show some info on KnightCap\n" \ "target try challenging when bored\n" \ "notarget undo the target command\n" \ "mm play n games against another computer\n" \ "color <1|0> enable/disable color ascii board display\n" \ "bclean clean the brain\n" \ ""); } int parse_stdin(char *line) { char *p; Move move; char tok[1000]; int hsecs, level, mins; static int increment; char load_file[100]; int on, i; int done = 0; p = line; while (next_token(&p, tok, "\n\r")) { /* is it a move? */ if (parse_move(tok, &state->position, &move)) { if (player_moved(move.from, move.to)) done = 1; } if (strcmp(tok,"go") == 0) { state->computer = next_to_play(&state->position); done = 1; } if (strcmp(tok,"black") == 0) { state->computer = -1; state->colour = -1; done = 1; } if (strcmp(tok,"white") == 0) { state->computer = 1; state->colour = 1; done = 1; } if (strcmp(tok,"new") == 0) { reset_board(); redraw(); done = 1; } if (sscanf(tok, "time %d", &hsecs) == 1) { timer_estimate(hsecs/100, hsecs/100, increment); done = 1; } if (sscanf(tok, "mtim %d %d", &hsecs, &increment) == 2) { timer_estimate((60*hsecs), (60*hsecs), increment); done = 1; } if (sscanf(tok, "otim %d", &hsecs) == 1) { if (hsecs <= 0) lprintf(0, "win on time!\n"); done = 1; } if (sscanf(tok, "level %d %d %d", &level, &mins, &increment) == 3) { lprintf(0,"setting increment to %d\n", increment); if (opponent) prog_printf("level %d %d %d\n", level, mins, increment); done = 1; } if (sscanf(tok, "testfin %s", load_file) == 1) { test_fin(load_file); done = 1; } if (sscanf(tok, "load %s", load_file) == 1) { restore_game(load_file); done = 1; } if (sscanf(tok, "save %s", load_file) == 1) { save_game(load_file); done = 1; } if (sscanf(tok, "target %s", play_target) == 1) { done = 1; } if (strcmp(tok, "notarget") == 0) { play_target[0] = 0; done = 1; } if (strcmp(tok, "quit") == 0) { if (!state->auto_exit) { state->quit = 1; state->stop_search = 2; prog_printf("quit\n"); prog_exit(); exit(0); } else { /* xboard doesn't tell us the outcome, so we do it manually */ state->won = STALEMATE; state->stop_search = 2; state->computer = 0; } done = 1; } if (strcmp(tok, "print") == 0) { print_board(state->position.board); done = 1; } if (sscanf(tok,"demo %d", &on) == 1) { demo_mode = on; done = 1; } if (sscanf(tok,"seek %d", &on) == 1) { seeking = on; done = 1; } if (sscanf(tok,"answer %d", &on) == 1) { seek_answering = on; done = 1; } if (sscanf(tok,"color %d", &on) == 1) { color_display = on; done = 1; } if (sscanf(tok,"mm %d", &match_remaining) == 1) { match_points = 0; match_total = 0; done = 1; } if (sscanf(tok,"robot %d", &on) == 1) { state->ics_robot = on; done = 1; } if (sscanf(tok,"autoplay %d", &on) == 1) { state->autoplay = on; done = 1; } if (sscanf(tok,"display %d", &on) == 1) { no_display = !on; done = 1; } if (sscanf(tok,"mull %d", &on) == 1) { state->use_mulling = on; if (opponent == KNIGHTCAP) prog_printf("mull %d\n", on); done = 1; } if (sscanf(tok,"pbrain %d", &on) == 1) { state->use_pbrain = on; done = 1; } if (sscanf(tok,"ponder %d", &on) == 1) { always_think = state->always_think = on; if (opponent == CRAFTY) if (on == 1) { prog_printf("ponder on\n"); } else if (on == 0) { prog_printf("ponder off\n"); } else if (opponent == KNIGHTCAP) prog_printf("ponder %d\n", on); done = 1; } if (sscanf(tok,"mtime %d", &i) == 1) { state->move_time = i; if (opponent == CRAFTY) prog_printf("st %d\n", i); else if (opponent == KNIGHTCAP) prog_printf("mtime %d\n", i); done = 1; } if (sscanf(tok,"undo %d", &i) == 1) { undo_menu(i); done = 1; } if (sscanf(tok,"redo %d", &i) == 1) { undo_menu(-i); done = 1; } if (strcmp(tok, "eval") == 0) { eval_debug(&state->position); done = 1; } if (strcmp(tok,"grad") == 0) { #if LEARN_EVAL float grad[__TOTAL_COEFFS__]; state->stored_move_num = 0; td_store_pos(&state->position); state->td_comp = next_to_play(&state->position); td_gradient(grad); #endif done = 1; } if (strcmp(tok, "ppn") == 0) { lprintf(0,"%s\n", position_to_ppn(&state->position)); done = 1; } if (strncmp(tok, "ppnset", 6) == 0) { char *p2 = strchr(tok,' '); if (p2) { while ((*p2) == ' ') p2++; ppn_to_position(p2, &state->position); } done = 1; } if (sscanf(tok,"espeed %d", &i) == 1) { eval_speed(&state->position, i); done = 1; } if (strcmp(tok, "help") == 0) { help(); done = 1; } if (strcmp(tok, "about") == 0) { about(); done = 1; } if (strcmp(tok, "bclean") == 0) { brain_clean(); done = 1; } } return done; } /* users can send us moves and commands on stdin */ static void check_stdin(void) { char line[200]; int n; fd_set set; struct timeval tval; static int nostdin; if (nostdin) return; FD_ZERO(&set); FD_SET(0, &set); tval.tv_sec = 0; tval.tv_usec = 0; if (select(1, &set, NULL, NULL, &tval) != 1) return; n = read(0, line, sizeof(line)-1); if (n <= 0) { lprintf(0,"disabling stdin\n"); nostdin = 1; if (state->auto_exit) { /* xboard often doesn't tell us the outcome, so we set state->won to STALEMATE and then work it out manually */ state->won = STALEMATE; state->stop_search = 2; state->computer = 0; } close(0); return; } line[n] = 0; if (!parse_stdin(line)) { if (prog_running()) { prog_printf("%s", line); } } } void update_display(void) { if (!no_display) { if (ascii_display) print_board(state->position.board); else draw_all(); } } void match_hook(void) { static int player = 1; if (match_remaining <= 0) return; /* perhaps we are already playing */ if (state->computer && !state->position.winner) return; if (state->position.winner) { if (state->position.winner == STALEMATE) { state->won = STALEMATE; match_points += 0.5; } else if (state->position.winner == player) { match_points += 1; state->won = 1; } else state->won = 0; state->position.winner = 0; match_total++; match_remaining--; lprintf(0,"match %1.1f points out of %d games\n", match_points, match_total); player = -player; } if (match_remaining <= 0) return; state->computer = 0; /* Give time to analyse last game */ if (state->use_mulling) { sleep(30); } else { #if LEARN_EVAL td_update(); td_dump("coeffs.dat"); #endif } reset_board(); prog_printf("new\n"); if (player == 1) { if (opponent == CRAFTY) prog_printf("white\n"); else if (opponent == KNIGHTCAP) prog_printf("black\n"); state->computer = 1; } else { if (opponent == CRAFTY) { prog_printf("white\n"); prog_printf("go\n"); } else if (opponent == KNIGHTCAP) prog_printf("white\n"); state->computer = -1; } } void idle_func(void) { int i; Move m1; static uint32 hash; static int loops; static int seek_loops; static int try_target=1; static int computer; check_stdin(); if (prog_check_move(&m1, next_to_play(&state->position))) { player_moved(m1.from, m1.to); } if (state->computer != 0) { computer = state->computer; state->colour = state->computer; } if (state->moved) { if (next_to_play(&state->position) == state->computer) { m1 = state->game_record[state->position.move_num]; if (legal_move(&state->position, &m1)) { prog_tell_move(&state->position, &m1); if (state->display_position) dump_ppn(&state->position); display_move(&m1); if (!state->ics_robot && (state->position.flags & FLAG_ACCEPT_DRAW)) prog_printf("draw\n"); if (state->position.winner) { state->computer = 0; if (state->position.winner == STALEMATE) prog_printf("draw\n"); if (state->ics_robot) { /* make sure we are right! */ prog_printf("refresh\n"); } } if (!state->ics_robot && !state->demo_mode) save_game("current.save"); } state->stop_search = 0; } state->moved = 0; } if (state->auto_exit && (state->position.winner || state->won == STALEMATE) && !state->analysed && !state->use_mulling) { FILE *f = fopen("results","a"); float res; /* work out who won (or was winning when it ended) */ if (!state->position.winner) { for (i=state->position.move_num-1;i>=0;i--) { if (state->game_record[i].v != INFINITY) { if (state->game_record[i].v > 2*PAWN_VALUE) { state->position.winner = 1; } else if (state->game_record[i].v < -2*PAWN_VALUE) { state->position.winner = -1; } else { state->position.winner = STALEMATE; } break; } } } if (computer == state->position.winner) { res = 1; state->won = 1; } else if (computer == -state->position.winner) { res = 0; state->won = 0; } else if (state->position.winner == STALEMATE) { res = 0.5; state->won = STALEMATE; } else { res = 0; } if (f) { fprintf(f,"%g\n", res); fclose(f); } #if LEARN_EVAL if (!state->analysed) { if (!state->use_pbrain) { td_update(); td_dump("coeffs.dat"); } else { state->moves_mulled = 0; state->use_mulling = 1; } } #endif } if (state->auto_exit && state->moves_mulled >= MAX_MOVES_MULLED && state->open) { lprintf(0,"***%d\n", state->moves_mulled); state->use_mulling=0; state->stop_search=1; sleep(5); system("killall sleep"); sleep(5); system("killall sleep"); exit(0); } if (need_redraw || hash != state->position.hash1) { need_redraw = 0; hash = state->position.hash1; update_display(); loops=0; } if (state->demo_mode && !state->position.winner && next_to_play(&state->position) != state->computer) { state->stop_search = 1; state->computer = next_to_play(&state->position); } if (!process_exists(move_pid) || (state->ics_robot && !prog_running())) { state->quit = 1; state->stop_search = 2; prog_printf("quit\n"); prog_exit(); exit(0); } if (state->computer) try_target = 1; if (state->ics_robot && state->computer == 0 && *play_target && try_target && state->open && state->analysed) { try_target = 0; prog_printf("match %s\n", play_target); } if (state->computer != 0) seek_loops = 0; if (++seek_loops > SEEK_WAIT_TIME*(1000*1000/SLEEP_TIME)) { if (state->open && state->ics_robot && state->computer == 0 && seeking && state->autoplay) { prog_printf("seeks\n"); seek_loops = 0; } } #if 1 if (++loops > REFRESH_TIME*(1000*1000/SLEEP_TIME)) { if (state->ics_robot && state->computer && state->computer != next_to_play(&state->position)) prog_printf("refresh\n"); loops=0; if (state->ics_robot && state->computer == 0 && *play_target && (random() % 1) == 0) { try_target = 1; } } #endif match_hook(); usleep(SLEEP_TIME); } void save_game(char *fname) { int fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd == -1) { perror(fname); return; } write(fd, state, sizeof(*state)); close(fd); } void restore_game(char *fname) { int fd = open(fname, O_RDONLY); if (fd == -1) { perror(fname); return; } read(fd, state, sizeof(*state)); close(fd); } /* undo the last n ply of the game Passing a negative number gives redo */ void undo_menu(int n) { int i; state->computer = 0; state->stop_search = 1; state->demo_mode = 0; n = state->position.move_num - n; if (n < 0) n = 0; setup_board(&state->position); for (i=0;igame_record[i]; if (!legal_move(&state->position, move)) break; do_move(&state->position, &state->position, move); } state->stop_search = 1; redraw(); } static void move_thread(void) { int pondered = 0; unsigned mull_counter=0; #if USE_PBRAIN if (state->use_pbrain) state->use_pbrain = brain_open(BRAIN_FILE); else brain_open(BRAIN_FILE); #endif #if TPROF tprof_init(); #endif #if 0 chdir("prof"); #endif #if APLINUX if (getcid() != 0) { move_slave(); exit(); } #endif while (!state->quit) { Move move; if (!process_exists(display_pid)) { #if USE_PBRAIN brain_close(); #endif exit(0); } if (state->need_reset) { hash_reset(); order_reset(); state->need_reset = 0; } #if LEARN_EVAL if (state->computer == 0 && !state->analysed && !state->ics_robot && !state->demo_mode && !state->use_mulling && !state->auto_exit) { td_update(); td_dump("coeffs.dat"); state->analysed = 1; state->colour = 0; } #endif if (state->computer == 0 && !state->analysed) { /* if we get bored waiting then start to mull on our brain */ analyse_game(); } if (state->computer == 0 && state->use_mulling) { if (mull_counter++ > MULL_WAIT_TIME*(1000*1000/SLEEP_TIME)) brain_mull(); } else { mull_counter = 0; } if (next_to_play(&state->position) != state->computer || state->moved || state->position.winner) { if (state->always_think && !state->position.winner && !pondered && state->computer == -next_to_play(&state->position)) { zero_move(&move); ponder_move(&state->position, &move); if (!is_zero_move(&move) && legal_move(&state->position, &move)) { state->game_record[state->position.move_num] = move; if (state->display_position) dump_ppn(&state->position); state->moved = 1; wake_up(); pondered = 0; } else { pondered = 1; } } if (!pondered || !state->computer) usleep(SLEEP_TIME); continue; } if (!pondered) usleep(SLEEP_TIME); pondered = 0; if (make_move(&state->position, &move)) { if (next_to_play(&state->position) == state->computer && legal_move(&state->position, &move)) { state->game_record[state->position.move_num] = move; state->moved = 1; wake_up(); } } else { state->computer = 0; } } #if USE_PBRAIN brain_close(); #endif #if TPROF tprof_end(); #endif sleep(1); exit(0); } static void create_threads(void) { int page_size; int len, shmid; int pid1, pid2; page_size = getpagesize(); len = sizeof(*state); len = (len + page_size) & ~(page_size-1); shmid = shmget(IPC_PRIVATE, len, SHM_R | SHM_W); if (shmid == -1) { printf("can't get shared memory\n"); exit(1); } state = (struct state *)shmat(shmid, 0, 0); if (!state || state == (struct state *)-1) { printf("can't attach to shared memory\n"); exit(1); } shmctl(shmid, IPC_RMID, 0); memset(state, 0, len); state->move_time = default_move_time; state->use_mulling = USE_PBRAIN; state->use_pbrain = 1; state->hash_table_size = def_hash_table_size; reset_board(); init_movements(); #ifdef __FreeBSD__ signal(SIGCHLD, SIG_IGN); #else signal(SIGCLD, SIG_IGN); #endif pid1 = getpid(); #ifdef APLINUX move_pid = getpid(); if (getcid() != 0 || fork() != 0) { log_close(); move_thread(); exit(0); } #else if ((pid2=fork()) != 0) { if (getpid() == pid1) { display_pid = pid2; } else { display_pid = pid1; } move_thread(); exit(0); } #endif if (getpid() == pid1) { move_pid = pid2; } else { move_pid = pid1; } } static void usage(void) { printf("\n" \ "-w \n" \ "-c \n" \ "-s \n" \ "-n no display\n" \ "-a ascii display\n" \ "-B b&w ascii display\n" \ "-e issue seeks in ICS mode\n" \ "-z answer computer seeks in ICS mode\n" \ "-d demo mode\n" \ "-m play n games against another computer\n"\ "-I ICS mode\n" \ "-M set ICS master\n" \ "-t