Created
April 5, 2018 21:14
-
-
Save FirefoxMetzger/e98dc6a52deed5130a9d35df401a14d8 to your computer and use it in GitHub Desktop.
A small parser with some visualization for go .sgf replays
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import sgf # pip install sgf -- simple parser for the file format | |
def decode_position(pos_string): | |
# position in .sgf is char[2] and row-first, e.g. "fd" | |
positions = "abcdefghijklmnopqrs" | |
x = positions.index(pos_string[0]) | |
y = positions.index(pos_string[1]) | |
return y, x | |
def render(black_board, white_board): | |
for y in range(black_board.shape[0]): | |
if y < 10: | |
row = "0"+str(y) | |
else: | |
row = str(y) | |
for x in range(black_board.shape[1]): | |
if white_board[y,x]: | |
row += "W" | |
elif black_board[y,x]: | |
row += "B" | |
else: | |
row +="-" | |
print(row) | |
def floodfill(liberties,y,x): | |
""" | |
flood fill a region that is now known to have liberties. 1.0 signals a liberty, 0.0 signals | |
undecided and -1.0 a known non-liberty (black stone) | |
liberties is an np.array of currently known liberties and non-liberties | |
""" | |
#"hidden" stop clause - not reinvoking for "liberty" or "non-liberty", only for "unknown". | |
if not liberties[y][x]: | |
liberties[y][x] = 1.0 | |
if y > 0: | |
floodfill(liberties,y-1,x) | |
if y < liberties.shape[0] - 1: | |
floodfill(liberties,y+1,x) | |
if x > 0: | |
floodfill(liberties,y,x-1) | |
if x < liberties.shape[1] - 1: | |
floodfill(liberties,y,x+1) | |
def capture_pieces(black_board, white_board): | |
"""Remove all pieces from the board that have | |
no liberties. This function modifies the input variables in place. | |
black_board is a 19x19 np.array with value 1.0 if a black stone is | |
present and 0.0 otherwise. | |
white_board is a 19x19 np.array similar to black_board. | |
""" | |
has_stone = np.logical_or(black_board,white_board) | |
white_liberties = np.zeros((19,19)) | |
black_liberties = np.zeros((19,19)) | |
# stones in opposite color have no liberties | |
white_liberties[black_board] = -1.0 | |
black_liberties[white_board] = -1.0 | |
for y in range(has_stone.shape[0]): | |
for x in range(has_stone.shape[1]): | |
if not has_stone[y,x]: | |
floodfill(white_liberties,y,x) | |
floodfill(black_liberties,y,x) | |
white_liberties[white_liberties == 0.0] = -1.0 | |
black_liberties[black_liberties == 0.0] = -1.0 | |
white_board[white_liberties == -1.0] = 0.0 | |
black_board[black_liberties == -1.0] = 0.0 | |
def parse(location): | |
with open(location, "r") as f: | |
collection = sgf.parse(f.read()) | |
# assume only a single game per file | |
game = collection[0] | |
black_board = np.zeros((19,19), dtype=bool) | |
white_board = np.zeros((19,19), dtype=bool) | |
# set initial stones for black | |
stones = game.root.properties["AB"] | |
for position in stones: | |
pos = decode_position(position) | |
black_board[pos] = 1.0 | |
yield black_board, white_board, pos | |
turn_white = True | |
for move in game.rest: | |
tag = "W" if turn_white else "B" | |
move_string = move.properties[tag][0] | |
if move_string == "": # pass move | |
turn_white = not turn_white | |
yield black_board, white_board, (None, None) | |
pos = decode_position(move_string) | |
if white_board[pos] or black_board[pos]: | |
raise Exception("Can't move on top of another stone at %d , %d" % pos) | |
if turn_white: | |
white_board[pos] = True | |
else: | |
black_board[pos] = True | |
capture_pieces(black_board,white_board) | |
turn_white = not turn_white | |
yield black_board, white_board, pos | |
if __name__ == "__main__": | |
replay = "" | |
for black, white, last_move in parse(replay): | |
print("Move at: %d, %d" % last_move) | |
print(render(black, white)) | |
print("=" * 20) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment