#! /usr/bin/env python import sys import os import re # give up with an error message def die(message, *args): if args: sys.stderr.write("%s\n" % (message % tuple(args),)) else: sys.stderr.write("%s\n", message) sys.exit(1) # complain about an sgf file being unparseable def carp(fn, line, game, move, message, history): print "error in file %s, line %d, game %d, move %d: %s" % (fn, line, game, move, message) for i in history: print i[1:13] print i[13:] print sys.exit(1) # output statistics for a game and or match def print_stats(m_hits, m_dances, m_lostpips, winner, hits, dances, lostpips, match_over): # for both players, add up match totals for i in [-1, 1]: m_hits[i] += hits[i] m_dances[i] += dances[i] m_lostpips[i] += lostpips[i] # print game stats print "%4d %4d %4d %4d %6d %6d" % (\ hits[winner], hits[winner] - hits[-winner], dances[winner], dances[-winner] - dances[winner], lostpips[winner], lostpips[-winner] - lostpips[winner]) if not match_over: return # print match stats print "# %4d %4d %4d %4d %6d %6d" % (\ m_hits[winner], m_hits[winner] - m_hits[-winner], m_dances[winner], m_dances[-winner] - m_dances[winner], m_lostpips[winner], m_lostpips[-winner] - m_lostpips[winner]) # given a path to an sgf file, process it def scan_file(fn): game = 0 hist = [] # board positions - for error messages and debugging # a set of dictionaries for players 1 = playe 'B' in the sgf file, # -1 is player 'W' m_hits = {-1: 0, 1: 0} m_dances = {-1: 0, 1: 0} m_lostpips = {-1: 0, 1: 0} # regular expression to find start of game in sgf file r_sog = re.compile(r'^\(;FF.*RE\[(.).*\].*') # regular expression to extract player ('B'or 'W'), the dice roll and # the move made (in sgf style notation) r_dice = re.compile(r';([BW])\[(\d\d)(\w{0,8})\]') # report the file name being processed print '## ', fn f = open(fn, "r") line = 0 # where we are in file while True: l = f.readline() if not l: # terminate while loop on end of file break line += 1 l = l.rstrip(); r = r_sog.match(l) if r: # start of a game. If it's not the first game, report stats # for the game which just ended if game > 0: print_stats(m_hits, m_dances, m_lostpips, winner, hits, dances, lostpips, False) # reset for next game game += 1 bar = {-1: 0, 1: 0} # pieces currently on bar dances = {-1: 0, 1: 0} # player could not enter hits = {-1: 0, 1: 0} # player was hit lostpips = {-1: 0, 1: 0} # setback in race from hits # set up pieces for enw game board = [0, -2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2 ] winner = 1 if r.groups()[0] == 'W': winner = -1 move = 0 continue # process a move r = r_dice.match(l) if r: # cube actions etc. don't have dice rolls, so we won't get here hist.append(board[:]) player = 1 move += 1 # set player to +/-1 if it's B/W if r.groups()[0] == 'W': player = -1 if r.groups()[2] == '': # couldn't move at all if bar[player] != 0: # couldn't move and had piece(s) on bar dances[player] += 1 continue # move was possible - get sgf move string mvs = list(r.groups()[2]) # convert string to tuples (from, to) mvs = [(mvs[i], mvs[i + 1]) for i in range(0, len(mvs), 2)] for source, dest in mvs: # from move is 'a' .. 'x' for 1..24 point for 'B', # 24..1 for 'W' # 'y' for bar, 'z' for bear off tray'(both players) source = ord('y') - ord(source) dest = ord('y') - ord(dest) if source != 0: # if player is 'W' make move negative, since 'a' # for 'W' is the 24 point. Indexing with a negative # index in Python counts backwards from the end of # an arry if board[source] * player <= 0: carp(fn, line, game, move, "piece moved from from empty or opp's point", hist) if bar[player] != 0: carp(fn, line, game, move, "piece moved while still on bar") board[source] -= player else: # source was 'y', then it's moving a piece off the # bar if bar[player] == 0: carp(fn, line, game, move, "piece moved from empty bar", hist) bar[player] -= 1 if dest > 0: # check that it's a legal desitnation # stored count is ... -3, -2, -1 = 3, 2, 1 'W' pieces # 0 = empty, 1, 2, 3 ... 1, 2, 3... 'B' pieces if board[dest] * player <= -2: carp(fn, line, game, move, "move to blocked point", hist) if board[dest] * player < 0: # destination has one opponent's piece board[dest] = 0 bar[-player] += 1 hits[player] += 1 if player < 0: lostpips[-player] += 25 - dest else: lostpips[-player] += dest board[dest] += player f.close() # reached end of file, report last game's stats and match stats print_stats(m_hits, m_dances, m_lostpips, winner, hits, dances, lostpips, True) def main(): if len(sys.argv) != 2: sys.stderr.write("invoke with path to directory with .sgf files\n") sys.exit(1) # get a list of files in .sgf directory sgf_path = sys.argv[1] flist = os.listdir(sgf_path) # and process them for f in flist: (name, ext) = os.path.splitext(f) if ext != '.sgf': continue scan_file("%s%s%s" % (sgf_path, os.sep, f)) main()