pgn.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. # pgn.py rel. 1.1 17-sep-2004
  2. #
  3. # Demonstration of the parsing module, implementing a pgn parser.
  4. #
  5. # The aim of this parser is not to support database application,
  6. # but to create automagically a pgn annotated reading the log console file
  7. # of a lecture of ICC (Internet Chess Club), saved by Blitzin.
  8. # Of course you can modify the Abstract Syntax Tree to your purpose.
  9. #
  10. # Copyright 2004, by Alberto Santini http://www.albertosantini.it/chess/
  11. #
  12. from pyparsing import alphanums, nums, quotedString
  13. from pyparsing import Combine, Forward, Group, Literal, oneOf, OneOrMore, Optional, Suppress, ZeroOrMore, Word
  14. from pyparsing import ParseException
  15. #
  16. # define pgn grammar
  17. #
  18. tag = Suppress("[") + Word(alphanums) + Combine(quotedString) + Suppress("]")
  19. comment = Suppress("{") + Word(alphanums + " ") + Suppress("}")
  20. dot = Literal(".")
  21. piece = oneOf("K Q B N R")
  22. file_coord = oneOf("a b c d e f g h")
  23. rank_coord = oneOf("1 2 3 4 5 6 7 8")
  24. capture = oneOf("x :")
  25. promote = Literal("=")
  26. castle_queenside = oneOf("O-O-O 0-0-0 o-o-o")
  27. castle_kingside = oneOf("O-O 0-0 o-o")
  28. move_number = Optional(comment) + Word(nums) + dot
  29. m1 = file_coord + rank_coord # pawn move e.g. d4
  30. m2 = file_coord + capture + file_coord + rank_coord # pawn capture move e.g. dxe5
  31. m3 = file_coord + "8" + promote + piece # pawn promotion e.g. e8=Q
  32. m4 = piece + file_coord + rank_coord # piece move e.g. Be6
  33. m5 = piece + file_coord + file_coord + rank_coord # piece move e.g. Nbd2
  34. m6 = piece + rank_coord + file_coord + rank_coord # piece move e.g. R4a7
  35. m7 = piece + capture + file_coord + rank_coord # piece capture move e.g. Bxh7
  36. m8 = castle_queenside | castle_kingside # castling e.g. o-o
  37. check = oneOf("+ ++")
  38. mate = Literal("#")
  39. annotation = Word("!?", max=2)
  40. nag = " $" + Word(nums)
  41. decoration = check | mate | annotation | nag
  42. variant = Forward()
  43. half_move = Combine((m3 | m1 | m2 | m4 | m5 | m6 | m7 | m8) + Optional(decoration)) \
  44. + Optional(comment) +Optional(variant)
  45. move = Suppress(move_number) + half_move + Optional(half_move)
  46. variant << "(" + OneOrMore(move) + ")"
  47. # grouping the plies (half-moves) for each move: useful to group annotations, variants...
  48. # suggested by Paul McGuire :)
  49. move = Group(Suppress(move_number) + half_move + Optional(half_move))
  50. variant << Group("(" + OneOrMore(move) + ")")
  51. game_terminator = oneOf("1-0 0-1 1/2-1/2 *")
  52. pgnGrammar = Suppress(ZeroOrMore(tag)) + ZeroOrMore(move) + Optional(Suppress(game_terminator))
  53. def parsePGN( pgn, bnf=pgnGrammar, fn=None ):
  54. try:
  55. return bnf.parseString( pgn )
  56. except ParseException as err:
  57. print(err.line)
  58. print(" "*(err.column-1) + "^")
  59. print(err)
  60. if __name__ == "__main__":
  61. # input string
  62. pgn = """
  63. [Event "ICC 5 0 u"]
  64. [Site "Internet Chess Club"]
  65. [Date "2004.01.25"]
  66. [Round "-"]
  67. [White "guest920"]
  68. [Black "IceBox"]
  69. [Result "0-1"]
  70. [ICCResult "White checkmated"]
  71. [BlackElo "1498"]
  72. [Opening "French defense"]
  73. [ECO "C00"]
  74. [NIC "FR.01"]
  75. [Time "04:44:56"]
  76. [TimeControl "300+0"]
  77. 1. e4 e6 2. Nf3 d5 $2 3. exd5 (3. e5 g6 4. h4) exd5 4. Qe2+ Qe7 5. Qxe7+ Bxe7 6. d3 Nf6 7. Be3
  78. Bg4 8. Nbd2 c5 9. h3 Be6 10. O-O-O Nc6 11. g4 Bd6 12. g5 Nd7 13. Rg1 d4 14.
  79. g6 fxg6 15. Bg5 Rf8 16. a3 Bd5 17. Re1+ Nde5 18. Nxe5 Nxe5 19. Bf4 Rf5 20.
  80. Bxe5 Rxe5 21. Rg5 Rxe1# {Black wins} 0-1
  81. """
  82. # parse input string
  83. tokens = parsePGN(pgn, pgnGrammar)
  84. print(tokens.dump())