Update codebase to remove clang warnings (and a couple of legit errors
[typhoon.git] / src / script.c
1 /**
2
3 Copyright (c) Scott Gasch
4
5 Module Name:
6
7     script.c
8
9 Abstract:
10
11     Some glue code to support running test scripts and keeping track
12     of test suite results.
13
14 Author:
15
16     Scott Gasch ([email protected]) 18 May 2004
17
18 Revision History:
19
20     $Id: script.c 355 2008-07-01 15:46:43Z scott $
21
22 **/
23
24 #include "chess.h"
25
26 // ----------------------------------------------------------------------
27 //
28 // Global testsuite position counters
29 // 
30 #define SUITE_MAX_SOLUTIONS                   (4)
31 #define SUITE_MAX_AVOIDS                      (4)
32 #define SUITE_NUM_HISTOGRAM                   (8)
33
34 //
35 // A big struct of counters we want to maintain when running test scripts.
36 //
37 typedef struct _SUITE_COUNTERS
38 {
39     CHAR szId[SMALL_STRING_LEN_CHAR];
40     ULONG uNumSolutions;
41     MOVE mvSolution[SUITE_MAX_SOLUTIONS];
42     ULONG uNumAvoids;
43     MOVE mvAvoid[SUITE_MAX_AVOIDS];
44     ULONG uCorrect;
45     ULONG uIncorrect;
46     ULONG uTotal;
47     UINT64 u64TotalNodeCount;
48     double dCurrentSolutionTime;
49     ULONG uCurrentSolvedPlies;
50     ULONG uSigmaDepth;
51     double dSigmaSolutionTime;
52     double dAverageNps;
53     double dAverageFirstMoveBeta;
54     double dAverageTimeToSolution;
55     ULONG uHistogram[SUITE_NUM_HISTOGRAM];
56 }
57 SUITE_COUNTERS;
58 static SUITE_COUNTERS g_SuiteCounters;
59
60
61 static void 
62 _ClearSuiteData(void)
63 /**
64
65 Routine description:
66
67     Clear data/counters we have for the current test script.
68
69 Parameters:
70
71     void
72
73 Return value:
74
75     static void
76
77 **/
78 {
79     g_SuiteCounters.uNumSolutions = 0;
80     memset(g_SuiteCounters.mvSolution,
81            0,
82            sizeof(g_SuiteCounters.mvSolution));
83     g_SuiteCounters.uNumAvoids = 0;
84     memset(g_SuiteCounters.mvAvoid,
85            0,
86            sizeof(g_SuiteCounters.mvAvoid));
87     memset(g_SuiteCounters.szId,
88            0,
89            sizeof(g_SuiteCounters.szId));
90     g_SuiteCounters.uCurrentSolvedPlies = 0;
91     g_SuiteCounters.dCurrentSolutionTime = 0;
92 }
93
94
95 COMMAND(IdCommand)
96 /**
97
98 Routine description:
99
100     This function implements the 'id' engine command.  
101
102     Usage:
103     
104         id [optional string]
105
106         With no agruments, id returns the current position's id tag.
107         With a string argument, id sets the current position's id tag.
108
109         id tags are useful[?] for naming positions in test suites...
110
111 Parameters:
112
113     The COMMAND macro hides four arguments from the input parser:
114
115         CHAR *szInput : the full line of input
116         ULONG argc    : number of argument chunks
117         CHAR *argv[]  : array of ptrs to each argument chunk
118         POSITION *pos : a POSITION pointer to operate on
119
120 Return value:
121
122     void
123
124 **/
125 {
126     CHAR *p;
127     ULONG u;
128
129     if (argc < 2)
130     {
131         Trace("The current position's id is: \"%s\"\n",
132               g_SuiteCounters.szId);
133     }
134     else
135     {
136         p = szInput;
137         while(*p && !isspace(*p))
138         {
139             p++;
140         }
141         while(*p && isspace(*p))
142         {
143             p++;
144         }
145         u = 0;
146         while(*p)
147         {
148             if (!isspace(*p))
149             {
150                 g_SuiteCounters.szId[u] = *p;
151                 u++;
152             }
153             if (u > ARRAY_LENGTH(g_SuiteCounters.szId))
154             {
155                 break;
156             }
157             p++;
158         }
159     }
160 }
161
162 COMMAND(SolutionCommand)
163 /**
164
165 Routine description:
166
167     This function implements the 'solution' engine command.  
168
169     Usage:
170     
171         solution [optional move string]
172
173         With no agruments, solution prints out a list of the "correct
174         solution" move(s) set for the current position.
175
176         With a move string argument solution sets another move as the
177         correct answer to the current position.  The move string can
178         be in ICS or SAN format.
179
180         When the engine is allowed to search a position it will
181         compare its best move from the search against the solution
182         move(s) for the position and keep track of how many it gets
183         right/wrong and how long it took to solve.
184
185         A position can have up to three solutions.  A position cannot
186         have both solution moves and avoid moves.
187
188 Parameters:
189
190     The COMMAND macro hides four arguments from the input parser:
191
192         CHAR *szInput : the full line of input
193         ULONG argc    : number of argument chunks
194         CHAR *argv[]  : array of ptrs to each argument chunk
195         POSITION *pos : a POSITION pointer to operate on
196
197 Return value:
198
199     void
200
201 **/
202 {
203     ULONG u;
204     MOVE mv;
205
206     if (argc < 2)
207     {
208         Trace("There %s %u solution move%s set:\n",
209               (g_SuiteCounters.uNumSolutions > 1) ? "are" : "is",
210               g_SuiteCounters.uNumSolutions,
211               (g_SuiteCounters.uNumSolutions > 1) ? "s" : "");
212         for(u = 0;
213             u < g_SuiteCounters.uNumSolutions;
214             u++)
215         {
216             Trace("    %s\n", MoveToSan(g_SuiteCounters.mvSolution[u], pos));
217         }
218     }
219     else
220     {
221         mv.uMove = 0;
222         switch(LooksLikeMove(argv[1]))
223         {
224             case MOVE_SAN:
225                 mv = ParseMoveSan(argv[1], pos);
226                 break;
227             case MOVE_ICS:
228                 mv = ParseMoveIcs(argv[1], pos);
229                 break;
230             case NOT_MOVE:
231                 break;
232         }
233
234         if (mv.uMove == 0)
235         {
236             Trace("Error (bad move): %s\n", argv[1]);
237             return;
238         }
239         
240         if (g_SuiteCounters.uNumSolutions < SUITE_MAX_SOLUTIONS)
241         {
242             g_SuiteCounters.mvSolution[g_SuiteCounters.uNumSolutions] = mv;
243             g_SuiteCounters.uNumSolutions++;
244         }
245     }
246 }
247
248
249 COMMAND(AvoidCommand)
250 /**
251
252 Routine description:
253
254     This function implements the 'avoid' engine command.  
255
256     Usage:
257     
258         avoid [optional move string]
259
260         With no agruments, avoid prints out a list of the "don't play
261         this" move(s) set for the current position.
262
263         With a move string argument, avoid sets another move as the
264         incorrect answer to the current position.  The move string can
265         be in ICS or SAN format.
266
267         When the engine is allowed to search a position it will
268         compare its best move from the search against the avoid
269         move(s) for the position and keep track of how many it gets
270         right/wrong and how long it took to solve.
271
272         A position can have up to three avoid moves.  A position
273         cannot have both solution moves and avoid moves.
274
275 Parameters:
276
277     The COMMAND macro hides four arguments from the input parser:
278
279         CHAR *szInput : the full line of input
280         ULONG argc    : number of argument chunks
281         CHAR *argv[]  : array of ptrs to each argument chunk
282         POSITION *pos : a POSITION pointer to operate on
283
284 Return value:
285
286     void
287
288 **/
289 {
290     ULONG u;
291     MOVE mv;
292
293     if (argc < 2)
294     {
295         Trace("There %s %u avoid move%s set:\n",
296               (g_SuiteCounters.uNumAvoids > 1) ? "are" : "is",
297               g_SuiteCounters.uNumAvoids,
298               (g_SuiteCounters.uNumAvoids > 1) ? "s" : "");
299         for(u = 0;
300             u < g_SuiteCounters.uNumAvoids;
301             u++)
302         {
303             Trace("    %s\n", MoveToSan(g_SuiteCounters.mvAvoid[u], pos));
304         }
305     }
306     else
307     {
308         mv.uMove = 0;
309         switch(LooksLikeMove(argv[1]))
310         {
311             case MOVE_SAN:
312                 mv = ParseMoveSan(argv[1], pos);
313                 break;
314             case MOVE_ICS:
315                 mv = ParseMoveIcs(argv[1], pos);
316                 break;
317             case NOT_MOVE:
318                 break;
319         }
320
321         if (mv.uMove == 0)
322         {
323             Trace("Error (bad move): %s\n", argv[1]);
324             return;
325         }
326         
327         if (g_SuiteCounters.uNumAvoids < SUITE_MAX_AVOIDS)
328         {
329             g_SuiteCounters.mvAvoid[g_SuiteCounters.uNumAvoids] = mv;
330             g_SuiteCounters.uNumAvoids++;
331         }
332     }
333 }
334
335
336
337 COMMAND(ScriptCommand)
338 /**
339
340 Routine description:
341
342     This function implements the 'script' engine command.  
343
344     Usage:
345     
346         script <required filename>
347
348         The script command opens the reads the required filename and
349         reads it line by line.  Each line of the script file is
350         treated as a command and executed in sequence.  Each command
351         is executed fully before the next script line is read and
352         executed.  When all lines in the script file have been read
353         and executed the engine will print out a post-script report of
354         test suite statistics and then begin to pay attention to
355         commands from the keyboard again.
356
357         Scripts are meant to facilitate execution of test suites.
358
359 Parameters:
360
361     The COMMAND macro hides four arguments from the input parser:
362
363         CHAR *szInput : the full line of input
364         ULONG argc    : number of argument chunks
365         CHAR *argv[]  : array of ptrs to each argument chunk
366         POSITION *pos : a POSITION pointer to operate on
367
368 Return value:
369
370     void
371
372 **/
373 {
374     FILE *p;
375     static CHAR szLine[SMALL_STRING_LEN_CHAR];
376     double dSuiteStart;
377     ULONG u, v, uMax;
378     double dMult;
379
380     if (argc < 2)
381     {
382         Trace("Error (Usage: script <filename>): %s\n", argv[0]);
383         return;
384     }
385     if (FALSE == SystemDoesFileExist(argv[1]))
386     {
387         Trace("Error (file doesn't exist): %s\n", argv[1]);
388         return;
389     }
390     
391     p = fopen(argv[1], "rb");
392     if (NULL != p)
393     {
394         // Doing the script...
395         dSuiteStart = SystemTimeStamp();
396         memset(&(g_SuiteCounters), 0, sizeof(g_SuiteCounters));
397         while(fgets(szLine, ARRAY_LENGTH(szLine), p))
398         {
399             Trace("SCRIPT> %s\n", szLine);
400             if (!STRNCMPI(szLine, "go", 2))
401             {
402                 (void)Think(pos);
403                 g_SuiteCounters.u64TotalNodeCount += 
404                     g_Options.u64NodesSearched;
405             }
406             else
407             {
408                 PushNewInput(szLine);
409                 if (TRUE == g_fExitProgram) break;
410                 ParseUserInput(FALSE);
411             }
412         }
413         fclose(p);
414
415         // Banner and end results
416         dMult = (double)g_Options.uMyIncrement / (double)SUITE_NUM_HISTOGRAM;
417         Banner();
418         Trace("\n"
419               "TEST SCRIPT execution complete.  Final statistics:\n"
420               "--------------------------------------------------\n"
421               "    correct solutions   : %u\n"
422               "    incorrect solutions : %u\n"
423               "    total problems      : %u\n"
424               "    total nodecount     : %" 
425                    COMPILER_LONGLONG_UNSIGNED_FORMAT "\n"
426               "    avg. search speed   : %6.1f nps\n"
427               "    avg. solution time  : %3.1f sec\n"
428               "    avg. search depth   : %4.1f ply\n"
429               "    script time         : %6.1f sec\n\n",
430               g_SuiteCounters.uCorrect,
431               g_SuiteCounters.uIncorrect,
432               g_SuiteCounters.uTotal,
433               g_SuiteCounters.u64TotalNodeCount,
434               ((double)g_SuiteCounters.u64TotalNodeCount / 
435                (SystemTimeStamp() - dSuiteStart)),
436               (g_SuiteCounters.dSigmaSolutionTime / 
437                (double)g_SuiteCounters.uCorrect),
438               ((double)g_SuiteCounters.uSigmaDepth / 
439                (double)g_SuiteCounters.uTotal),
440               (SystemTimeStamp() - dSuiteStart));
441         
442         // Histogram stuff
443         if (g_SuiteCounters.uTotal > 0) {
444             uMax = g_SuiteCounters.uHistogram[0];
445             for (u = 1; u < SUITE_NUM_HISTOGRAM; u++) 
446             {
447                 if (g_SuiteCounters.uHistogram[u] > uMax) 
448                 {
449                     uMax = g_SuiteCounters.uHistogram[u];
450                 }
451             }
452             ASSERT(uMax > 0);
453             for (u = 0; u < SUITE_NUM_HISTOGRAM; u++) 
454             {
455                 Trace("%4.1f .. %4.1f: ", 
456                       (double)u * dMult, (double)(u + 1) * dMult);
457                 for (v = 0; 
458                      v < 50 * g_SuiteCounters.uHistogram[u] / uMax; 
459                      v++) 
460                 {
461                     Trace("*");
462                 }
463                 Trace("\n");
464             }
465         }
466     }
467 }
468
469 FLAG 
470 WeAreRunningASuite(void)
471 /**
472
473 Routine description:
474
475     A function that returns TRUE if the engine is running a test suite
476     and FALSE otherwise.
477
478 Parameters:
479
480     void
481
482 Return value:
483
484     FLAG
485
486 **/
487 {
488     if (g_SuiteCounters.uNumSolutions +
489         g_SuiteCounters.uNumAvoids)
490     {
491         return(TRUE);
492     }
493     return(FALSE);
494 }
495
496
497 FLAG 
498 CheckTestSuiteMove(MOVE mv, SCORE iScore, ULONG uDepth)
499 /**
500
501 Routine description:
502
503     This function is called by RootSearch when it has finished a
504     search.  The MOVE parameter is the best move that the search
505     found.  This code checks that move against the solution or avoid
506     moves for the current position, determines if we "got it right"
507     and updates the test suite counters accordingly.
508
509 Parameters:
510
511     MOVE mv
512
513 Return value:
514
515     FLAG
516
517 **/
518 {
519     FLAG fSolved = FALSE;
520     ULONG u;
521
522     if (!WeAreRunningASuite()) return FALSE;
523
524     //
525     // There is(are) solution move(s).
526     //
527     if (g_SuiteCounters.uNumSolutions)
528     {
529         ASSERT(g_SuiteCounters.uNumAvoids == 0);
530         fSolved = FALSE;
531         for (u = 0; u < g_SuiteCounters.uNumSolutions; u++)
532         {
533             if ((mv.cFrom == g_SuiteCounters.mvSolution[u].cFrom) &&
534                 (mv.cTo == g_SuiteCounters.mvSolution[u].cTo))
535             {
536                 fSolved = TRUE;
537                 break;
538             }
539         }
540         if (!fSolved) {
541             g_SuiteCounters.uCurrentSolvedPlies = 0;
542             g_SuiteCounters.dCurrentSolutionTime = 0.0;
543         } else {
544             if (g_SuiteCounters.uCurrentSolvedPlies == 0) {
545                 g_SuiteCounters.dCurrentSolutionTime = 
546                     (SystemTimeStamp() - g_MoveTimer.dStartTime);
547             }
548             g_SuiteCounters.uCurrentSolvedPlies++;
549         }
550     }
551     else if (g_SuiteCounters.uNumAvoids) 
552     {
553         //
554         // Handle avoid-move problems.
555         //
556         fSolved = TRUE;
557         for (u = 0; u < g_SuiteCounters.uNumAvoids; u++)
558         {
559             if ((mv.cFrom == g_SuiteCounters.mvAvoid[u].cFrom) &&
560                 (mv.cTo == g_SuiteCounters.mvAvoid[u].cTo))
561             {
562                 fSolved = FALSE;
563             }
564         }
565         if (!fSolved) 
566         {
567             g_SuiteCounters.uCurrentSolvedPlies = 0;
568             g_SuiteCounters.dCurrentSolutionTime = 0.0;
569         } else {
570             if (g_SuiteCounters.uCurrentSolvedPlies == 0) {
571                 g_SuiteCounters.dCurrentSolutionTime = 
572                     (SystemTimeStamp() - g_MoveTimer.dStartTime);
573             }
574             g_SuiteCounters.uCurrentSolvedPlies++;
575         }
576     }
577     else
578     {
579         UtilPanic(SHOULD_NOT_GET_HERE,
580                   NULL, NULL, NULL, NULL,
581                   __FILE__, __LINE__);
582     }
583
584     //
585     // Handle "FastScript" variable for solution moves.
586     //
587     if ((g_Options.fFastScript) &&
588         (g_SuiteCounters.uCurrentSolvedPlies > 4) &&
589         (iScore >= 0))
590     {
591         if (((g_Options.uMyIncrement != 0) &&
592              (SystemTimeStamp() - g_MoveTimer.dStartTime >=
593               (double)g_Options.uMyIncrement / 3)) ||
594             ((g_Options.uMaxDepth != 0) &&
595              (uDepth >= g_Options.uMaxDepth / 3)))
596         {
597             Trace("FAST SCRIPT --> stop searching now\n");
598             g_MoveTimer.bvFlags |= TIMER_STOPPING;
599         }
600     }
601     return(fSolved);
602 }
603
604
605 void 
606 PostMoveTestSuiteReport(SEARCHER_THREAD_CONTEXT *ctx)
607 /**
608
609 Routine description:
610
611     If we're running a suite, print a little blurb after each move to
612     act as a running total of how we're doing.
613
614 Parameters:
615
616     SEARCHER_THREAD_CONTEXT *ctx
617
618 Return value:
619
620     void
621
622 **/
623 {
624     MOVE mv = ctx->mvRootMove;
625     ULONG uDepth = ctx->uRootDepth / ONE_PLY, y;
626     double dMult;
627
628     //
629     // This is the real move the searched picked when all was said and
630     // done (i.e. it ran out of time or reached a fixed depth).  See
631     // if it's right.
632     //
633     if (WeAreRunningASuite())
634     {
635         g_SuiteCounters.uTotal++;
636         g_SuiteCounters.uSigmaDepth += uDepth;
637         
638         if (CheckTestSuiteMove(mv, -1, uDepth))
639         {
640             Trace("Problem %s solved in %3.1f sec.\n", 
641                   g_SuiteCounters.szId,
642                   g_SuiteCounters.dCurrentSolutionTime);
643             g_SuiteCounters.uCorrect++;
644             g_SuiteCounters.dSigmaSolutionTime += 
645                 g_SuiteCounters.dCurrentSolutionTime;
646
647             dMult = (double)g_Options.uMyIncrement / 
648                 (double)SUITE_NUM_HISTOGRAM;
649             ASSERT(SUITE_NUM_HISTOGRAM > 0);
650             for (y = 1; y <= SUITE_NUM_HISTOGRAM; y++)
651             {
652                 if (g_SuiteCounters.dCurrentSolutionTime < ((double)y * dMult))
653                 {
654                     g_SuiteCounters.uHistogram[y-1]++;
655                     break;
656                 }
657                 if (y == SUITE_NUM_HISTOGRAM)
658                 {
659                     //
660                     // This one is actually > time-to-think.  This
661                     // really happens... like we had 20 sec to think
662                     // and solved it in 20.1 or something before the
663                     // stop-searching timer was checked.
664                     //
665                     g_SuiteCounters.uHistogram[y-1]++;
666                 }
667             }
668         }
669         else
670         {
671             Trace(">>>>> Problem %s was not solved. <<<<<\n\n",
672                   g_SuiteCounters.szId);
673             g_SuiteCounters.uIncorrect++;
674         }
675         Trace("Current suite score: %u correct of %u problems.\n", 
676               g_SuiteCounters.uCorrect,
677               g_SuiteCounters.uTotal);
678         _ClearSuiteData();
679     }
680 }