everything is still broken
This commit is contained in:
1 parent
b031f1fd82
commit
d0f49b5f72
6 files changed
+187
-169
No files matched your search
@@ -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
@@ -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)
|
||||
@@ -0,0 +1,4 @@
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
class LoadSGFPopup(BoxLayout):
|
||||
pass
|
||||
+25
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user