At the time that the SAN specification was developed, many different sources of information concerning chess notation were consulted, and of course the FIDE Laws were included.
The FIDE information about notation was incomplete, inconsistent, and ambiguous back then, and it still is today.
In some places, the FIDE Laws use zero with castling and in others they use the uppercase letter ""O". Also, there was plenty of variance in move notion in texts published under the FIDE imprimatur. There were a half dozen different ways of recording en passant moves. One of FIDE's problems is they made a misguided attempt to please everyone when what they really needed was a tyrant who would provide a unique move/notation mapping.
If you're really interested, then you can dig through the FIDE Laws and look for the castling references:
http://www.fide.com/fide/handbook.html? ... ew=article
Those were the rules in effect twenty years ago; there have been some changes in just the past couple of months. But even so, the FIDE Laws are incomplete and still do not cover the case of when both file letter and rank number of the origin square are needed to disambiguate some moves.
Believe me, I've been through all of this many times. There are good reasons why SAN is the way it is.
--------
Oscar's SAN generator code:
Code: Select all
static void SanRecLoadFromMove(SanRec * const sanrecptr, const Move move)
{
// This routine loads the given SAN record using the given move.
char *chptr = sanrecptr->chvec;
const Mc mc = GetMc(move);
const Man frman = GetFrMan(move);
const Man toman = GetToMan(move);
const Sq frsq = GetFrSq(move);
const Sq tosq = GetToSq(move);
SanRecReset(sanrecptr);
// Process according to the move's special case
switch (mc)
{
case McReg: // Regular move; no special case
if (IsManPawn(frman))
{
*chptr++ = MapSqToFileChar(frsq);
if (IsManNotVacant(toman))
{
*chptr++ = 'x';
*chptr++ = MapSqToFileChar(tosq);
};
*chptr++ = MapSqToRankChar(tosq);
}
else
{
*chptr++ = MapManToPieceChar(frman);
if (TestMf(move, MfAndf))
*chptr++ = MapSqToFileChar(frsq);
if (TestMf(move, MfAndr))
*chptr++ = MapSqToRankChar(frsq);
if (IsManNotVacant(toman))
*chptr++ = 'x';
*chptr++ = MapSqToFileChar(tosq);
*chptr++ = MapSqToRankChar(tosq);
};
break;
case McEPC: // En passant capture
*chptr++ = MapSqToFileChar(frsq);
*chptr++ = 'x';
*chptr++ = MapSqToFileChar(tosq);
*chptr++ = MapSqToRankChar(tosq);
break;
case McCQS: // Castles queenside
*chptr++ = 'O';
*chptr++ = '-';
*chptr++ = 'O';
*chptr++ = '-';
*chptr++ = 'O';
break;
case McCKS: // Castles kingside
*chptr++ = 'O';
*chptr++ = '-';
*chptr++ = 'O';
break;
case McPPN: // Pawn promotes to knight
case McPPB: // Pawn promotes to bishop
case McPPR: // Pawn promotes to rook
case McPPQ: // Pawn promotes to queen
*chptr++ = MapSqToFileChar(frsq);
if (IsManNotVacant(toman))
{
*chptr++ = 'x';
*chptr++ = MapSqToFileChar(tosq);
};
*chptr++ = MapSqToRankChar(tosq);
*chptr++ = '=';
*chptr++ = MapMcToPieceChar(mc);
break;
default:
break;
};
// Check and checkmate
if (TestMf(move, MfChck))
{
if (TestMf(move, MfMate))
*chptr++ = '#';
else
*chptr++ = '+';
};
*chptr++ = '\0';
}
Oscar's disambiguation code:
Code: Select all
static void PositionNotateDisambiguation(const Position * const positionptr, const MoveSeg * const movesegptr)
{
// This routine notates disambiguation of the moves in the given move segment for the given position.
const Move * const baseptr = movesegptr->baseptr;
const ui limit = movesegptr->count;
ui index0;
Sq tosq;
Census census;
ui landings[SqLen];
CensusLoadFromBoard(&census, &positionptr->board);
for (tosq = SqA1; tosq <= SqH8; tosq++)
landings[tosq] = 0;
for (index0 = 0; index0 < limit; index0++)
landings[GetToSq(baseptr[index0])]++;
for (index0 = 0; index0 < limit; index0++)
{
const Move move0 = baseptr[index0];
const Man frman0 = GetFrMan(move0);
const Sq tosq0 = GetToSq(move0);
if ((landings[tosq0] > 1) && IsManQRBN(frman0) && (census.mancount[frman0] > 1))
{
const Sq frsq0 = GetFrSq(move0);
const File frfile0 = MapSqToFile(frsq0);
const Rank frrank0 = MapSqToRank(frsq0);
ui index1;
ui msc = 0, mfc = 0, mrc = 0;
for (index1 = 0; index1 < limit; index1++)
{
const Move move1 = baseptr[index1];
const Sq frsq1 = GetFrSq(move1);
if ((tosq0 == GetToSq(move1)) && (frman0 == GetFrMan(move1)) && (frsq0 != frsq1))
{
msc++;
if (MapSqToFile(frsq1) == frfile0)
mfc++;
if (MapSqToRank(frsq1) == frrank0)
mrc++;
};
};
if (msc > 0)
{
if ((mfc == 0) || (mrc > 0))
SetMf(movesegptr->baseptr[index0], MfAndf);
if (mfc > 0)
SetMf(movesegptr->baseptr[index0], MfAndr);
};
};
};
}