You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

174 lines
5.5 KiB

  1. import argparse
  2. import os
  3. import sys
  4. from subprocess import check_call, DEVNULL, STDOUT, CalledProcessError
  5. parser = argparse.ArgumentParser(description='Convert sgf go records into a kifu format.')
  6. parser.add_argument('sgfFile')
  7. parser.add_argument('-se', "--splitevery", dest="step", type=int, default=50)
  8. parser.add_argument('-cn', "--continuousNumbers", action="store_true", dest="cn", default=False)
  9. parser.add_argument('-c', "--compile", action="store_true", dest="c", default=False)
  10. parser.add_argument('-o', "--open", action="store_true", dest="o", default=False)
  11. args = parser.parse_args(sys.argv[1:])
  12. filePath = args.sgfFile
  13. fileBase = ".".join(filePath.split(".")[:-1])
  14. splitBoardAt = [x for x in range(0, 400, args.step)]
  15. with open(filePath, 'r') as myfile:
  16. sgfData = myfile.read().replace("\n", "").split(";")[1:]
  17. sgfData[-1] = sgfData[-1][:-1]
  18. header = sgfData[0]
  19. moves = sgfData[1:]
  20. def get_tag_from_header(tag):
  21. eventIdxStart = header.lower().find(f"{tag.lower()}[")
  22. eventIdxEnd = -1
  23. if eventIdxStart != -1 :
  24. eventIdxEnd = header.find("]", eventIdxStart)
  25. return header[len(tag) + 1 + eventIdxStart:eventIdxEnd]
  26. return ""
  27. def extract_coordinates(move):
  28. try:
  29. firstCoordinate = move[2]
  30. secondCoordinate = ord(move[3])-96
  31. # mirror at x axis so it looks normal
  32. secondCoordinate = parsedHeader["boardSize"] - secondCoordinate + 1
  33. if ord(move[2]) >= ord("i"):
  34. firstCoordinate = chr(ord(firstCoordinate) + 1)
  35. return firstCoordinate, secondCoordinate
  36. except IndexError:
  37. # wierd move
  38. return -1,-1
  39. def generate_title():
  40. out = []
  41. if parsedHeader["event"] != "":
  42. out.extend([parsedHeader["event"], "\\\\"])
  43. out.append(parsedHeader["playerBlack"])
  44. if parsedHeader["rankBlack"] != "":
  45. out.extend(["[", parsedHeader["rankBlack"], "]"])
  46. out.extend([" - ", parsedHeader["playerWhite"]])
  47. if parsedHeader["rankWhite"] != "":
  48. out.extend(["[", parsedHeader["rankWhite"], "]"])
  49. return "".join(out)
  50. def generate_moves():
  51. finished = False
  52. outText = []
  53. for i in range(len(splitBoardAt)-1):
  54. currentSplit = splitBoardAt[i]
  55. nextSplit = splitBoardAt[i+1]
  56. outText.append("\\begin{psgoboard}\n\t")
  57. # old moves
  58. for j in range(currentSplit):
  59. firstCoordinate, secondCoordinate = extract_coordinates(moves[j])
  60. if not (firstCoordinate == -1 or secondCoordinate == -1):
  61. outText.append(f"\\move*{{{firstCoordinate}}}{{{secondCoordinate}}} ")
  62. if j % 5 == 4:
  63. outText.append("\n\t")
  64. elif secondCoordinate < 10: # nice spacing
  65. outText.append(" ")
  66. # new moves
  67. for j in range(nextSplit-currentSplit):
  68. firstCoordinate, secondCoordinate = extract_coordinates(moves[currentSplit+j])
  69. if not (firstCoordinate == -1 or secondCoordinate == -1):
  70. outText.append(f"\\move{{{firstCoordinate}}}{{{secondCoordinate}}} ")
  71. if j % 5 == 4:
  72. outText.append("\n\t")
  73. elif secondCoordinate < 10: # nice spacing
  74. outText.append(" ")
  75. # was it the last move?
  76. if currentSplit+j == len(moves)-1:
  77. finished = True
  78. break
  79. outText.append("\n\\end{psgoboard}\n")
  80. if finished:
  81. break
  82. elif not args.cn:
  83. outText.append("\n\\setcounter{gomove}{0}\n")
  84. return "".join(outText)
  85. parsedHeader = {
  86. "event" : get_tag_from_header("EV"),
  87. "gameName" : get_tag_from_header("GN"),
  88. "date" : get_tag_from_header("RD"),
  89. "boardSize" : int(get_tag_from_header("SZ")),
  90. "playerBlack" : get_tag_from_header("PB"),
  91. "playerWhite" : get_tag_from_header("PW"),
  92. "rankBlack" : get_tag_from_header("BR"),
  93. "rankWhite" : get_tag_from_header("WR"),
  94. "komi" : get_tag_from_header("KM"),
  95. "result" : get_tag_from_header("RE")
  96. }
  97. # title = generate_title()
  98. event = parsedHeader["event"]
  99. date = parsedHeader["date"]
  100. result = parsedHeader["result"]
  101. playerWhite = parsedHeader["playerWhite"]
  102. playerBlack = parsedHeader["playerBlack"]
  103. moves = generate_moves()
  104. outText = f"""
  105. \\documentclass[a4paper]{{article}}
  106. \\usepackage{{psgo}}
  107. \\usepackage[ngerman]{{babel}}
  108. \\usepackage[margin=2cm,nohead]{{geometry}}
  109. \\usepackage{{tabularx}}
  110. \\newcolumntype{{R}}{{>{{\\raggedleft\\arraybackslash}}X}}
  111. \\setgounit{{0.5cm}}
  112. \\setcounter{{gomove}}{{0}}
  113. \\begin{{document}}
  114. \\sffamily
  115. \\def\\arraystretch{{2}}
  116. \\begin{{center}}
  117. \\vspace*{{1cm}}
  118. {{\\Huge {event} \\par}}
  119. \\vspace{{1cm}}
  120. {{\\huge {date} \\par}}
  121. \\vspace{{2cm}}
  122. \\begin{{tabularx}}{{\\textwidth}}{{ R | c | X }}
  123. \\hline
  124. \\stone{{black}} {playerBlack} & \\textbf{{{result}}} & {playerWhite} \\stone{{white}} \\\\\\hline
  125. & 7.5 komi & \\\\\\hline
  126. \\end{{tabularx}}
  127. \\vspace{{3cm}}
  128. {moves}
  129. \\end{{center}}
  130. \\end{{document}}
  131. """
  132. with open(f"{fileBase}.tex", 'w') as outFile:
  133. outFile.write(outText)
  134. # should be compiled to pdf?
  135. if args.c:
  136. try:
  137. check_call(['latex', f"{fileBase}.tex"], stdout=DEVNULL, stderr=STDOUT)
  138. check_call(['dvips', f"{fileBase}.dvi", "-P", "pdf"], stdout=DEVNULL, stderr=STDOUT)
  139. check_call(['ps2pdf', f"{fileBase}.ps"], stdout=DEVNULL, stderr=STDOUT)
  140. except CalledProcessError:
  141. print("error")
  142. else:
  143. if args.o:
  144. os.system(f'"{fileBase}.pdf"')