3 Copyright (c) Scott Gasch
11 Some glue code to support running test scripts and keeping track
12 of test suite results.
20 $Id: script.c 355 2008-07-01 15:46:43Z scott $
26 // ----------------------------------------------------------------------
28 // Global testsuite position counters
30 #define SUITE_MAX_SOLUTIONS (4)
31 #define SUITE_MAX_AVOIDS (4)
32 #define SUITE_NUM_HISTOGRAM (8)
35 // A big struct of counters we want to maintain when running test scripts.
37 typedef struct _SUITE_COUNTERS
39 CHAR szId[SMALL_STRING_LEN_CHAR];
41 MOVE mvSolution[SUITE_MAX_SOLUTIONS];
43 MOVE mvAvoid[SUITE_MAX_AVOIDS];
47 UINT64 u64TotalNodeCount;
48 double dCurrentSolutionTime;
49 ULONG uCurrentSolvedPlies;
51 double dSigmaSolutionTime;
53 double dAverageFirstMoveBeta;
54 double dAverageTimeToSolution;
55 ULONG uHistogram[SUITE_NUM_HISTOGRAM];
58 static SUITE_COUNTERS g_SuiteCounters;
67 Clear data/counters we have for the current test script.
79 g_SuiteCounters.uNumSolutions = 0;
80 memset(g_SuiteCounters.mvSolution,
82 sizeof(g_SuiteCounters.mvSolution));
83 g_SuiteCounters.uNumAvoids = 0;
84 memset(g_SuiteCounters.mvAvoid,
86 sizeof(g_SuiteCounters.mvAvoid));
87 memset(g_SuiteCounters.szId,
89 sizeof(g_SuiteCounters.szId));
90 g_SuiteCounters.uCurrentSolvedPlies = 0;
91 g_SuiteCounters.dCurrentSolutionTime = 0;
100 This function implements the 'id' engine command.
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.
109 id tags are useful[?] for naming positions in test suites...
113 The COMMAND macro hides four arguments from the input parser:
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
131 Trace("The current position's id is: \"%s\"\n",
132 g_SuiteCounters.szId);
137 while(*p && !isspace(*p))
141 while(*p && isspace(*p))
150 g_SuiteCounters.szId[u] = *p;
153 if (u > ARRAY_LENGTH(g_SuiteCounters.szId))
162 COMMAND(SolutionCommand)
167 This function implements the 'solution' engine command.
171 solution [optional move string]
173 With no agruments, solution prints out a list of the "correct
174 solution" move(s) set for the current position.
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.
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.
185 A position can have up to three solutions. A position cannot
186 have both solution moves and avoid moves.
190 The COMMAND macro hides four arguments from the input parser:
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
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" : "");
213 u < g_SuiteCounters.uNumSolutions;
216 Trace(" %s\n", MoveToSan(g_SuiteCounters.mvSolution[u], pos));
222 switch(LooksLikeMove(argv[1]))
225 mv = ParseMoveSan(argv[1], pos);
228 mv = ParseMoveIcs(argv[1], pos);
236 Trace("Error (bad move): %s\n", argv[1]);
240 if (g_SuiteCounters.uNumSolutions < SUITE_MAX_SOLUTIONS)
242 g_SuiteCounters.mvSolution[g_SuiteCounters.uNumSolutions] = mv;
243 g_SuiteCounters.uNumSolutions++;
249 COMMAND(AvoidCommand)
254 This function implements the 'avoid' engine command.
258 avoid [optional move string]
260 With no agruments, avoid prints out a list of the "don't play
261 this" move(s) set for the current position.
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.
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.
272 A position can have up to three avoid moves. A position
273 cannot have both solution moves and avoid moves.
277 The COMMAND macro hides four arguments from the input parser:
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
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" : "");
300 u < g_SuiteCounters.uNumAvoids;
303 Trace(" %s\n", MoveToSan(g_SuiteCounters.mvAvoid[u], pos));
309 switch(LooksLikeMove(argv[1]))
312 mv = ParseMoveSan(argv[1], pos);
315 mv = ParseMoveIcs(argv[1], pos);
323 Trace("Error (bad move): %s\n", argv[1]);
327 if (g_SuiteCounters.uNumAvoids < SUITE_MAX_AVOIDS)
329 g_SuiteCounters.mvAvoid[g_SuiteCounters.uNumAvoids] = mv;
330 g_SuiteCounters.uNumAvoids++;
337 COMMAND(ScriptCommand)
342 This function implements the 'script' engine command.
346 script <required filename>
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.
357 Scripts are meant to facilitate execution of test suites.
361 The COMMAND macro hides four arguments from the input parser:
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
375 static CHAR szLine[SMALL_STRING_LEN_CHAR];
382 Trace("Error (Usage: script <filename>): %s\n", argv[0]);
385 if (FALSE == SystemDoesFileExist(argv[1]))
387 Trace("Error (file doesn't exist): %s\n", argv[1]);
391 p = fopen(argv[1], "rb");
394 // Doing the script...
395 dSuiteStart = SystemTimeStamp();
396 memset(&(g_SuiteCounters), 0, sizeof(g_SuiteCounters));
397 while(fgets(szLine, ARRAY_LENGTH(szLine), p))
399 Trace("SCRIPT> %s\n", szLine);
400 if (!STRNCMPI(szLine, "go", 2))
403 g_SuiteCounters.u64TotalNodeCount +=
404 g_Options.u64NodesSearched;
408 PushNewInput(szLine);
409 if (TRUE == g_fExitProgram) break;
410 ParseUserInput(FALSE);
415 // Banner and end results
416 dMult = (double)g_Options.uMyIncrement / (double)SUITE_NUM_HISTOGRAM;
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));
443 if (g_SuiteCounters.uTotal > 0) {
444 uMax = g_SuiteCounters.uHistogram[0];
445 for (u = 1; u < SUITE_NUM_HISTOGRAM; u++)
447 if (g_SuiteCounters.uHistogram[u] > uMax)
449 uMax = g_SuiteCounters.uHistogram[u];
453 for (u = 0; u < SUITE_NUM_HISTOGRAM; u++)
455 Trace("%4.1f .. %4.1f: ",
456 (double)u * dMult, (double)(u + 1) * dMult);
458 v < 50 * g_SuiteCounters.uHistogram[u] / uMax;
470 WeAreRunningASuite(void)
475 A function that returns TRUE if the engine is running a test suite
488 if (g_SuiteCounters.uNumSolutions +
489 g_SuiteCounters.uNumAvoids)
498 CheckTestSuiteMove(MOVE mv, SCORE iScore, ULONG uDepth)
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.
519 FLAG fSolved = FALSE;
522 if (!WeAreRunningASuite()) return FALSE;
525 // There is(are) solution move(s).
527 if (g_SuiteCounters.uNumSolutions)
529 ASSERT(g_SuiteCounters.uNumAvoids == 0);
531 for (u = 0; u < g_SuiteCounters.uNumSolutions; u++)
533 if ((mv.cFrom == g_SuiteCounters.mvSolution[u].cFrom) &&
534 (mv.cTo == g_SuiteCounters.mvSolution[u].cTo))
541 g_SuiteCounters.uCurrentSolvedPlies = 0;
542 g_SuiteCounters.dCurrentSolutionTime = 0.0;
544 if (g_SuiteCounters.uCurrentSolvedPlies == 0) {
545 g_SuiteCounters.dCurrentSolutionTime =
546 (SystemTimeStamp() - g_MoveTimer.dStartTime);
548 g_SuiteCounters.uCurrentSolvedPlies++;
551 else if (g_SuiteCounters.uNumAvoids)
554 // Handle avoid-move problems.
557 for (u = 0; u < g_SuiteCounters.uNumAvoids; u++)
559 if ((mv.cFrom == g_SuiteCounters.mvAvoid[u].cFrom) &&
560 (mv.cTo == g_SuiteCounters.mvAvoid[u].cTo))
567 g_SuiteCounters.uCurrentSolvedPlies = 0;
568 g_SuiteCounters.dCurrentSolutionTime = 0.0;
570 if (g_SuiteCounters.uCurrentSolvedPlies == 0) {
571 g_SuiteCounters.dCurrentSolutionTime =
572 (SystemTimeStamp() - g_MoveTimer.dStartTime);
574 g_SuiteCounters.uCurrentSolvedPlies++;
579 UtilPanic(SHOULD_NOT_GET_HERE,
580 NULL, NULL, NULL, NULL,
585 // Handle "FastScript" variable for solution moves.
587 if ((g_Options.fFastScript) &&
588 (g_SuiteCounters.uCurrentSolvedPlies > 4) &&
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)))
597 Trace("FAST SCRIPT --> stop searching now\n");
598 g_MoveTimer.bvFlags |= TIMER_STOPPING;
606 PostMoveTestSuiteReport(SEARCHER_THREAD_CONTEXT *ctx)
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.
616 SEARCHER_THREAD_CONTEXT *ctx
624 MOVE mv = ctx->mvRootMove;
625 ULONG uDepth = ctx->uRootDepth / ONE_PLY, y;
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
633 if (WeAreRunningASuite())
635 g_SuiteCounters.uTotal++;
636 g_SuiteCounters.uSigmaDepth += uDepth;
638 if (CheckTestSuiteMove(mv, -1, uDepth))
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;
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++)
652 if (g_SuiteCounters.dCurrentSolutionTime < ((double)y * dMult))
654 g_SuiteCounters.uHistogram[y-1]++;
657 if (y == SUITE_NUM_HISTOGRAM)
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.
665 g_SuiteCounters.uHistogram[y-1]++;
671 Trace(">>>>> Problem %s was not solved. <<<<<\n\n",
672 g_SuiteCounters.szId);
673 g_SuiteCounters.uIncorrect++;
675 Trace("Current suite score: %u correct of %u problems.\n",
676 g_SuiteCounters.uCorrect,
677 g_SuiteCounters.uTotal);