3 Copyright (c) Scott Gasch
11 Functions to support MakeMove and UnmakeMove along with some other
12 miscellanious move support functions.
20 $Id: movesup.c 345 2007-12-02 22:56:42Z scott $
27 FasterExposesCheck(POSITION *pos,
34 This is just like ExposesCheck (see below) but it does not bother
35 checking whether its possible to expose check with the piece at
36 cRemove because we already know it is.
51 register COOR cBlockIndex;
54 ULONG uColor = GET_COLOR(pos->rgSquare[cLocation].pPiece);
56 ASSERT(IS_KING(pos->rgSquare[cLocation].pPiece));
57 iIndex = (int)cLocation - (int)cRemove;
59 ASSERT(IS_ON_BOARD(cRemove));
60 ASSERT(IS_ON_BOARD(cLocation));
61 ASSERT(!IS_EMPTY(pos->rgSquare[cLocation].pPiece));
62 ASSERT(0 != (CHECK_VECTOR_WITH_INDEX(iIndex, BLACK) & (1 << QUEEN)));
64 iDelta = CHECK_DELTA_WITH_INDEX(iIndex);
67 for (cBlockIndex = cRemove + iDelta;
68 IS_ON_BOARD(cBlockIndex);
69 cBlockIndex += iDelta)
71 ASSERT(cBlockIndex != cRemove);
72 ASSERT(cBlockIndex != cLocation);
73 xPiece = pos->rgSquare[cBlockIndex].pPiece;
74 if (!IS_EMPTY(xPiece))
79 if (OPPOSITE_COLORS(xPiece, uColor))
82 // Does it move the right way to attack?
84 iIndex = (int)cBlockIndex - (int)cLocation;
85 if (0 != (CHECK_VECTOR_WITH_INDEX(iIndex, WHITE) &
86 (1 << (PIECE_TYPE(xPiece)))))
93 // Either we found another friendly piece or an enemy piece
94 // that was unable to reach us. Either way we are safe.
101 // We fell off the board without seeing another piece.
103 return(ILLEGAL_COOR);
109 ExposesCheck(POSITION *pos,
116 Test if removing the piece at cRemove exposes the piece at
117 cLocation to attack by the other side. Note: there must be a
118 piece at cLocation because the color of that piece is used to
119 determine what side is "checking" and what side is "being
124 POSITION *pos : the board
125 COOR cRemove : the square where a piece hypothetically removed from
126 COOR cLocation : the square where the attackee is sitting
130 COOR : the location of an attacker piece or 0x88 (!IS_ON_BOARD) if
131 the removal of cRemove does not expose check.
136 register COOR cBlockIndex;
140 ASSERT(IS_ON_BOARD(cRemove));
141 ASSERT(IS_ON_BOARD(cLocation));
142 ASSERT(!IS_EMPTY(pos->rgSquare[cLocation].pPiece));
144 iIndex = (int)cLocation - (int)cRemove;
147 // If there is no way for a queen sitting at the square removed to
148 // reach the square we are testing (i.e. the two squares are not
149 // on the same rank, file, or diagonal) then there is no way
150 // removing it can expose cLocation to check.
152 if (0 == (CHECK_VECTOR_WITH_INDEX(iIndex, BLACK) & (1 << QUEEN)))
154 return(ILLEGAL_COOR);
156 iDelta = CHECK_DELTA_WITH_INDEX(iIndex);
157 for (cBlockIndex = cLocation + iDelta;
158 IS_ON_BOARD(cBlockIndex);
159 cBlockIndex += iDelta)
161 if (cBlockIndex == cRemove)
165 xPiece = pos->rgSquare[cBlockIndex].pPiece;
166 if (!IS_EMPTY(xPiece))
171 if (OPPOSITE_COLORS(xPiece, pos->rgSquare[cLocation].pPiece))
174 // Does it move the right way to attack?
176 iIndex = (int)cBlockIndex - (int)cLocation;
177 if (0 != (CHECK_VECTOR_WITH_INDEX(iIndex, GET_COLOR(xPiece)) &
178 (1 << (PIECE_TYPE(xPiece)))))
185 // Either we found another friendly piece or an enemy piece
186 // that was unable to reach us. Either way we are safe.
188 return(ILLEGAL_COOR);
193 // We fell off the board without seeing another piece.
195 return(ILLEGAL_COOR);
200 ExposesCheckEp(POSITION *pos,
212 COOR cTest : The square from whence the attack comes
213 COOR cIgnore : Ignore this square, the pawn moved
214 COOR cBlock : This square is where the pawn moved to and now blocks
215 COOR cKing : The square under attack
224 register COOR cBlockIndex;
228 ASSERT(IS_ON_BOARD(cTest));
229 ASSERT(IS_ON_BOARD(cIgnore));
230 ASSERT(IS_ON_BOARD(cBlock));
231 ASSERT(IS_ON_BOARD(cKing));
233 iIndex = (int)cKing - (int)cTest;
236 // If there is no way for a queen sitting at the square removed to
237 // reach the square we are testing (i.e. the two squares are not
238 // on the same rank, file, or diagonal) then no problemo.
240 if (0 == (CHECK_VECTOR_WITH_INDEX(iIndex, BLACK) & (1 << QUEEN)))
242 return(ILLEGAL_COOR);
245 iDelta = CHECK_DELTA_WITH_INDEX(iIndex);
246 for (cBlockIndex = cKing + iDelta;
247 IS_ON_BOARD(cBlockIndex);
248 cBlockIndex += iDelta)
250 if (cBlockIndex == cTest) continue;
251 if (cBlockIndex == cIgnore) continue;
252 if (cBlockIndex == cBlock) return(ILLEGAL_COOR);
254 xPiece = pos->rgSquare[cBlockIndex].pPiece;
255 if (!IS_EMPTY(xPiece))
257 if (OPPOSITE_COLORS(xPiece, pos->rgSquare[cKing].pPiece))
260 // Does it move the right way to attack?
262 iIndex = (int)cBlockIndex - (int)cKing;
263 if (0 != (CHECK_VECTOR_WITH_INDEX(iIndex, GET_COLOR(xPiece)) &
264 (1 << (PIECE_TYPE(xPiece)))))
271 // Either we found another friendly piece or an enemy piece
272 // that was unable to reach us. Either way we are safe.
274 return(ILLEGAL_COOR);
279 // We fell off the board without seeing another piece.
281 return(ILLEGAL_COOR);
286 IsAttacked(COOR cTest, POSITION *pos, ULONG uSide)
291 Determine whether the square cTest is attacked by the side uSide.
295 COOR cTest : the square we want to determine if is under attack
296 POSITION *pos : the board
297 ULONG uSide : the side we want to see if is attacking cTest
301 FLAG : TRUE if uSide attacks cTest, FALSE otherwise
311 static int iPawnLoc[2] = { -17, +15 };
312 static PIECE pPawn[2] = { BLACK_PAWN, WHITE_PAWN };
314 ASSERT(IS_ON_BOARD(cTest));
315 ASSERT(IS_VALID_COLOR(uSide));
316 ASSERT((pos->uNonPawnCount[uSide][0] - 1) < 16);
319 // Begin at the end because the king is the 0th item in the list and
320 // we want to consider king moves last.
322 for (u = pos->uNonPawnCount[uSide][0] - 1;
326 cLocation = pos->cNonPawns[uSide][u];
327 ASSERT(IS_ON_BOARD(cLocation));
328 pPiece = pos->rgSquare[cLocation].pPiece;
329 ASSERT(!IS_EMPTY(pPiece));
330 ASSERT(!IS_PAWN(pPiece));
331 ASSERT(GET_COLOR(pPiece) == uSide);
332 iIndex = (int)cLocation - (int)cTest;
334 if (!(CHECK_VECTOR_WITH_INDEX(iIndex, uSide) &
335 (1 << (PIECE_TYPE(pPiece)))))
341 // These pieces do not slide, we don't need to look for
342 // blockers. If they can get there then there is nothing
343 // we can do to stop them.
345 if (IS_KNIGHT(pPiece) || IS_KING(pPiece))
351 // Well, here we have a sliding piece (bishop, rook or queen)
352 // that is on the same line (file, rank or diagonal) as the
353 // cTest. We now have to see if the attacker (pPiece) is
354 // blocked or is free to attack cTest.
356 iDelta = NEG_DELTA_WITH_INDEX(iIndex);
357 for (cBlockIndex = cTest + iDelta;
358 cBlockIndex != cLocation;
359 cBlockIndex += iDelta)
361 if (!IS_EMPTY(pos->rgSquare[cBlockIndex].pPiece))
366 if (cBlockIndex == cLocation)
373 // Check the locations a pawn could attack cTest from.
375 cLocation = cTest + iPawnLoc[uSide];
376 if (IS_ON_BOARD(cLocation) &&
377 (pos->rgSquare[cLocation].pPiece == pPawn[uSide]))
383 if (IS_ON_BOARD(cLocation) &&
384 (pos->rgSquare[cLocation].pPiece == pPawn[uSide]))
393 InCheck(POSITION *pos, ULONG uSide)
398 Determine if a side is in check or not.
402 POSITION *pos : the board
403 ULONG uSide : the side we want to determine if is in check
407 FLAG : TRUE if side is in check, FALSE otherwise.
416 ASSERT(IS_VALID_COLOR(uSide));
418 cKingLoc = pos->cNonPawns[uSide][0];
420 pKing = pos->rgSquare[cKingLoc].pPiece;
421 ASSERT(IS_KING(pKing));
422 ASSERT(IS_VALID_COLOR(uSide));
423 ASSERT(GET_COLOR(pKing) == uSide);
425 return(IsAttacked(cKingLoc, pos, FLIP(uSide)));
430 _SanityCheckPawnMove(POSITION *pos, MOVE mv)
447 COOR cFrom = mv.cFrom;
454 // General sanity checking...
456 if (!IS_ON_BOARD(cFrom) || !IS_ON_BOARD(cTo))
462 // Sanity check promotions.
466 if (GET_COLOR(p) == WHITE)
468 if ((RANK(cTo) != 8) && (RANK(cFrom) != 7)) return(FALSE);
472 if ((RANK(cTo) != 1) && (RANK(cFrom) != 2)) return(FALSE);
475 if (!IS_KNIGHT(mv.pPromoted) &&
476 !IS_BISHOP(mv.pPromoted) &&
477 !IS_ROOK(mv.pPromoted) &&
478 !IS_QUEEN(mv.pPromoted))
485 // Handle capture moves.
490 // Must be capturing something
492 if (IS_SPECIAL_MOVE(mv) && (!mv.pPromoted))
497 if (!IS_EMPTY(pos->rgSquare[cTo].pPiece)) return(FALSE);
504 if (IS_EMPTY(pos->rgSquare[cTo].pPiece)) return(FALSE);
505 if (!OPPOSITE_COLORS(mv.pMoved, mv.pCaptured)) return(FALSE);
509 // Must be in the capturing position.
511 if (WHITE == GET_COLOR(p))
513 if (((cFrom - 17) != cTo) && ((cFrom - 15) != cTo)) return(FALSE);
517 if (((cFrom + 17) != cTo) && ((cFrom + 15) != cTo)) return(FALSE);
520 if ((IS_SPECIAL_MOVE(mv)) &&
522 (pos->cEpSquare != cTo)) return(FALSE);
528 // If the pawn is not capturing, the TO square better be empty.
530 if (!IS_EMPTY(pos->rgSquare[cTo].pPiece)) return(FALSE);
533 // A non-capturing pawn can only push forward. One or two squares
534 // based on whether or not its the initial move.
536 if (WHITE == GET_COLOR(p))
538 if (cTo == cFrom - 16) return(TRUE);
539 if ((cTo == cFrom - 32) &&
541 (IS_EMPTY(pos->rgSquare[cFrom - 16].pPiece))) return(TRUE);
545 if (cTo == cFrom + 16) return(TRUE);
546 if ((cTo == cFrom + 32) &&
548 (IS_EMPTY(pos->rgSquare[cFrom + 16].pPiece))) return(TRUE);
556 _SanityCheckPieceMove(POSITION *pos, MOVE mv)
573 PIECE pPieceType = p >> 1;
575 COOR cFrom = mv.cFrom;
589 ASSERT(IS_KING(mv.pMoved));
590 ASSERT(IS_SPECIAL_MOVE(mv));
595 // Make sure the piece moves in the way the move describes.
597 iIndex = (int)cFrom - (int)cTo;
598 if (0 == (CHECK_VECTOR_WITH_INDEX(iIndex, BLACK) &
605 // If we get here the piece can move in the way described by the
606 // move but we still have to check to see if there are any other
607 // pieces in the way if the piece moved is not a king or knight
608 // (pawns handled in their own routine, see above).
610 if ((pPieceType == QUEEN) ||
611 (pPieceType == ROOK) ||
612 (pPieceType == BISHOP))
614 iDelta = CHECK_DELTA_WITH_INDEX(iIndex);
615 for (cBlock = cFrom + iDelta;
619 if (!IS_EMPTY(pos->rgSquare[cBlock].pPiece))
624 if (cBlock != cTo) return(FALSE);
628 // Ok the piece can move the way they asked and there are no other
629 // pieces in the way... but it cannot expose its own king to check
632 cKing = pos->cNonPawns[pos->uToMove][0];
633 cAttack = ExposesCheck(pos,
636 if ((ILLEGAL_COOR != cAttack) && (cTo != cAttack))
639 // Ok if we are here then removing this piece from the from square
640 // does expose the king to check and the move does not capture the
641 // checking piece. But it's still ok as long as the TO location
642 // still blocks the checking piece.
644 iIndex = cAttack - cKing;
645 iDelta = CHECK_DELTA_WITH_INDEX(iIndex);
646 for (cBlock = cAttack + iDelta;
650 if ((cBlock == cTo) ||
651 !IS_EMPTY(pos->rgSquare[cBlock].pPiece)) break;
653 if (cBlock == cKing) return(FALSE);
664 SanityCheckMove(POSITION *pos, MOVE mv)
669 Sanity check a move -- Note: Not a strict chess legality checker!
682 register PIECE p = mv.pMoved;
688 else if (mv.cTo == mv.cFrom)
692 else if (IS_EMPTY(pos->rgSquare[mv.cFrom].pPiece))
696 else if (GET_COLOR(mv.pMoved) != pos->uToMove)
700 else if ((!IS_EMPTY(pos->rgSquare[mv.cTo].pPiece)) &&
706 if (!IS_SPECIAL_MOVE(mv))
710 if (IS_EMPTY(pos->rgSquare[mv.cTo].pPiece))
715 if (((pos->rgSquare[mv.cTo].pPiece) != mv.pCaptured) ||
716 (!OPPOSITE_COLORS(mv.pCaptured, mv.pMoved)))
722 if (!IS_EMPTY(pos->rgSquare[mv.cTo].pPiece) &&
730 if (!IS_PAWN(p) && !IS_KING(p))
738 return(_SanityCheckPawnMove(pos, mv));
742 return(_SanityCheckPieceMove(pos, mv));
748 LooksLikeFile(CHAR c)
753 Does some char look like a file (i.e. 'a' <= char <= 'h')
765 if ((toupper(c) >= 'A') &&
773 FLAG LooksLikeRank(CHAR c)
778 Does char look like a coor rank (i.e. '1' <= char <= '8')
790 if (((c - '0') >= 1) &&
800 LooksLikeCoor(char *szData)
805 Do the first two chars in szData look like a file/rank?
820 if (LooksLikeFile(f) &&
830 StripMove(CHAR *szMove)
835 Strip decoration punctuation marks out of a move.
847 CHAR cIgnore[] = "?!x+#-=\r\n"; // chars stripped from szMove
848 static CHAR szStripped[10];
852 memset(szStripped, 0, sizeof(szStripped));
857 if (NULL == strchr(cIgnore, *p))
859 szStripped[y++] = *p;
867 ULONG LooksLikeMove(char *szData)
872 Does some char pointer point at some text that looks like a move?
873 If so, does the move look like SAN or ICS style?
885 CHAR *szStripped = StripMove(szData);
887 static CHAR cPieces[] = { 'P', 'N', 'B', 'R', 'Q', 'K', '\0' };
890 // A (stripped) move must be at least two characters long even in
893 if ((strlen(szStripped) < 2) || (strlen(szStripped) > 7))
901 if (!STRNCMPI(szStripped, "OOO", 3) ||
902 !STRNCMPI(szStripped, "OO", 2) ||
903 !STRNCMPI(szStripped, "00", 2) || // duh
904 !STRNCMPI(szStripped, "000", 3))
912 if (LooksLikeCoor(szStripped) &&
913 LooksLikeCoor(szStripped + 2))
921 if (LooksLikeCoor(szStripped) &&
922 strlen(szStripped) == 2)
930 if ((LooksLikeRank(szStripped[0]) ||
931 LooksLikeFile(szStripped[0])) &&
932 LooksLikeCoor(&szStripped[1]))
943 for (u = 0; u < ARRAY_LENGTH(cPieces); u++)
945 if (szStripped[0] == cPieces[u]) // must be uppercase!
947 if (LooksLikeCoor(&szStripped[1]))
951 if (LooksLikeFile(szStripped[1]) &&
952 LooksLikeCoor(&szStripped[2]))
956 if (LooksLikeRank(szStripped[1]) &&
957 LooksLikeCoor(&szStripped[2]))
967 if ((strlen(szStripped) == 4) &&
968 LooksLikeCoor(szStripped) &&
969 szStripped[2] == '=')
971 if (NULL != strchr(cPieces, szStripped[3]))
980 if ((strlen(szStripped) == 3) &&
981 LooksLikeCoor(szStripped) &&
982 (NULL != strchr(cPieces, szStripped[2])))
992 SelectBestWithHistory(SEARCHER_THREAD_CONTEXT *ctx,
998 Pick the best (i.e. move with the highest "score" assigned to it
999 at generation time) that has not been played yet this ply and move
1000 it to the front of the move list to be played next.
1004 SEARCHER_THREAD_CONTEXT *ctx,
1014 register ULONG uEnd = ctx->sMoveStack.uEnd[ctx->uPly];
1015 register SCORE iBestVal;
1019 MOVE_STACK_MOVE_VALUE_FLAGS mvfTemp;
1021 ASSERT(ctx->sMoveStack.uBegin[ctx->uPly] <= uEnd);
1022 ASSERT(u >= ctx->sMoveStack.uBegin[ctx->uPly]);
1026 // Linear search from u..ctx->sMoveStack.uEnd[ctx->uPly] for the
1027 // move with the best value.
1029 iBestVal = ctx->sMoveStack.mvf[u].iValue;
1030 mv = ctx->sMoveStack.mvf[u].mv;
1031 if (!IS_CAPTURE_OR_PROMOTION(mv))
1033 iBestVal += g_HistoryCounters[mv.pMoved][mv.cTo];
1037 for (v = u + 1; v < uEnd; v++)
1039 iVal = ctx->sMoveStack.mvf[v].iValue;
1040 mv = ctx->sMoveStack.mvf[v].mv;
1041 if (!IS_CAPTURE_OR_PROMOTION(mv))
1043 iVal += g_HistoryCounters[mv.pMoved][mv.cTo];
1045 if (iVal > iBestVal)
1053 // Note: the if here slows down the code, just swap em.
1055 mvfTemp = ctx->sMoveStack.mvf[u];
1056 ctx->sMoveStack.mvf[u] = ctx->sMoveStack.mvf[uLoc];
1057 ctx->sMoveStack.mvf[uLoc] = mvfTemp;
1062 SelectBestNoHistory(SEARCHER_THREAD_CONTEXT *ctx,
1066 Routine description:
1068 Pick the best (i.e. move with the highest "score" assigned to it
1069 at generation time) that has not been played yet this ply and move
1070 it to the front of the move list to be played next.
1074 SEARCHER_THREAD_CONTEXT *ctx,
1084 register ULONG uEnd = ctx->sMoveStack.uEnd[ctx->uPly];
1085 register SCORE iBestVal;
1088 MOVE_STACK_MOVE_VALUE_FLAGS mvfTemp;
1090 ASSERT(ctx->sMoveStack.uBegin[ctx->uPly] <= uEnd);
1091 ASSERT(u >= ctx->sMoveStack.uBegin[ctx->uPly]);
1095 // Linear search from u..ctx->sMoveStack.uEnd[ctx->uPly] for the
1096 // move with the best value.
1098 iBestVal = ctx->sMoveStack.mvf[u].iValue;
1100 for (v = u + 1; v < uEnd; v++)
1102 iVal = ctx->sMoveStack.mvf[v].iValue;
1103 if (iVal > iBestVal)
1111 // Note: the if here slows down the code, just swap em.
1113 mvfTemp = ctx->sMoveStack.mvf[u];
1114 ctx->sMoveStack.mvf[u] = ctx->sMoveStack.mvf[uLoc];
1115 ctx->sMoveStack.mvf[uLoc] = mvfTemp;
1119 SelectMoveAtRoot(SEARCHER_THREAD_CONTEXT *ctx,
1123 Routine description:
1127 SEARCHER_THREAD_CONTEXT *ctx,
1137 ULONG uEnd = ctx->sMoveStack.uEnd[ctx->uPly];
1138 SCORE iBestVal = -INFINITY;
1141 MOVE_STACK_MOVE_VALUE_FLAGS mvfTemp;
1143 ASSERT(ctx->sMoveStack.uBegin[ctx->uPly] <= uEnd);
1144 ASSERT(u >= ctx->sMoveStack.uBegin[ctx->uPly]);
1146 ASSERT(MOVE_COUNT(ctx, ctx->uPly) >= 1);
1149 // Find the first move that we have not already searched. It will
1150 // provide our initial iBestVal.
1154 if (!(ctx->sMoveStack.mvf[v].bvFlags & MVF_MOVE_SEARCHED))
1156 iBestVal = ctx->sMoveStack.mvf[v].iValue;
1163 if (v >= uEnd) return;
1166 // Search the rest of the moves that have not yet been tried by
1167 // RootSearch and find the one with the best value.
1169 for (v = uLoc + 1; v < uEnd; v++)
1171 if (!(ctx->sMoveStack.mvf[v].bvFlags & MVF_MOVE_SEARCHED))
1173 iVal = ctx->sMoveStack.mvf[v].iValue;
1174 if (iVal > iBestVal)
1183 // Move the best move we found into position u.
1185 mvfTemp = ctx->sMoveStack.mvf[u];
1186 ctx->sMoveStack.mvf[u] = ctx->sMoveStack.mvf[uLoc];
1187 ctx->sMoveStack.mvf[uLoc] = mvfTemp;
1191 static UINT64 g_uPerftNodeCount;
1192 static UINT64 g_uPerftTotalNodes;
1193 static UINT64 g_uPerftGenerates;
1196 Perft(SEARCHER_THREAD_CONTEXT *ctx,
1203 g_uPerftTotalNodes += 1;
1204 ASSERT(uDepth < MAX_PLY_PER_SEARCH);
1207 g_uPerftNodeCount += 1;
1212 g_uPerftGenerates += 1;
1213 GenerateMoves(ctx, mv, GENERATE_DONT_SCORE);
1216 for (u = ctx->sMoveStack.uBegin[uPly];
1217 u < ctx->sMoveStack.uEnd[uPly];
1220 mv = ctx->sMoveStack.mvf[u].mv;
1221 if (MakeMove(ctx, mv))
1223 Perft(ctx, uDepth - 1);
1224 UnmakeMove(ctx, mv);
1229 COMMAND(PerftCommand)
1232 Routine description:
1234 This function implements the 'perft' engine command. I have no
1235 idea what 'perft' means but some people use this command to
1236 benchmark the speed of the move generator code. Note: the way
1237 this is implemented here does nothing whatsoever with the move
1238 scoring code (i.e. the SEE)
1246 The COMMAND macro hides four arguments from the input parser:
1248 CHAR *szInput : the full line of input
1249 ULONG argc : number of argument chunks
1250 CHAR *argv[] : array of ptrs to each argument chunk
1251 POSITION *pos : a POSITION pointer to operate on
1259 LIGHTWEIGHT_SEARCHER_CONTEXT ctx;
1262 double dBegin, dTime;
1267 Trace("Usage: perft <required_depth>\n");
1270 uDepth = atoi(argv[1]);
1271 if (uDepth >= MAX_DEPTH_PER_SEARCH)
1273 Trace("Error: invalid depth.\n");
1277 InitializeLightweightSearcherContext(pos, &ctx);
1279 g_uPerftTotalNodes = g_uPerftGenerates = 0ULL;
1280 dBegin = SystemTimeStamp();
1281 for (u = 1; u <= uDepth; u++)
1283 g_uPerftNodeCount = 0ULL;
1284 Perft((SEARCHER_THREAD_CONTEXT *)&ctx, u);
1285 Trace("%u. %" COMPILER_LONGLONG_UNSIGNED_FORMAT " node%s, "
1286 "%" COMPILER_LONGLONG_UNSIGNED_FORMAT " generate%s.\n",
1289 (g_uPerftNodeCount > 1) ? "s" : "",
1291 (g_uPerftGenerates > 1) ? "s" : "");
1293 dTime = SystemTimeStamp() - dBegin;
1294 dNps = (double)g_uPerftTotalNodes;
1296 Trace("%" COMPILER_LONGLONG_UNSIGNED_FORMAT " total nodes, "
1297 "%" COMPILER_LONGLONG_UNSIGNED_FORMAT " total generates "
1298 "in %6.2f seconds.\n",
1302 Trace("That's approx %.0f moves/sec\n", dNps);