czardas Posted February 2, 2010 Share Posted February 2, 2010 (edited) Parsing Short Algebraic Notation This script is the main back end of a search engine. The search engine will attempt to match positions on the chessboard, after parsing an indeterminate number of moves, from a number of chess games. Although I have not yet written a front end, an important stage in development has been reached: i.e. parsing the moves. As the number of games being parsed is likely be quite high: speed and performance are a priority. Arguments are ordered such that the most frequently occuring scenarios are encountered (and dealt with) first. This demo walks you through three sample games, move by move, displaying each position as a two dimensional array. These games do not demonstrate the full flexibility of the script, however all the algorithms have been tested and appear to be working. Portable Game Notation is the most commonly used format for storing chess moves, either in a file or on a website. One reason that PGN is so popular is that it is easy to read and modify the contents. However chess players are not generally aware of correct PGN syntax, and there is also a degree of inconsistancy to be found in auto-generated PGN output. This method attempts to accomodate (at least the most frequent of) these inconsistancies. A few instances of files containing corrupt syntax may not be worth accomodating. Further research is required. This is perhaps the most complicated script I have produced to date. Although an in depth knowledge of chess is not required, an understanding of the basic rules of the game would make the code more accessible to the reader. Anyone is free to adapt this script to suit his or her own needs. For example: the code could be used to create a chess game viewer. I would be happy to answer any questions regarding the method I have used. Much of the code has been commented, to guide you through the jungle. I have also provided some links which you may find useful. It is hoped that at least some part of this script will be of value or interest to others besides myself. expandcollapse popup#include <Array.au3> #include <String.au3> Global $board[8][8] #comments-start ---------------------------------------------------------------- --------------------------------------- | h1 | g1 | f1 | e1 | d1 | c1 | b1 | a1 | |----|----|----|----|----|----|----|----| | h2 | g2 | f2 | e2 | d2 | c2 | b2 | a2 | |----|----|----|----|----|----|----|----| | h3 | g3 | f3 | e3 | d3 | c3 | b3 | a3 | |----|----|----|----|----|----|----|----| | h4 | g4 | f4 | e4 | d4 | c4 | b4 | a4 | |----|----|----|----|----|----|----|----| | h5 | g5 | f5 | e5 | d5 | c5 | b5 | a5 | |----|----|----|----|----|----|----|----| | h6 | g6 | f6 | e6 | d6 | c6 | b6 | a6 | |----|----|----|----|----|----|----|----| | h7 | g7 | f7 | e7 | d7 | c7 | b7 | a7 | |----|----|----|----|----|----|----|----| | h8 | g8 | f8 | e8 | d8 | c8 | b8 | a8 | --------------------------------------- The chessboard ($board) is viewed from black's perspective: This design choice sets white's back rank as the first row: this being the most intuitive orientation for the intended purpose. Each square is represented by two digit coordinates => h1 = 00, g2 = 11, f3 = 22 etc... A possible alternative would be to set a8 = 00 (white's perspective), however this would entail inverting every numerical coordinate. #comments-end ------------------------------------------------------------------ Global $nPath[9][8][9] #comments-start ---------------------------------------------------------------- A knight attacking a target square can have between two and eight possible locations. Calculating the coordinates for each possible location is unecessary. $nPath stores knight pathways in relation to 64 possible target squares on the chessboard. #comments-end ------------------------------------------------------------------ Local $scope, $pattern $scope = "2344443234666643468888644688886446888864468888643466664323444432" ; Number of pathways to 64 target squares. $pattern = "1221132220142321101524221116252312172624132725142615" & _ ; 336 Possible (two digit) coordinates for the knight's original location. "0222310323323004243331002005253432012106263533022207273634032337350424360525" & _ "12320141133302420040143403430141103015350444024211311636054503431232173706460444133307470545143406461535" & _ "22421151234312521050244413531151204025451454125221412646155513532242274716561454234317571555244416562545" & _ "32522161335322622060345423632161305035552464226231513656256523633252375726662464335327672565345426663555" & _ "42623171436332723070446433733171406045653474327241614666357533734262476736763474436337773575446436764565" & _ "5272415373424054744341507055754442517156764543527257774644537347455474465575" & _ "6251635250645351606554526166555362675654635755645665" ;=> 8th rank (target squares) $s = 1 For $i = 0 To 7 For $j = 0 To 7 $nPath[$i][$j][0] = StringMid($scope, $s, 1) ; Knight path variations: 2, 3, 4, 6 or 8 assigned to each square. $s += 1 Next Next $s = 1 For $i = 0 To 7 For $j = 0 To 7 For $k = 1 To $nPath[$i][$j][0] $nPath[$i][$j][$k] = StringMid($pattern, $s, 2) ; Populate the third dimension with knight pathway coordinates. $s += 2 Next Next Next Global $xPath[9][8][2][9] #comments-start ---------------------------------------------------------------- Any square (except the 4 corner squares) can be seen as the intersection point between two opposing diagonal paths. $xPath stores coordinate values for 26 diagonal paths in relation to each of the 64 squares on the chessboard. This array is used to determine which pieces (if any) are diagonally pinned. #comments-end ------------------------------------------------------------------ $scope = "8765432178765432678765435678765445678765345678762345678712345678" & _ ; Diagonal scope on 64 target squares. "1234567823456787345678764567876556787654678765437876543287654321" ; Second board: as above but with opposite diagonal inclination. $pattern = "001122334455667701122334455667021324354657031425364704152637051627061707" & _ ; Diagonal pathways originating in white's back rank. "102132435465760011223344556677011223344556670213243546570314253647041526370516270617" & _ "20314253647510213243546576001122334455667701122334455667021324354657031425364704152637051627" & _ "304152637420314253647510213243546576001122334455667701122334455667021324354657031425364704152637" & _ "405162733041526374203142536475102132435465760011223344556677011223344556670213243546570314253647" & _ "50617240516273304152637420314253647510213243546576001122334455667701122334455667021324354657" & _ "607150617240516273304152637420314253647510213243546576001122334455667701122334455667" & _ "706071506172405162733041526374203142536475102132435465760011223344556677" & _ ;=> 8th rank "000110021120031221300413223140051423324150061524334251600716253443526170" & _ ; Second board "011002112003122130041322314005142332415006152433425160071625344352617017263544536271" & _ "02112003122130041322314005142332415006152433425160071625344352617017263544536271273645546372" & _ "031221300413223140051423324150061524334251600716253443526170172635445362712736455463723746556473" & _ "041322314005142332415006152433425160071625344352617017263544536271273645546372374655647347566574" & _ "05142332415006152433425160071625344352617017263544536271273645546372374655647347566574576675" & _ "061524334251600716253443526170172635445362712736455463723746556473475665745766756776" & _ "071625344352617017263544536271273645546372374655647347566574576675677677" ;=> 8th rank $s = 1 For $k = 0 To 1 ; The second chessboard is placed behind the first. For $i = 0 To 7 For $j = 0 To 7 $xPath[$i][$j][$k][0] = StringMid($scope, $s, 1) $s += 1 Next Next Next $s = 1 For $k = 0 To 1 For $i = 0 To 7 For $j = 0 To 7 For $l = 1 To $xPath[$i][$j][$k][0] ; Populate the fourth dimension with diagonal pathways. $xPath[$i][$j][$k][$l] = StringMid($pattern, $s, 2) $s += 2 Next Next Next Next #comments-start ---------------------------------------------------------------- A PGN (portable game notation) file contains one or more chess games. Information such as players names, dates, venue etc are indicated by tags (inside square brackets). Commentry, variation lines and evaluation symbols sometimes appear as separate elements (normally inside brackets). Sometimes a special FEN (Forsyth Edwards Notation) tag is used to indicate a different starting position. In FEN: uppercase (KQBNRP) represents white's pieces, and lowercase (kqbnrp) represents black's pieces. => example on line 215 The same convention will be used to represent and identify the pieces on the chessboard. The moves of the game are written in SAN (Short Algebraic Notation). => See examples at EOF (lines 813 to 849) #comments-end ------------------------------------------------------------------ Local $PGN, $samplePGN, $rawPGN, $rawMoves, $games, $position _loadSamplePGN($samplePGN) ; Load the demonstration games. ; To circumvent inconsistancies in different PGN layouts, it may be necessary to tidy up a little before parsing. $rawPGN = $samplePGN ; Replace this line with => $rawPGN = FileRead("name_of_file.pgn") $rawPGN = StringRegExpReplace($rawPGN, "[\n][\h]*", @LF) ; Remove any horizontal white spaces after a line feed - align left. $rawPGN = StringRegExpReplace($rawPGN, '[%][^\r]+[\r]', @CR) ; Remove developer's inline comments. $rawPGN = StringRegExpReplace($rawPGN, "[\r\n]{3,}", @CRLF & @CRLF) ; Tidy the stack. $rawPGN = StringStripWS($rawPGN, 3) ; As above $rawPGN = StringReplace($rawPGN, @CRLF & @CRLF &'[', @CRLF & '|[') ; Create a game separator '|' for each game. $PGN = StringSplit($rawPGN, '|') ; All game data is stored for future access, as required. Local $position, $games[UBound($PGN)][2] ; To hold starting positions and the moves only. For $i = 1 To $PGN[0] ; Populate the first column with each starting position - FEN. If StringInStr($PGN[$i], "[FEN ", 0) Then ; The game does not start from the standard postion. $position = _StringBetween($PGN[$i], '[FEN "', '"]') $games[$i][0] = $position[0] Else $games[$i][0] = -1 ; Default starting position. EndIf Next #comments-start ---------------------------------------------------------------- Now we must tease out the information we wish to use. The starting position 'FEN' is obligatory. The information needed for a search engine includes whether a king can still castle or not, and whether a pawn can be captured using en passant. The 50 move rule (the number of moves since the last pawn move or capture of a piece) is only really relevant to certain endgames, and can generally be ignored. We want the search results to return all matching positions regardless of move order or sequence. Some attempt has been made to identify and remove non SAN elements (Further research is required). #comments-end ------------------------------------------------------------------ $rawPGN = StringRegExpReplace($rawPGN, '[;][^\r]+[\r]|[\x5b][^\x5d]+[\x5d]|[\x28][^\x29]+[\x29]|[\x7b][^\x7d]+[\x7d]|[0-9]+[.]{2,}|[$][0-9]+|(1-0)|(0-1)|(1/2-1/2)|[*]|[!?]+', '') #comments-start ---------------------------------------------------------------- Regular Expressions used (above) to identify and remove the non essential data include: 'Rest of line' comments: [;][^\r]+[\r] Tags in square brackets: [\x5b][^\x5d]+[\x5d] Alternative move variations in round brackets: [\x28][^\x29]+[\x29] Comments in curly brackets: [\x7b][^\x7d]+[\x7d] Black's move number plus trailing dots: [0-9]+[.]{2,} Nags (Evaluation remarks): [$][0-9]+ Game result: (1-0)|(0-1)|(1/2-1/2)|[*] Evaluation remarks (Non SAN): [!?]+ Move numbers: [0-9]+[.] #comments-end ------------------------------------------------------------------ $rawPGN = StringRegExpReplace($rawPGN, '[\s]+', ' ') ; Spaces separate white moves from black. $rawPGN = StringRegExpReplace($rawPGN, '[\x20]*[.][\x20]*|[\x20]*[0-9]+[.][\x20]*', '.') ; Dots act as move separators. $rawPGN = StringReplace($rawPGN, '++', '+') ; Replace double check symbol (non SAN) with check (SAN). $rawPGN = StringRegExpReplace($rawPGN, '[\s]*[\x7c][\s]*[.]*[\s]*', '|') ; Compession => forces the first and final moves flush with each game separator. $rawPGN = StringStripWS($rawPGN , 3) If StringLeft($rawPGN, 1) = "." Then $rawPGN = StringTrimLeft($rawPGN, 1) ; If the string starts with a dot, remove it. $rawMoves = StringSplit($rawPGN, '|') For $i = 1 To $PGN[0] $games[$i][1] = $rawMoves[$i] Next ; Clear memory. $rawPGN = 0 $rawMoves = 0 Local $FEN, $forsythEdwards, $edwardsForsyth, $fenBoard, $castlingOptions, $enPassantSquare Local $SAN, $notation, $piece, $target, $rank, $r, $y, $file, $f, $x, $partialCoordinate Local $moves, $m, $movesLimit, $floor, $partialElement, $player, $g Local $pieceType, $knights[1], $bishops[1], $rooks[1], $kings[2], $queens[1], $moreQueens[1] Local $attackers, $array, $lateralPath, $diagonal, $pins, $ref, $selected Local $errorReport, $player1, $player2, $status ; Standard starting position (FEN) Const $setUp = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" ; => position - player - castling - enpassent target - 50 move rule - move number For $g = 1 To UBound($games) -1 ; Parsing begins. If $games[$g][0] = -1 Then $FEN = $setUp Else $FEN = $games[$g][0] EndIf $forsythEdwards = StringSplit($FEN, " ") $castlingOptions = $forsythEdwards[3] ; To be used as search criteria. $edwardsForsyth = StringReverse($forsythEdwards[1]) ; Sets up the pieces from black's POV. $fenBoard = StringSplit($edwardsForsyth, "/") ; Separates the eight ranks of the chessboard. For $i = 1 To 8 ; ranks 1 to 8 $y = $i - 1 ; vertical coordinate $x = 0 ; horizontal coordinate For $j = 1 To StringLen($fenBoard[$i]) If StringIsDigit(StringMid($fenBoard[$i], $j, 1)) Then ; Indicates one or more unoccupied squares. For $k = 1 To StringMid($fenBoard[$i], $j, 1) $board[$y][$x] = "-" $x += 1 Next Else $piece = StringMid($fenBoard[$i], $j, 1) $board[$y][$x] = $piece ; Places chess pieces on the chessboard. Select ; To keep tabs on king coordinates for later reference. Case $piece == "K" $kings[0] = $y & $x ; White king location Case $piece == "k" $kings[1] = $y & $x ; Black king location EndSelect $x += 1 EndIf Next Next $moves = StringSplit($games[$g][1], ".") ; Load the moves of each game. $movesLimit = $moves[0] ; Limiting the number of moves to parse is a useful search engine feature. Any limit set must not exceed the number of moves in the game. _displayInitialPosition() ; => For the purposes of this demonstration. If $forsythEdwards[2] == "w" Then ; Which player makes the first move? $floor = 1 ; White starts Else $player = 1 ; Black to move. $m = 1 ; Move Number $SAN = $moves[1] _parseMove() _displayCurrentPosition() ; => For demonstration purposes only. $floor = 2 EndIf If $moves[0] >= $floor Then For $m = $floor To $movesLimit $partialElement = StringSplit($moves[$m], " ") ; Split each element into two moves (one by white and one by black), or just one white move. $player = 0 ; White to move. For $s = 1 To $partialElement[0] $SAN = $partialElement[$s] _parseMove() _displayCurrentPosition() $player = 1 Next Next EndIf Next Func _parseMove() ; Read SAN and move the appropriate pieces on the chessboard. $notation = StringRegExpReplace($SAN, "[+|#]", "") ; Check (+) and Checkmate (#) symbols do not influence move coordinates, so we get rid of them. ; Locating the target square. If StringInStr($notation, "=") Then $target = StringMid($notation, StringInStr($notation, "=") -2, 2) ; A pawn promotes. Else $target = StringRight($notation, 2) ; With any other move type except castles. EndIf ; Determine the type of move to make. If StringRegExp(StringLeft($notation, 1), "(?-i)[K|Q|B|N|R]", 0) = 1 Then ; A piece moves (as opposed to a pawn move or castles). $notation = StringReplace($notation, "x", "") ; Remove the symbol 'x' => Captured pieces are automatically replaced when the board is updated. _squareGetCoordinates($y, $x) ; Convert the rank and file of the target square to numerical coordinates. Select ; Which type of piece to move? Case StringLeft($notation, 1) = "N" ; A knight move. _knightGetCoordinates($knights) If $knights[0] > 1 Then ; More than one knight attacks the target square. => Elimination process _eliminationProcess($knights) ; Seed out the insignificant attackers. Else ; The knight's location was discovered on the first attempt. $board[StringLeft($knights[1], 1)][StringRight($knights[1], 1)] = "-" ; Remove the knight. EndIf If $player = 0 Then $board[$y][$x] = "N" ; Place a white knight on the target square. Else $board[$y][$x] = "n" ; Place a black knight on the target square. EndIf Case StringLeft($notation, 1) = "B" ; A bishop move. $pieceType = "B" _bishopGetCoordinates($bishops) If $bishops[0] > 1 Then ; More than one bishop attacks the target square. _eliminationProcess($bishops) ; As above. Else ; The bishop's location was discovered on the first attempt. $board[StringLeft($bishops[1], 1)][StringRight($bishops[1], 1)] = "-" ; Remove the bishop. EndIf If $player = 0 Then $board[$y][$x] = "B" ; Place a white bishop on the target square. Else $board[$y][$x] = "b" ; Place a black bishop on the target square. EndIf Case StringLeft($notation, 1) = "Q" ; A queen move. $pieceType = "Q" _bishopGetCoordinates($queens) ; Queens move diagonally. _rookGetCoordinates($moreQueens) ; And along ranks and files. If $moreQueens[0] > 0 Then $queens[0] += $moreQueens[0] _ArrayDelete($moreQueens, 0) _ArrayConcatenate($queens, $moreQueens) EndIf If $queens[0] > 1 Then ; More than one queen attacks the target square. _eliminationProcess($queens) ; As above. Else ; The queen's location was discovered on the first attempt. $board[StringLeft($queens[1], 1)][StringRight($queens[1], 1)] = "-" ; Remove the queen. EndIf If $player = 0 Then $board[$y][$x] = "Q" ; Place a white queen on the target square. Else $board[$y][$x] = "q" ; Place a black queen on the target square. EndIf Case StringLeft($notation, 1) = "K" ; A king move. $rank = StringLeft($kings[$player],1) $file = StringRight($kings[$player],1) $board[$rank][$file] = "-" ; Remove the king from the original square. If $player = 0 Then $board[$y][$x] = "K" ; Place the white king on the target square. If $rank = 0 And $file = 3 Then _castlingSetState() ; Castling will no longer be an option for white. EndIf Else $board[$y][$x] = "k" ; Place the black king on the target square. If $rank = 7 And $file = 3 Then _castlingSetState(); Castling will no longer be an option for black. EndIf EndIf $kings[$player] = $y & $x ; Keep tabs on current king location. Case Else ; A Rook move. $pieceType = "R" _rookGetCoordinates($rooks) If $rooks[0] > 1 Then ; More than one rook attacks the target square. _eliminationProcess($rooks) ; As above. Else ; The rook's location was discovered on the first attempt. $board[StringLeft($rooks[1], 1)][StringRight($rooks[1], 1)] = "-" EndIf If $rooks[1] = "00" Or $rooks[1] = "07" Or $rooks[1] = "70" Or $rooks[1] = "77" Then _castlingSetState() ; Castling will no longer be possible on either the king's wing or the queen's wing. EndIf If $player = 0 Then $board[$y][$x] = "R" ; Place a white Rook on the target square. Else $board[$y][$x] = "r" ; Place a black Rook on the target square. EndIf EndSelect ElseIf StringRegExp(StringLeft($notation, 1), "(?-i)[a-h]", 0) = 1 Then ; A pawn move. _squareGetCoordinates($y, $x) ; Remove the pawn from the square it came from. If StringInStr($notation, "x") And $x > 104 - Asc(StringLeft($notation, 1)) Then ; A pawn captures a piece and moves closer to the a file. If $player = 0 Then $board[$y - 1][$x - 1] = "-" Else $board[$y + 1][$x - 1] = "-" EndIf _enPassant() ; Test if a pawn is being captured en passant and remove it accordingly. ElseIf StringInStr($notation, "x") And $x < 104 - Asc(StringLeft($notation, 1)) Then ; A pawn captures a piece and moves closer to the h file. If $player = 0 Then $board[$y - 1][$x + 1] = "-" Else $board[$y + 1][$x + 1] = "-" EndIf _enPassant() ; As above. ElseIf $player = 0 Then If $y = 3 And $board[2][$x] = "-" Then ; The pawn moves two steps forward. $board[$y - 2][$x] = "-" $z = $y - 1 $enPassantSquare = $z & $x ; Update en passant square coordinates. Else ; Otherwise pawns always move one step forward. $board[$y - 1][$x] = "-" EndIf Else If $y = 4 And $board[5][$x] = "-" Then ; As above. $board[$y + 2][$x] = "-" $z = $y + 1 $enPassantSquare = $z & $x ; NOTE: Pawns may only be captured en passant on the very next move by the opponent. Else ; The pawn moves one step forward. $board[$y + 1][$x] = "-" EndIf EndIf ; Place a pawn or promoted piece (queen, bishop, knight or rook) on the target square. If $player = 0 And $y < 7 Then $board[$y][$x] = "P" ; Place a white pawn on the target square. ElseIf $player = 0 And $y = 7 Then $board[$y][$x] = StringRight($notation, 1) ; Place a promoted white piece on the target square. ElseIf $player = 1 And $y > 0 Then $board[$y][$x] = "p" ; Place a black pawn on the target square. ElseIf $player = 1 And $y = 0 Then $board[$y][$x] = StringLower(StringRight($notation, 1)) ; Place a promoted black piece on the target square. EndIf ElseIf $notation = "o-o" Then ; Castles Kingside If $player = 0 Then ; White castles $board[0][0] = "-" $board[0][1] = "K" $board[0][2] = "R" $board[0][3] = "-" Else ; Black castles $board[7][0] = "-" $board[7][1] = "k" $board[7][2] = "r" $board[7][3] = "-" EndIf _castlingSetState() If $player = 0 Then $kings[$player] = "01" Else $kings[$player] = "71" EndIf ElseIf $notation = "o-o-o" Then ; Castles Queenside If $player = 0 Then ; White castles $board[0][7] = "-" $board[0][5] = "K" $board[0][4] = "R" $board[0][3] = "-" Else ; Black castles $board[7][7] = "-" $board[7][5] = "k" $board[7][4] = "r" $board[7][3] = "-" EndIf _castlingSetState() If $player = 0 Then $kings[$player] = "05" ; Keep tabs on current king location (white). Else $kings[$player] = "75" ; As above (for black). EndIf EndIf EndFunc Func _squareGetCoordinates(ByRef $r, ByRef $f) If $target = StringRegExp($target,"[a-h][1-8]?") = 1 Then $errorReport = "Parsing failed during game " & $g & " at move " & $m & "." & @CRLF & "Process terminated." _errorLog() MsgBox(0, "Unable to parse coordinates.", $errorReport) Exit ; Handling for this error => Pending. EndIf $r = StringRight($target, 1) -1 $f = 104 - Asc(StringLeft($target, 1)) EndFunc Func _knightGetCoordinates(ByRef $array) $selected = "" For $i = 1 To $nPath[$y][$x][0] $rank = StringLeft($nPath[$y][$x][$i], 1) $file = StringRight($nPath[$y][$x][$i], 1) If $player = 0 And $board[$rank][$file] == "N" Then $selected &= $nPath[$y][$x][$i] & "," ElseIf $player = 1 And $board[$rank][$file] == "n" Then $selected &= $nPath[$y][$x][$i] & "," EndIf Next $selected = StringTrimRight($selected, 1) $array = StringSplit($selected, ",") EndFunc ; Searches diagonally outwards from the target square in 4 directions, until a bishop (queen) or another obsticle is encountered. Func _bishopGetCoordinates(ByRef $array) For $i = UBound($array) - 1 To 1 Step -1 ; Clear all elements left over from any previous bishop (or queen) move. _ArrayDelete($array, $i) Next $rank = $y + 1 $file = $x + 1 While $rank < 8 And $file < 8 If $board[$rank][$file] == StringUpper($pieceType) And $player = 0 Then _ArrayAdd($array, $rank & $file) ExitLoop ElseIf $board[$rank][$file] == StringLower($pieceType) And $player = 1 Then _ArrayAdd($array, $rank & $file) ExitLoop ElseIf $board[$rank][$file] <> "-" Then ExitLoop EndIf $rank += 1 $file += 1 WEnd $rank = $y + 1 $file = $x - 1 While $rank < 8 And $file > -1 If $board[$rank][$file] == StringUpper($pieceType) And $player = 0 Then _ArrayAdd($array, $rank & $file) ExitLoop ElseIf $board[$rank][$file] == StringLower($pieceType) And $player = 1 Then _ArrayAdd($array, $rank & $file) ExitLoop ElseIf $board[$rank][$file] <> "-" Then ExitLoop EndIf $rank += 1 $file -= 1 WEnd $rank = $y - 1 $file = $x + 1 While $rank > -1 And $file < 8 If $board[$rank][$file] == StringUpper($pieceType) And $player = 0 Then _ArrayAdd($array, $rank & $file) ExitLoop ElseIf $board[$rank][$file] == StringLower($pieceType) And $player = 1 Then _ArrayAdd($array, $rank & $file) ExitLoop ElseIf $board[$rank][$file] <> "-" Then ExitLoop EndIf $rank -= 1 $file += 1 WEnd $rank = $y - 1 $file = $x - 1 While $rank > -1 And $file > -1 If $board[$rank][$file] == StringUpper($pieceType) And $player = 0 Then _ArrayAdd($array, $rank & $file) ExitLoop ElseIf $board[$rank][$file] == StringLower($pieceType) And $player = 1 Then _ArrayAdd($array, $rank & $file) ExitLoop ElseIf $board[$rank][$file] <> "-" Then ExitLoop EndIf $rank -= 1 $file -= 1 WEnd $array[0] = UBound($array) -1 EndFunc ; Searches outwards from the target square in 4 directions (along the rank and file), until a rook (queen) or another obsticle is encountered. Func _rookGetCoordinates(ByRef $array) For $i = UBound($array) - 1 To 1 Step -1 ; Clear all elements left over from any previous rook (or queen) move. _ArrayDelete($array, $i) Next $file = $x + 1 While $file < 8 If $board[$y][$file] == StringUpper($pieceType) And $player = 0 Then _ArrayAdd($array, $y & $file) ExitLoop ElseIf $board[$y][$file] == StringLower($pieceType) And $player = 1 Then _ArrayAdd($array, $y & $file) ExitLoop ElseIf $board[$y][$file] <> "-" Then ExitLoop EndIf $file += 1 WEnd $file = $x - 1 While $file > -1 If $board[$y][$file] == StringUpper($pieceType) And $player = 0 Then _ArrayAdd($array, $y & $file) ExitLoop ElseIf $board[$y][$file] == StringLower($pieceType) And $player = 1 Then _ArrayAdd($array, $y & $file) ExitLoop ElseIf $board[$y][$file] <> "-" Then ExitLoop EndIf $file -= 1 WEnd $rank = $y + 1 While $rank < 8 If $board[$rank][$x] == StringUpper($pieceType) And $player = 0 Then _ArrayAdd($array, $rank & $x) ExitLoop ElseIf $board[$rank][$x] == StringLower($pieceType) And $player = 1 Then _ArrayAdd($array, $rank & $x) ExitLoop ElseIf $board[$rank][$x] <> "-" Then ExitLoop EndIf $rank += 1 WEnd $rank = $y - 1 While $rank > -1 If $board[$rank][$x] == StringUpper($pieceType) And $player = 0 Then _ArrayAdd($array, $rank & $x) ExitLoop ElseIf $board[$rank][$x] == StringLower($pieceType) And $player = 1 Then _ArrayAdd($array, $rank & $x) ExitLoop ElseIf $board[$rank][$x] <> "-" Then ExitLoop EndIf $rank -= 1 WEnd $array[0] = UBound($array) -1 EndFunc Func _eliminationProcess(ByRef $attackers) If StringLen($notation) < 5 Then ; Location coordinates are either partially given, or not at all. If StringLen($notation) = 4 Then ; Partial location coordinates are given. _selectPartialCoordinates($attackers) ; Select the pieces where partial coordinates are given. EndIf If $attackers[0] > 1 Then ; At least one piece must be pinned. _eliminateDiagonalPins($attackers) ; Pins by an opposing queen or bishop. If $attackers[0] > 1 Then ; There must be at least one piece pinned laterally along a rank or file. _eliminateLateralPins($attackers) ; Pins by an opposing queen or a rook. If $attackers[0] = 1 Then $board[StringLeft($attackers[1], 1)][StringRight($attackers[1], 1)] = "-" Else ; Corrupt input, or possibly some fairy chess variant. $errorReport = "A problem was encountered during game " & $g & " at move " & $m & "." _errorLog() ; Testing and error analysis. EndIf Else ; The piece or pieces were pinned diagonally. $board[StringLeft($attackers[1], 1)][StringRight($attackers[1], 1)] = "-" EndIf Else $board[StringLeft($attackers[1], 1)][StringRight($attackers[1], 1)] = "-" EndIf ElseIf StringLen($notation) = 5 Then ; On rare occasions all coordinates are given. $target = StringMid($notation, 2, 2) _squareGetCoordinates($rank, $file) $board[$rank][$file] = "-" EndIf EndFunc Func _selectPartialCoordinates(ByRef $array) $selected = "" ; To store the coordinates of pieces found on the given rank or file. $partialCoordinate = StringMid($notation, 2, 1) If StringIsAlpha($partialCoordinate) Then ; File is given $ref = 104 - Asc($partialCoordinate) For $i = 1 To $array[0] If StringRight($array[$i], 1) = $ref Then $selected &= $array[$i] & "," EndIf Next Else ; Rank is given $ref = $partialCoordinate - 1 For $i = 1 To $array[0] If StringLeft($array[$i], 1) = $ref Then $selected &= $array[$i] & "," EndIf Next EndIf $selected = StringTrimRight($selected, 1) $array = StringSplit($selected, ",") EndFunc Func _eliminateDiagonalPins(ByRef $array) $pins = "" ; Used to store index numbers of each element found to contain the coordinates of a diagonally pinned piece. For $i = 1 To $array[0] ; For all pieces attacking the target's coordinates. $rank = StringLeft($array[$i], 1) $file = StringRight($array[$i], 1) For $j = 0 To 1 ; Along two diagonal inclinations. $diagonal = "" ; String to be concatenated with pieces found along each diagonal. For $k = 1 To $xPath[$rank][$file][$j][0] ; scope $target = $xPath[$rank][$file][$j][$k] If $target <> $array[$i] Then $diagonal &= $board[StringLeft($target, 1)][StringRight($target, 1)] Else $diagonal &= "-" EndIf Next If $player = 0 Then If StringRegExp($diagonal, '(?-i)[q|b][-]+[K]', 0) = 1 Or StringRegExp($diagonal, '(?-i)[K][-]+[q|b]', 0) = 1 Then ; An illegal self check has occured! $pins &= $i ; Concatenate the string of index numbers requiring deletion. EndIf Else If StringRegExp($diagonal, '(?-i)[Q|B][-]+[k]', 0) = 1 Or StringRegExp($diagonal, '(?-i)[k][-]+[Q|B]', 0) = 1 Then ; As above. $pins &= $i EndIf EndIf Next Next If $pins <> "" Then ; Eliminate pinned pieces from the array. For $j = StringLen($pins) To 1 Step -1 _ArrayDelete($array, StringMid($pins, $j, 1)) $array[0] -= 1 Next EndIf EndFunc Func _eliminateLateralPins(ByRef $array) $pins = "" ; Used to store index numbers of each element found to contain the coordinates of a piece pinned along a rank or file. $r = StringLeft($kings[$player], 1) $f = StringRight($kings[$player], 1) For $i = 1 To $array[0] ; For all pieces attacking the target square. $lateralPath = "" ; To represent pieces found along the rank or file on which the king (of the player who's turn it is) is located. If StringLeft($array[$i], 1) = $r Then For $j = 0 To 7 ; Build a string representing pieces found on the same rank as the king. If $r & $j <> $array[$i] Then $lateralPath &= $board[$r][$j] Else $lateralPath &= "-" EndIf Next ElseIf StringRight($array[$i], 1) = $f Then For $j = 0 To 7 ; Build a string representing pieces found on the same file as the king. If $j & $f <> $array[$i] Then $lateralPath &= $board[$j][$f] Else $lateralPath &= "-" EndIf Next EndIf If $player = 0 Then If StringRegExp($lateralPath, '[q|r][-]+[K]', 0) = 1 Or StringRegExp($lateralPath, '[K][-]+[q|r]', 0) = 1 Then ; An illegal self check has occured! $pins &= $i ; Concatenate the string of index numbers requiring deletion. EndIf Else If StringRegExp($lateralPath, '[Q|R][-]+[k]', 0) = 1 Or StringRegExp($lateralPath, '[k][-]+[Q|R]', 0) = 1 Then ; As above. $pins &= $i EndIf EndIf Next If $pins <> "" Then ; Eliminate each pinned piece's coordinates from the array. For $j = StringLen($pins) To 1 Step -1 ; Delete the elements in reverse order to avoid corrupting the index numbers during the process. _ArrayDelete($array, StringMid($pins, $j, 1)) $array[0] -= 1 Next EndIf EndFunc Func _castlingSetState() ; Update current available castling options for both players. If $castlingOptions <> "-" Then If $player = 0 Then If StringInStr($notation, 'R') = 0 And StringRegExp($castlingOptions, '(?-i)[K|Q]', 0) = 1 Then $castlingOptions = StringRegExpReplace($castlingOptions, '(?-i)[KQ|K|Q]', "") ; White can no longer castle on either wing. ElseIf StringInStr($notation, 'R', 1) = 1 Then If $rooks[1] = "00" Then $castlingOptions = StringReplace($castlingOptions, "K", "", 0, 1) ; White can no longer castle kingside. ElseIf $rooks[1] = "07" Then $castlingOptions = StringReplace($castlingOptions, "Q", "", 0, 1) ; White can no longer castle queenside. EndIf EndIf Else If StringInStr($notation, 'R') = 0 And StringRegExp($castlingOptions, '(?-i)[k|q]', 0) = 1 Then $castlingOptions = StringRegExpReplace($castlingOptions, '(?-i)[kq|k|q]', "") ; Black can no longer castle either on either wing. ElseIf StringInStr($notation, 'R', 1) = 1 Then If $rooks[1] = "70" Then $castlingOptions = StringReplace($castlingOptions, "k", "", 0, 1) ; Black can no longer castle kingside. ElseIf $rooks[1] = "77" Then $castlingOptions = StringReplace($castlingOptions, "q", "", 0, 1) ; Black can no longer castle queenside. EndIf EndIf EndIf If $castlingOptions == "" Then $castlingOptions = "-" EndIf EndIf EndFunc Func _enPassant() If $board[$y][$x] == "-" Then ; A pawn is captured en passant. If $player = 0 Then $board[$y - 1][$x] = "-" ; Remove the captured white pawn from the 4th rank. Else $board[$y + 1][$x] = "-" ; Remove the captured black pawn from the 5th rank. EndIf EndIf EndFunc Func _errorLog() ; For testing and error analysis. FileWriteLine("error.log", $errorReport) EndFunc Func _displayInitialPosition() ; For demonstration purposes only. $player1 = StringRegExpReplace($PGN[$g], '[\s\S]+White "[\s]*([^"]+)"[\s\S]+', '\1') $player2 = StringRegExpReplace($PGN[$g], '[\s\S]+Black "[\s]*([^"]+)"[\s\S]+', '\1') If $player1 <> "czardas" Then $status = "Game " &$g &" - " & $player1 & " v " & $player2 Else $status = "Game " &$g &" - Test Position" EndIf _ArrayDisplay($board, $status) ; Show the position on the board at the start of each game. EndFunc Func _displayCurrentPosition() ; For demonstration purposes only. If $player = 0 Then $status = "Game " & $g & " - Position after " & $m & ". " & $SAN Else $status = "Game " & $g & " - Position after " & $m & "... " & $SAN EndIf _ArrayDisplay($board, $status) ; Show the position on the board after parsing each move. EndFunc Func _loadSamplePGN(ByRef $sample) ; For demonstration purposes only. MsgBox(0, "Instructions", "Please read the title of each display." & @CRLF & "Press ESC to move on to the next screen.") $sample = '[Event ""]' & @CRLF & _ ; First Game '[Site "Vienna"]' & @CRLF & _ '[Date "1910"]' & @CRLF & _ '[Round ""]' & @CRLF & _ '[White "Reti"]' & @CRLF & _ '[Black "Tartakower"]' & @CRLF & _ '[Result "1-0"]' & @CRLF & @CRLF & _ '1.e4 c6 2.d4 d5 3.Nc3 dxe4 4.Nxe4 Nf6 5.Qd3 e5 6.dxe5 Qa5+ 7.Bd2 Qxe5' & @CRLF & _ '8.O-O-O Nxe4 9.Qd8+ Kxd8 10.Bg5+ (10.Bg5+ Kc7 11.Bd8#) 1-0' & @CRLF & @CRLF & _ '[Event "-"]' & @CRLF & _ ; Second Game '[Site "Italian Opera House - Paris"]' & @CRLF & _ '[Date "1858"]' & @CRLF & _ '[Round "?"]' & @CRLF & _ '[White "Paul Morphy"]' & @CRLF & _ '[Black "Duke Karl / Count Isouard"]' & @CRLF & _ '[Result "1-0"]' & @CRLF & _ '[ECO "C41"]' & @CRLF & @CRLF & _ '1.e4 e5 2.Nf3 d6 3.d4 Bg4 {This is a weak move' & @CRLF & _ 'already.--Fischer} 4.dxe5 Bxf3 5.Qxf3 dxe5 6.Bc4 Nf6 7.Qb3 Qe7' & @CRLF & _ '8.Nc3 c6 9.Bg5 {Black is in what is like a zugzwang position' & @CRLF & _ 'here. He can not develop the Queens knight because the pawn' & @CRLF & _ 'is hanging. The bishop is blocked because of the' & @CRLF & _ 'Queen.-- Fischer} b5 10.Nxb5 cxb5 11.Bxb5+ Nbd7 12.O-O-O Rd8' & @CRLF & _ '13.Rxd7 Rxd7 14.Rd1 Qe6 15.Bxd7+ Nxd7 16.Qb8+ Nxb8 17.Rd8# 1-0' & @CRLF & @CRLF & _ '[Event "London, England"]' & @CRLF & _ ; Third Game '[Site "London, England"]' & @CRLF & _ '[Date "1867.??.??"]' & @CRLF & _ '[EventDate "?"]' & @CRLF & _ '[Round "?"]' & @CRLF & _ '[Result "1-0"]' & @CRLF & _ '[White "Bird"]' & @CRLF & _ '[Black "Steinitz"]' & @CRLF & _ '[ECO "C60"]' & @CRLF & @CRLF & _ '1.e4 e5 2.Nf3 Nc6 3.Bb5 Nf6 4.d4 exd4 5.e5 Ne4 6.Nxd4 Be7' & @CRLF & _ '7.O-O Nxd4 8.Qxd4 Nc5 9.f4 b6 10.f5 Nb3 11.Qe4 Nxa1 12.f6 Bc5+' & @CRLF & _ '13.Kh1 Rb8 14.e6 Rg8 15.Qxh7 Rf8 16.exf7+ Rxf7 17.Re1+ Be7' & @CRLF & _ '18.Qg8+ Rf8 19.f7# 1-0' EndFunc Related Links: http://www.very-best.de/pgn-spec.htm http://homepage.mac.com/s_lott/books/python/html/p05/p05c06_chess.html Edited March 29, 2016 by czardas TheSaint 1 operator64 ArrayWorkshop Link to comment Share on other sites More sharing options...
trancexx Posted February 4, 2010 Share Posted February 4, 2010 Code is obfuscated by nature. Somebody build a gui board. Great, really! czardas 1 ♡♡♡ . eMyvnE Link to comment Share on other sites More sharing options...
czardas Posted February 4, 2010 Author Share Posted February 4, 2010 (edited) Firstly I apologize for the length and complexity of the code. Admittedly some parts could be simplified, however it was partly a deliberate choice to avoid interrupting the flow of arguments (with too many function calls). Perhaps that's a moot point, because any speed gained is likely to be minimal. I don't blame people for finding this script difficult to follow, it took me a few weeks to get my head around it myself. The language of chess is complex to say the least. Another reason for having to write so much code is the complexity inherent in Short Algebraic Notation. Reading the links in the first post will illustrate this fact.Code is obfuscated by nature. Somebody build a gui board.Great, really!Obfuscated by nature I will probably get around to building a board, but it isn't really a main priority for me right now (the search engine doesn't really require a board, though it would make sense to include one). I already have a number of very good chess game viewers; however they don't all include the features that I would like to have, so hopefully I'll get round to it: I might need some help with it though. Thank you Trancexx for your nice comments, and all others who have taken the time to read, or run, the script.As soon as I have more time, I will try to find ways to improve it (hopefully make it easier to understand) and impliment the search features. That shouldn't really take so long, says he optimistically. Edited February 4, 2010 by czardas operator64 ArrayWorkshop Link to comment Share on other sites More sharing options...
guiltyking Posted March 28, 2016 Share Posted March 28, 2016 Thanks, for algorithm. used to convert Coordinate notations to SAN in (White Side). ps. remark dancing popup msgbox. expandcollapse popup#include <Array.au3> ;#include <String.au3> Global $board[9][9] Local $FEN= "rnbqkb1r/ppppp1bp/5p1p/5N2/3P4/5N2/PPP1PPPP/RN1QKB1R w KQkq - 0 5" ; Standard starting position (FEN)"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" ; => position - player - castling - enpassent target - 50 move rule - move number _EPD_Board_SetUp($FEN) MsgBox($MB_SYSTEMMODAL, "BOARD", _EPD_Board_Show()) Local $CON= "f5g7" ; Coordinate notation _contoSAN($CON) Func _EPD_Board_SetUp($FEN) Local $forsythEdwards, $edwardsForsyth, $fenBoard, $castlingOptions, $piece, $y, $x, $kings[2] $forsythEdwards = StringSplit($FEN, " ") $castlingOptions = $forsythEdwards[3] ; To be used as search criteria. $edwardsForsyth = StringReverse($forsythEdwards[1]) ; Sets up the pieces from black's POV. $fenBoard = StringSplit($edwardsForsyth, "/") ; Separates the eight ranks of the chessboard. For $i = 8 To 1 step -1; ranks 1 to 8 $y = $i ; vertical coordinate $x = 1 ; horizontal coordinate For $j = StringLen($fenBoard[$i]) To 1 step -1 If StringIsDigit(StringMid($fenBoard[$i], $j, 1)) Then ; Indicates one or more unoccupied squares. For $k = 1 To StringMid($fenBoard[$i], $j, 1) $board[$y][$x] = "-" $x += 1 Next Else $piece = StringMid($fenBoard[$i], $j, 1) $board[$y][$x] = $piece ; Places chess pieces on the chessboard. MsgBox($MB_SYSTEMMODAL, $y&":"&$x, $piece,0.31) Select ; To keep tabs on king coordinates for later reference. Case $piece == "K" $kings[0] = $y & $x ; White king location Case $piece == "k" $kings[1] = $y & $x ; Black king location EndSelect $x += 1 EndIf Next Next EndFunc Func _EPD_Board_Show() local $m For $y = 1 To 8 For $x = 1 To 8 $m &= $board[$y][$x] Next $m &= @CRLF Next Return $m EndFunc Func _contoSAN($CON) local $a = StringMid($CON, 1, 1) local $Y = StringMid($CON, 2, 1), $X If $a = "a" Then $X=1 ElseIf $a = "b" Then $X=2 ElseIf $a = "c" Then $X=3 ElseIf $a = "d" Then $X=4 ElseIf $a = "e" Then $X=5 ElseIf $a = "f" Then $X=6 ElseIf $a = "g" Then $X=7 ElseIf $a = "h" Then $X=8 EndIf local $PIECE = $board[$Y][$X] If $PIECE = "P" or $PIECE = "p" Then ; NO purIFY needed BESTMOVE CORDINATE NOTATION for pawn $SAN = $CON Else ; purIFY BESTMOVE CORDINATE NOTATION TO SAN $SAN = $board[$Y][$X] $SAN = StringUpper($SAN) $SAN &= $CON EndIf MsgBox($MB_SYSTEMMODAL, "_contoSAN", "CON: "& $CON&@CRLF&@CRLF&$X&":"&$Y& " PIECE: "& $PIECE&@CRLF&@CRLF&"SANmove: "&$SAN) return $SAN EndFunc czardas 1 Link to comment Share on other sites More sharing options...
czardas Posted March 29, 2016 Author Share Posted March 29, 2016 (edited) @guiltyking Hey, this script has been lying dormant for ages. I meant to make a GUI but couldn't figure out how to drag and drop a piece (png image with transparency) on a board made of 64 labels. There ought to be several ways to do it and I should try again. I wrote this code for a bet, because someone said I wouldn't be able to do it. It was a good learning experience for me, but it's a pretty old script now. I remember being very happy getting it to work. It's the only example I have ever written which uses a 4D array. The position search criteria is the easy part - simply compare FEN (which is as good as using any other form of hash). When I look at what I wrote now, I can see a lot of room for improvement in my code. Thanks for looking and for your response. Edit: I just noticed an old (and out of date) UDF function in the original code was preventing this from running. It's working again now! Edited March 29, 2016 by czardas operator64 ArrayWorkshop Link to comment Share on other sites More sharing options...
NorhanFoda Posted December 1, 2016 Share Posted December 1, 2016 @czardas That's a great effort ! I was wondering if you have a C++ library that convert pgn file to a series of FEN strings for each move I need it urgently, please czardas 1 Link to comment Share on other sites More sharing options...
czardas Posted December 1, 2016 Author Share Posted December 1, 2016 (edited) Thanks. This was an early challenge I set for myself. Actually someone said I wouldn't be able to do it and that inspired me (oops I see that I already said this). Hopefully one day I'll find time to improve it. Anyway, I'm afraid I don't know where such a library exists. You might find something on GitHub. Alternatively you could look for an open source chess program written in c++ and try to locate the code used in that program. Edited December 1, 2016 by czardas operator64 ArrayWorkshop Link to comment Share on other sites More sharing options...
NorhanFoda Posted December 6, 2016 Share Posted December 6, 2016 Thank you! I am already have open source chess programs and trying to locate the code and keep going, you've done a great work czardas 1 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now