everything is still broken

This commit is contained in:
Sander Land committed 2020-04-14 21:47:03 +02:00
1 parent b031f1fd82
commit d0f49b5f72
6 files changed
+187 -169

No files matched your search

+76 -6
View File
@@ -1,4 +1,4 @@
import os
import os, random
from datetime import datetime
from game_node import GameNode
@@ -9,6 +9,8 @@ from typing import List
class IllegalMoveException(Exception):
pass
class KaTrainSGF(SGF):
_NODE_CLASS = GameNode
class Game:
GAME_COUNTER = 0
@@ -24,10 +26,10 @@ class Game:
self.game_id = datetime.strftime(datetime.now(), "%Y-%m-%d %H %M %S")
self.visits = [
[analysis_settings["pass_visits"], analysis_settings["visits"], analysis_settings["analyze_all_visits"]],
[analysis_settings["pass_visits_fast"], analysis_settings["visits_fast"], analysis_settings["analyze_all_visits_fast"]],
[analysis_options["pass_visits"], analysis_options["visits"], analysis_options["analyze_all_visits"]],
[analysis_options["pass_visits_fast"], analysis_options["visits_fast"], analysis_options["analyze_all_visits_fast"]],
]
self.train_settings = Config.get("trainer")
#self.train_settings = Config.get("trainer")
if move_tree:
self.root = move_tree
@@ -197,6 +199,74 @@ class Game:
f.write(self.root.sgf())
return f"SGF with analysis written to {file_name}"
def ai_move(self):
ts = self.train_settings
while not self.parent.game.current_node.analysis_ready:
self.info.text = "Thinking..."
self.ai_thinking = True
time.sleep(0.05)
self.ai_thinking = False
# select move
current_move = self.parent.game.current_node
pos_moves = [
(d["move"], float(d["scoreLead"]), d["evaluation"]) for i, d in enumerate(current_move.ai_moves) if i == 0 or int(d["visits"]) >= ts["balance_play_min_visits"]
]
sel_moves = pos_moves[:1]
# don't play suicidal to balance score - pass when it's best
if self.ai_balance.active and pos_moves[0][0] != "pass":
sel_moves = [
(move, score, move_eval)
for move, score, move_eval in pos_moves
if move_eval > ts["balance_play_randomize_eval"]
and -current_move.player_sign * score > 0
or move_eval > ts["balance_play_min_eval"]
and -current_move.player_sign * score > ts["balance_play_target_score"]
] or sel_moves
aimove = Move.from_gtp(random.choice(sel_moves)[0], player=self.parent.game.next_player)
if len(sel_moves) > 1:
aimove.x_comment["ai"] = "AI Balance on, moves considered: " + ", ".join(f"{move} ({aimove.format_score(score)})" for move, score, _ in sel_moves) + "\n"
self.play(aimove)
class KaTrainSGF(SGF):
_MOVE_CLASS = GameNode
def num_undos(self, move):
if self.train_settings["num_undo_prompts"] < 1:
return int(move.undo_threshold < self.train_settings["num_undo_prompts"])
else:
return self.train_settings["num_undo_prompts"]
def analyze_extra(self,mode):
stones = {s.coords for s in self.parent.game.stones}
current_move = self.current_node
if not current_move.analysis:
self.info.text = "Wait for initial analysis to complete before doing a board-sweep or refinement"
return
played_moves = self.parent.game.moves
if mode == "extra":
visits = sum([d["visits"] for d in current_move.analysis]) + self.visits[0][1]
self.info.text = f"Performing additional analysis to {visits} visits"
self._request_analysis(current_move, min_visits=visits, priority=self.game_counter - 1_000)
return
elif mode == "sweep":
analyze_moves = [Move(coords=(x, y)).gtp() for x in range(self.parent.game_size) for y in
range(self.parent.game_size) if (x, y) not in stones]
visits = self.visits[self.ai_fast.active][2]
self.info.text = f"Refining analysis of entire board to {visits} visits"
priority = self.game_counter - 1_000_000_000
else: # mode=='refine':
analyze_moves = [a["move"] for a in current_move.analysis]
visits = current_move.analysis[0]["visits"] + self.visits[1][2]
self.info.text = f"Refining analysis of candidate moves to {visits} visits"
priority = self.game_counter - 1_000
for gtpcoords in analyze_moves:
self._send_analysis_query(
{
"id": f"AA:{current_move.id}:{gtpcoords}",
"moves": [[m.bw_player(), m.gtp()] for m in played_moves] + [
[current_move.bw_player(True), gtpcoords]],
"includeOwnership": False,
"maxVisits": visits,
"priority": priority,
}
)
-151
View File
@@ -12,7 +12,6 @@ from kivy.uix.checkbox import CheckBox
from kivy.uix.filechooser import FileChooserListView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup
@@ -76,153 +75,3 @@ class Controls(GridLayout):
else:
self._do_aimove()
self.redraw(include_board=False)
# engine action functions
def _do_aimove(self):
ts = self.train_settings
while not self.parent.game.current_node.analysis_ready:
self.info.text = "Thinking..."
self.ai_thinking = True
time.sleep(0.05)
self.ai_thinking = False
# select move
current_move = self.parent.game.current_node
pos_moves = [
(d["move"], float(d["scoreLead"]), d["evaluation"]) for i, d in enumerate(current_move.ai_moves) if i == 0 or int(d["visits"]) >= ts["balance_play_min_visits"]
]
sel_moves = pos_moves[:1]
# don't play suicidal to balance score - pass when it's best
if self.ai_balance.active and pos_moves[0][0] != "pass":
sel_moves = [
(move, score, move_eval)
for move, score, move_eval in pos_moves
if move_eval > ts["balance_play_randomize_eval"]
and -current_move.player_sign * score > 0
or move_eval > ts["balance_play_min_eval"]
and -current_move.player_sign * score > ts["balance_play_target_score"]
] or sel_moves
aimove = Move.from_gtp(random.choice(sel_moves)[0], player=self.parent.game.next_player)
if len(sel_moves) > 1:
aimove.x_comment["ai"] = "AI Balance on, moves considered: " + ", ".join(f"{move} ({aimove.format_score(score)})" for move, score, _ in sel_moves) + "\n"
self.play(aimove)
def num_undos(self, move):
if self.train_settings["num_undo_prompts"] < 1:
return int(move.undo_threshold < self.train_settings["num_undo_prompts"])
else:
return self.train_settings["num_undo_prompts"]
def _do_undo(self):
if (
self.ai_lock.active
and self.auto_undo.active(self.parent.game.current_node.player)
and len(self.parent.game.current_node.parent.children) > self.num_undos(self.parent.game.current_node)
and not self.train_settings.get("dont_lock_undos")
):
self.info.text = f"Can't undo this move more than {self.num_undos(self.parent.game.current_node)} time(s) when locked"
return
self.parent.game.undo()
self.update_evaluation()
def _do_redo(self):
self.parent.game.redo()
self.update_evaluation()
def _do_redo_branch(self, direction):
self.parent.game.switch_branch(direction)
self.update_evaluation()
def _do_init(self, board_size=None, komi=None, move_tree=None):
self.game_counter += 1 # prioritize newer games
self.parent.game_size = board_size or 19
self.komi = float(komi or self.config.get("board").get(f"komi_{board_size}", 6.5))
self.parent.game = Game(board_size, move_tree)
self._request_analysis(self.parent.game.root, priority=self.game_counter)
self.redraw(include_board=True)
self.ready = True
if self.ai_lock.active:
self.ai_lock.checkbox._do_press()
for el in [self.ai_lock.checkbox, self.hints.black, self.hints.white, self.ai_auto.black, self.ai_auto.white, self.auto_undo.black, self.auto_undo.white, self.ai_move]:
el.disabled = False
def _do_analyze_extra(self, mode):
stones = {s.coords for s in self.parent.game.stones}
current_move = self.parent.game.current_node
if not current_move.analysis:
self.info.text = "Wait for initial analysis to complete before doing a board-sweep or refinement"
return
played_moves = self.parent.game.moves
if mode == "extra":
visits = sum([d["visits"] for d in current_move.analysis]) + self.visits[0][1]
self.info.text = f"Performing additional analysis to {visits} visits"
self._request_analysis(current_move, min_visits=visits, priority=self.game_counter - 1_000)
return
elif mode == "sweep":
analyze_moves = [SGFNode(coords=(x, y)).gtp() for x in range(self.parent.game_size) for y in range(self.parent.game_size) if (x, y) not in stones]
visits = self.visits[self.ai_fast.active][2]
self.info.text = f"Refining analysis of entire board to {visits} visits"
priority = self.game_counter - 1_000_000_000
else: # mode=='refine':
analyze_moves = [a["move"] for a in current_move.analysis]
visits = current_move.analysis[0]["visits"] + self.visits[1][2]
self.info.text = f"Refining analysis of candidate moves to {visits} visits"
priority = self.game_counter - 1_000
for gtpcoords in analyze_moves:
self._send_analysis_query(
{
"id": f"AA:{current_move.id}:{gtpcoords}",
"moves": [[m.bw_player(), m.gtp()] for m in played_moves] + [[current_move.bw_player(True), gtpcoords]],
"includeOwnership": False,
"maxVisits": visits,
"priority": priority,
}
)
def analyze_movetree(self, root, faster=False):
self._do_init(root["SZ"], root["KM"])
self.parent.game.root = root
handicap = root["HA"]
if handicap is not None and root["AB"] is None:
self.parent.game.place_handicap_stones(handicap)
analysis_priority = self.game_counter - 1_000_000
for move in self.parent.game.root.moves_in_tree:
self._request_analysis(move, faster=faster, priority=analysis_priority) # ensure next move analysis works
def _do_analyze_sgf(self, sgf):
try:
root = KaTrainSGF.parse(sgf)
except:
root = GameNode()
if root.empty:
fileselect_popup = Popup(title="Double Click SGF file to analyze", size_hint=(0.8, 0.8))
fc = FileChooserListView(multiselect=False, path=os.path.expanduser(self.config.get("sgf")["load"]), filters=["*.sgf"])
blui = BoxLayout(orientation="horizontal", size_hint=(1, 0.1))
cbfast = CheckBox(color=(0.95, 0.95, 0.95, 1))
cbrewind = CheckBox(color=(0.95, 0.95, 0.95, 1), active=True)
for widget in [Label(text="Analyze Extra Fast"), cbfast, Label(text="Rewind to start"), cbrewind]:
blui.add_widget(widget)
bl = BoxLayout(orientation="vertical")
bl.add_widget(fc)
bl.add_widget(blui)
fileselect_popup.add_widget(bl)
def readfile(files, _mouse):
fileselect_popup.dismiss()
self.analyze_movetree(KaTrainSGF.parse_file(files[0]))
fc.on_submit = readfile
fileselect_popup.open()
return
else:
self.analyze_movetree(root)
def output_sgf(self):
for pl in Move.PLAYERS:
if self.parent.game.root[f"P{pl}"] not in ["KaTrain","Player",None,""]:
self.parent.game.root[f"P{pl}"] = "KaTrain" if self.ai_auto.active(pl) else "Player"
return self.parent.game.write_sgf(self.komi)
+4
View File
@@ -0,0 +1,4 @@
from kivy.uix.boxlayout import BoxLayout
class LoadSGFPopup(BoxLayout):
pass
+25
View File
@@ -147,6 +147,31 @@
id: value
bold: True
<LoadSGFPopup>:
fast: fast
rewind: rewind
filesel: filesel
orientation: 'vertical'
FileChooserListView:
id: filesel
multiselect: False
filters: ["*.sgf"]
path: os.path.expanduser("~")
BoxLayout:
orientation: 'horizontal'
Label:
text: "Analyze Extra Fast"
Checkbox:
id: fast
color: (0.95, 0.95, 0.95)
Label:
text: "Rewind to Start"
Checkbox:
id: rewind
active: True
color: (0.95, 0.95, 0.95)
<BadukPanWidget>:
size: self.parent.height, self.parent.height
engine: self.parent.controls
+80 -10
View File
@@ -9,6 +9,8 @@ from kivy.clock import Clock
from queue import Queue
from game import Game, IllegalMoveException, KaTrainSGF, Move
from game_node import GameNode
from kivy.uix.popup import Popup
from gui.popups import LoadSGFPopup
OUTPUT_ERROR = -1
OUTPUT_INFO = 0
@@ -39,9 +41,9 @@ class KaTrainGui(BoxLayout):
print(message)
def _load_config(self):
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) # for pyinstaller
config_file = sys.argv[1] if len(sys.argv) > 1 else os.path.join(base_path, "config.json")
try:
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) # for pyinstaller
config_file = sys.argv[1] if len(sys.argv) > 1 else os.path.join(base_path, "config.json")
self.log(f"Using config file {config_file}",OUTPUT_INFO)
self._config_store = JsonStore(config_file)
except FileNotFoundError:
@@ -77,13 +79,48 @@ class KaTrainGui(BoxLayout):
self.log(f"Exception in Engine thread: {e}",OUTPUT_ERROR)
raise
def action(self, message, *args):
def __call__(self, message, *args):
if self.game:
self.message_queue.put([self.game.game_id, message, *args])
def _do_new_game(self,board_size=None):
self.game = Game(self,self.engine,self.config("analysis"),self.config("board"),board_size=board_size)
def _do_aimove(self):
self.game.ai_move()
def _do_undo(self):
if (
self.controls.ai_lock.active
and self.contols.auto_undo.active(self.game.current_node.player)
and len(self.game.current_node.parent.children) > self.num_undos(self.game.current_node)
and not self.train_settings.get("dont_lock_undos")
):
self.info.text = f"Can't undo this move more than {self.num_undos(self.game.current_node)} time(s) when locked"
return
self.game.undo()
self.controls.update_evaluation()
def _do_redo(self):
self.game.redo()
self.update_evaluation()
def _do_switch_branch(self, direction):
self.game.switch_branch(direction)
self.update_evaluation()
def _do_init(self, board_size=None, komi=None, move_tree=None):
self.game_counter += 1 # prioritize newer games
self.game_size = board_size or 19
self.komi = float(komi or self.config.get("board").get(f"komi_{board_size}", 6.5))
self.game = Game(board_size, move_tree)
self._request_analysis(self.game.root, priority=self.game_counter)
self.redraw(include_board=True)
self.ready = True
if self.ai_lock.active:
self.ai_lock.checkbox._do_press()
for el in [self.ai_lock.checkbox, self.hints.black, self.hints.white, self.ai_auto.black, self.ai_auto.white, self.auto_undo.black, self.auto_undo.white, self.ai_move]:
el.disabled = False
def play(self, move: Move, faster=False, analysis_priority=None):
try:
@@ -99,6 +136,39 @@ class KaTrainGui(BoxLayout):
def _do_play(self, *args):
self.play(Move(args[0], player=self.board.next_player))
def _do_analyze_extra(self, mode):
self.game.analyze_extra(mode)
def analyze_movetree(self, root, faster=False):
self._do_init(root["SZ"], root["KM"])
self.parent.game.root = root
handicap = root["HA"]
if handicap is not None and root["AB"] is None:
self.parent.game.place_handicap_stones(handicap)
analysis_priority = self.game_counter - 1_000_000
for move in self.parent.game.root.moves_in_tree:
self._request_analysis(move, faster=faster, priority=analysis_priority) # ensure next move analysis works
def _do_analyze_sgf(self, sgf):
fileselect_popup = Popup(title="Double Click SGF file to analyze", size_hint=(0.8, 0.8))
popup_contents = LoadSGFPopup()
fileselect_popup.add_widget(popup_contents)
def readfile(files, _mouse):
fileselect_popup.dismiss()
self.analyze_movetree(KaTrainSGF.parse_file(files[0]))
popup_contents.filesel.on_submit = readfile
fileselect_popup.open()
def output_sgf(self):
for pl in Move.PLAYERS:
if self.parent.game.root[f"P{pl}"] not in ["KaTrain","Player",None,""]:
self.parent.game.root[f"P{pl}"] = "KaTrain" if self.ai_auto.active(pl) else "Player"
return self.parent.game.write_sgf(self.komi)
def redraw(self, include_board=False):
if include_board:
Clock.schedule_once(self.board.draw_board, -1) # main thread needs to do this
@@ -107,19 +177,19 @@ class KaTrainGui(BoxLayout):
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == "up":
self.controls.action("undo")
self("undo")
elif keycode[1] == "down":
self.controls.action("redo")
self("redo")
elif keycode[1] == "right":
self.controls.action("redo-branch", 1)
self("switch-branch", 1)
elif keycode[1] == "left":
self.controls.action("redo-branch", -1)
self("switch-branch", -1)
elif keycode[1] == "s":
self.controls.action("analyze-extra", "sweep")
self("analyze-extra", "sweep")
elif keycode[1] == "x":
self.controls.action("analyze-extra", "extra")
self("analyze-extra", "extra")
elif keycode[1] == "r":
self.controls.action("analyze-extra", "refine")
self("analyze-extra", "refine")
elif keycode[1] == "a":
if not self.controls.ai_thinking:
self.controls.ai_move.trigger_action(duration=0)
+2 -2
View File
@@ -178,7 +178,7 @@ class SGFNode:
class SGF:
_MOVE_CLASS = SGFNode
_NODE_CLASS = SGFNode
@staticmethod
def parse(input_str) -> SGFNode:
@@ -218,7 +218,7 @@ class SGF:
self._parse_branch(SGFNode(parent=current_move))
elif match[0] == ";":
if not current_move.empty: # ignore ; that generate empty nodes
current_move = self._MOVE_CLASS(parent=current_move)
current_move = self._NODE_CLASS(parent=current_move)
else:
prop, value = match[1], match[2].strip()[1:-1]
current_move[prop] = value