SAN to Coordinate Notation in C#
What is SAN?
Standard algebraic notation is a way to represent moves in a chess game. For instance the white rook in the bottom left of the chess board is square a1. If it moves up 1 square it is at a2. To represent the move in coordinate notation, you would write a1a2. Coordinate notation is easy for computers to read, but it gets a little messy for a human to look at entire games like that.

In standard algebraic notation the move might be written as Ra2 (Rook to square a2). What makes this conversion difficult is you need to generate all valid moves before you know the real source square. If there was another white rook on the board, you would first have to check to which rook was allowed to move to square a2.
When is it used?
If you are parsing a PGN file you'll need to make this conversion at some point. Loading games from PGN files was a very useful tool for debugging and testing game endings.
The Code.
static DictionaryfileToInt = new Dictionary { { "a", 1 }, { "b", 2 }, { "c", 3 }, { "d", 4 }, { "e", 5 }, { "f", 6 }, { "g", 7 }, { "h", 8 } }; static Move ConvertSANtoCoordinates(Board b, PieceType p, string san_token) { string firstFile = ""; string firstRank = ""; string secondFile = ""; string secondRank = ""; string files = "abcdefgh"; string ranks = "12345678"; string promotes = "RNB"; PieceType promote = PieceType.Queen; int destFile = -1; int destRank = -1; int sourceFile = -1; int sourceRank = -1; // castles are easy, so we check for them first if (t == "O-O") { //king side castle if (b.WhosTurn == PlayerColor.Black) { destRank = 8; destFile = 7; } else { destRank = 1; destFile = 7; } } else if (t == "O-O-O") { //queen side castle if (b.WhosTurn == PlayerColor.Black) { destRank = 8; destFile = 3; } else { destRank = 1; destFile = 3; } } else { // a non castling move, so interate through the chars in the SAN token for (int i = 0; i < san_token.Length; i++) { string st = san_token.Substring(i, 1); // is this character a valid file? if (files.Contains(st)) { // is this the first time the token is referencing a file? if (firstFile == "") firstFile = st; else secondFile = st; } // is this character a valid rank? if (ranks.Contains(st)) { // is this the first time the token is referencing a rank? if (firstRank == "") firstRank = st; else secondRank = st; } // is this character representing a promotion? if (promotes.Contains(st)) { if (st == "N") { promote = PieceType.Knight; } else if (st == "B") { promote = PieceType.Bishop; } else if (st == "R") { promote = PieceType.Rook; } } } //now figure out the source and dest rank and file based on what was in the SAN token if (secondFile == "") { destFile = fileToInt[firstFile]; } else { sourceFile = fileToInt[firstFile]; destFile = fileToInt[secondFile]; } if (secondRank == "") { destRank = Convert.ToInt16(firstRank); } else { sourceRank = Convert.ToInt16(firstRank); destRank = Convert.ToInt16(secondRank); } } if ((destRank == -1) && (destFile == -1)) throw new Exception("destination square is ambiguous"); // get a list of all valid moves on the board ValidMoveEntry e = b.FindValidMoves(); List moves = e.moves; Move nm = null; // this will store the coordinate notiation that we will return //iterate through the available moves and try to find one that matches //the source piece specified in the SAN token and one that matches //the source and dest rank & file if we have them foreach (Move m in moves) { int mdRank = 8 - (m.To / 8); int mdFile = (m.To % 8) + 1; if (p == b.GetPieceType(m.From)) { if (mdFile == destFile) { if (mdRank == destRank) { // The source piece type and the dest rank and file match // skip this move if the sourceRank or sourceFiles have been specified in the SAN and they don't // match. int msRank = 8 - (m.From / 8); int msFile = (m.From % 8) + 1; if (sourceRank > -1) if (sourceRank != msRank) continue; if (sourceFile > -1) if (sourceFile != msFile) continue; // make sure the promotion matches too if (m.PromoteType != 0) { if ((byte)promote != m.PromoteType) continue; } //try the move just to make sure it's valid MoveResult r = b.MakeMove(m); if ((r & MoveResult.Invalid) != MoveResult.Invalid) { if ((r & MoveResult.PromotePawn) != MoveResult.PromotePawn) b.UnmakeMoveAI(m.From, m.To); // we already found a move, throw an exception if (nm != null) throw new Exception("Move was not unique"); nm = new Move((byte)m.From, (byte)m.To, (byte)promote); } } } } } if (nm == null) throw new Exception("specified move not found on the board"); return nm; }