Changeset - 966ee650fabf
[Not reviewed]
default
0 7 0
Laman - 3 years ago 2022-03-05 23:24:13

updated comments
7 files changed with 81 insertions and 15 deletions:
0 comments (0 inline, 0 general)
src/diana/diana.py
Show inline comments
 
@@ -8,12 +8,17 @@ from .sgfparser import ParserError
 
from .sgfparser.collection import Collection
 
from .drawer.svg import Svg
 
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")
 
		w = node.get_prop("W")
 
		if b is not None:
 
			yield ("b", b)
 
@@ -52,12 +57,19 @@ W: {PW} {WR}
 
		notes = open(os.path.join(cfg.output_dir, "{0}.txt".format(self._short_name)), 'w')
 
		notes.write(info_str)
 
		notes.close()
 
		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()
 

	
 
		self._set_move(start)
 

	
 
		# draw current state
 
@@ -70,13 +82,13 @@ W: {PW} {WR}
 
		for k in range(start, end):
 
			if k >= len(self._moves):
 
				break
 

	
 
			color, move = self._moves[k]
 
			if move == tuple():
 
				template.overlays.append((k, "pass"))  # !!
 
				template.overlays.append((k, "pass"))
 
				continue
 
			else:
 
				(c, r) = move
 

	
 
			if not self._move(color, c, r):
 
				if cfg.keep_broken:
 
@@ -90,12 +102,15 @@ W: {PW} {WR}
 
		return template
 

	
 
	def fetch_game_info(self, field_names, default=None):
 
		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")
 
		white_stones = self._record.root.get_prop("AW")
 
		if black_stones:
 
			for p in black_stones:
 
@@ -104,19 +119,26 @@ W: {PW} {WR}
 
			for p in white_stones:
 
				self._game.board[p.r][p.c] = WHITE
 

	
 
		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:
 
				msg += ". aborted"
 
				print(msg)
 
				return False
src/diana/drawer/svg.py
Show inline comments
 
@@ -20,12 +20,18 @@ class Svg(Drawer):
 
	def __init__(self, start=0):
 
		super().__init__(start)
 
		self.boardSize = 480
 
		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 == ""]
 
		moves = [p for p in points if p.color and p.label]
 
		labels = [p for p in points if not p.color and p.label]
 

	
 
@@ -33,11 +39,17 @@ class Svg(Drawer):
 
			"boardSize": self.boardSize, "padding": self.padding, "stones": stones, "moves": moves,
 
			"labels": labels, "adjustFont": adjust_font, "bgcolor": bgcolor}
 

	
 
		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()
 

	
 
		super().save(filename)
src/diana/go.py
Show inline comments
 
@@ -9,12 +9,18 @@ class Go:
 
	def __init__(self):
 
		self.board = [[EMPTY]*19 for i in range(19)]
 
		self.move_count = 0
 
		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
 

	
 
		self.board[x][y] = color
 

	
 
		for i, j in ((-1, 0), (1, 0), (0, -1), (0, 1)):
 
@@ -36,16 +42,16 @@ class Go:
 
		if self.board[x][y] == EMPTY:
 
			return True
 
		if self.board[x][y] != color:
 
			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):
 
			for j in range(19):
 
				if self.temp[i][j]:
 
					self.board[i][j] = EMPTY
src/diana/sgfparser/__init__.py
Show inline comments
 
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
 

	
 
	return i
 

	
 

	
 
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)):
 
		c = i-k
 
		if k+len(line) > i:
 
			break
src/diana/sgfparser/collection.py
Show inline comments
 
from .node import Node
 
from . import skip_whitespace, ParserError
 
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)
 
		if i >= len(s):
 
			return
 
		elif not GameTree.fits(s, i):
 
@@ -15,28 +17,40 @@ class Collection:
 
			(i, x) = GameTree.create(s, i)
 
			self.game_trees.append(x)
 
		if i < len(s):
 
			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
 

	
 

	
 
class GameTree:
 
	def __init__(self):
 
		self.nodes = []
 
		self.branches = []
 

	
 
	@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()
 

	
 
		i = skip_whitespace(s, start + 1)
 
		if not Node.fits(s, i):
 
			raise ParserError("expected a Node starting with ';'", s, i)
 
@@ -62,28 +76,28 @@ class GameTree:
 

	
 
		if i >= len(s) or s[i] != ")":
 
			raise ParserError("expected end of a GameTree marked by ')'", s, i)
 
		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():
 
			yield GameRecord(self._build_subtree(node))
 

	
 
	def get_node(self, i):
 
		if 0 <= i < len(self.nodes):
 
			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 = []
 

	
 
		while node.parent:
 
			new_node = node.parent.copy()
src/diana/sgfparser/game_record.py
Show inline comments
 
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())
 

	
 
	def export(self):
 
		return self.root.export()
src/diana/sgfparser/node.py
Show inline comments
 
@@ -83,27 +83,27 @@ class Node:
 
			res = self.children[i]
 
			del self.children[i]
 
			res.parent = None
 
			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
 
		return res
 

	
 
	def get_prop(self, name, default=None):
 
		if name in self.properties:
 
			return self.properties[name].value
 
		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):
 
		# there is a beautiful recursive solution, which this stack is too narrow to contain
 
		stack = [(self, 1, 1)]
 
		output = []
0 comments (0 inline, 0 general)