3 Copyright (c) Scott Gasch
11 Routines that translate from an internal POSITION structure into a
12 FEN string and vice versa.
20 $Id: fen.c 345 2007-12-02 22:56:42Z scott $
26 char g_chPieceTypeToAbbrev[] = { '_', 'p', 'n', 'b', 'r', 'q', 'k' };
27 char *g_szPieceAbbrevs = "pPnNbBrRqQkK";
29 FLAG LooksLikeFen(char *p)
34 Does some string look like a FEN?
38 char *p : the string in question
42 FLAG : TRUE if yes, FALSE otherwise
47 return(FenToPosition(&q, p));
51 #define MAX_FEN_LEN_BYTES (128)
54 PositionToFen(IN POSITION *pos)
59 Translate a POSITION structure into a FEN string and return a pointer
60 to that string. This function allocates memory that the caller must
61 free. This code _is_ thread safe.
65 POSITION *p : the position to translate
69 char * : FEN representing POSITION *p. NULL on error.
73 ULONG uNumEmpty; // num emmpty squares in a row
75 PIECE p; // the piece there
76 char *pHeap = NULL; // ptr to a block of heap
77 char *pch; // used to build the string
80 // Alloc a buffer. The caller must free this. Note: I contend that
81 // the longest possible FEN string capable of being generated by this
82 // code is something like:
84 // "k1q1r1r1/b1b1n1n1/p1p1p1p1/p1p1p1p1/K1Q1R1R1/B1B1N1N1/P1P1P1P1/P1P1P1P1 w KQkq d3 49"
86 // ...which is 85 characters long. I allocate 128 here to be safe.
88 pHeap = SystemAllocateMemory(MAX_FEN_LEN_BYTES);
91 memset(pHeap, 0, MAX_FEN_LEN_BYTES);
94 // The first field represents the placement of the pieces on the
95 // board. The board contents are specified starting with the
96 // eighth rank and ending with the first rank. For each rank, the
97 // squares are specified from file a to file h. White pieces are
98 // identified by uppercase SAN piece letters ("PNBRQK") and black
99 // pieces are identified by lowercase SAN piece letters
100 // ("pnbrqk"). Empty squares are represented by the digits one
101 // through eight; the digit used represents the count of
102 // contiguous empty squares along a rank. A solidus character "/"
103 // is used to separate data of adjacent ranks.
112 ASSERT(uNumEmpty < 9);
113 *pch++ = (char)(uNumEmpty + '0');
114 ASSERT(strlen(pHeap) < MAX_FEN_LEN_BYTES);
117 if ((c % 8) == 0) *pch++ = '/';
118 ASSERT(strlen(pHeap) < MAX_FEN_LEN_BYTES);
122 p = pos->rgSquare[c].pPiece;
129 ASSERT(IS_VALID_PIECE(p));
132 ASSERT(uNumEmpty < 9);
133 *pch++ = (char)(uNumEmpty + '0');
134 ASSERT(strlen(pHeap) < MAX_FEN_LEN_BYTES);
138 *pch = g_chPieceTypeToAbbrev[PIECE_TYPE(p)];
139 ASSERT(strlen(pHeap) < MAX_FEN_LEN_BYTES);
140 if (IS_WHITE_PIECE(p))
142 *pch = (char)toupper(*pch);
143 ASSERT(strlen(pHeap) < MAX_FEN_LEN_BYTES);
150 ASSERT(uNumEmpty < 9);
151 *pch++ = (char)(uNumEmpty + '0');
152 ASSERT(strlen(pHeap) < MAX_FEN_LEN_BYTES);
157 // The second field represents the active color. A lower case "w"
158 // is used if White is to move; a lower case "b" is used if Black
159 // is the active player.
162 ASSERT(strlen(pHeap) < MAX_FEN_LEN_BYTES);
163 ASSERT(IS_VALID_COLOR(pos->uToMove));
164 if (WHITE == pos->uToMove)
172 ASSERT(strlen(pHeap) < MAX_FEN_LEN_BYTES);
175 // The third field represents castling availability. This
176 // indicates potential future castling that may of may not be
177 // possible at the moment due to blocking pieces or enemy attacks.
178 // If there is no castling availability for either side, the
179 // single character symbol "-" is used. Otherwise, a combination
180 // of from one to four characters are present. If White has
181 // kingside castling availability, the uppercase letter "K"
182 // appears. If White has queenside castling availability, the
183 // uppercase letter "Q" appears. If Black has kingside castling
184 // availability, the lowercase letter "k" appears. If Black has
185 // queenside castling availability, then the lowercase letter "q"
186 // appears. Those letters which appear will be ordered first
187 // uppercase before lowercase and second kingside before
188 // queenside. There is no white space between the letters.
192 ASSERT(strlen(pHeap) < MAX_FEN_LEN_BYTES);
196 CastleInfoString(pos->bvCastleInfo),
197 CoorToString(pos->cEpSquare),
200 return(pHeap); // Note: caller must free
205 _FenToPosition_DoPiecePart(IN OUT POSITION *pos,
211 The part of FenToPosition that parses the board/piece data in the
212 FEN string and sets the appropriate parts of POSITION *p.
221 static FLAG : TRUE on success, FALSE otherwise
227 ULONG uPieceCounters[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
228 ULONG uNumNonPawns[2] = { 0, 0 };
236 // Parse board position
239 ASSERT(!isspace(*pPieces));
240 while(!isspace(*pPieces) && *pPieces)
242 if (cSquare > ARRAY_SIZE(pos->rgSquare))
244 Trace("FenToPosition: Ignoring extra piece/board information.\n");
248 pPieceIndex = strchr(g_szPieceAbbrevs, *pPieces);
249 if (NULL == pPieceIndex)
256 cSquare = 16 * uFile;
261 // Incrementing the file here handles fens without
262 // delimiting slashes too.
271 if (isdigit(*pPieces))
273 cSquare += (*pPieces - '0');
277 Trace("FenToPosition: Ignoring bad piece '%c' in FEN "
285 // The position of the piece code in the g_szPieceAbbrevs array
286 // cooresponds to the piece type.
288 uPieceIndex = (ULONG)((BYTE *)pPieceIndex -
289 (BYTE *)g_szPieceAbbrevs);
291 p = (PIECE)uPieceIndex;
292 ASSERT(IS_VALID_PIECE(p));
293 uColor = PIECE_COLOR(p);
294 ASSERT(IS_VALID_COLOR(uColor));
299 // The pawn list is not ordered at all, add the new pawn's
300 // location to the list and point from the pawn to the list.
302 pos->cPawns[uColor][uPieceCounters[uPieceIndex]] = cSquare;
303 pos->rgSquare[cSquare].uIndex = uPieceCounters[uPieceIndex];
304 pos->rgSquare[cSquare].pPiece = p;
305 pos->uPawnMaterial[uColor] += VALUE_PAWN;
311 if (pos->cNonPawns[uColor][0] != ILLEGAL_COOR)
313 Trace("_FenToPosition: I can't handle positions with "
314 "more than one king, sorry.\n");
317 pos->cNonPawns[uColor][0] = cSquare;
324 if (IS_WHITE_SQUARE_COOR(cSquare))
326 pos->uWhiteSqBishopCount[uColor]++;
329 uIndex = uNumNonPawns[uColor] + 1;
330 pos->cNonPawns[uColor][uIndex] = cSquare;
331 uNumNonPawns[uColor]++;
333 pos->rgSquare[cSquare].pPiece = p;
334 pos->rgSquare[cSquare].uIndex = uIndex;
335 pos->uNonPawnMaterial[uColor] += PIECE_VALUE(p);
337 uPieceCounters[uPieceIndex]++;
344 // Set the piece counts based on uPieceCounter contents
346 if (IS_ON_BOARD(pos->cNonPawns[WHITE][0]))
348 uNumNonPawns[WHITE]++;
350 pos->uNonPawnCount[WHITE][0] = uNumNonPawns[WHITE];
351 if (IS_ON_BOARD(pos->cNonPawns[BLACK][0]))
353 uNumNonPawns[BLACK]++;
355 pos->uNonPawnCount[BLACK][0] = uNumNonPawns[BLACK];
356 FOREACH_COLOR(uColor)
358 pos->uPawnCount[uColor] = uPieceCounters[(PAWN << 1) | uColor];
359 for (p = KNIGHT; p <= KING; p++)
361 pos->uNonPawnCount[uColor][p] =
362 uPieceCounters[(p << 1) | uColor];
365 ASSERT((pos->uPawnCount[WHITE] * VALUE_PAWN) == pos->uPawnMaterial[WHITE]);
366 ASSERT((pos->uPawnCount[BLACK] * VALUE_PAWN) == pos->uPawnMaterial[BLACK]);
368 if ((ILLEGAL_COOR == pos->cNonPawns[BLACK][0]) ||
369 (ILLEGAL_COOR == pos->cNonPawns[WHITE][0]))
371 Trace("FenToPosition: Warning, missing king location.\n");
379 _FenToPosition_DoSideToMove(IN OUT POSITION *p,
385 The part of FenToPosition that parses side to move part of the FEN
386 string and sets POSITION *p appropriately.
395 static FLAG : TRUE on success, FALSE otherwise
399 p->uToMove = BAD_COLOR;
400 while(!isspace(*pFen) && *pFen)
402 if ((toupper(*pFen) == 'W') ||
408 else if ((toupper(*pFen) == 'B'))
415 Trace("FenToPosition: Ignoring invalid side to move '%c'.\n",
421 if (BAD_COLOR == p->uToMove)
423 Trace("FenToPosition: Never figured out a side to move, defaulting to "
432 _FenToPosition_DoCastlingPermissions(IN OUT POSITION *p,
438 Do the castling part of the FenToPosition function. Sets the proper
439 castling bits in the POSITION pointed to by p based on the text in
444 POSITION *p : position to affect
445 char *pCastle : pointer to castle part of FEN string
449 static FLAG : TRUE on success, FALSE otherwise
453 ASSERT(p->bvCastleInfo == 0);
454 while(!isspace(*pCastle) && *pCastle)
459 if ((p->rgSquare[E1].pPiece == WHITE_KING) &&
460 (p->rgSquare[H1].pPiece == WHITE_ROOK))
462 p->bvCastleInfo |= CASTLE_WHITE_SHORT;
466 if ((p->rgSquare[E1].pPiece == WHITE_KING) &&
467 (p->rgSquare[A1].pPiece == WHITE_ROOK))
469 p->bvCastleInfo |= CASTLE_WHITE_LONG;
473 if ((p->rgSquare[E8].pPiece == BLACK_KING) &&
474 (p->rgSquare[H8].pPiece == BLACK_ROOK))
476 p->bvCastleInfo |= CASTLE_BLACK_SHORT;
480 if ((p->rgSquare[E8].pPiece == BLACK_KING) &&
481 (p->rgSquare[A8].pPiece == BLACK_ROOK))
483 p->bvCastleInfo |= CASTLE_BLACK_LONG;
490 Trace("FenToPosition: Ignoring invalid castling option char"
491 "acter '%c'.\n", *pCastle);
501 _FenToPosition_DoEnPassant(IN OUT POSITION *p,
507 The part of FenToPosition that handles translating an en-passant
508 square in the FEN string into the appropriate settings in the
513 POSITION *p : the position to populate
514 char *pEnPassant : the string that describes how to populate it
518 static FLAG : TRUE on success, FALSE otherwise
522 COOR cEp = ILLEGAL_COOR;
523 ASSERT(!isspace(*pEnPassant));
525 if (*pEnPassant != '-')
527 cEp = FILE_RANK_TO_COOR((tolower(pEnPassant[0]) - 'a'),
528 (tolower(pEnPassant[1]) - '0'));
529 if ((!RANK3(cEp)) && (!RANK6(cEp)))
531 Trace("FenToPosition: Ignoring bogus enpassant coord '%c%c'\n",
532 pEnPassant[0], pEnPassant[1]);
536 if (WHITE == p->uToMove)
538 if ((p->rgSquare[cEp + 15].pPiece != WHITE_PAWN) &&
539 (p->rgSquare[cEp + 17].pPiece != WHITE_PAWN))
541 Trace("FenToPosition: Ignoring bogus en-passant "
542 "coordinate '%c%c'\n", pEnPassant[0], pEnPassant[1]);
548 ASSERT(p->uToMove == BLACK);
549 if ((p->rgSquare[cEp - 15].pPiece != BLACK_PAWN) &&
550 (p->rgSquare[cEp - 17].pPiece != BLACK_PAWN))
552 Trace("FenToPosition: Ignoring bogus en-passant "
553 "coordinate '%c%c'\n", pEnPassant[0], pEnPassant[1]);
559 ASSERT(VALID_EP_SQUARE(cEp));
565 _FenToPosition_DoFiftyOrEpdOpcodes(POSITION *p,
571 Do the fifty-moves-without-progress part or handle the EPD
590 q = FindChunk(szFifty, u);
594 //printf("%u: %s\n", u-1, q);
597 if ((i > 0) && (i < 100))
599 p->uFifty = (ULONG)i;
603 if (!STRCMPI(q, "bm"))
605 op = FindChunk(szFifty, u);
607 if (NULL == op) break;
611 q = FindChunk(szFifty, u);
619 FenToPosition(IN OUT POSITION *p,
625 Translates a FEN string into a POSITION structure.
629 POSITION *p : the POSITION structure to populate
630 char *szFen : the FEN string that describes how to populate it
634 FLAG : TRUE on success, FALSE otherwise
638 char *szCapturedFen = SystemAllocateMemory((ULONG)strlen(szFen) + 2);
639 char *szPieces, *szToMove, *szCastle, *szEnPassant, *szFifty;
643 // Initialize the captured fen buffer with a trailing whitespace char
645 ASSERT(NULL != szCapturedFen);
646 strcpy(szCapturedFen, szFen);
647 strcat(szCapturedFen, " ");
650 // Parse it into chunks
652 szPieces = FindChunk(szCapturedFen, 1);
653 szToMove = FindChunk(szCapturedFen, 2);
654 szCastle = FindChunk(szCapturedFen, 3);
655 szEnPassant = FindChunk(szCapturedFen, 4);
656 szFifty = FindChunk(szCapturedFen, 5);
657 if ((NULL == szPieces) ||
658 (NULL == szToMove) ||
659 (NULL == szCastle) ||
660 (NULL == szEnPassant))
666 // Clear the position struct
668 memset(p, 0, sizeof(POSITION));
669 for (u = 0; u < ARRAY_SIZE(p->cPawns[WHITE]); u++)
671 p->cPawns[WHITE][u] = p->cPawns[BLACK][u] = ILLEGAL_COOR;
674 for (u = 0; u < ARRAY_SIZE(p->cNonPawns[WHITE]); u++)
676 p->cNonPawns[WHITE][u] = p->cNonPawns[BLACK][u] = ILLEGAL_COOR;
680 // Do the pieces / board
682 if (FALSE == _FenToPosition_DoPiecePart(p, szPieces))
687 if (FALSE == _FenToPosition_DoSideToMove(p, szToMove))
692 if (FALSE == _FenToPosition_DoCastlingPermissions(p, szCastle))
697 if (FALSE == _FenToPosition_DoEnPassant(p, szEnPassant))
701 _FenToPosition_DoFiftyOrEpdOpcodes(p, szFifty);
703 p->u64NonPawnSig = ComputeSig(p);
704 p->u64PawnSig = ComputePawnSig(p);
705 p->iMaterialBalance[WHITE] =
706 ((SCORE)(p->uNonPawnMaterial[WHITE] + p->uPawnMaterial[WHITE]) -
707 (SCORE)(p->uNonPawnMaterial[BLACK] + p->uPawnMaterial[BLACK]));
708 p->iMaterialBalance[BLACK] = p->iMaterialBalance[WHITE] * -1;
711 VerifyPositionConsistency(p, FALSE);
713 SystemFreeMemory(szCapturedFen);