Skip to content

Instantly share code, notes, and snippets.

@FirefoxMetzger
Created April 5, 2018 21:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FirefoxMetzger/e98dc6a52deed5130a9d35df401a14d8 to your computer and use it in GitHub Desktop.
Save FirefoxMetzger/e98dc6a52deed5130a9d35df401a14d8 to your computer and use it in GitHub Desktop.
A small parser with some visualization for go .sgf replays
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