#!/usr/bin/python # usage: # newstosgf < article # -or- # newstosgf -v sgf-viewer < article # # newstosgf defaults to using cgoban with particular options. If you # want to change the default, search for the string "default viewer here", # and change the value of the "viewer" variable. # read in a news article and spawn an sgf viewer for each diagram in the # article, one viewer at a time (as long as your sgf viewer doesn't background # itself). # this is version 0.96. # Mon Nov 22 18:02:37 PST 1999 greatly improved left, right, bottom, top # positioning. Also made it so a board # would be shown even if there was no nonboard # line following it # Mon Nov 22 20:07:42 PST 1999 Eliminated one-line diagrams that were getting # prepended to real diagrams # Sun Nov 28 11:24:25 PST 1999 Expanded letter options from a-g to a-j. # Sun Nov 28 18:23:28 PST 1999 Added '+' as ignored; many people use it # for hoshi. # Mon Nov 29 19:42:07 PST 1999 Added max_width and min_width variables. # Added some lines of all letters as top or # bottom, instead of a line of labels # Sun Dec 5 19:17:13 PST 1999 Rewritten in large part, in order to reduce # the amount of code that relies on regex's. # Also decomposed the problem into subprocedures # much better. # Please consider this software under the Gnu General Public License version # 2. There are copies of the license all over the internet; you should # have no trouble finding one. It should be at # http://www.gnu.org/copyleft/gpl.html. import string import sys import regex import os #def count(line,ch): # v = 0 # for c in line: # if c == ch: # v = v + 1 # return v def even(number): n=string.atoi(number) if int(n/2)*2 == n: return 1 else: return 0 def escaped(line): result='' for ch in line: if ch == ']': result=result+('\\]') else: result=result+(ch) return result sys.stderr.write('result is '+result+'\n') # change row,col to sgf coordinates, taking into account which corner # of the board we're in and the width of the board (assumed to be square) def pos(top,left,row,col,width,height): #0..7 is normal, 8 is skipped - not. In sgf, it's not skipped. On the # board, it's skipped size=max(width,height) lrow=row lcol=col if not top: lrow=size-height+lrow if not left: lcol=size-width+lcol lrow=chr(ord('a')+lrow) lcol=chr(ord('a')+lcol) return lcol+lrow def show_board(board): print for row in range(0,len(board)): print board[row] print def refine_board_1(raw,board_regex,left_edge_regex,right_edge_regex,\ max_width,min_width): ###################################################################### # just pull out some raw data, don't worry too much about getting # it completely right yet. # # this handles left and right edges based on |'s ###################################################################### board=[] height = len(raw) top = 0 left = 0 for row in range(0,height): line = raw[row] for width in range(max_width,min_width-1,-1): matched = board_regex[width].match(line) if matched <> -1: break # got the right width now, and regs are set cooked_line='' for col in range(0,width): lpos=board_regex[width].regs[1+col][0] rpos=board_regex[width].regs[1+col][1] stone=line[lpos:rpos] cooked_line = cooked_line+stone board.append(cooked_line) for width in range(max_width,min_width-1,-1): matched = left_edge_regex[width].match(line) # if both, fine, just do whatever. If the board is square, we're # fine, and if the board is retangular, it doesn't matter much which # side suffers if matched <> -1: left = 1 matched = right_edge_regex[width].match(line) if matched <> -1: left = 0 return board,top,left def sequential_letters(str): s = string.lower(str) yup = 1 # only check up through H; if it matches that closely, assume the # whole thing is matching. Things get slightly more complicated at I for i in range(0,min(len(s),8)): if s[i] <> chr(ord('a') + i): yup = 0 break return yup def horiz_edge(str): yup = 1 for ch in str: if not ch in '-_+': yup = 0 break return yup def refine_board_2(board,top,left): ###################################################################### # handle rows of letters and rows of dashes ###################################################################### if sequential_letters(board[0]) or horiz_edge(board[0]): board = board[1:] top = 1 height = len(board) if sequential_letters(board[height-1]) or horiz_edge(board[height-1]): board = board[:-1] top = 0 # again, if we're both top and bottom, so what. If we're square, we'll # just fit, and if we're rectangular it doesn't matter which edge # suffers return board,top,left def refine_board_3(board,top,left): ###################################################################### # handle columns of digits along the edges, identifying board # coordinates (as opposed to numbers identifying a sequence of moves) ###################################################################### height = len(board) left_is = 1 right_is = 1 for row in range(0,height): inv=height-row n = str(inv) left_num = n[-1:] right_num = n[0:1] if board[row][0] <> left_num: left_is = 0 if board[row][len(board[row])-1] <> right_num: right_is = 0 if left_is: # left is all coordinates, so strip it left = 1 for row in range(0,height): board[row] = board[row][1:] if right_is: # right is all coordinates, so strip it left = 0 for row in range(0,height): board[row] = board[row][:-1] # again, if both left_is and right_is, and we're square, then we'll # "just fit", and if we're rectangular, then it doesn't matter which # edge suffers return board,top,left def refine_board_4(board,top,left): ###################################################################### # handle columns of |'s ###################################################################### height = len(board) left_is = 1 right_is = 1 for row in range(0,height): if board[row][0] <> '|': left_is = 0 if board[row][-1:] <> '|': right_is = 0 if left_is: # left is all -'s, so strip it left = 1 for row in range(0,height): board[row] = board[row][1:] if right_is: # right is all -'s, so strip it left = 0 for row in range(0,height): board[row] = board[row][:-1] # again, if both left_is and right_is, and we're square, then we'll # "just fit", and if we're rectangular, then it doesn't matter which # edge suffers return board,top,left def refine_board(raw,board_regex,left_edge_regex,right_edge_regex,\ max_width,min_width): board,top,left=refine_board_1(raw,board_regex,\ left_edge_regex,right_edge_regex,max_width,min_width) # rows of letters and rows of dashes board,top,left=refine_board_2(board,top,left) # columns of digits board,top,left=refine_board_3(board,top,left) # columns of |'s board,top,left=refine_board_4(board,top,left) return board,top,left def board_width(board): height=len(board) max_width=-1 for row in range(0,height): l = len(board[row]) if l > max_width: max_width = l return max_width def convert(board,black_to_play,white_to_play,top,left,comment): height = len(board) width=board_width(board) sgf = '(\n;GM[1]FF[3]SZ['+str(max(width,height))+'];\n' for row in range(0,height): line=board[row] width = len(line) for col in range(0,len(line)): stone = string.upper(line[col]) if stone in 'X#': sgf=sgf+'AB['+pos(top,left,row,col,width,height)+']\n' elif stone == 'O': sgf=sgf+'AW['+pos(top,left,row,col,width,height)+']\n' elif stone in '.,+-_*': # do nothing! comma, plus and dash get used for hoshi sometimes pass elif stone >= 'A' and stone <= 'T': # output a letter sgf=sgf+'LB['+pos(top,left,row,col,width,height)+':'+stone+']\n' elif stone >= '0' and stone <= '9': # output a stone of the appropriate color, if any, # and always output a label if black_to_play: if white_to_play: # do nothing, we don't want both colors! pass else: # just black to play, do a stone if even(stone): sgf=sgf+'AW['+pos(top,left,row,col,width,height)+']\n' else: sgf=sgf+'AB['+pos(top,left,row,col,width,height)+']\n' else: if white_to_play: # just white to play, do a stone if even(stone): sgf=sgf+'AB['+pos(top,left,row,col,width,height)+']\n' else: sgf=sgf+'AW['+pos(top,left,row,col,width,height)+']\n' sgf=sgf+'LB['+pos(top,left,row,col,width,height)+':'+stone+']\n' else: sys.stderr.write('internal error, bad character found:'+\ stone+'\n') sgf=sgf+'C[\n' for i in range(0,len(comment)): sgf=sgf+comment[i] sgf=sgf+']\n)\n' return sgf def display(sgf,viewer,tempfile): file = open(tempfile,'w') file.write(sgf) file.close() os.system(viewer+' '+tempfile) os.remove(tempfile) def who_to_play(line,black_regex1,black_regex2,white_regex1,white_regex2): lower_line=string.lower(line) black_to_play = 0 white_to_play = 0 if black_regex1.match(lower_line) != -1: black_to_play=1 if black_regex2.match(lower_line) != -1: black_to_play=1 if white_regex1.match(lower_line) != -1: white_to_play=1 if white_regex2.match(lower_line) != -1: white_to_play=1 return black_to_play,white_to_play def process_input_stream(input,viewer,tempfile): # we actually want 19x19 max, but the regex's like to match the rightmost # part of a line, which sometimes bumps the left edge of the board when # there are numbers down the right edge. So we just match 20. max_width=20 min_width=4 # some people use -'s for hoshi's, and we need dashes for edge detection # and some people use *'s for hoshi's too. Criminy, there's a lot # of variation in what gets used for hoshis s='-\*_xX#oO\\.,+0-9a-tA-T' bs='\\(['+s+']\\)' board_regex={} left_edge_regex={} right_edge_regex={} for i in range(min_width-1,max_width): pattern=''+i*(bs+'[-_ ]')+bs+'' board_regex[i+1]=regex.compile('^.*'+pattern+'.*$') left_edge_regex[i+1]=regex.compile('^.*| *'+pattern+'.*$') right_edge_regex[i+1]=regex.compile('^.*'+pattern+' *|.*$') black_regex1=regex.compile('^.*black to play.*$') white_regex1=regex.compile('^.*white to play.*$') black_regex2=regex.compile('^.*black 1\>.*$') white_regex2=regex.compile('^.*white 1\>.*$') row = 0 comment=[] raw_board=[] commenting=0 black_to_play=0 white_to_play=0 done = 0 while 1: if done: break line = input.readline() if not line: # generate one fake line at the end, to force the last board # to be shown even if there isn't a line of nonboard stuff # after it line='' done = 1 if len(line) == 1: # we do this so the news headers don't end up in the comments commenting = 1 matched = 0 if commenting == 1: comment.append(escaped(line)) bt,wt = who_to_play(line, black_regex1,black_regex2,white_regex1,white_regex2) black_to_play = bt or black_to_play white_to_play = wt or white_to_play matched = 0 for width in range(max_width,min_width-1,-1): if board_regex[width].match(line) <> -1: raw_board.append(line) row = row + 1 matched = 1 break if not matched: if row >= 2: # we have a "board" that's at least 2 lines tall. Refine it, # convert it to SGF, and display it board,top,left = refine_board(raw_board,board_regex,\ left_edge_regex,right_edge_regex,max_width,min_width) sgf = convert(board,black_to_play,white_to_play,top,left,comment) display(sgf,viewer,tempfile) comment=[] raw_board = [] row = 0 def main(): if sys.argv[2:] and sys.argv[1] == '-v': viewer = sys.argv[2] else: # default viewer here viewer = 'cgoban-font -nostealth -edit' tempfile="newstosgf.temp" process_input_stream(sys.stdin,viewer,tempfile) main()