3 Copyright (c) Scott Gasch
11 Maintain a list of game moves. This list is used to take back
12 official moves. It's also used to produce PGN at the end of a
13 game and to detect draws by repetition. This code also maintains
14 the official "current position" of the game in progress.
22 $Id: gamelist.c 355 2008-07-01 15:46:43Z scott $
29 ULONG g_uMoveNumber[2] = { 1, 1 };
31 ALIGN64 POSITION g_RootPosition;
37 #define MOVE_POOL_SIZE (256)
38 GAME_MOVE g_MovePool[MOVE_POOL_SIZE];
41 _GetMoveFromPool(void)
46 Fast GAME_MOVE allocator that uses a preallocated pool.
58 ULONG x = (rand() % (MOVE_POOL_SIZE - 15));
61 for (y = x; y < x + 15; y++)
63 if (g_MovePool[y].fInUse == FALSE)
65 LockIncrement(&(g_MovePool[y].fInUse));
66 return(&(g_MovePool[y]));
72 return(SystemAllocateMemory(sizeof(GAME_MOVE)));
76 _ReturnMoveToPool(GAME_MOVE *p)
81 Return a GAME_MOVE to the pool.
93 ULONG x = (ULONG)((BYTE *)p - (BYTE *)&g_MovePool);
95 x /= sizeof(GAME_MOVE);
96 if (x < MOVE_POOL_SIZE)
98 g_MovePool[x].fInUse = FALSE;
110 _FreeIfNotNull(void *p)
115 Free a pointer if it's not null.
137 _FreeGameMove(GAME_MOVE *p)
142 Free memory allocated to store a game move.
156 _FreeIfNotNull(p->szComment);
157 _FreeIfNotNull(p->szDecoration);
158 _FreeIfNotNull(p->szMoveInSan);
159 _FreeIfNotNull(p->szMoveInIcs);
160 _FreeIfNotNull(p->szUndoPositionFen);
161 _ReturnMoveToPool(p);
170 Free the whole game list.
185 if (NULL != g_GameData.sMoveList.pFlink)
187 while(FALSE == IsListEmpty(&(g_GameData.sMoveList)))
189 p = RemoveHeadList(&(g_GameData.sMoveList));
190 q = CONTAINING_STRUCT(p, GAME_MOVE, links);
195 _FreeIfNotNull(g_GameData.sHeader.szGameDescription);
196 _FreeIfNotNull(g_GameData.sHeader.szLocation);
197 _FreeIfNotNull(g_GameData.sHeader.sPlayer[WHITE].szName);
198 _FreeIfNotNull(g_GameData.sHeader.sPlayer[WHITE].szDescription);
199 _FreeIfNotNull(g_GameData.sHeader.sPlayer[BLACK].szName);
200 _FreeIfNotNull(g_GameData.sHeader.sPlayer[BLACK].szDescription);
201 _FreeIfNotNull(g_GameData.sHeader.szInitialFen);
202 memset(&g_GameData, 0, sizeof(g_GameData));
211 Reset the game list (between games etc...)
224 ASSERT(g_uAllocs == 0);
225 memset(g_MovePool, 0, sizeof(g_MovePool));
226 g_uMoveNumber[BLACK] = g_uMoveNumber[WHITE] = 1;
228 InitializeListHead(&(g_GameData.sMoveList));
229 g_GameData.sHeader.result.eResult = RESULT_IN_PROGRESS;
233 SetRootPosition(CHAR *szFen)
238 Set the root position.
250 if (TRUE == FenToPosition(&g_RootPosition, szFen))
253 g_uToMove = g_RootPosition.uToMove;
254 TellGamelistThatIPlayColor(FLIP(g_RootPosition.uToMove));
255 if (0 != strcmp(szFen, STARTING_POSITION_IN_FEN))
257 g_GameData.sHeader.szInitialFen = STRDUP(szFen);
259 if (g_GameData.sHeader.szInitialFen)
265 if (g_Options.fShouldPost)
267 DumpPosition(&g_RootPosition);
275 GetRootPosition(void)
280 Get the root position.
295 memcpy(&p, &g_RootPosition, sizeof(POSITION));
299 return(&g_RootPosition);
304 SetGameResultAndDescription(GAME_RESULT result)
309 Set the game result and a description (both for PGN).
321 g_GameData.sHeader.result = result;
324 GAME_RESULT GetGameResult(void)
341 return g_GameData.sHeader.result;
346 GetNthOfficialMoveRecord(ULONG n)
351 Get the official move record for move N.
363 DLIST_ENTRY *p = g_GameData.sMoveList.pFlink;
366 while((p != &(g_GameData.sMoveList)) &&
375 q = CONTAINING_STRUCT(p, GAME_MOVE, links);
383 DoesSigAppearInOfficialGameList(UINT64 u64Sig)
388 Does sig X appear in the official move list?
400 DLIST_ENTRY *p = g_GameData.sMoveList.pBlink;
403 while(p != &(g_GameData.sMoveList))
405 q = CONTAINING_STRUCT(p, GAME_MOVE, links);
406 if (q->u64PositionSigAfterMove == u64Sig)
410 if ((q->mv.pCaptured) || (IS_PAWN(q->mv.pMoved)))
419 ULONG CountOccurrancesOfSigInOfficialGameList(UINT64 u64Sig)
424 How many times does sig X appear in the official move list?
436 DLIST_ENTRY *p = g_GameData.sMoveList.pBlink;
440 while(p != &(g_GameData.sMoveList))
442 q = CONTAINING_STRUCT(p, GAME_MOVE, links);
443 if (q->u64PositionSigAfterMove == u64Sig)
447 if ((q->mv.pCaptured) || (IS_PAWN(q->mv.pMoved)))
458 IsLegalDrawByRepetition(void)
463 Have we just drawn the game by repetition?
475 POSITION *pos = GetRootPosition();
476 UINT64 u64Sig = (pos->u64PawnSig ^ pos->u64NonPawnSig);
478 if (3 == CountOccurrancesOfSigInOfficialGameList(u64Sig))
492 Dump the move list to stdout.
507 Trace("\twhite\tblack\n"
509 q = GetNthOfficialMoveRecord(u);
510 while((q != NULL) && (q->mv.uMove != 0))
512 if (GET_COLOR(q->mv.pMoved) == WHITE)
514 Trace("%2u.\t%s\t", q->uNumber, q->szMoveInSan);
518 Trace("%s\n", q->szMoveInSan);
521 q = GetNthOfficialMoveRecord(u);
532 Dump the PGN of the current game to stdout.
544 DLIST_ENTRY *p = g_GameData.sMoveList.pFlink;
548 Trace("[Event \"Computer Chess Game\"]\n");
549 if (g_GameData.sHeader.szLocation != NULL)
551 Trace("[Site \"%s\"]\n", g_GameData.sHeader.szLocation);
553 Trace("[Date \"%s\"]\n", SystemGetDateString());
554 Trace("[Round 0]\n");
555 Trace("[White \"%s\"]\n", g_GameData.sHeader.sPlayer[WHITE].szName);
556 Trace("[Black \"%s\"]\n", g_GameData.sHeader.sPlayer[BLACK].szName);
557 switch(g_GameData.sHeader.result.eResult)
559 case RESULT_BLACK_WON:
560 Trace("[Result \"0-1\"]\n");
562 case RESULT_WHITE_WON:
563 Trace("[Result \"1-0\"]\n");
566 Trace("[Result \"1/2-1/2\"]\n");
569 Trace("[Result \"*\"]\n");
572 if (g_GameData.sHeader.sPlayer[WHITE].uRating != 0)
574 Trace("[WhiteElo \"%u\"]\n", g_GameData.sHeader.sPlayer[WHITE].uRating);
576 if (g_GameData.sHeader.sPlayer[BLACK].uRating != 0)
578 Trace("[BlackElo \"%u\"]\n", g_GameData.sHeader.sPlayer[BLACK].uRating);
580 if (NULL != g_GameData.sHeader.szInitialFen)
582 Trace("[InitialFEN \"%s\"]\n", g_GameData.sHeader.szInitialFen);
585 Trace("[Time \"%s\"]\n", SystemGetTimeString());
587 9.6.1: Tag: TimeControl
589 This uses a list of one or more time control fields. Each field contains a
590 descriptor for each time control period; if more than one descriptor is present
591 then they are separated by the colon character (":"). The descriptors appear
592 in the order in which they are used in the game. The last field appearing is
593 considered to be implicitly repeated for further control periods as needed.
595 There are six kinds of TimeControl fields.
597 The first kind is a single question mark ("?") which means that the time
598 control mode is unknown. When used, it is usually the only descriptor present.
600 The second kind is a single hyphen ("-") which means that there was no time
601 control mode in use. When used, it is usually the only descriptor present.
603 The third Time control field kind is formed as two positive integers separated
604 by a solidus ("/") character. The first integer is the number of moves in the
605 period and the second is the number of seconds in the period. Thus, a time
606 control period of 40 moves in 2 1/2 hours would be represented as "40/9000".
608 The fourth TimeControl field kind is used for a "sudden death" control period.
609 It should only be used for the last descriptor in a TimeControl tag value. It
610 is sometimes the only descriptor present. The format consists of a single
611 integer that gives the number of seconds in the period. Thus, a blitz game
612 would be represented with a TimeControl tag value of "300".
614 The fifth TimeControl field kind is used for an "incremental" control period.
615 It should only be used for the last descriptor in a TimeControl tag value and
616 is usually the only descriptor in the value. The format consists of two
617 positive integers separated by a plus sign ("+") character. The first integer
618 gives the minimum number of seconds allocated for the period and the second
619 integer gives the number of extra seconds added after each move is made. So,
620 an incremental time control of 90 minutes plus one extra minute per move would
621 be given by "4500+60" in the TimeControl tag value.
623 The sixth TimeControl field kind is used for a "sandclock" or "hourglass"
624 control period. It should only be used for the last descriptor in a
625 TimeControl tag value and is usually the only descriptor in the value. The
626 format consists of an asterisk ("*") immediately followed by a positive
627 integer. The integer gives the total number of seconds in the sandclock
628 period. The time control is implemented as if a sandclock were set at the
629 start of the period with an equal amount of sand in each of the two chambers
630 and the players invert the sandclock after each move with a time forfeit
631 indicated by an empty upper chamber. Electronic implementation of a physical
632 sandclock may be used. An example sandclock specification for a common three
633 minute egg timer sandclock would have a tag value of "*180".
635 Additional TimeControl field kinds will be defined as necessary.
640 while(p != &(g_GameData.sMoveList))
642 q = CONTAINING_STRUCT(p, GAME_MOVE, links);
643 if (GET_COLOR(q->mv.pMoved) == WHITE)
645 Trace("%2u. %s ", q->uNumber, q->szMoveInSan);
649 Trace("%s ", q->szMoveInSan);
659 switch(g_GameData.sHeader.result.eResult)
661 case RESULT_BLACK_WON:
664 case RESULT_WHITE_WON:
675 if (strlen(g_GameData.sHeader.result.szDescription)) {
676 Trace("%s\n", g_GameData.sHeader.result.szDescription);
681 OfficiallyTakebackMove(void)
686 Take back a move (i.e. unmake it and delete it from the move
701 POSITION *pos = &g_RootPosition;
703 if (FALSE == IsListEmpty(&(g_GameData.sMoveList)))
705 p = RemoveTailList(&(g_GameData.sMoveList));
707 q = CONTAINING_STRUCT(p, GAME_MOVE, links);
708 if (FALSE == FenToPosition(pos, q->szUndoPositionFen))
710 UtilPanic(INCONSISTENT_STATE,
712 q->szUndoPositionFen,
718 VerifyPositionConsistency(pos, FALSE);
720 ASSERT(g_uMoveNumber[g_uToMove] > 0);
721 g_uMoveNumber[g_uToMove] -= 1;
722 g_uToMove = FLIP(g_uToMove);
729 OfficiallyMakeMove(MOVE mv,
736 Officially make a move (i.e. add it to the game list)
749 static SEARCHER_THREAD_CONTEXT ctx;
752 POSITION *pos = &g_RootPosition;
753 UINT64 u64SigBefore = pos->u64PawnSig ^ pos->u64NonPawnSig;
755 ReInitializeSearcherContext(pos, &ctx);
757 ASSERT(GET_COLOR(mv.pMoved) == g_uToMove);
758 if (FALSE == MakeUserMove(&ctx, mv))
763 q = _GetMoveFromPool();
768 q->uNumber = g_uMoveNumber[g_uToMove];
769 ASSERT(q->uNumber > 0);
770 g_uMoveNumber[g_uToMove] += 1;
771 ASSERT(g_uMoveNumber[g_uToMove] != 0);
772 q->mv.uMove = mv.uMove;
774 q->szDecoration = NULL;
775 q->iMoveScore = iMoveScore;
777 q->szMoveInSan = STRDUP(MoveToSan(mv, pos));
784 q->szMoveInIcs = STRDUP(MoveToIcs(mv));
791 q->szUndoPositionFen = PositionToFen(pos);
795 InsertTailList(&(g_GameData.sMoveList), &(q->links));
796 q->u64PositionSigAfterMove = (ctx.sPosition.u64NonPawnSig ^
797 ctx.sPosition.u64PawnSig);
798 q->u64PositionSigBeforeMove = u64SigBefore;
799 g_uToMove = FLIP(g_uToMove);
802 // Update the root position
804 memcpy(pos, &(ctx.sPosition), sizeof(POSITION));
805 VerifyPositionConsistency(&(ctx.sPosition), FALSE);
818 What color am I playing?
830 switch(g_Options.ePlayMode)
841 GetOpponentsColor(void)
846 What color is the opponent playing?
858 return(FLIP(GetMyColor()));
862 SetOpponentsName(CHAR *sz)
867 Set my opponent's name for the PGN record. Hello, my name is
868 Inigo Montoya. You killed my father. Prepare to die.
880 _FreeIfNotNull(g_GameData.sHeader.sPlayer[GetOpponentsColor()].szName);
881 g_GameData.sHeader.sPlayer[GetOpponentsColor()].szName = STRDUP(sz);
883 if (g_GameData.sHeader.sPlayer[GetOpponentsColor()].szName)
895 Set my name for the PGN record. Some people call me... Tim?
907 ULONG u = GetMyColor();
909 _FreeIfNotNull(g_GameData.sHeader.sPlayer[u].szName);
910 g_GameData.sHeader.sPlayer[u].szName = STRDUP("typhoon");
912 if (g_GameData.sHeader.sPlayer[u].szName)
917 _FreeIfNotNull(g_GameData.sHeader.sPlayer[u].szDescription);
918 g_GameData.sHeader.sPlayer[u].szDescription =
919 STRDUP("Ver: " VERSION " Build Time: " __TIME__ " " __DATE__);
921 if (g_GameData.sHeader.sPlayer[u].szDescription)
926 g_GameData.sHeader.sPlayer[u].fIsComputer = TRUE;
935 Set my rating for the PGN record.
947 g_GameData.sHeader.sPlayer[GetMyColor()].uRating = u;
951 SetOpponentsRating(ULONG u)
956 Set the opponent's rating for the PGN record.
968 g_GameData.sHeader.sPlayer[GetOpponentsColor()].uRating = u;
972 TellGamelistThatIPlayColor(ULONG u)
977 The gamelist needs to know what color the computer is playing.
978 This routine sets it.
990 ULONG uOldColor = GetMyColor();
993 ASSERT(IS_VALID_COLOR(u));
994 ASSERT(IS_VALID_COLOR(uOldColor));
998 x = g_GameData.sHeader.sPlayer[uOldColor];
999 g_GameData.sHeader.sPlayer[uOldColor] =
1000 g_GameData.sHeader.sPlayer[u];
1001 g_GameData.sHeader.sPlayer[u] = x;
1006 GetMoveNumber(ULONG uColor)
1009 Routine description:
1011 What move number is it?
1023 return(g_uMoveNumber[uColor]);
1027 MakeStatusLine(void)
1030 Routine description:
1042 char buf[SMALL_STRING_LEN_CHAR];
1043 ASSERT(IS_VALID_COLOR(g_uToMove));
1045 if (g_Options.fStatusLine)
1048 if (!g_Options.fRunningUnderXboard)
1050 if ((TRUE == g_Options.fPondering) &&
1051 (g_Options.mvPonder.uMove != 0))
1053 snprintf(buf, SMALL_STRING_LEN_CHAR - 1,
1055 MoveToSan(g_Options.mvPonder, &g_RootPosition));
1057 snprintf(buf, SMALL_STRING_LEN_CHAR - strlen(buf),
1060 (g_uToMove == WHITE) ? "white" : "black",
1061 g_uMoveNumber[g_uToMove]);
1068 _ParsePgnHeaderLine(CHAR *p, CHAR **ppVar, CHAR **ppVal)
1071 Routine description:
1087 if (*p != '[') return;
1091 while(*p && !isspace(*p)) p++;
1095 while(*p && *p != ']') p++;
1099 _ParsePgnHeaderTag(CHAR *p)
1102 Routine description:
1104 Parse PGN headers and extract people's names, ratings, game
1119 _ParsePgnHeaderLine(p, &pVar, &pVal);
1120 if ((NULL == pVar) || (NULL == pVal))
1125 if (!STRNCMPI(pVar, "BLACK ", 6))
1128 while(*p && *p != ']') p++;
1132 _FreeIfNotNull(g_GameData.sHeader.sPlayer[BLACK].szName);
1133 g_GameData.sHeader.sPlayer[BLACK].szName = STRDUP(pVal);
1135 if (g_GameData.sHeader.sPlayer[BLACK].szName)
1144 else if (!STRNCMPI(pVar, "WHITE ", 6))
1147 while(*p && *p != ']') p++;
1151 _FreeIfNotNull(g_GameData.sHeader.sPlayer[WHITE].szName);
1152 g_GameData.sHeader.sPlayer[WHITE].szName = STRDUP(pVal);
1154 if (g_GameData.sHeader.sPlayer[WHITE].szName)
1163 else if (!STRNCMPI(pVar, "RESULT", 6))
1167 if (!STRNCMPI(p, "1-0", 3))
1170 res.eResult = RESULT_WHITE_WON;
1171 strcpy(res.szDescription, "PGN game");
1172 SetGameResultAndDescription(res);
1174 else if (!STRNCMPI(p, "0-1", 3))
1177 res.eResult = RESULT_BLACK_WON;
1178 strcpy(res.szDescription, "PGN game");
1179 SetGameResultAndDescription(res);
1181 else if (!STRNCMPI(p, "1/2", 3))
1184 res.eResult = RESULT_DRAW;
1185 strcpy(res.szDescription, "PGN game");
1186 SetGameResultAndDescription(res);
1188 while(*p && *p != ']') p++;
1197 LoadPgn(CHAR *szPgn)
1200 Routine description:
1202 Load a PGN blob and create a move list from it.
1218 ULONG uMoveCount = 0;
1221 FLAG fOldPost = g_Options.fShouldPost;
1223 g_Options.fShouldPost = FALSE;
1225 SetRootToInitialPosition();
1226 pos = GetRootPosition();
1229 // Get rid of newlines, non-space spaces and .'s
1233 if (isspace(*p)) *p = ' ';
1234 if (*p == '.') *p = ' ';
1241 while(*p && isspace(*p)) p++;
1242 while(*p && (isdigit(*p) || (*p == '.'))) p++;
1245 _ParsePgnHeaderTag(p);
1246 while(*p && *p != ']') p++;
1251 while(*p && *p != '}') p++;
1254 else if (isalpha(*p))
1257 // Copy the thing we think is a move.
1259 memset(szMove, 0, sizeof(szMove));
1261 while(*p && (!isspace(*p)))
1263 if (x < (ARRAY_LENGTH(szMove) - 1))
1271 switch (LooksLikeMove(szMove))
1274 mv = ParseMoveSan(szMove, pos);
1277 mv = ParseMoveIcs(szMove, pos);
1284 //if (FALSE == SanityCheckMove(pos, mv))
1286 // Trace("Error in PGN(?) Bad chunk \"%s\"\n", szMove);
1290 if (FALSE == OfficiallyMakeMove(mv, 0, TRUE))
1292 Trace("Error in PGN(?) Can't play move \"%s\"\n", szMove);
1295 pos = GetRootPosition();
1300 while(*p && !isspace(*p)) p++;
1305 g_Options.fShouldPost = fOldPost;
1311 CleanupString(char *pIn)
1314 Routine description:
1316 Ckeans up a PGN tag containing part of the opening line
1317 name... gets rid of the quotes around the name and the newline.
1329 static char buf[256];
1334 // Skip leading whitespace in input
1337 while(isspace(*p)) p++;
1338 if (*p == '\0') return(buf);
1343 // Copy A-Z, 0-9, space, -, ., (, or )
1358 if ((q - buf) > (sizeof(buf) - 1)) break;