Update codebase to remove clang warnings (and a couple of legit errors
[typhoon.git] / src / main.c
1 /**
2
3 Copyright (c) Scott Gasch
4
5 Module Name:
6
7     main.c
8
9 Abstract:
10
11     Program entry point and setup code.
12
13 Author:
14
15     Scott Gasch ([email protected]) 8 Apr 2004
16
17 Revision History:
18
19     $Id: main.c 355 2008-07-01 15:46:43Z scott $
20
21 **/
22
23 #include "chess.h"
24
25 FILE *g_pfLogfile = NULL;
26 GAME_OPTIONS g_Options;
27 CHAR g_szInitialCommand[SMALL_STRING_LEN_CHAR];
28 ULONG g_uInputThreadHandle = (ULONG)-1;
29
30 void
31 Banner(void)
32 /**
33
34 Routine description:
35
36     Describe the compile/build options and program version.
37
38 Parameters:
39
40     void
41
42 Return value:
43
44     void
45
46 **/
47 {
48     char *p;
49
50     Trace("Typhoon %s (built on %s %s):\n", VERSION, __DATE__, __TIME__);
51     Trace("    Copyright (C) 2000-2007, Scott Gasch ([email protected])\n");
52     Trace("    " REVISION);
53     Trace("    " COMPILER_STRING);
54     Trace("    Make profile used: %s\n", PROFILE);
55 #ifdef MP
56     Trace("    Multiprocessor enabled; %u searcher thread%s\n",
57           g_Options.uNumProcessors, (g_Options.uNumProcessors > 1) ? "s" : "");
58 #else
59     Trace("    Single processor version\n");
60 #endif
61 #ifdef DEBUG
62     Trace("    DEBUG build\n");
63 #endif
64 #ifdef TEST
65     Trace("    Testcode compiled in\n");
66 #endif
67 #ifdef EVAL_DUMP
68     Trace("    Evaluation terms\n");
69 #endif
70 #ifdef EVAL_TIME
71     Trace("    Evaluation timing\n");
72 #endif
73 #ifdef PERF_COUNTERS
74     Trace("    Performance counters\n");
75 #ifdef TEST_NULL
76     Trace("    Testing nullmove prediction algorithms\n");
77 #endif
78 #endif
79 #ifdef DUMP_TREE
80     Trace("    Search tree dumpfile generation enabled\n");
81 #endif
82     Trace("    Hash sizes: %u Mb (main), %u Mb / thread (pawn), %u Mb / thread (eval)\n", 
83           (g_uHashTableSizeEntries * sizeof(HASH_ENTRY)) / MB,
84           PAWN_HASH_TABLE_SIZE * sizeof(PAWN_HASH_ENTRY) / MB,
85           EVAL_HASH_TABLE_SIZE * sizeof(EVAL_HASH_ENTRY) / MB);
86     Trace("    QCheckPlies: %u\n", QPLIES_OF_NON_CAPTURE_CHECKS);
87     Trace("    FutilityBase: %u\n", FUTILITY_BASE_MARGIN);
88     p = ExportEvalDNA();
89     Trace("    Logging Eval DNA.\n");
90     Log(p);
91     free(p);
92 #ifdef DO_IID
93     Trace("    IID R-factor: %u ply\n", IID_R_FACTOR / ONE_PLY);
94 #endif
95 }
96
97
98 void
99 EndLogging(void)
100 /**
101
102 Routine description:
103
104     Close the main logfile.
105
106 Parameters:
107
108     void
109
110 Return value:
111
112     void
113
114 **/
115 {
116     if (NULL != g_pfLogfile) {
117         fflush(g_pfLogfile);
118         fflush(stdout);
119         fclose(g_pfLogfile);
120         g_pfLogfile = NULL;
121     }
122 }
123
124
125 FLAG
126 BeginLogging(void)
127 /**
128
129 Routine description:
130
131     Open the main logfile.
132
133 Parameters:
134
135     void
136
137 Return value:
138
139     FLAG
140
141 **/
142 {
143     if (NULL != g_pfLogfile) {
144         EndLogging();
145     }
146     if (!strlen(g_Options.szLogfile) ||
147         !strcmp(g_Options.szLogfile, "-")) {
148         return(FALSE);
149     }
150
151     // If the logfile we want to write already exists, back it up.
152     if (TRUE == SystemDoesFileExist(g_Options.szLogfile)) {
153         VERIFY(BackupFile(g_Options.szLogfile));
154     }
155     ASSERT(FALSE == SystemDoesFileExist(g_Options.szLogfile));
156
157     g_pfLogfile = fopen(g_Options.szLogfile, "wb+");
158     if (NULL == g_pfLogfile) {
159         UtilPanic(INITIALIZATION_FAILURE,
160                   (void *)"Can't open logfile", 
161                   (void *)&(g_Options.szLogfile), 
162                   (void *)"write", 
163                   NULL,
164                   __FILE__, __LINE__);
165     }
166     return(TRUE);
167 }
168
169
170 FLAG
171 PreGameReset(FLAG fResetBoard)
172 /**
173
174 Routine description:
175
176     Reset internal state between games.
177
178 Parameters:
179
180     void
181
182 Return value:
183
184     FLAG
185
186 **/
187 {
188     if (TRUE == fResetBoard)
189     {
190         SetRootToInitialPosition();
191     }
192     SetMyName();
193     ClearDynamicMoveOrdering();
194     ClearHashTable();
195     ResetOpeningBook();
196     return(TRUE);
197 }
198
199
200 static ULONG
201 _ParseHashOption(char *p,
202                  ULONG uEntrySize)
203 {
204     ULONG u, v;
205     CHAR c;
206
207     if ((!STRCMPI(p, "none")) || (!STRCMPI(p, "no"))) return(0);
208     if (2 == sscanf(p, "%u%1c", &u, &c))
209     {
210         v = u;
211         switch(c)
212         {
213             case 'K':
214             case 'k':
215                 u *= 1024;
216                 break;
217             case 'M':
218             case 'm':
219                 u *= 1024 * 1024;
220                 break;
221             case 'G':
222             case 'g':
223                 u *= 1024 * 1024 * 1024;
224                 break;
225             default:
226                 Trace("Error (unrecognized size): \"%s\"\n", p);
227                 break;
228         }
229         if (u < v)
230         {
231             Trace("Error (size too large): \"%s\"\n", p);
232             u = v;
233         }
234         u /= uEntrySize;
235     }
236     else
237     {
238         u = (ULONG)atoi(p);
239     }
240
241     while(!IS_A_POWER_OF_2(u))
242     {
243         u &= (u - 1);
244     }
245     return(u);
246 }
247
248
249
250 void
251 InitializeOptions(int argc, char *argv[])
252 /**
253
254 Routine description:
255
256     Set global options to their initial state.  This code also is
257     responsible for parsing the commandline in order to override the
258     default state of global options.
259
260 Parameters:
261
262     int argc,
263     char *argv[]
264
265 Return value:
266
267     void
268
269 **/
270 {
271     int i;
272
273     //
274     // Defaults
275     //
276     memset(&g_Options, 0, sizeof(g_Options));
277     g_szInitialCommand[0] = '\0';
278     g_Options.uMyClock = g_Options.uOpponentsClock = 600;
279     g_Options.fGameIsRated = FALSE;
280     g_Options.fOpponentIsComputer = FALSE;
281     g_Options.uSecPerMove = 0;
282     g_Options.uMaxDepth = MAX_PLY_PER_SEARCH - 1;
283     g_Options.fShouldPonder = TRUE;
284     g_Options.fPondering = g_Options.fThinking = FALSE;
285     g_Options.mvPonder.uMove = 0;
286     g_Options.fShouldPost = TRUE;
287     g_Options.fForceDrawWorthZero = FALSE;
288     g_Options.uMyIncrement = 0;
289     g_Options.uMovesPerTimePeriod = 0;
290     g_Options.szAnalyzeProgressReport[0] = '\0';
291     g_Options.fShouldAnnounceOpening = TRUE;
292     g_Options.eClock = CLOCK_NONE;
293     g_Options.eGameType = GAME_UNKNOWN;
294     g_Options.ePlayMode = FORCE_MODE;
295     g_Options.fNoInputThread = FALSE;
296     g_Options.fVerbosePosting = TRUE;
297     strcpy(g_Options.szEGTBPath, "/egtb/three;/etc/four;/egtb/five");
298     strcpy(g_Options.szLogfile, "typhoon.log");
299     strcpy(g_Options.szBookName, "book.bin");
300     g_Options.uNumHashTableEntries = 0x10000;
301     g_Options.uNumProcessors = 1;
302     g_Options.fStatusLine = TRUE;
303     g_Options.iResignThreshold = 0;
304     g_Options.u64MaxNodeCount = 0ULL;
305
306     i = 1;
307     while(i < argc)
308     {
309 #ifdef MP
310         if ((!STRCMPI(argv[i], "--cpus")) && (argc > i))
311         {
312             g_Options.uNumProcessors = (ULONG)atoi(argv[i+1]);
313             if ((g_Options.uNumProcessors == 0) ||
314                 (g_Options.uNumProcessors > 64))
315             {
316                 g_Options.uNumProcessors = 2;
317             }
318             i++;
319         }
320         else
321 #endif
322         if ((!STRCMPI(argv[i], "--command")) && (argc > i))
323         {
324             strncpy(g_szInitialCommand,
325                     argv[i+1],
326                     SMALL_STRING_LEN_CHAR - 2);
327             strcat(g_szInitialCommand, "\r\n");
328             i++;
329         }
330         else if ((!STRCMPI(argv[i], "--hash")) && (argc > i))
331         {
332             g_Options.uNumHashTableEntries =
333                 _ParseHashOption(argv[i+1],
334                                  sizeof(HASH_ENTRY));
335             i++;
336         }
337         else if ((!STRCMPI(argv[i], "--egtbpath")) && (argc > i))
338         {
339             if (!strcmp(argv[i+1], "-")) {
340                 g_Options.szEGTBPath[0] = '\0';
341             } else {
342                 strncpy(g_Options.szEGTBPath, argv[i+1], SMALL_STRING_LEN_CHAR);
343             }
344             i++;
345         }
346         else if (!STRCMPI(argv[i], "--book") && argc > i)
347         {
348             if (!strcmp(argv[i+1], "-")) {
349                 g_Options.szBookName[0] = '\0';
350             } else {
351                 strncpy(g_Options.szBookName, argv[i+1], SMALL_STRING_LEN_CHAR);
352             }
353             i++;
354         }
355         else if (!STRCMPI(argv[i], "--batch"))
356         {
357             g_Options.fNoInputThread = TRUE;
358         }
359         else if ((!STRCMPI(argv[i], "--dnafile")) && (argc > i)) 
360         {
361             if (!ReadEvalDNA(argv[i+1])) 
362             {
363                 UtilPanic(INITIALIZATION_FAILURE,
364                           (void *)"Bad DNA file",
365                           (void *)&(argv[i+1]),
366                           (void *)"read",
367                           NULL,
368                           __FILE__, __LINE__);
369             }
370             i++;
371         }
372         else if ((!STRCMPI(argv[i], "--logfile")) && (argc > i)) 
373         {
374             strncpy(g_Options.szLogfile, argv[i+1],
375                     SMALL_STRING_LEN_CHAR);
376             i++;
377         }
378         else if (!STRCMPI(argv[i], "--help")) {
379             Trace("Usage: %s [--batch] [--command arg] [--logfile arg] [--egtbpath arg]\n"
380                   "                [--dnafile arg] [--cpus arg] [--hash arg]\n\n"
381                   "    --batch    : operate the engine without an input thread\n"
382                   "    --book     : specify the opening book to use or '-' for none\n"
383                   "    --command  : specify initial command(s) (requires arg)\n"
384                   "    --cpus     : indicate the number of cpus to use (1..64)\n"
385                   "    --hash     : indicate desired hash size (e.g. 16m, 1g)\n"
386                   "    --egtbpath : supplies the egtb path or '-' for none\n"
387                   "    --logfile  : indicate desired output logfile name or '-' for none\n"
388                   "    --dnafile  : indicate desired eval profile input (requres arg)\n\n"
389                   "Example: %s --hash 256m --cpus 2\n"
390                   "         %s --logfile test.out --batch --command \"test\" --dnafile test.in\n", argv[0], argv[0], argv[0]);
391             exit(-1);
392         }
393         else
394         {
395             Trace("Error (unknown argument): \"%s\"; try --help\n", argv[i]);
396         }
397
398         //
399         // TODO: parse other commandline options and override above
400         //
401
402         i++;
403     }
404 }
405
406
407 void
408 CleanupOptions(void)
409 /**
410
411 Routine description:
412
413     Cleanup options...
414
415 Parameters:
416
417     void
418
419 Return value:
420
421     void
422
423 **/
424 {
425     NOTHING;
426 }
427
428
429 FLAG
430 MainProgramInitialization(int argc, char *argv[])
431 /**
432
433 Routine description:
434
435     Perform main program initialization.  This includes stuff like:
436     precomputing some data structures, resetting the global options,
437     parsing the commandline to override said options, and calling out
438     to other modules to tell them to initialize themselves.
439
440 Parameters:
441
442     int argc,
443     char *argv[]
444
445 Return value:
446
447     FLAG
448
449 **/
450 {
451     srand((unsigned int)time(0));
452     InitializeOptions(argc, argv);
453     InitializeTreeDump();
454     InitializeEGTB();
455     InitializeSigSystem();
456     InitializeInteriorNodeRecognizers();
457     InitializeSearchDepthArray();
458     InitializeWhiteSquaresTable();
459     InitializeVectorDeltaTable();
460     InitializeSwapTable();
461     InitializeDistanceTable();
462     InitializeOpeningBook();
463     InitializeDynamicMoveOrdering();
464     InitializeHashSystem();
465     InitializePositionHashSystem();
466 #ifdef MP
467     InitializeParallelSearch();
468 #endif
469     VERIFY(PreGameReset(TRUE));
470     (void)BeginLogging();
471     return TRUE;
472 }
473
474
475 FLAG
476 MainProgramCleanup(void)
477 /**
478
479 Routine description:
480
481     Perform main program cleanup.  Tear down the stuff we set up in
482     MainProgramInitialization.
483
484 Parameters:
485
486     void
487
488 Return value:
489
490     FLAG
491
492 **/
493 {
494     CleanupOpeningBook();
495     CleanupEGTB();
496     CleanupDynamicMoveOrdering();
497 #ifdef MP
498     CleanupParallelSearch();
499 #endif
500     CleanupPositionHashSystem();
501     CleanupHashSystem();
502     CleanupTreeDump();
503     CleanupOptions();
504     EndLogging();
505     return(TRUE);
506 }
507
508
509 #ifdef TEST
510 FLAG
511 RunStartupProgramTest(void)
512 /**
513
514 Routine description:
515
516     If we built this version with -DTEST then this code is the start
517     of the self-test that runs at program startup time.
518
519 Parameters:
520
521     void
522
523 Return value:
524
525     FLAG
526
527 **/
528 {
529     ULONG u, x, y;
530
531 #ifdef _X86_
532     Trace("Testing CPP macros...\n");
533     for (u = 0; u < 1000000; u++)
534     {
535         x = rand() * rand();
536         y = rand() * rand();
537         x = x & ~(0x80000000);
538         y = y & ~(0x80000000);
539
540         if (MAX(x, y) != MAXU(x, y))
541         {
542             UtilPanic(TESTCASE_FAILURE,
543                       NULL, "max maxu", NULL, NULL, __FILE__, __LINE__);
544         }
545
546         if (MAXU(x, y) != MAXU(y, x))
547         {
548             UtilPanic(TESTCASE_FAILURE,
549                       NULL, "maxu assoc", NULL, NULL, __FILE__, __LINE__);
550         }
551
552         if (MIN(x, y) != MINU(x, y))
553         {
554             UtilPanic(TESTCASE_FAILURE,
555                       NULL, "min minu", NULL, NULL, __FILE__, __LINE__);
556         }
557
558         if (MINU(x, y) != MINU(y, x))
559         {
560             UtilPanic(TESTCASE_FAILURE,
561                       NULL, "minu assoc", NULL, NULL, __FILE__, __LINE__);
562         }
563
564         if (abs(x - y) != ABS_DIFF(x, y))
565         {
566             UtilPanic(TESTCASE_FAILURE,
567                       NULL, "abs_diff", NULL, NULL, __FILE__, __LINE__);
568         }
569
570         if (ABS_DIFF(x, y) != ABS_DIFF(y, x))
571         {
572             UtilPanic(TESTCASE_FAILURE,
573                       NULL, "abs_diff assoc", NULL, NULL, __FILE__, __LINE__);
574         }
575     }
576 #endif
577
578     TestDraw();
579 #ifdef EVAL_DUMP
580     TestEval();
581 #endif
582     TestBitboards();
583     TestSan();
584     TestIcs();
585     TestGetAttacks();
586     TestMoveGenerator();
587     TestLegalMoveGenerator();
588     TestFenCode();
589     TestLiftPlaceSlidePiece();
590     TestExposesCheck();
591     TestIsAttacked();
592     TestMakeUnmakeMove();
593     TestSearch();
594
595     return(TRUE);
596 }
597 #endif // TEST
598
599
600
601 void DeclareTerminalStates(GAME_RESULT result) {
602     switch(result.eResult) {
603     case RESULT_IN_PROGRESS:
604         return;
605     case RESULT_BLACK_WON:
606         Trace("0-1 {%s}\n", result.szDescription);
607         SetGameResultAndDescription(result);
608         DumpPgn();
609         g_Options.ePlayMode = FORCE_MODE;
610         break;
611     case RESULT_WHITE_WON:
612         Trace("1-0 {%s}\n", result.szDescription);
613         SetGameResultAndDescription(result);
614         DumpPgn();
615         g_Options.ePlayMode = FORCE_MODE;
616         break;
617     case RESULT_DRAW:
618         Trace("1/2-1/2 {%s}\n", result.szDescription);
619         Trace("tellics draw\n");
620         SetGameResultAndDescription(result);
621         DumpPgn();
622         g_Options.ePlayMode = FORCE_MODE;
623         break;
624     case RESULT_ABANDONED:
625     case RESULT_UNKNOWN:
626         ASSERT(FALSE);
627         SetGameResultAndDescription(result);
628         DumpPgn();
629         g_Options.ePlayMode = FORCE_MODE;
630         break;
631     }
632 }
633
634
635 // Main work loop
636 void MainProgramLoop() {
637     POSITION pos;
638     GAME_RESULT result;
639     FLAG fThink;
640     FLAG fPonder;
641
642     while (!g_fExitProgram) {
643         
644         // Prepare to search or ponder
645         memcpy(&pos, GetRootPosition(), sizeof(POSITION));
646         fThink = fPonder = FALSE;
647         result.eResult = RESULT_IN_PROGRESS;
648         result.szDescription[0] = '\0';
649         
650         // What should we do?
651         switch(g_Options.ePlayMode) {
652         case I_PLAY_WHITE:
653             fThink = (pos.uToMove == WHITE);
654             fPonder = (pos.uToMove == BLACK);
655             break;
656         case I_PLAY_BLACK:
657             fThink = (pos.uToMove == BLACK);
658             fPonder = (pos.uToMove == WHITE);
659             break;
660         case FORCE_MODE:
661         default:
662             fThink = fPonder = FALSE;
663             break;
664         }
665
666         if (fThink) {
667             result = Think(&pos);
668             if ((NumberOfPendingInputEvents() != 0) &&
669                 (FALSE == g_fExitProgram))
670             {
671                 MakeStatusLine();
672                 ParseUserInput(FALSE);
673             }
674             DeclareTerminalStates(result);
675         } else if (fPonder) {
676             if (g_Options.fShouldPonder) {
677                 result = Ponder(&pos);
678             }
679             if (FALSE == g_fExitProgram)
680             {
681                 MakeStatusLine();
682                 ParseUserInput(FALSE);
683             }
684             DeclareTerminalStates(result);
685         } else {
686             MakeStatusLine();
687             ParseUserInput(FALSE);
688         }
689     }
690 }
691
692
693 int CDECL
694 main(int argc, char *argv[])
695 /**
696
697 Routine description:
698
699     Chess engine entry point and main loop.
700
701 Parameters:
702
703     int argc,
704     char *argv[]
705
706 Return value:
707
708     int CDECL
709
710 **/
711 {
712     // Initial setup work...
713     Trace("Setting up, please wait...\n");
714     if (FALSE == SystemDependentInitialization())
715     {
716         Trace("main: Operating system dependent init code failed, "
717               "aborting.\n");
718         exit(-1);
719     }
720     if (FALSE == MainProgramInitialization(argc, argv))
721     {
722         Trace("main: Main program init code failed, aborting.\n");
723         exit(-1);
724     }
725     Banner();
726 #ifdef TEST
727     (void)RunStartupProgramTest();
728 #endif
729     if (TRUE == g_Options.fNoInputThread)
730     {
731         InitInputSystemInBatchMode();
732     }
733     else
734     {
735         g_uInputThreadHandle = InitInputSystemWithDedicatedThread();
736     }
737     
738     // Enter the main work loop code...
739     MainProgramLoop();  
740     
741     // If we get here g_fExitProgram is set -- clean up.
742     Trace("MAIN THREAD: thread terminating.\n");
743     if (FALSE == MainProgramCleanup())
744     {
745         Trace("main: Main program cleanup code failed, aborting.\n");
746         exit(-1);
747     }
748     exit(0); 
749 }