tools/sgf2gtp.py: alternative implementation of SGF to GTP converter
[pachi/t.git] / tools / sgf2gtp.py
blob49dda67f91a8ece4ebf65fab57a0659a161b8bf7
1 #! /usr/bin/env python
3 import sys
4 import argparse
5 import re
7 from sgflib import SGFParser
9 DEBUG = False
11 parser = argparse.ArgumentParser(description="""
12 This script converts SGF files to GTP format so that you can feed them
13 to Pachi, insert genmove at the right places etc. Might not work on
14 obscure SGF files.
16 When called with FILENAMES argument, it will create according output files
17 with .gtp extension instead of .sgf, unless --no-gtp option is specified.
18 """)
20 parser.add_argument('FILENAMES', help='List of sgf games.', nargs='*', default=[])
21 parser.add_argument('-g', help='Automatically append genmove command for the other color.', action='store_true')
22 parser.add_argument('-n', help='Output at most first MOVENUM moves.', metavar='MOVENUM', type=int, default=10**10)
23 parser.add_argument('--no-gtp', help='Do not create the .gtp files.', action='store_true')
24 args = vars(parser.parse_args())
26 class UnknownNode(Exception):
27 pass
29 def get_atr(node, atr):
30 try:
31 return node.data[atr].data[0]
32 except KeyError:
33 return None
35 def col2num(column, board_size):
36 a, o, z = map(ord, ['a', column, 'z'])
37 if a <= o <= z:
38 return a + board_size - o
39 raise Exception( "Wrong column character: '%s'"%(column,) )
41 def is_pass_move(coord, board_size):
42 # the pass move is represented either by [] ( = empty coord )
43 # OR by [tt] (for boards <= 19 only)
44 return len(coord) == 0 or ( board_size <= 19 and coord == 'tt' )
46 def process_gametree(gametree, fout):
47 # cursor for tree traversal
48 c = gametree.cursor()
49 # first node is the header
50 header = c.node
52 handicap = get_atr(header, 'HA')
53 board_size = int(get_atr(header, 'SZ'))
54 komi = get_atr(header, 'KM')
55 player_next, player_other = "B", "W"
57 print >>fout, "boardsize", board_size
58 print >>fout, "clear_board"
59 if komi:
60 print >>fout, "komi", komi
61 if handicap and handicap != '0':
62 print >>fout, "fixed_handicap", handicap
63 player_next, player_other = player_other, player_next
65 def print_game_step(coord):
66 if is_pass_move(coord, board_size):
67 print >>fout, "play", player_next, "pass"
68 else:
69 x, y = coord
70 # The reason for this incredibly weird thing is that
71 # the GTP protocol excludes `i` in the coordinates
72 # (do you see any purpose in this??)
73 if x >= 'i':
74 x = chr(ord(x)+1)
75 y = str(col2num(y, board_size))
76 print >>fout, "play", player_next, x+y
78 movenum = 0
79 # walk the game tree forward
80 while 1:
81 # sgf2gtp.pl ignores n = 0
82 if c.atEnd or (args['n'] and movenum >= args['n']):
83 break
84 c.next()
85 movenum += 1
87 coord = get_atr(c.node, player_next)
88 if coord != None:
89 print_game_step(coord)
90 else:
91 # MAYBE white started?
92 # or one of the players plays two time in a row
93 player_next, player_other = player_other, player_next
94 coord = get_atr(c.node, player_next)
95 if coord != None:
96 print_game_step(coord)
97 else:
98 # TODO handle weird sgf files better
99 raise UnknownNode
101 player_next, player_other = player_other, player_next
103 if args['g']:
104 print >>fout, "genmove", player_next
106 def process_sgf_file(fin, fout):
107 sgfdata = fin.read()
108 col = SGFParser(sgfdata).parse()
110 for gametree in col:
111 try:
112 process_gametree(gametree, fout)
113 except UnknownNode:
114 # Try next game tree in this file
115 if DEBUG:
116 print >>sys.stderr, "Unknown Node"
117 continue
119 if __name__ == "__main__":
120 if not len(args['FILENAMES']):
121 process_sgf_file(sys.stdin, sys.stdout)
122 else:
123 for in_filename in args['FILENAMES']:
124 if args['no_gtp']:
125 fout = sys.stdout
126 else:
127 if re.search('sgf$', in_filename):
128 filename_base = in_filename[:-3]
129 else:
130 filename_base = in_filename
131 # Save the .gtp file
132 out_filename = filename_base + 'gtp'
134 fout = open(out_filename, 'w')
136 fin = open(in_filename, 'r')
138 process_sgf_file(fin, fout)
140 fin.close()
141 if not args['no_gtp']:
142 fout.close()