Initial checkin for typhoon chess engine.
[typhoon.git] / src / autoplayer / main.cpp
1
2 /*
3  *      INCLUDES
4  */
5
6 #include <stdio.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10 #include <signal.h>
11 #include <time.h>
12 #include <sys/time.h>
13 #include <sys/types.h>
14 #include <math.h>
15 #include <sys/wait.h>
16
17
18 /*
19  *      GLOBALS
20  */
21
22 int start_time;
23 bool pipe_logging = false; 
24 int only_opening = -1;
25 FILE *pgn_log = NULL;
26 FILE *match_log = NULL;
27
28
29
30
31 /*
32  *      Misc
33  */
34
35 void strip_newline(char *s)
36 {
37         int i;
38         
39         for (i = 0;; ++i)
40                 {
41                 if (!s[i] || s[i] == '\r' || s[i] == '\n')
42                         {
43                         s[i] = 0;
44                         return;
45                         }
46                 }
47 }
48
49 void copy_result(char *to, char *from)
50 {
51         int i;
52         
53         for (i = 0; i < strlen(from); ++i)
54                 if (from[i] == '0' || from[i] == '1')
55                         break;
56         strcpy(to, &from[i]);
57         for (i = 0; i < strlen(to); ++i)
58                 if (to[i] == '}')
59                         break;
60         to[i + 1] = 0;
61 }
62
63
64
65
66 /*
67  *      MyLock
68  */
69
70 #include <pthread.h>
71 #include <semaphore.h>
72
73 class MyLock
74 {
75 public:
76         MyLock();
77         void get();
78         void release();
79
80 private:
81         pthread_mutex_t the_lock;
82 };
83
84 MyLock::MyLock()
85 {
86         pthread_mutex_init(&the_lock, NULL);
87 }
88
89 void MyLock::get()
90 {
91         for (;;)
92                 {
93                 if (pthread_mutex_lock(&the_lock) == 0)  // EBUSY??
94                         break;
95                 usleep(1);
96                 }
97 }
98
99 void MyLock::release()
100 {
101         pthread_mutex_unlock(&the_lock);
102 }
103
104 MyLock glock;
105
106
107
108
109 /*
110  *      Settings
111  */
112
113 class Settings
114 {
115 public:
116         Settings() { count = 0; };
117         bool read(char *filename);
118         char * get(char *name);
119
120 private:
121         char map[20][2][1000];
122         int count;
123 };
124
125 Settings settings;
126
127 bool Settings::read(char *filename)
128 {
129         FILE *f;
130         char line[1000];
131         char var[1000];
132
133         printf("Reading settings file: %s\n", filename);
134         count = 0;
135         f = fopen(filename, "r");
136         if (!f)
137                 {
138                 printf("Couldn't open file.\n");
139                 return false;
140                 }
141         for (;;)
142                 {
143                 line[0] = 0;
144                 fgets(line, 1000, f);
145                 if (!line[0])
146                         break;
147                 strip_newline(line);
148                 sscanf(line, "%s", var);
149                 strcpy(map[count][0], var);
150                 strcpy(map[count][1], line + strlen(var) + 1);
151                 count++;
152                 }
153         fclose(f);
154         return true;
155 }
156
157 char * Settings::get(char *name)
158 {
159         int i;
160         
161         for (i = 0; i < count; ++i)
162                 if (!strcmp(map[i][0], name))
163                         return map[i][1];
164         printf("Setting not found: %s\n", name);
165         exit(1);
166 }
167
168
169
170
171 /*
172  *      ChildProc
173  */
174
175
176
177
178
179 /*
180  *      ChessEngine
181  */
182
183 class ChessEngine : private ChildProc
184 {
185 public:
186         void start(char *cmd_line, char *init_str_arg);
187         void stop();
188         void new_game();
189         void send_move(char *move);
190         void get_move(char *move);
191         bool is_game_over() { return game_over; }
192         char * get_result() { return result; }
193         
194 private:
195         bool game_over;
196         char result[100];
197         char init_str[1000];
198 };
199
200 void ChessEngine::start(char *cmd_line, char *init_str_arg)
201 {
202         ChildProc::start(cmd_line);
203         send("xboard\n");
204         strcpy(init_str, init_str_arg);
205 }
206
207 void ChessEngine::stop()
208 {
209         send("quit\n");
210         ChildProc::stop();
211 }
212
213 void ChessEngine::new_game()
214 {
215         char temp[1000];
216         
217         // stop the engine and flush leftover output
218         // only necessary when reusing the engine
219         /*send("?\n");
220         usleep(100000);
221         flush();*/
222
223         send("new\n");
224         send("easy\n");
225         sprintf(temp, "%s\n", init_str);
226         send(temp);
227         send("force\n");
228
229         game_over = false;
230 }
231
232 void ChessEngine::send_move(char *move)
233 {
234         char s[100];
235         
236         if (game_over)
237                 return;
238         sprintf(s, "%s\n", move);
239         send(s);
240 }
241
242 void ChessEngine::get_move(char *move)
243 {
244         char line[1000];
245         char tok[1000];
246         
247         move[0] = 0;
248         while (poll())  // does the engine think the game is over?
249                 {
250                 receive(line, 1000);
251                 if (strchr(line, '{'))
252                         {
253                         game_over = true;
254                         copy_result(result, line);
255                         }
256                 }
257         if (game_over)
258                 return;
259         send("go\n");
260         for (;;)
261                 {
262                 receive(line, 1000);
263                 if (!line[0])
264                         {
265                         usleep(1000);
266                         continue;
267                         }               
268                 if (strchr(line, '{'))
269                         {
270                         game_over = true;
271                         copy_result(result, line);
272                         break;
273                         }
274                 sscanf(line, "%s", tok);
275                 if (!strcmp(tok, "move"))
276                         {
277                         sscanf(line, "move %s", move);
278                         break;
279                         }
280                 }
281         if (!game_over)
282                 send("force\n");
283 }
284
285
286
287
288 /*
289  *      WorkItems
290  */
291
292 const int max_work_items = 5000;
293 const int max_game_ply = 400;
294
295 struct WorkItem
296 {
297         volatile int status;
298         bool printed;
299         
300         int white_engine;
301         int opening_id;
302         char opening[200];
303         char move[max_game_ply + 2][10];
304         int moves;
305         char result[100];
306 };
307
308 WorkItem *work_item[max_work_items];
309 int work_items;
310
311 bool make_work_items(char *filename)
312 {
313         FILE *f;
314         char line[1000];
315         int openings = 0;
316         WorkItem *w;
317
318         printf("Creating work items from file: %s\n", filename);
319         work_items = 0;
320         f = fopen(filename, "r");
321         if (!f)
322                 {
323                 printf("Couldn't open file.\n");
324                 return false;
325                 }
326         for (;;)
327                 {
328                 line[0] = 0;
329                 fgets(line, 1000, f);
330                 if (!line[0])
331                         break;
332                 strip_newline(line);
333                 openings++;
334                 
335                 if (only_opening != -1 && openings != only_opening)
336                         continue;
337                 
338                 w = new WorkItem;
339                 w->status = 0;
340                 w->opening_id = openings;
341                 strcpy(w->opening, line);
342                 w->white_engine = 1;
343                 work_item[work_items++] = w;
344
345                 w = new WorkItem;
346                 w->status = 0;
347                 w->opening_id = openings;
348                 strcpy(w->opening, line);
349                 w->white_engine = 2;
350                 work_item[work_items++] = w;
351                 }
352         fclose(f);
353         printf("%d work items created.\n", work_items);
354         return true;
355 }
356
357 void print_game(WorkItem *w)
358 {
359         char white_name[100];
360         char black_name[100];
361         char line[1000];
362         char short_result[100];
363         char *temp;
364         int m, i, line_break;
365         
366         if (w->white_engine == 1)
367                 {
368                 strcpy(white_name, settings.get("engine1"));
369                 strcpy(black_name, settings.get("engine2"));
370                 }
371         else
372                 {
373                 strcpy(white_name, settings.get("engine2"));
374                 strcpy(black_name, settings.get("engine1"));
375                 }
376         sprintf(line, "Opening: %d, %s vs. %s, result: %s\n",
377                         w->opening_id,
378                         white_name,
379                         black_name,
380                         w->result);
381         printf(line);
382         fprintf(match_log, line);
383         fflush(match_log);
384         
385         strcpy(short_result, w->result);
386         temp = strchr(short_result, ' ');
387         if (temp)
388                 *temp = 0;
389         fprintf(pgn_log, "[Round \"%d.%d\"]\n", w->opening_id, w->white_engine);
390         fprintf(pgn_log, "[White \"%s\"]\n", white_name);
391         fprintf(pgn_log, "[Black \"%s\"]\n", black_name);
392         fprintf(pgn_log, "[Result \"%s\"]\n", short_result);
393         fprintf(pgn_log, "\n");
394         m = 0;
395         line_break = 0;
396         for (i = 1;; ++i)
397                 {
398                 if (m == w->moves)
399                         {
400                         fprintf(pgn_log, "%s\n", w->result);
401                         break;
402                         }
403                 fprintf(pgn_log, "%d. %s ", i, w->move[m++]);
404                 if (m < w->moves)
405                         fprintf(pgn_log, "%s ", w->move[m++]);
406                 if (m > line_break + 8)
407                         {
408                         fprintf(pgn_log, "\n");
409                         line_break = m;
410                         }
411                 }
412         fprintf(pgn_log, "\n");
413         fflush(pgn_log);
414         
415         w->printed = true;
416 }
417
418 void print_stats(int items)
419 {
420         int i;
421         int win = 0, lose = 0, draw = 0;
422         int games = 0;
423         double percent;
424         double elo;
425         char elo_sign = '+';
426         int elapsed_time;
427         int min, sec;
428         double spg = 10.0;
429         double percent_done;
430         double eta;
431         int eta_min, eta_sec;
432         
433         for (i = 0; i < items; ++i)
434                 {
435                 if (work_item[i]->status != 2)  // shouldn't happen
436                         break;
437                 if (strstr(work_item[i]->result, "1-0"))
438                         {
439                         if (work_item[i]->white_engine == 1)
440                                 win++;
441                         else
442                                 lose++;
443                         }
444                 if (strstr(work_item[i]->result, "0-1"))
445                         {
446                         if (work_item[i]->white_engine == 1)
447                                 lose++;
448                         else
449                                 win++;
450                         }
451                 if (strstr(work_item[i]->result, "1/2-1/2"))
452                         draw++;
453                 }
454         games = win + lose + draw;
455         
456         percent = (double)win + (0.5 * (double)draw);
457         percent /= (double)games;
458         if (percent > 0.99999)
459                 elo = 400.0;
460         else if (percent < 0.00001)
461                 elo = -400.0;
462         else
463                 elo = -400.0 * log(1.0 / percent - 1.0) / log(10.0);
464         if (elo < 0.0)
465                 {
466                 elo_sign = '-';
467                 elo = -elo;
468                 }
469         elapsed_time = time(NULL) - start_time;
470         sec = elapsed_time;
471         min = sec / 60;
472         sec -= min * 60;
473         if (elapsed_time)
474                 spg = (double)(time(NULL) - start_time) / (double)games;
475         percent_done = (double)games / (double)work_items;
476         eta = (double)work_items - (double)games;
477         eta *= spg;
478         eta_sec = (int)eta;
479         eta_min = eta_sec / 60;
480         eta_sec -= eta_min * 60;
481         
482         printf("%s vs. %s: +%d -%d =%d, %.2f%c, ELO: %c%d\n",
483                         settings.get("engine1"),
484                         settings.get("engine2"),
485                         win, lose, draw,
486                         (float)(percent * 100.0), '%',
487                         elo_sign, (int)elo);
488         printf("%d games in %d:%02d, %.2f seconds/game, %.1f%c done, ETA: %d:%02d\n",
489                         games, min, sec, (float)spg,
490                         (float)(percent_done * 100.0), '%',
491                         eta_min, eta_sec);
492         printf("\n");
493 }
494
495
496
497 /*
498  *      WorkerThread
499  */
500
501 class WorkerThread
502 {
503 public:
504         void do_work(int id_arg);
505         void do_item(WorkItem *w);
506
507 private:
508         void do_opening(WorkItem *w);
509         void add_move(WorkItem *w, char *move);
510
511         int id;
512         ChessEngine engine1, engine2;
513 };
514
515 void WorkerThread::do_work(int id_arg)
516 {
517         int i;
518         
519         id = id_arg;
520         for (;;)
521                 {
522                 glock.get();
523                 for (i = 0; i < work_items; ++i)
524                         if (work_item[i]->status == 0)
525                                 break;
526                 if (i != work_items)
527                         work_item[i]->status = 1;
528                 glock.release();
529
530                 if (i == work_items)
531                         break;
532                 do_item(work_item[i]);
533                 }
534 }
535
536 void WorkerThread::do_item(WorkItem *w)
537 {
538         char move[100];
539         ChessEngine *etm;  // engine to move
540         ChessEngine *etw;  // engine to wait
541         ChessEngine *temp;
542
543         w->printed = false;
544         w->moves = 0;
545         
546         engine1.start(settings.get("cmd1"), settings.get("init1"));
547         engine2.start(settings.get("cmd2"), settings.get("init2"));
548         engine1.new_game();
549         engine2.new_game();
550         do_opening(w);
551         
552         etm = &engine1;
553         etw = &engine2;
554         if ((w->moves & 1) == 1 && w->white_engine == 1)
555                 {
556                 etm = &engine2;
557                 etw = &engine1;
558                 }
559         if ((w->moves & 1) == 0 && w->white_engine == 2)
560                 {
561                 etm = &engine2;
562                 etw = &engine1;
563                 }
564         for (;;)
565                 {
566                 etm->get_move(move);
567                 if (etm->is_game_over())
568                         {
569                         strcpy(w->result, etm->get_result());
570                         break;
571                         }
572                 etw->send_move(move);
573                 add_move(w, move);
574                 if (w->moves >= max_game_ply)
575                         {
576                         strcpy(w->result, "1/2-1/2 {Game too long}");
577                         break;
578                         }
579                 temp = etm;
580                 etm = etw;
581                 etw = temp;
582                 }
583         
584         engine1.stop();
585         engine2.stop();
586         w->status = 2;
587 }
588
589 void WorkerThread::do_opening(WorkItem *w)
590 {
591         char move[100];
592         int i = 0, j = 0;
593         
594         for (;;)
595                 {
596                 move[i] = w->opening[j];
597                 if (!move[i] || move[i] == ' ')
598                         {
599                         move[i] = 0;
600                         engine1.send_move(move);
601                         engine2.send_move(move);
602                         add_move(w, move);
603                         i = -1;
604                         }
605                 if (!w->opening[j])
606                         break;
607                 i++;
608                 j++;
609                 }
610 }
611
612 void WorkerThread::add_move(WorkItem *w, char *move)
613 {
614         strcpy(w->move[w->moves++], move);
615 }
616
617
618
619
620 /*
621  *      threads
622  */
623
624 WorkerThread *worker[100];
625 pthread_t *mythread[100];
626 volatile int thread_count = 0;
627
628 void * thread_proc(void *arg)
629 {
630         int id;
631         
632         glock.get();
633         id = thread_count++;
634         glock.release();
635         worker[id] = new WorkerThread;
636         worker[id]->do_work(id);
637         delete worker[id];
638         pthread_exit(&mythread[id]);
639         delete mythread[id];  // ?
640 }
641
642
643 void start_threads(int count)
644 {
645         int i;
646         int x = 0;
647
648         printf("Starting threads...\n");
649         for (i = 0; i < count; ++i)
650                 {
651                 mythread[i] = new pthread_t;
652                 pthread_create(mythread[i], NULL, thread_proc, (void *)&x);
653                 }
654         printf("Started %d threads.\n", i);
655 }
656
657
658
659
660 /*
661  *      main
662  */
663
664 main(int argc, char *argv[])
665 {
666         int i;
667         char settings_filename[100];
668         int threads = 1;
669         
670         start_time = time(NULL);
671         strcpy(settings_filename, "settings.txt");
672
673         for (i = 1;;)
674                 {
675                 if (i >= argc)
676                         break;
677                 if (!strcmp(argv[i], "-l"))
678                         {
679                         pipe_logging = true;
680                         }
681                 else if (!strcmp(argv[i], "-o"))
682                         {
683                         only_opening = atoi(argv[++i]);
684                         }
685                 else if (!strcmp(argv[i], "-t"))
686                         {
687                         threads = atoi(argv[++i]);
688                         }
689                 else if (argv[i][0] == '-')
690                         {
691                         printf("Usage: shiai [options] [settings file]\n");
692                         printf("\n");
693                         printf(" -h   : Display this screen\n");
694                         printf(" -l   : Display I/O with engines (\"log\")\n");
695                         printf(" -o # : Run opening number #\n");
696                         printf(" -t # : Run with # threads\n");
697                         printf("\n");
698                         exit(0);
699                         }
700                 else
701                         {
702                         strcpy(settings_filename, argv[i]);
703                         }
704                 i++;
705                 }
706         
707         if (!settings.read(settings_filename))
708                 exit(1);
709         if (!make_work_items(settings.get("openings")))
710                 exit(1);
711         
712
713         pgn_log = fopen(settings.get("pgnlog"), "w");
714         if (!pgn_log)
715                 {
716                 printf("Couldn't open %s for writing.\n", settings.get("pgnlog"));
717                 exit(1);
718                 }
719
720   match_log = fopen(settings.get("matchlog"), "w");
721         if (!match_log)
722                 {
723                 printf("Couldn't open %s for writing.\n", settings.get("matchlog"));
724                 exit(1);
725                 }
726         
727         if (threads <= 0 || threads > 16)
728                 threads = 1;
729         start_threads(threads);
730         for (i = 0; i < work_items; ++i)
731                 {
732                 while (work_item[i]->status != 2)
733                         sleep(1);
734                 print_game(work_item[i]);
735                 print_stats(i + 1);
736                 }
737         
738         fclose(pgn_log);
739         fclose(match_log);
740         return 0;
741 }