139 lines
4.3 KiB
Python
139 lines
4.3 KiB
Python
# tag::scoring_imports[]
|
|
from __future__ import absolute_import
|
|
from collections import namedtuple
|
|
|
|
from tugo.game_logic.gotypes import Player, Point
|
|
|
|
|
|
|
|
# tag::scoring_territory[]
|
|
class Territory:
|
|
def __init__(self, territory_map): # <1>
|
|
self.num_black_territory = 0
|
|
self.num_white_territory = 0
|
|
self.num_black_stones = 0
|
|
self.num_white_stones = 0
|
|
self.num_dame = 0
|
|
self.dame_points = []
|
|
for point, status in territory_map.items(): # <2>
|
|
if status == Player.black:
|
|
self.num_black_stones += 1
|
|
elif status == Player.white:
|
|
self.num_white_stones += 1
|
|
elif status == 'territory_b':
|
|
self.num_black_territory += 1
|
|
elif status == 'territory_w':
|
|
self.num_white_territory += 1
|
|
elif status == 'dame':
|
|
self.num_dame += 1
|
|
self.dame_points.append(point)
|
|
|
|
# <1> A `territory_map` splits the board into stones, territory and neutral points (dame).
|
|
# <2> Depending on the status of a point, we increment the respective counter.
|
|
# end::scoring_territory[]
|
|
|
|
|
|
# tag::scoring_game_result[]
|
|
class GameResult(namedtuple('GameResult', 'b w komi')):
|
|
@property
|
|
def winner(self):
|
|
if self.b > self.w + self.komi:
|
|
return Player.black
|
|
return Player.white
|
|
|
|
@property
|
|
def winning_margin(self):
|
|
w = self.w + self.komi
|
|
return abs(self.b - w)
|
|
|
|
def __str__(self):
|
|
w = self.w + self.komi
|
|
if self.b > w:
|
|
return 'B+%.1f' % (self.b - w,)
|
|
return 'W+%.1f' % (w - self.b,)
|
|
# end::scoring_game_result[]
|
|
|
|
|
|
""" evaluate_territory:
|
|
Map a board into territory and dame.
|
|
|
|
Any points that are completely surrounded by a single color are
|
|
counted as territory; it makes no attempt to identify even
|
|
trivially dead groups.
|
|
"""
|
|
|
|
|
|
# tag::scoring_evaluate_territory[]
|
|
def evaluate_territory(board):
|
|
|
|
status = {}
|
|
for r in range(1, board.num_rows + 1):
|
|
for c in range(1, board.num_cols + 1):
|
|
p = Point(row=r, col=c)
|
|
if p in status: # <1>
|
|
continue
|
|
stone = board.get(p)
|
|
if stone is not None: # <2>
|
|
status[p] = board.get(p)
|
|
else:
|
|
group, neighbors = _collect_region(p, board)
|
|
if len(neighbors) == 1: # <3>
|
|
neighbor_stone = neighbors.pop()
|
|
stone_str = 'b' if neighbor_stone == Player.black else 'w'
|
|
fill_with = 'territory_' + stone_str
|
|
else:
|
|
fill_with = 'dame' # <4>
|
|
for pos in group:
|
|
status[pos] = fill_with
|
|
return Territory(status)
|
|
|
|
# <1> Skip the point, if you already visited this as part of a different group.
|
|
# <2> If the point is a stone, add it as status.
|
|
# <3> If a point is completely surrounded by black or white stones, count it as territory.
|
|
# <4> Otherwise the point has to be a neutral point, so we add it to dame.
|
|
# end::scoring_evaluate_territory[]
|
|
|
|
|
|
""" _collect_region:
|
|
|
|
Find the contiguous section of a board containing a point. Also
|
|
identify all the boundary points.
|
|
"""
|
|
|
|
|
|
# tag::scoring_collect_region[]
|
|
def _collect_region(start_pos, board, visited=None):
|
|
|
|
if visited is None:
|
|
visited = {}
|
|
if start_pos in visited:
|
|
return [], set()
|
|
all_points = [start_pos]
|
|
all_borders = set()
|
|
visited[start_pos] = True
|
|
here = board.get(start_pos)
|
|
deltas = [(-1, 0), (1, 0), (0, -1), (0, 1)]
|
|
for delta_r, delta_c in deltas:
|
|
next_p = Point(row=start_pos.row + delta_r, col=start_pos.col + delta_c)
|
|
if not board.is_on_grid(next_p):
|
|
continue
|
|
neighbor = board.get(next_p)
|
|
if neighbor == here:
|
|
points, borders = _collect_region(next_p, board, visited)
|
|
all_points += points
|
|
all_borders |= borders
|
|
else:
|
|
all_borders.add(neighbor)
|
|
return all_points, all_borders
|
|
# end::scoring_collect_region[]
|
|
|
|
|
|
# tag::scoring_compute_game_result[]
|
|
def compute_game_result(game_state):
|
|
territory = evaluate_territory(game_state.board)
|
|
return GameResult(
|
|
territory.num_black_territory + territory.num_black_stones,
|
|
territory.num_white_territory + territory.num_white_stones,
|
|
komi=7.5)
|
|
# end::scoring_compute_game_result[]
|