/* * INCLUDES */ #include #include #include #include #include #include #include #include #include #include /* * GLOBALS */ int start_time; bool pipe_logging = false; int only_opening = -1; FILE *pgn_log = NULL; FILE *match_log = NULL; /* * Misc */ void strip_newline(char *s) { int i; for (i = 0;; ++i) { if (!s[i] || s[i] == '\r' || s[i] == '\n') { s[i] = 0; return; } } } void copy_result(char *to, char *from) { int i; for (i = 0; i < strlen(from); ++i) if (from[i] == '0' || from[i] == '1') break; strcpy(to, &from[i]); for (i = 0; i < strlen(to); ++i) if (to[i] == '}') break; to[i + 1] = 0; } /* * MyLock */ #include #include class MyLock { public: MyLock(); void get(); void release(); private: pthread_mutex_t the_lock; }; MyLock::MyLock() { pthread_mutex_init(&the_lock, NULL); } void MyLock::get() { for (;;) { if (pthread_mutex_lock(&the_lock) == 0) // EBUSY?? break; usleep(1); } } void MyLock::release() { pthread_mutex_unlock(&the_lock); } MyLock glock; /* * Settings */ class Settings { public: Settings() { count = 0; }; bool read(char *filename); char * get(char *name); private: char map[20][2][1000]; int count; }; Settings settings; bool Settings::read(char *filename) { FILE *f; char line[1000]; char var[1000]; printf("Reading settings file: %s\n", filename); count = 0; f = fopen(filename, "r"); if (!f) { printf("Couldn't open file.\n"); return false; } for (;;) { line[0] = 0; fgets(line, 1000, f); if (!line[0]) break; strip_newline(line); sscanf(line, "%s", var); strcpy(map[count][0], var); strcpy(map[count][1], line + strlen(var) + 1); count++; } fclose(f); return true; } char * Settings::get(char *name) { int i; for (i = 0; i < count; ++i) if (!strcmp(map[i][0], name)) return map[i][1]; printf("Setting not found: %s\n", name); exit(1); } /* * ChildProc */ class ChildProc { public: ChildProc(); virtual ~ChildProc(); virtual void start(char *cmd_line); virtual void stop(); void send(char *line); void receive(char *line, int size); bool poll(); void flush(); char * get_cmd() { return cmd; } private: void init(); char cmd[100]; bool running; int pid; int to_prog[2]; int from_prog[2]; FILE *to; FILE *from; }; ChildProc::ChildProc() { init(); } ChildProc::~ChildProc() { stop(); } void ChildProc::init() { strcpy(cmd, ""); pid = 0; to = NULL; from = NULL; running = false; } void ChildProc::start(char *cmd_line) { char *argv[100]; char arg_buff[100][100]; int args = 0, i = 0, j = 0; argv[0] = arg_buff[0]; for (;;) { argv[args][i] = cmd_line[j]; if (!argv[args][i] || argv[args][i] == ' ') { argv[args][i] = 0; args++; argv[args] = arg_buff[args]; i = -1; // will get incremented to 0 } if (!cmd_line[j]) break; i++; j++; } argv[args] = NULL; stop(); glock.get(); pipe(to_prog); pipe(from_prog); if ((pid = fork()) == 0) // what to do with glock here? { dup2(to_prog[0], 0); dup2(from_prog[1], 1); close(to_prog[0]); close(to_prog[1]); close(from_prog[0]); close(from_prog[1]); dup2(1, fileno(stderr)); // force stderr to the pipe execvp(argv[0], argv); perror(argv[0]); exit(1); } if (pid == -1) { printf("fork() failed, command line: %s\n", cmd_line); exit(1); } close(to_prog[0]); close(from_prog[1]); from = fdopen(from_prog[0], "r"); to = fdopen(to_prog[1], "w"); setbuf(from, NULL); setbuf(to, NULL); glock.release(); running = true; strcpy(cmd, argv[0]); } void ChildProc::stop() { int x; if (!running) return; glock.get(); fclose(to); fclose(from); close(to_prog[1]); close(from_prog[0]); glock.release(); kill(pid, SIGTERM); waitpid(pid, &x, 0); init(); } void ChildProc::send(char *line) { char temp[1000]; int i; if (!running) return; strcpy(temp, line); strip_newline(temp); glock.get(); fprintf(to, "%s\n", temp); glock.release(); if (pipe_logging) printf("%s send: %s\n", cmd, temp); } void ChildProc::receive(char *line, int size) { int i; line[0] = 0; if (!running) return; if (!poll()) return; glock.get(); fgets(line, size, from); glock.release(); strip_newline(line); if (pipe_logging) printf("%s recv: %s\n", cmd, line); } bool ChildProc::poll() { fd_set myset; timeval mytime; int r; FD_ZERO(&myset); FD_SET(from_prog[0], &myset); mytime.tv_sec = 0; mytime.tv_usec = 0; glock.get(); r = select(from_prog[0] + 1, &myset, NULL, NULL, &mytime); glock.release(); return (r > 0) ? true : false; } void ChildProc::flush() { char line[1000]; while (poll()) { receive(line, 1000); if (!line[0]) break; } } /* * ChessEngine */ class ChessEngine : private ChildProc { public: void start(char *cmd_line, char *init_str_arg); void stop(); void new_game(); void send_move(char *move); void get_move(char *move); bool is_game_over() { return game_over; } char * get_result() { return result; } private: bool game_over; char result[100]; char init_str[1000]; }; void ChessEngine::start(char *cmd_line, char *init_str_arg) { ChildProc::start(cmd_line); send("xboard\n"); strcpy(init_str, init_str_arg); } void ChessEngine::stop() { send("quit\n"); ChildProc::stop(); } void ChessEngine::new_game() { char temp[1000]; // stop the engine and flush leftover output // only necessary when reusing the engine /*send("?\n"); usleep(100000); flush();*/ send("new\n"); send("easy\n"); sprintf(temp, "%s\n", init_str); send(temp); send("force\n"); game_over = false; } void ChessEngine::send_move(char *move) { char s[100]; if (game_over) return; sprintf(s, "%s\n", move); send(s); } void ChessEngine::get_move(char *move) { char line[1000]; char tok[1000]; move[0] = 0; while (poll()) // does the engine think the game is over? { receive(line, 1000); if (strchr(line, '{')) { game_over = true; copy_result(result, line); } } if (game_over) return; send("go\n"); for (;;) { receive(line, 1000); if (!line[0]) { usleep(1000); continue; } if (strchr(line, '{')) { game_over = true; copy_result(result, line); break; } sscanf(line, "%s", tok); if (!strcmp(tok, "move")) { sscanf(line, "move %s", move); break; } } if (!game_over) send("force\n"); } /* * WorkItems */ const int max_work_items = 5000; const int max_game_ply = 400; struct WorkItem { volatile int status; bool printed; int white_engine; int opening_id; char opening[200]; char move[max_game_ply + 2][10]; int moves; char result[100]; }; WorkItem *work_item[max_work_items]; int work_items; bool make_work_items(char *filename) { FILE *f; char line[1000]; int openings = 0; WorkItem *w; printf("Creating work items from file: %s\n", filename); work_items = 0; f = fopen(filename, "r"); if (!f) { printf("Couldn't open file.\n"); return false; } for (;;) { line[0] = 0; fgets(line, 1000, f); if (!line[0]) break; strip_newline(line); openings++; if (only_opening != -1 && openings != only_opening) continue; w = new WorkItem; w->status = 0; w->opening_id = openings; strcpy(w->opening, line); w->white_engine = 1; work_item[work_items++] = w; w = new WorkItem; w->status = 0; w->opening_id = openings; strcpy(w->opening, line); w->white_engine = 2; work_item[work_items++] = w; } fclose(f); printf("%d work items created.\n", work_items); return true; } void print_game(WorkItem *w) { char white_name[100]; char black_name[100]; char line[1000]; char short_result[100]; char *temp; int m, i, line_break; if (w->white_engine == 1) { strcpy(white_name, settings.get("engine1")); strcpy(black_name, settings.get("engine2")); } else { strcpy(white_name, settings.get("engine2")); strcpy(black_name, settings.get("engine1")); } sprintf(line, "Opening: %d, %s vs. %s, result: %s\n", w->opening_id, white_name, black_name, w->result); printf(line); fprintf(match_log, line); fflush(match_log); strcpy(short_result, w->result); temp = strchr(short_result, ' '); if (temp) *temp = 0; fprintf(pgn_log, "[Round \"%d.%d\"]\n", w->opening_id, w->white_engine); fprintf(pgn_log, "[White \"%s\"]\n", white_name); fprintf(pgn_log, "[Black \"%s\"]\n", black_name); fprintf(pgn_log, "[Result \"%s\"]\n", short_result); fprintf(pgn_log, "\n"); m = 0; line_break = 0; for (i = 1;; ++i) { if (m == w->moves) { fprintf(pgn_log, "%s\n", w->result); break; } fprintf(pgn_log, "%d. %s ", i, w->move[m++]); if (m < w->moves) fprintf(pgn_log, "%s ", w->move[m++]); if (m > line_break + 8) { fprintf(pgn_log, "\n"); line_break = m; } } fprintf(pgn_log, "\n"); fflush(pgn_log); w->printed = true; } void print_stats(int items) { int i; int win = 0, lose = 0, draw = 0; int games = 0; double percent; double elo; char elo_sign = '+'; int elapsed_time; int min, sec; double spg = 10.0; double percent_done; double eta; int eta_min, eta_sec; for (i = 0; i < items; ++i) { if (work_item[i]->status != 2) // shouldn't happen break; if (strstr(work_item[i]->result, "1-0")) { if (work_item[i]->white_engine == 1) win++; else lose++; } if (strstr(work_item[i]->result, "0-1")) { if (work_item[i]->white_engine == 1) lose++; else win++; } if (strstr(work_item[i]->result, "1/2-1/2")) draw++; } games = win + lose + draw; percent = (double)win + (0.5 * (double)draw); percent /= (double)games; if (percent > 0.99999) elo = 400.0; else if (percent < 0.00001) elo = -400.0; else elo = -400.0 * log(1.0 / percent - 1.0) / log(10.0); if (elo < 0.0) { elo_sign = '-'; elo = -elo; } elapsed_time = time(NULL) - start_time; sec = elapsed_time; min = sec / 60; sec -= min * 60; if (elapsed_time) spg = (double)(time(NULL) - start_time) / (double)games; percent_done = (double)games / (double)work_items; eta = (double)work_items - (double)games; eta *= spg; eta_sec = (int)eta; eta_min = eta_sec / 60; eta_sec -= eta_min * 60; printf("%s vs. %s: +%d -%d =%d, %.2f%c, ELO: %c%d\n", settings.get("engine1"), settings.get("engine2"), win, lose, draw, (float)(percent * 100.0), '%', elo_sign, (int)elo); printf("%d games in %d:%02d, %.2f seconds/game, %.1f%c done, ETA: %d:%02d\n", games, min, sec, (float)spg, (float)(percent_done * 100.0), '%', eta_min, eta_sec); printf("\n"); } /* * WorkerThread */ class WorkerThread { public: void do_work(int id_arg); void do_item(WorkItem *w); private: void do_opening(WorkItem *w); void add_move(WorkItem *w, char *move); int id; ChessEngine engine1, engine2; }; void WorkerThread::do_work(int id_arg) { int i; id = id_arg; for (;;) { glock.get(); for (i = 0; i < work_items; ++i) if (work_item[i]->status == 0) break; if (i != work_items) work_item[i]->status = 1; glock.release(); if (i == work_items) break; do_item(work_item[i]); } } void WorkerThread::do_item(WorkItem *w) { char move[100]; ChessEngine *etm; // engine to move ChessEngine *etw; // engine to wait ChessEngine *temp; w->printed = false; w->moves = 0; engine1.start(settings.get("cmd1"), settings.get("init1")); engine2.start(settings.get("cmd2"), settings.get("init2")); engine1.new_game(); engine2.new_game(); do_opening(w); etm = &engine1; etw = &engine2; if ((w->moves & 1) == 1 && w->white_engine == 1) { etm = &engine2; etw = &engine1; } if ((w->moves & 1) == 0 && w->white_engine == 2) { etm = &engine2; etw = &engine1; } for (;;) { etm->get_move(move); if (etm->is_game_over()) { strcpy(w->result, etm->get_result()); break; } etw->send_move(move); add_move(w, move); if (w->moves >= max_game_ply) { strcpy(w->result, "1/2-1/2 {Game too long}"); break; } temp = etm; etm = etw; etw = temp; } engine1.stop(); engine2.stop(); w->status = 2; } void WorkerThread::do_opening(WorkItem *w) { char move[100]; int i = 0, j = 0; for (;;) { move[i] = w->opening[j]; if (!move[i] || move[i] == ' ') { move[i] = 0; engine1.send_move(move); engine2.send_move(move); add_move(w, move); i = -1; } if (!w->opening[j]) break; i++; j++; } } void WorkerThread::add_move(WorkItem *w, char *move) { strcpy(w->move[w->moves++], move); } /* * threads */ WorkerThread *worker[100]; pthread_t *mythread[100]; volatile int thread_count = 0; void * thread_proc(void *arg) { int id; glock.get(); id = thread_count++; glock.release(); worker[id] = new WorkerThread; worker[id]->do_work(id); delete worker[id]; pthread_exit(&mythread[id]); delete mythread[id]; // ? } void start_threads(int count) { int i; int x = 0; printf("Starting threads...\n"); for (i = 0; i < count; ++i) { mythread[i] = new pthread_t; pthread_create(mythread[i], NULL, thread_proc, (void *)&x); } printf("Started %d threads.\n", i); } /* * main */ main(int argc, char *argv[]) { int i; char settings_filename[100]; int threads = 1; start_time = time(NULL); strcpy(settings_filename, "settings.txt"); for (i = 1;;) { if (i >= argc) break; if (!strcmp(argv[i], "-l")) { pipe_logging = true; } else if (!strcmp(argv[i], "-o")) { only_opening = atoi(argv[++i]); } else if (!strcmp(argv[i], "-t")) { threads = atoi(argv[++i]); } else if (argv[i][0] == '-') { printf("Usage: shiai [options] [settings file]\n"); printf("\n"); printf(" -h : Display this screen\n"); printf(" -l : Display I/O with engines (\"log\")\n"); printf(" -o # : Run opening number #\n"); printf(" -t # : Run with # threads\n"); printf("\n"); exit(0); } else { strcpy(settings_filename, argv[i]); } i++; } if (!settings.read(settings_filename)) exit(1); if (!make_work_items(settings.get("openings"))) exit(1); pgn_log = fopen(settings.get("pgnlog"), "w"); if (!pgn_log) { printf("Couldn't open %s for writing.\n", settings.get("pgnlog")); exit(1); } match_log = fopen(settings.get("matchlog"), "w"); if (!match_log) { printf("Couldn't open %s for writing.\n", settings.get("matchlog")); exit(1); } if (threads <= 0 || threads > 16) threads = 1; start_threads(threads); for (i = 0; i < work_items; ++i) { while (work_item[i]->status != 2) sleep(1); print_game(work_item[i]); print_stats(i + 1); } fclose(pgn_log); fclose(match_log); return 0; }