diff --git a/src/diana/diana.py b/src/diana/diana.py --- a/src/diana/diana.py +++ b/src/diana/diana.py @@ -11,6 +11,11 @@ from .drawer.tikz import Tikz def collect_moves(root): + """Walk the primary variation and collect the move coordinates. + + :param Node root: a node serving as a root for the tree traversal + :return: sequence of move colors and coordinates (col, row) + :rtype: Iterator[("b" or "w", (int, int))]""" node = root while len(node.children) > 0: b = node.get_prop("B") @@ -55,6 +60,13 @@ W: {PW} {WR} print("done") def create_diagram(self, start, end): + """Create and return a diagram drawer instance. + + :param int start: the initial move + :param int end: the first omitted move, similar to sequence slices + :return: a diagram instance + :rtype: Drawer""" + # initialize the diagram template = Svg() @@ -73,7 +85,7 @@ W: {PW} {WR} color, move = self._moves[k] if move == tuple(): - template.overlays.append((k, "pass")) # !! + template.overlays.append((k, "pass")) continue else: (c, r) = move @@ -93,6 +105,9 @@ W: {PW} {WR} return {k: self._record.get(k, default) for k in field_names} def _set_move(self, k): + """Rewind the internal game state to move k. + + :param int k: the move number""" self._game = go.Go() black_stones = self._record.root.get_prop("AB") @@ -107,13 +122,20 @@ W: {PW} {WR} for i in range(k): (color, move) = self._moves[i] if move == tuple(): - continue # pass + continue # pass self._move(color, *move) def _move(self, color, c, r): + """Make a single move. + + :param str color: "b" or "w" + :param int c: column + :param int r: row + :return: True if we can continue with another move, False on abort""" if not self._game.move(BLACK if color=='b' else WHITE, c, r): - # !! we do not honor http://red-bean.com/sgf/ff5/m_vs_ax.htm at the moment - msg = "illegal move: {0} at {1},{2}".format(self._game.move_count + 1, c, r) + # we do not honor http://red-bean.com/sgf/ff5/m_vs_ax.htm at the moment + # we accept and process only legal moves + msg = "illegal move: {0} at {1},{2}".format(self._game.move_count+1, c, r) if cfg.keep_broken: print(msg) else: diff --git a/src/diana/drawer/svg.py b/src/diana/drawer/svg.py --- a/src/diana/drawer/svg.py +++ b/src/diana/drawer/svg.py @@ -23,6 +23,12 @@ class Svg(Drawer): self.padding = 30 def render(self, template_name, bgcolor=""): + """Render the template with the current drawer state. + + :param str template_name: a template file name in the templ directory + :param bgcolor: a background color + :type bgcolor: an SVG color, which is the same as a CSS color + :return: the rendered template string""" points = [p for (i, p) in sorted(self._index.values(), key=lambda x: x[0])] stones = [p for p in points if p.color and p.label == ""] @@ -36,6 +42,12 @@ class Svg(Drawer): return self._env.get_template(template_name).render(params) def save(self, filename, template="templ.svg", bgcolor=""): + """Render the template and save it with the filename. + + :param str filename: a path where to save the diagram. SVG and TXT suffixes will be appended + :param str template: a template file name in the templ directory + :param bgcolor: a background color + :type bgcolor: an SVG color, which is the same as a CSS color""" file = open(filename+".svg", 'w') file.write(self.render(template, bgcolor)) file.close() diff --git a/src/diana/go.py b/src/diana/go.py --- a/src/diana/go.py +++ b/src/diana/go.py @@ -12,6 +12,12 @@ class Go: self.temp = [[]] def move(self, color, y, x): + """Update the board with the specified move. + + :param int color: BLACK or WHITE + :param int y: the row number + :param int x: the column number + :return: True on success, False on an invalid move""" if self.board[x][y] != EMPTY: return False @@ -39,10 +45,10 @@ class Go: return False self.temp[x][y] = True - return self._flood_fill(color, x - 1, y) or \ - self._flood_fill(color, x + 1, y) or \ - self._flood_fill(color, x, y - 1) or \ - self._flood_fill(color, x, y + 1) + return self._flood_fill(color, x-1, y) or \ + self._flood_fill(color, x+1, y) or \ + self._flood_fill(color, x, y-1) or \ + self._flood_fill(color, x, y+1) def _remove(self): for i in range(19): diff --git a/src/diana/sgfparser/__init__.py b/src/diana/sgfparser/__init__.py --- a/src/diana/sgfparser/__init__.py +++ b/src/diana/sgfparser/__init__.py @@ -1,4 +1,9 @@ def skip_whitespace(s, start): + """Find the first non-whitespace character in a string. + + :param str s: an input string + :param int start: an index where the search starts + :return: index of the first non-whitespace character or len(s)""" i = start while i < len(s) and s[i].isspace(): i += 1 @@ -7,6 +12,12 @@ def skip_whitespace(s, start): def str_row_col(s, i): + """Translate a string index i to a row and col number. + + :param str s: an input string + :param int i: an index pointing into s + :return: a string position as (row, col) + :rtype: (int, int)""" k = 0 (r, c) = (0, 0) for (r, line) in enumerate(s.splitlines(True)): diff --git a/src/diana/sgfparser/collection.py b/src/diana/sgfparser/collection.py --- a/src/diana/sgfparser/collection.py +++ b/src/diana/sgfparser/collection.py @@ -4,6 +4,8 @@ from .game_record import GameRecord class Collection: + """Game collection type, a list of game trees.""" + def __init__(self, s): self.game_trees = [] i = skip_whitespace(s, 0) @@ -18,6 +20,7 @@ class Collection: raise ParserError("expected EOF", s, i) def list_games(self): + """:rtype: Iterator[GameRecord]""" for tree in self.game_trees: for game in tree.list_games(): yield game @@ -30,10 +33,21 @@ class GameTree: @staticmethod def fits(s, i): + """Decide if a GameTree fits at the suggested string location. + + :param str s: an SGF string + :param int i: a string location + :return: True if there might be a GameTree at the location, False if not""" return i < len(s) and s[i] == "(" @staticmethod def create(s, start): + """Create a GameTree from the string and move the current position pointer. + + :param str s: an SGF string + :param int start: a location in s + :return: the resulting tree and a pointer to its end + :rtype: (int, GameTree)""" assert GameTree.fits(s, start) res = GameTree() @@ -65,8 +79,8 @@ class GameTree: i = skip_whitespace(s, i + 1) return (i, res) - ## Expand multiple games into distinct GameTrees and yield each. def list_games(self): + """Expand multiple games into distinct GameTrees and yield each.""" if len(self.nodes) == 0: return None for node in self.nodes[0].list_gi_nodes(): @@ -77,10 +91,10 @@ class GameTree: return self.nodes[i] return None - ## Create and return a new game tree containing the provided Node. - # - # Ancestor nodes are copied, descendants are moved from the seed_node. def _build_subtree(self, seed_node): + """Create and return a new game tree containing the provided Node. + + Ancestor nodes are copied, descendants are moved from the seed_node.""" node = seed_node.copy() node.set_children(seed_node.children) seed_node.children = [] diff --git a/src/diana/sgfparser/game_record.py b/src/diana/sgfparser/game_record.py --- a/src/diana/sgfparser/game_record.py +++ b/src/diana/sgfparser/game_record.py @@ -1,8 +1,9 @@ from .node import Node -## Wrapper around a Node tree. class GameRecord: + """Wrapper around a Node tree.""" + def __init__(self, root=None): self.root = root or Node() self._game_info_node = next(root.list_gi_nodes()) diff --git a/src/diana/sgfparser/node.py b/src/diana/sgfparser/node.py --- a/src/diana/sgfparser/node.py +++ b/src/diana/sgfparser/node.py @@ -86,8 +86,8 @@ class Node: return res return None - ## Create a copy of the Node, with the same parent and deep copied properties, no copied children. def copy(self): + """Create a copy of the Node, with the same parent and deep copied properties, no copied children.""" res = Node() res.properties = {k: v.copy() for (k, v) in self.properties.items()} res.parent = self.parent @@ -99,8 +99,8 @@ class Node: else: return default - ## Returns textual representation of the Node itself, but disregards its children. def __str__(self): + """Returns textual representation of the Node itself, but disregards its children.""" return ";" + "".join(str(p) for p in self.properties.values()) def export(self):