summaryrefslogtreecommitdiff
path: root/src/genetic/main.cpp
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2016-06-01 18:58:58 -0700
committerScott Gasch <[email protected]>2016-06-01 18:58:58 -0700
commit3fd43cd5fcb22bb65bf2a92a25d95d801b11c9e0 (patch)
tree9b6443235d16ba17f094a1a1c7ae53d2bcb9267b /src/genetic/main.cpp
Initial checkin for typhoon chess engine.
Diffstat (limited to 'src/genetic/main.cpp')
-rw-r--r--src/genetic/main.cpp918
1 files changed, 918 insertions, 0 deletions
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 <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <math.h>
+#include <sys/wait.h>
+
+
+/*
+ * 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 <pthread.h>
+#include <semaphore.h>
+
+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;
+}