150 lines
5.2 KiB
Python
150 lines
5.2 KiB
Python
from kivy.storage.jsonstore import JsonStore
|
|
|
|
|
|
class MoveTree:
|
|
_move_id_counter = 0 # used to make a map to all moves across all games
|
|
|
|
def __init__(self, board_size):
|
|
self.root = Move(None, (None, None))
|
|
self.current = self.root
|
|
self.board_size = board_size
|
|
self.all_moves = {}
|
|
|
|
def play(self, move):
|
|
move = self.current.play(move)
|
|
if not move.id:
|
|
move.id = MoveTree._move_id_counter
|
|
MoveTree._move_id_counter += 1
|
|
self.all_moves[move.id] = move
|
|
|
|
def undo(self):
|
|
if self.current != self.root:
|
|
self.current = self.current.parent
|
|
|
|
def store_analysis(self,json):
|
|
id = int(json["id"])
|
|
move = self.all_moves.get(id)
|
|
if move: # else this should be old
|
|
move.set
|
|
else:
|
|
print("WARNING: ORPHANED ANALYSIS FOUND - RECENT NEW GAME?")
|
|
|
|
def moves(self): # flat list of moves to current
|
|
moves = []
|
|
p = self.current
|
|
while p != self.root:
|
|
moves.append(p)
|
|
p = p.parent
|
|
return moves[::-1]
|
|
|
|
def __iter__(self):
|
|
return self.moves.__iter__()
|
|
|
|
def __getitem__(self, ix):
|
|
if ix == -1:
|
|
return self.current
|
|
else:
|
|
return self.moves[ix]
|
|
|
|
def sgf(self):
|
|
return "SGF[]"
|
|
|
|
|
|
class Move:
|
|
GTP_COORD = "ABCDEFGHJKLMNOPQRSTUVWYXYZ"
|
|
PLAYERS = "BW"
|
|
SGF_COORD = [chr(i) for i in range(97, 123)]
|
|
|
|
def __init__(self, player, coords=None, gtpcoords=None, sgfcoords=None, robot=False):
|
|
self.id = None
|
|
self.player = player
|
|
self.coords = coords or (gtpcoords and self.gtp2ix(gtpcoords)) or self.sgf2ix(sgfcoords)
|
|
self.children = []
|
|
self.parent = None
|
|
self.robot = robot
|
|
self.analysis = None
|
|
self.outdated_evaluation = None
|
|
self.pass_analysis = None
|
|
self.evaluation = None
|
|
self.ownership = None
|
|
self.points_lost = 0
|
|
self.previous_temperature = None
|
|
self.comment = ""
|
|
|
|
def __repr__(self):
|
|
return f"{Move.PLAYERS[self.player]}{self.gtp()}"
|
|
|
|
def __eq__(self, other):
|
|
return self.coords == other.coords and self.player == other.player
|
|
|
|
def play(self, move: MoveTree):
|
|
try:
|
|
return self.children[self.children.index(move)]
|
|
except ValueError:
|
|
move.parent = self
|
|
self.children.append(move)
|
|
return move
|
|
|
|
def temperature(self):
|
|
if self.analysis:
|
|
best_score = float(self.analysis[0]["scoreMean"])
|
|
worst_score = -float(self.pass_analysis[0]["scoreMean"])
|
|
return best_score - worst_score
|
|
else:
|
|
return 0
|
|
|
|
def evaluate(self,analysis):
|
|
self.analysis = analysis
|
|
previous_move = self.parent
|
|
# TODO: update children?
|
|
best_score = float(previous_move.analysis[0]["scoreMean"])
|
|
worst_score = -float(previous_move.pass_analysis[0]["scoreMean"])
|
|
last_move_score = -float(self.analysis[0]["scoreMean"])
|
|
self.previous_temperature = best_score - worst_score
|
|
self.points_lost = best_score - last_move_score
|
|
prev_analysis_current_move = [d for d in previous_move.analysis if d["move"] == self.gtp()]
|
|
|
|
if abs(self.previous_temperature) > 0.5:
|
|
self.evaluation = (last_move_score - worst_score) / (best_score - worst_score)
|
|
self.move_options = [previous_move.analysis[0]["scoreMean"]]
|
|
else:
|
|
self.evaluation = None
|
|
if self.evaluation:
|
|
self.comment = f"Evaluation: {100*self.evaluation:.1f}%{' (AI Move)' if self.robot else ''}\n"
|
|
if prev_analysis_current_move:
|
|
self.outdated_evaluation = (prev_analysis_current_move[0]["scoreMean"] - worst_score) / (
|
|
best_score - worst_score
|
|
)
|
|
self.comment += f"(Was considered last move as: {100 * self.outdated_evaluation:.1f}%)\n"
|
|
else:
|
|
self.comment = "Temperature too low for evaluation\n"
|
|
self.comment += f"Estimate point loss: {self.points_lost:.1f}\n"
|
|
self.comment += f"Last move score was {last_move_score:.1f}\n"
|
|
self.comment += f"Score of top move was {previous_move.analysis[0]['scoreMean']:.1f} @ {previous_move.analysis[0]['move']}\n"
|
|
self.comment += f"Pass score was {worst_score:.1f}\n"
|
|
|
|
def gtp2ix(self, gtpmove):
|
|
if "pass" in gtpmove:
|
|
return (None, None)
|
|
return Move.GTP_COORD.index(gtpmove[0]), int(gtpmove[1:]) - 1
|
|
|
|
def sgf2ix(self, sgfmove_with_boardsize):
|
|
sgfmove, boardsize = sgfmove_with_boardsize
|
|
if sgfmove == "":
|
|
return (None, None)
|
|
return Move.SGF_COORD.index(sgfmove[0]), boardsize - Move.SGF_COORD.index(sgfmove[1]) - 1
|
|
|
|
def gtp(self):
|
|
if self.coords[0] is None:
|
|
return "pass"
|
|
return Move.GTP_COORD[self.coords[0]] + str(self.coords[1] + 1)
|
|
|
|
def sgfcoords(self, boardsize):
|
|
return f"{Move.SGF_COORD[self.coords[0]]}{Move.SGF_COORD[boardsize - self.coords[1] - 1]}"
|
|
|
|
def sgf(self, boardsize):
|
|
if self.coords[0] is None:
|
|
return f"{Move.PLAYERS[self.player]}[]"
|
|
else:
|
|
return f"{Move.PLAYERS[self.player]}[{self.sgfcoords(boardsize)}]"
|