From 3fd43cd5fcb22bb65bf2a92a25d95d801b11c9e0 Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Wed, 1 Jun 2016 18:58:58 -0700 Subject: Initial checkin for typhoon chess engine. --- src/genetic/main.cpp | 918 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 918 insertions(+) create mode 100644 src/genetic/main.cpp (limited to 'src/genetic/main.cpp') diff --git a/src/genetic/main.cpp b/src/genetic/main.cpp new file mode 100644 index 0000000..aa7ec73 --- /dev/null +++ b/src/genetic/main.cpp @@ -0,0 +1,918 @@ +/* + * 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; +} -- cgit v1.3