From 144e7b833180fe7c22eab0f49287180b772ed02a Mon Sep 17 00:00:00 2001 From: Sander Land Date: Wed, 19 Feb 2020 19:33:05 +0100 Subject: [PATCH] rewinds to start and has keyboard controls --- README.md | 21 +++++++++++++++++---- board.py | 12 +++++++++++- controller.py | 28 +++++++++++++++++++++++----- katrain.kv | 10 +++++----- katrain.py | 15 ++++++++++++--- 5 files changed, 68 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index bf1a824..e3905fb 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,10 @@ Options This can be < 0% in case of suicidal moves, or >100% when Kata did not consider the move before, or further analysis shows it to be better than the best one considered. * Score: How far one player is ahead. +* Keyboard controls + * Arrow up: undo + * Arrow down: redo + Play ---- @@ -70,10 +74,10 @@ Play * Play with a friend with instant feedback and/or undos for both, or see how many stones stronger you are with one undo. (But please play unranked and be honest to your opponent on what you're doing) * Analysis - * Copy the SGF into the text box. Note that branches are not supported and will lead to strange results. - * Choose whether or not to turn on `fast` to make the AI weaker but analyze faster. - * Click `Load` - * Alternatively click `Load` when the text box is empty to get a file chooser dialog. + * Click 'Load' when the text box is empty-ish to get a file chooser dialog. Note that branches are not supported and will lead to strange results. + * Select if you want fast analysis or rewinding to the start for reviewing. Note that the 'fast' checkbox still affects speed as well, + this is just an additional lowering of visits. + * Alternatively copy the SGF into the text box and click 'Load'. * Save game * Click save to get an sgf with comments saved in the sgfout/ directory (and a short version in the text box). @@ -94,6 +98,15 @@ The `trainer` block has the following options to tweak: The cfg file has additional configuration for kata. In particular, it changes the default to being more exploratory and score-based (and therefore nicer as an opponent, but weaker as analysis tool). +FAQ +=== +* The program is slow to start! + * The first startup of KataGo can be slow, after that it should be much faster. +* The program is running too slowly! + * Lower the visits count in `config.json` by half or so and try again. +* Can I play on sizes other than 9, 13 or 19? + * Type in `SZ[n]HA[h]` in the text box and hit 'load' for a game on a n by n board with h handicap stones, but note that the default KataGo does not support sizes above 19x19. + Contributing ============ * Feedback and pull requests are both very welcome. diff --git a/board.py b/board.py index 4ebf691..624bc72 100644 --- a/board.py +++ b/board.py @@ -74,7 +74,9 @@ class Move: return "" if eval and not sgf and self.children: # show undos and on previous move as well while playing - text = "".join(f"Auto undid move {m.gtp()} ({-self.temperature_stats[2] * (1-m.evaluation):.1f} pt)\n" for m in self.children if m.auto_undid) + "\n" + text = "".join(f"Auto undid move {m.gtp()} ({-self.temperature_stats[2] * (1-m.evaluation):.1f} pt)\n" for m in self.children if m.auto_undid) + if text: + text += "\n" else: text = "" @@ -270,6 +272,14 @@ class Board: self.current_move = self.current_move.parent self._init_chains() + def redo(self): + if self.current_move.children: + self.play(self.current_move.children[-1]) + + def rewind(self): + self.current_move = self.root + self._init_chains() + def place_handicap_stones(self, n_handicaps): near = 3 if self.board_size >= 13 else 2 far = self.board_size - 1 - near diff --git a/controller.py b/controller.py index d131d04..c3129d4 100644 --- a/controller.py +++ b/controller.py @@ -12,8 +12,11 @@ from queue import Queue from kivy.clock import Clock from kivy.storage.jsonstore import JsonStore +from kivy.uix.boxlayout import BoxLayout +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 from board import Board, IllegalMoveException, Move @@ -169,6 +172,11 @@ class EngineControls(GridLayout): self.info.text = f"Can't undo more than {self.train_settings['num_undo_prompts']} time(s) when locked" return self.board.undo() + self.update_evaluation() + + def _do_redo(self): + self.board.redo() + self.update_evaluation() def _do_init(self, board_size, komi=None): self.board_size = board_size @@ -182,21 +190,29 @@ class EngineControls(GridLayout): 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_sgf(self, sgf): + def _do_analyze_sgf(self, sgf, faster=False, rewind=False): sgfprops = {k: v for k, v in re.findall(r"\b(\w+)\[(.*?)\]", sgf)} size = int(sgfprops.get("SZ", self.board_size)) sgfmoves = re.findall(r"\b([BW])\[([a-z]{2})\]", sgf) if not sgfmoves and not sgfprops: fileselect_popup = Popup(title="Double Click SGF file to analyze", size_hint=(0.8, 0.8)) + fc = FileChooserListView(multiselect=False, path=os.path.expanduser("~"), 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)) + 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() with open(files[0]) as f: - self.action("analyze-sgf", f.read()) + self.action("analyze-sgf", f.read(), cbfast.active, cbrewind.active) - fc = FileChooserListView(multiselect=False, path=os.path.expanduser("~"), filters=["*.sgf"]) fc.on_submit = readfile - fileselect_popup.add_widget(fc) fileselect_popup.open() return self._do_init(size, sgfprops.get("KM")) @@ -206,7 +222,9 @@ class EngineControls(GridLayout): if handicap > 0: self._request_analysis(self.board.current_move) for move in moves: - self.play(move, faster=(self.ai_fast.active and move != moves[-1])) + self.play(move, faster=faster and move != moves[-1]) + if rewind: + self.board.rewind() # analysis thread def _analysis_read_thread(self): diff --git a/katrain.kv b/katrain.kv index 35cc4d3..8134863 100644 --- a/katrain.kv +++ b/katrain.kv @@ -40,13 +40,14 @@ checkbox: checkbox text: '' default_active: False + position: (0,0) CheckBoxHintLabel: - size_hint: 1,0.45 - font_size: root.height * 0.2 + size_hint: 1,0.45 if root.orientation=='vertical' else (0.8,1) + font_size: root.height * 0.2 if root.orientation=='vertical' else root.height text: root.text on_press: if not checkbox.disabled: checkbox._do_press() CheckBox: - size_hint: 1,0.55 + size_hint: 1,0.55 if root.orientation=='vertical' else (0.2,1) id: checkbox on_active: root.dispatch('on_active') active: root.default_active @@ -224,7 +225,7 @@ id: ai_balance default_active: False CheckBoxHint: - size_hint: 0.2, 0.5 + size_hint: 0.167, 0.5 text: 'lock\nai' id: ai_lock on_active: self.checkbox.disabled = hints.black.disabled = hints.white.disabled = ai_auto.black.disabled = ai_auto.white.disabled = auto_undo.black.disabled = auto_undo.white.disabled = ai_move.disabled = True @@ -269,7 +270,6 @@ StyledButton: text: 'Load' size_hint: 0.5, 1 - margin: (0,1) on_press: root.action("analyze-sgf",info.text) GridLayout: size_hint: 1, 0.05 diff --git a/katrain.py b/katrain.py index f86c39c..35aa8ac 100644 --- a/katrain.py +++ b/katrain.py @@ -2,9 +2,8 @@ import math import signal from kivy.app import App +from kivy.core.window import Window from kivy.graphics import * -from kivy.properties import NumericProperty, ObjectProperty -from kivy.uix.floatlayout import FloatLayout from kivy.uix.widget import Widget from board import Move @@ -186,7 +185,17 @@ class BadukPanWidget(Widget): class KaTrainGui(BoxLayout): - pass + def __init__(self, **kwargs): + super(KaTrainGui, self).__init__(**kwargs) + self._keyboard = Window.request_keyboard(None, self, "") + self._keyboard.bind(on_key_down=self._on_keyboard_down) + + def _on_keyboard_down(self, keyboard, keycode, text, modifiers): + if keycode[1] == "up": + self.controls.action("undo") + elif keycode[1] == "down": + self.controls.action("redo") + return True class KaTrainApp(App):