Comment: Chess game validation regex (PCRE)
Negative Lookahead(?!^(?!rnbqkbnr\np{8}(?:\n-{8}){4}\nP{8}\nRNBQKBNR\n\n)) ^start of string
Negative Lookahead(?!rnbqkbnr\np{8}(?:\n-{8}){4}\nP{8}\nRNBQKBNR\n\n) rnbqkbnrthe literal text rnbqkbnr (case sensitive) \na line-feed (newline) character (ASCII 10)
p{8}the literal p (11210 / 1608 / 7016) (case sensitive), repeated exactly 8 times
Non-Capturing Group(?:\n-{8}){4} {4}repeats the group contents below exactly 4 times:
\na line-feed (newline) character (ASCII 10)
-{8}the literal - (4510 / 558 / 2D16) , repeated exactly 8 times
\na line-feed (newline) character (ASCII 10)
P{8}the literal P (8010 / 1208 / 5016) (case sensitive), repeated exactly 8 times
\na line-feed (newline) character (ASCII 10)
RNBQKBNRthe literal text RNBQKBNR (case sensitive) \na line-feed (newline) character (ASCII 10)
\na line-feed (newline) character (ASCII 10)
Comment: assert initial state of board, if this is the first move
Atomic Group(?>
(\n)? # \1 = capture \n if black moves next (ensuring that white moves first), or unset/NPCG if white moves next
| # subroutines:
((.|\n(?=.)){2}) # (?3) = for moving within the board, without wrapping to the next board, (?2) = (?3){2}
((?= # (?4) = assert that position of just-consumed piece is vacated on the next turn
(\X{72}) # (?5) = skip to the position of the just-consumed piece on the next turn
-))
((?! # (?6) = assert that the piece at the current position belongs to the current player
((?=(?(1)[-A-Z]|[-a-z]))) # (?7) = assert that the piece at the current position belongs to the current player's opponent or is empty
))
((?5)(?(?=(.*\n)\n)[qnrb]|p)) # (?8) = black pawn that might be promoted, (?9) = .*\n
( # (?10) = assert that the just-consumed piece never moved in all past turns
(?<=(.))
((?<!(?!_|\11(?12))\PM{74}))
)
((?5)(?(?=(?9){8}\n)[QNRB]|P)) # (?13) = white pawn that might be promoted
) 1st Alternative
(\n)? # \1 = capture \n if black moves next (ensuring that white moves first), or unset/NPCG if white moves next
?repeats the group contents below at most once:
\na line-feed (newline) character (ASCII 10)
Comment: \1 = capture \n if black moves next (ensuring that white moves first), or unset/NPCG if white moves next
2nd Alternative # subroutines:
((.|\n(?=.)){2}) # (?3) = for moving within the board, without wrapping to the next board, (?2) = (?3){2}
((?= # (?4) = assert that position of just-consumed piece is vacated on the next turn
(\X{72}) # (?5) = skip to the position of the just-consumed piece on the next turn
-))
((?! # (?6) = assert that the piece at the current position belongs to the current player
((?=(?(1)[-A-Z]|[-a-z]))) # (?7) = assert that the piece at the current position belongs to the current player's opponent or is empty
))
((?5)(?(?=(.*\n)\n)[qnrb]|p)) # (?8) = black pawn that might be promoted, (?9) = .*\n
( # (?10) = assert that the just-consumed piece never moved in all past turns
(?<=(.))
((?<!(?!_|\11(?12))\PM{74}))
)
((?5)(?(?=(?9){8}\n)[QNRB]|P)) # (?13) = white pawn that might be promoted
Comment: subroutines:
{2}repeats the group contents below exactly 2 times:
.any character (except for line terminators)
\na line-feed (newline) character (ASCII 10)
.any character (except for line terminators)
Comment: (?3) = for moving within the board, without wrapping to the next board, (?2) = (?3){2}
Group 4((?= # (?4) = assert that position of just-consumed piece is vacated on the next turn
(\X{72}) # (?5) = skip to the position of the just-consumed piece on the next turn
-)) Positive Lookahead(?= # (?4) = assert that position of just-consumed piece is vacated on the next turn
(\X{72}) # (?5) = skip to the position of the just-consumed piece on the next turn
-) Comment: (?4) = assert that position of just-consumed piece is vacated on the next turn
\X{72}one Unicode extended grapheme cluster, repeated exactly 72 times
Comment: (?5) = skip to the position of the just-consumed piece on the next turn
-the literal text -
Group 6((?! # (?6) = assert that the piece at the current position belongs to the current player
((?=(?(1)[-A-Z]|[-a-z]))) # (?7) = assert that the piece at the current position belongs to the current player's opponent or is empty
)) Negative Lookahead(?! # (?6) = assert that the piece at the current position belongs to the current player
((?=(?(1)[-A-Z]|[-a-z]))) # (?7) = assert that the piece at the current position belongs to the current player's opponent or is empty
) Comment: (?6) = assert that the piece at the current position belongs to the current player
Group 7((?=(?(1)[-A-Z]|[-a-z]))) Positive Lookahead(?=(?(1)[-A-Z]|[-a-z])) Condition(?(1)[-A-Z]|[-a-z]) Uses the first branch if group 1 matched
If the condition is true, match this branch:[-A-Z] -the literal - (4510 / 558 / 2D16)
A-Zone character from A (6510) to Z (9010) (case sensitive)
Otherwise, match this branch:[-a-z] -the literal - (4510 / 558 / 2D16)
a-zone character from a (9710) to z (12210) (case sensitive)
Comment: (?7) = assert that the piece at the current position belongs to the current player's opponent or is empty
Group 8((?5)(?(?=(.*\n)\n)[qnrb]|p)) (?5)calls group 5 as a subroutine
Condition(?(?=(.*\n)\n)[qnrb]|p) Comment: (?8) = black pawn that might be promoted, (?9) = .*\n
Group 10( # (?10) = assert that the just-consumed piece never moved in all past turns
(?<=(.))
((?<!(?!_|\11(?12))\PM{74}))
) Group 13((?5)(?(?=(?9){8}\n)[QNRB]|P)) Comment: (?13) = white pawn that might be promoted
Group 14( # check if we're in check; \14 will be captured and empty if we're not in check, or unset if we are; call (?14) to assert we're not in check on the next turn
(?!
\X{,70}
(?:
# pawns (capture diagonally)
(?(1)k|p)(?=(?3){7}(?2)?(?(1)P|K)) |
# bishops, rooks, queens, or knights
(?i:
(?<E>(?6)K)? # decide between scanning forward (<E> is unset) or backwards (<E> is captured)
(?:
(?(E)|((?7)[BQ])) (?<B>()?((?(-1)-)(?3){7}(?(-2)(?2)))+) (?(E)(?-4)) | # bishops or queens
(?(E)|((?7)[RQ])) (?<R>-*|((?(-1)-)(?3){8})+) (?(E)(?-3)) | # rooks or queens
(?(E)|((?7) N )) (?<N>(?<=..)(?2){3}|(?=.)(?2){5}|(?2){8}(?2)?) (?(E)(?-2)) # knights
)
(?(E)|(?&E)) |
K(?<K>(?3){7,9})?K # kings
)
)
)
)?? Atomic Group(?>
(?>
# Handle squares that don't change (empty->empty or pieces that doesn't move)
(.)(?=(?5)\g{-1}) |
# Handle a piece that moves (and optionally captures an enemy piece)
(?(m)$) # allow only one move to be made per turn
(?>
(?(1)
(?:p(?4)(?: # black pawn
(?=(?3){8}((?3){9})? - (?8))(?(-1)(?=(?9){7}\n)) | # move 1 or 2 spaces forward
(?=(?3){7}(?2)? [A-Z](?8)) ) | # capture diagonally
(?=\X{73}--(?9){4}\n)(?|P(?=(p))|pP())(?<=(?=(?:-\X{8}){2}P)\PM{74}(?=\X{8}-(?5)p))\g{-1} ) # en passant
|
(?: # white pawn
- (?=(?13))(?=(?3){8}((?3){9})?P(?4))(?(-1)(?=(?9){4}\n)) | # move 1 or 2 spaces forward
[a-z](?=(?13))(?=(?3){7}(?2)? P(?4)) | # capture diagonally
(?=\X{73}--(?9){5}\n)(?|p(?=(P))|Pp())(?<=(?=p(?:\X{8}-){2})\PM{82}(?=-(?5)P)\PM{10})\g{-1} ) # en passant
) |
# Castling - must be tried before rook moves unless we give up atomicity
# Leaves the rightmost changed square unconsumed, so that "only allow matching one of these per turn" below can't silently consume an illegal move
(?6)([KkRr])(?10)
(?!
(?:--)?-
# Verify that we aren't castling through check
# There's no need to check for kings, because they can't threaten check at this spot without our king already being in check
()? # decide between bishops/queens or rooks/queens
()? # for bishops/queens, decide between one diagonal or the other
(?(1)
(?|\X{7}(?2)?P # pawns (capture diagonally)
|(\X{6}|\X{10}|\X{16}(?2)?)N # knights
|((?(-1)-)\X{7}(?(-2)(?2)))+[BQ] # bishops or queens
|((?(-1)-)\X{8} )+[RQ]) # rooks or queens (only look downward, because left/right are impossible in this context)
| (?<=p\PM{8}|p\PM{10} # pawns (capture diagonally)
|(?=(?:|(?2)|\X{8}|\X{12})n)\PM{20} # knights
|((?<= # bishops/queens or rooks/queens
(?!.*\n\n)
(?=
(?(-4)
\X # rooks or queens (only look upward, because left/right are impossible in this context)
|
(?(-3)(?2)) # bishops or queens
)
(?:(?(-4)[rq]|[bq])|-(?-1))
)
\PM{11}
)))
)
)
(?=(?5)-(-[Kk]|[Rr])\g{-6}-)--(?=\g{-1}(?10))-?\14 |
# bishops, rooks, queens, knights, or kings
(?<e>(?7).)? # decide between scanning forward (<e> is unset) or backwards (<e> is captured)
(?=
(?i:
(?|
(?(e)|(B|Q)) (?&B) (?(e)(B|Q)) | # bishops or queens
(?(e)|(R|Q)) (?&R) (?(e)(R|Q)) | # rooks or queens
(?(e)|(N )) (?&N) (?(e)(N )) | # knights
(?(e)|(K )) (?&K)? (?(e)(K )) # kings
)
)
(?(e)(?<=(?!(?7)).)(?4)|(?7).(?5)\g{-2}) # verify that the piece moved, and optionally captured piece, are of the correct color
)
(?(e)(?=(?5)\g{-1})|(?6).(?4)) # verify that the piece moved is the same type and color at its destination in the next turn's board position
)(?<m>) |
(?(+1)$)(.) # handle the destination/source square that a piece moved to/from (only allow matching one of these per turn)
)+\n
)+ \k<m>the text saved by group m
Comment: assert that a move has taken place
Positive Lookahead(?=\n(?14)) Comment: don't allow moving into check
\1?optionally matches the text saved by group 1
Comment: if white moved last, black moves next, and vice-versa
g modifier: global. Finds all matches instead of stopping after the first
x modifier: extended. Ignores unescaped whitespace and comments outside character classes
A modifier: Anchored. Forces the pattern to match only at the start of the search