detailed comment tab, save comment on note, styling

This commit is contained in:
Sander Land committed 2020-06-05 20:15:29 +02:00
1 parent 94439bddec
commit 3570bdf8b2
18 files changed
+76 -40

No files matched your search

+1
View File
@@ -5,6 +5,7 @@ Many thanks to these additional authors:
Matthew Allred ("Kameone") for design of the v1.1 UI and providing MacOS installation instructions. Matthew Allred ("Kameone") for design of the v1.1 UI and providing MacOS installation instructions.
"isty2e" for Korean translation. "isty2e" for Korean translation.
"Dontbtme" for detailed feedback and early testing of v1.0, and French translation. "Dontbtme" for detailed feedback and early testing of v1.0, and French translation.
"bale-go" for implementing and calibrating the 'calibrated rank' AI.
Additional thanks to: Additional thanks to:
"Seventeen" for explanatory youtube videos. "Seventeen" for explanatory youtube videos.
+2 -1
View File
@@ -2,11 +2,12 @@
# Installation for MacOS users # Installation for MacOS users
## <a name="MacPrereq"></a>Installation pre-requisites ## <a name="MacPrereq"></a>Installation pre-requisites
* Download and install [Python 3.7.5](https://www.python.org/downloads/release/python-375/)
* Install [Homebrew](https://brew.sh) by running the following command in terminal: * Install [Homebrew](https://brew.sh) by running the following command in terminal:
* ``` * ```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
``` ```
* Install Python using [Homebrew](https://brew.sh/) by executing `brew install python3`
* Alternatively download and install [Python 3.7.5](https://www.python.org/downloads/release/python-375/)
* Install Katago using [Homebrew](https://brew.sh/) by executing `brew install katago` * Install Katago using [Homebrew](https://brew.sh/) by executing `brew install katago`
* You can also follow instructions [here](https://github.com/lightvector/KataGo) to compile KataGo yourself. * You can also follow instructions [here](https://github.com/lightvector/KataGo) to compile KataGo yourself.
+1 -1
View File
@@ -1,5 +1,5 @@
# KaTrain v1.1 # <a name="manual"></a> KaTrain v1.1
[![Latest Release](https://img.shields.io/github/release/sanderland/katrain?label=download)](https://github.com/sanderland/katrain/releases) [![Latest Release](https://img.shields.io/github/release/sanderland/katrain?label=download)](https://github.com/sanderland/katrain/releases)
[![License:MIT](https://img.shields.io/pypi/l/katrain)](https://en.wikipedia.org/wiki/MIT_License) [![License:MIT](https://img.shields.io/pypi/l/katrain)](https://en.wikipedia.org/wiki/MIT_License)
![Build Status](https://github.com/sanderland/katrain/workflows/release/badge.svg) ![Build Status](https://github.com/sanderland/katrain/workflows/release/badge.svg)
+14 -7
View File
@@ -64,7 +64,7 @@ from katrain.core.game import Game, IllegalMoveException, KaTrainSGF
from katrain.core.sgf_parser import Move, ParseError from katrain.core.sgf_parser import Move, ParseError
from katrain.gui.kivyutils import * from katrain.gui.kivyutils import *
from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup, AIPopup from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup, AIPopup
from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL, LIGHTGREY
from katrain.gui.widgets.graph import ScoreGraph from katrain.gui.widgets.graph import ScoreGraph
from katrain.gui.widgets.filebrowser import I18NFileBrowser from katrain.gui.widgets.filebrowser import I18NFileBrowser
from katrain.gui.badukpan import AnalysisControls, BadukPanControls, BadukPanWidget from katrain.gui.badukpan import AnalysisControls, BadukPanControls, BadukPanWidget
@@ -431,21 +431,28 @@ class KaTrainGui(Screen, KaTrainBase):
class KaTrainApp(MDApp): class KaTrainApp(MDApp):
gui = ObjectProperty(None) gui = ObjectProperty(None)
language = StringProperty(DEFAULT_LANGUAGE) language = StringProperty(DEFAULT_LANGUAGE)
def __init__(self):
super().__init__()
# self.theme_cls.selected_color = WHITE
# self.theme_cls.secondary_text_color = LIGHTGREY
def build(self): def build(self):
self.icon = ICON # how you're supposed to set an icon self.icon = ICON # how you're supposed to set an icon
self.title = f"KaTrain v{VERSION}"
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Gray"
self.theme_cls.primary_hue = "200"
kv_file = find_package_resource("katrain/gui.kv") kv_file = find_package_resource("katrain/gui.kv")
popup_kv_file = find_package_resource("katrain/popups.kv") popup_kv_file = find_package_resource("katrain/popups.kv")
resource_add_path(os.path.split(kv_file)[0]) resource_add_path(os.path.split(kv_file)[0])
Builder.load_file(kv_file) Builder.load_file(kv_file)
Builder.load_file(popup_kv_file) Builder.load_file(popup_kv_file)
self.gui = KaTrainGui()
self.title = f"KaTrain v{VERSION}"
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Blue"
Window.bind(on_request_close=self.on_request_close) Window.bind(on_request_close=self.on_request_close)
self.gui = KaTrainGui()
return self.gui return self.gui
def on_language(self, _instance, language): def on_language(self, _instance, language):
@@ -459,7 +466,7 @@ class KaTrainApp(MDApp):
self.gui.controls.set_status("") self.gui.controls.set_status("")
def webbrowser(self, site_key): def webbrowser(self, site_key):
WEBSITES = {"homepage": HOMEPAGE, "support": HOMEPAGE + "#support"} WEBSITES = {"homepage": HOMEPAGE + "#manual", "support": HOMEPAGE + "#support"}
if site_key in WEBSITES: if site_key in WEBSITES:
webbrowser.open(WEBSITES[site_key]) webbrowser.open(WEBSITES[site_key])
+9 -8
View File
@@ -29,6 +29,7 @@ class GameNode(SGFNode):
def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None): def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None):
properties = copy.copy(super().sgf_properties()) properties = copy.copy(super().sgf_properties())
note = self.note.strip()
if self.points_lost and save_comments_class is not None and eval_thresholds is not None: if self.points_lost and save_comments_class is not None and eval_thresholds is not None:
show_class = save_comments_class[evaluation_class(self.points_lost, eval_thresholds)] show_class = save_comments_class[evaluation_class(self.points_lost, eval_thresholds)]
else: else:
@@ -39,7 +40,7 @@ class GameNode(SGFNode):
and self.analysis_ready and self.analysis_ready
and self.parent and self.parent
and self.parent.analysis_ready and self.parent.analysis_ready
): ) or note:
candidate_moves = self.parent.candidate_moves candidate_moves = self.parent.candidate_moves
top_x = Move.from_gtp(candidate_moves[0]["move"]).sgf(self.board_size) top_x = Move.from_gtp(candidate_moves[0]["move"]).sgf(self.board_size)
best_sq = [ best_sq = [
@@ -59,7 +60,7 @@ class GameNode(SGFNode):
+ "\n".join(properties.get("C", "")) + "\n".join(properties.get("C", ""))
+ "\nSGF with review generated by KaTrain." + "\nSGF with review generated by KaTrain."
] ]
if self.note.strip(): if note:
properties["C"] = ["\n".join(properties.get("C", "")) + f"\nNote: {self.note}"] properties["C"] = ["\n".join(properties.get("C", "")) + f"\nNote: {self.note}"]
return properties return properties
@@ -154,7 +155,7 @@ class GameNode(SGFNode):
pvtext = f"[u][ref={pvtext}][color={INFO_PV_COLOR}]{pvtext}[/color][/ref][/u]" pvtext = f"[u][ref={pvtext}][color={INFO_PV_COLOR}]{pvtext}[/color][/ref][/u]"
return pvtext return pvtext
def comment(self, sgf=False, teach=False, hints=False, interactive=True): def comment(self, sgf=False, teach=False, details=False, interactive=True):
single_move = self.move single_move = self.move
if not self.parent or not single_move: # root if not self.parent or not single_move: # root
return "" return ""
@@ -167,7 +168,7 @@ class GameNode(SGFNode):
text += i18n._("Info:winrate").format(winrate=self.format_winrate()) + "\n" text += i18n._("Info:winrate").format(winrate=self.format_winrate()) + "\n"
if self.parent and self.parent.analysis_ready: if self.parent and self.parent.analysis_ready:
previous_top_move = self.parent.candidate_moves[0] previous_top_move = self.parent.candidate_moves[0]
if sgf or hints: if sgf or details:
if previous_top_move["move"] != single_move.gtp(): if previous_top_move["move"] != single_move.gtp():
points_lost = self.points_lost points_lost = self.points_lost
if sgf and points_lost > 0.5: if sgf and points_lost > 0.5:
@@ -181,7 +182,7 @@ class GameNode(SGFNode):
) )
else: else:
text += i18n._("Info:best move") + "\n" text += i18n._("Info:best move") + "\n"
if previous_top_move.get("pv") and (sgf or hints): if previous_top_move.get("pv") and (sgf or details):
text += ( text += (
i18n._("Info:PV").format( i18n._("Info:PV").format(
pv=self.make_pv(single_move.player, previous_top_move["pv"], interactive) pv=self.make_pv(single_move.player, previous_top_move["pv"], interactive)
@@ -189,7 +190,7 @@ class GameNode(SGFNode):
+ "\n" + "\n"
) )
if sgf or hints or teach: if sgf or details or teach:
policy_ranking = self.parent.policy_ranking policy_ranking = self.parent.policy_ranking
currmove_policy_with_ix = [ currmove_policy_with_ix = [
(ix + 1, p) (ix + 1, p)
@@ -203,7 +204,7 @@ class GameNode(SGFNode):
) )
+ "\n" + "\n"
) )
if not currmove_policy_with_ix or currmove_policy_with_ix[0] != 1 and (sgf or hints): if not currmove_policy_with_ix or currmove_policy_with_ix[0] != 1 and (sgf or details):
text += ( text += (
i18n._("Info:policy best").format( i18n._("Info:policy best").format(
move=policy_ranking[0][1].gtp(), probability=policy_ranking[0][0] move=policy_ranking[0][1].gtp(), probability=policy_ranking[0][0]
@@ -215,7 +216,7 @@ class GameNode(SGFNode):
top_pv = self.analysis_ready and self.candidate_moves[0].get("pv") top_pv = self.analysis_ready and self.candidate_moves[0].get("pv")
if top_pv: if top_pv:
text += i18n._("Info:undo predicted PV").format(pv=f"{self.next_player}{' '.join(top_pv)}") + "\n" text += i18n._("Info:undo predicted PV").format(pv=f"{self.next_player}{' '.join(top_pv)}") + "\n"
if self.ai_thoughts and (sgf or hints): if self.ai_thoughts and (sgf or details):
text += "\n" + i18n._("Info:AI thoughts").format(thoughts=self.ai_thoughts) text += "\n" + i18n._("Info:AI thoughts").format(thoughts=self.ai_thoughts)
else: else:
text = i18n._("No analysis available") if sgf else i18n._("Analyzing move...") text = i18n._("No analysis available") if sgf else i18n._("Analyzing move...")
+16 -9
View File
@@ -12,6 +12,7 @@
#:import RED katrain.gui.style.RED #:import RED katrain.gui.style.RED
#:import WHITE katrain.gui.style.WHITE #:import WHITE katrain.gui.style.WHITE
#:import LIGHTGREY katrain.gui.style.LIGHTGREY
#:import BACKGROUND_COLOR katrain.gui.style.BACKGROUND_COLOR #:import BACKGROUND_COLOR katrain.gui.style.BACKGROUND_COLOR
#:import BOX_BACKGROUND_COLOR katrain.gui.style.BOX_BACKGROUND_COLOR #:import BOX_BACKGROUND_COLOR katrain.gui.style.BOX_BACKGROUND_COLOR
#:import TEXT_COLOR katrain.gui.style.TEXT_COLOR #:import TEXT_COLOR katrain.gui.style.TEXT_COLOR
@@ -19,14 +20,13 @@
#:import BOX_BACKGROUND_COLOR katrain.gui.style.BOX_BACKGROUND_COLOR #:import BOX_BACKGROUND_COLOR katrain.gui.style.BOX_BACKGROUND_COLOR
#:set SPINNER_OPTION_COLOR [64 / 255, 85 / 255, 110 / 255, 1] #:set SPINNER_OPTION_COLOR [64 / 255, 85 / 255, 110 / 255, 1]
#:set LIGHTGREY [0.7,0.7,0.7,1]
#:set BLACK [0.05,0.05,0.05,1] #:set BLACK [0.05,0.05,0.05,1]
#:set GREY [0.5,0.5,0.5,1] #:set GREY [0.5,0.5,0.5,1]
#:set GREEN [0.1,0.8,0.1,1] #:set GREEN [0.1,0.8,0.1,1]
#:set LIGHTBLUE [0.3,0.7,0.9,1] #:set LIGHTBLUE [0.3,0.7,0.9,1]
#:set YELLOW [0.8,0.8,0.1,1] #:set YELLOW [0.8,0.8,0.1,1]
#:set DARKRED [0.3,0.1,0.1,1] #:set DARKRED [0.3,0.1,0.1,1]
#:set ORANGE [242/255,96/255,34/255,1]
#:set SCORE_COLOR LIGHTBLUE #:set SCORE_COLOR LIGHTBLUE
#:set POINTLOSS_COLOR YELLOW #:set POINTLOSS_COLOR YELLOW
@@ -65,6 +65,7 @@
# new widgets # new widgets
<SizedButton>: <SizedButton>:
ripple_duration_in_slow: 0.6 ripple_duration_in_slow: 0.6
text_color: TEXT_COLOR text_color: TEXT_COLOR
@@ -301,7 +302,7 @@
min_size: min(self.height,self.width) min_size: min(self.height,self.width)
inactive_line_color: WHITE inactive_line_color: WHITE
inactive_fill_color: [1,1,1,0] inactive_fill_color: [1,1,1,0]
active_line_color: [242/255,96/255,34/255,1] active_line_color: ORANGE
active_fill_color: [1,1,1,0] active_fill_color: [1,1,1,0]
line_width: max(1.1,self.min_size/20) line_width: max(1.1,self.min_size/20)
line_color: root.active_line_color if self.active else root.inactive_line_color line_color: root.active_line_color if self.active else root.inactive_line_color
@@ -332,7 +333,8 @@
<TimerLabel@Label> <TimerLabel@Label>
color: GREEN timeout: False
color: ORANGE if self.timeout else GREEN
markup: True markup: True
multiline: False multiline: False
font_name: 'fonts/digital-7 (mono).ttf' font_name: 'fonts/digital-7 (mono).ttf'
@@ -341,6 +343,7 @@
spacing: CP_SPACING spacing: CP_SPACING
padding: CP_PADDING * 4, CP_PADDING * 2 padding: CP_PADDING * 4, CP_PADDING * 2
paused: True paused: True
timeout: self.state[1] <= 0
MDBoxLayout: MDBoxLayout:
size_hint: 0.7,1 size_hint: 0.7,1
MDFloatLayout: MDFloatLayout:
@@ -355,6 +358,7 @@
valign: 'middle' valign: 'middle'
TimerLabel: TimerLabel:
id: timer id: timer
timeout: root.timeout
text: "{:01d}:{:02d}".format(int(root.state[0]+0.99)//60,int((root.state[0]+0.99) % 60) ) text: "{:01d}:{:02d}".format(int(root.state[0]+0.99)//60,int((root.state[0]+0.99) % 60) )
text_size: self.size text_size: self.size
font_size: 0.85*self.height font_size: 0.85*self.height
@@ -368,6 +372,7 @@
text_size: self.width, self.height * 0.95 text_size: self.width, self.height * 0.95
halign: 'right' halign: 'right'
valign: 'top' valign: 'top'
timeout: root.timeout
TimerLabel: TimerLabel:
size_hint: 0.1,1 size_hint: 0.1,1
font_size: self.height * 0.35 font_size: self.height * 0.35
@@ -377,6 +382,7 @@
id: periods id: periods
text: str(root.state[1]) text: str(root.state[1])
multiline: False multiline: False
timeout: root.timeout
MDBoxLayout: MDBoxLayout:
size_hint: 0.2,1 size_hint: 0.2,1
PauseButton: PauseButton:
@@ -631,14 +637,16 @@
CollapsablePanel: CollapsablePanel:
size_hint_y_open: 1 size_hint_y_open: 1
closed_label: 'closedlabel:info&notes' closed_label: 'closedlabel:info&notes'
options: ['info','notes'] options: ['info','info-details','notes']
option_colors: [WHITE,WHITE] option_colors: [WHITE,WHITE,YELLOW]
option_active: [True,False] option_active: [True,False,False]
on_option_state: on_option_state:
info.size_hint_y = int(args[1]['info'])+1e-9 info.size_hint_y = int(args[1]['info'])+1e-9
info.opacity = int(args[1]['info']) info.opacity = int(args[1]['info'])
info.detailed = args[1]['info-details']
notebox.size_hint_y = int(args[1]['notes'])*0.66+1e-9 notebox.size_hint_y = int(args[1]['notes'])*0.66+1e-9
notebox.opacity = int(args[1]['notes']) notebox.opacity = int(args[1]['notes'])
app.gui.update_state()
options_height: root.tab_option_height options_height: root.tab_option_height
BGBoxLayout: BGBoxLayout:
background_color: BOX_BACKGROUND_COLOR background_color: BOX_BACKGROUND_COLOR
@@ -651,6 +659,7 @@
background_color: DARKRED background_color: DARKRED
ScrollableLabel: ScrollableLabel:
id: info id: info
detailed: False
line_height: 1.25 line_height: 1.25
markup: True markup: True
on_ref_press: root.katrain.board_gui.show_pv_from_comments(args[1]) on_ref_press: root.katrain.board_gui.show_pv_from_comments(args[1])
@@ -700,8 +709,6 @@
pos_hint: {'center_y': 0.5} pos_hint: {'center_y': 0.5}
width: self.height width: self.height
active: root.default_active active: root.default_active
selected_color: WHITE
unselected_color: LIGHTGREY
on_active: if app.gui: app.gui.update_state() on_active: if app.gui: app.gui.update_state()
<AnalysisDropdownMenu>: <AnalysisDropdownMenu>:
+2 -2
View File
@@ -84,11 +84,11 @@ class ControlsPanel(BoxLayout):
self.active_comment_node = current_node.parent self.active_comment_node = current_node.parent
lock_ai = katrain.config("trainer/lock_ai") and katrain.play_analyze_mode == MODE_PLAY lock_ai = katrain.config("trainer/lock_ai") and katrain.play_analyze_mode == MODE_PLAY
hints = katrain.analysis_controls.hints.active and not lock_ai details = self.info.detailed and not lock_ai
info = "" info = ""
if current_node.move and not current_node.is_root: if current_node.move and not current_node.is_root:
info = self.active_comment_node.comment( info = self.active_comment_node.comment(
teach=katrain.players_info[self.active_comment_node.player].being_taught, hints=hints teach=katrain.players_info[self.active_comment_node.player].being_taught, details=details
) )
if self.active_comment_node.analysis_ready: if self.active_comment_node.analysis_ready:
+1 -1
View File
@@ -302,7 +302,7 @@ class PlayerInfo(MDBoxLayout, BackgroundMixin):
class Timer(BGBoxLayout): class Timer(BGBoxLayout):
state = ListProperty([30, 5, 1]) state = ListProperty([30, 5, 1])
timeout = BooleanProperty(False)
class AnalysisToggle(MDBoxLayout): class AnalysisToggle(MDBoxLayout):
text = StringProperty("") text = StringProperty("")
+1
View File
@@ -7,6 +7,7 @@ DEFAULT_FONT = "fonts/NotoSans-Regular.ttf"
# basic color definitions # basic color definitions
RED = [0.8, 0.1, 0.1, 1] RED = [0.8, 0.1, 0.1, 1]
WHITE = [0.95, 0.95, 0.95, 1] WHITE = [0.95, 0.95, 0.95, 1]
LIGHTGREY = [0.7,0.7,0.7,1]
# gui colors # gui colors
Binary file not shown.
@@ -133,6 +133,9 @@ msgstr "Point Loss"
msgid "tab:info" msgid "tab:info"
msgstr "Info" msgstr "Info"
msgid "tab:info-details"
msgstr "Details"
msgid "tab:notes" msgid "tab:notes"
msgstr "Notes" msgstr "Notes"
Binary file not shown.
@@ -608,3 +608,7 @@ msgstr ""
#. TODO #. TODO
msgid "ai:p:rank" msgid "ai:p:rank"
msgstr "Calibrated Rank Bot" msgstr "Calibrated Rank Bot"
#. TODO
msgid "tab:info-details"
msgstr "+Analysis"
Binary file not shown.
@@ -538,3 +538,6 @@ msgstr ""
msgid "ai:p:rank" msgid "ai:p:rank"
msgstr "ㅋㅋCalibrated Rank Bot" msgstr "ㅋㅋCalibrated Rank Bot"
msgid "tab:info-details"
msgstr "ㅋㅋ+Analysis"
Binary file not shown.
@@ -549,3 +549,7 @@ msgstr ""
#. TODO #. TODO
msgid "ai:p:rank" msgid "ai:p:rank"
msgstr "Calibrated Rank Bot" msgstr "Calibrated Rank Bot"
#. TODO
msgid "tab:info-details"
msgstr "+Analysis"
+15 -11
View File
@@ -28,10 +28,12 @@
helper_text_mode: "none" helper_text_mode: "none"
hint_text: "" hint_text: ""
helper_text: "" helper_text: ""
color_mode: 'custom'
line_color_focus: TEXT_COLOR
<LabelledPathInput>: <LabelledPathInput>:
font_size: dp(16) font_size: dp(16)
multiline: True # just for wrapping multiline: False
helper_text_mode: "on_error" helper_text_mode: "on_error"
helper_text: i18n._("file not found") helper_text: i18n._("file not found")
@@ -47,6 +49,7 @@
size: DESC_FONT_SIZE*2,DESC_FONT_SIZE*2 size: DESC_FONT_SIZE*2,DESC_FONT_SIZE*2
size_hint: None,None size_hint: None,None
<QuickInputButton@SizedRectangleButton> <QuickInputButton@SizedRectangleButton>
on_left_press: if self.target: self.target.text = self.text on_left_press: if self.target: self.target.text = self.text
size: DESC_FONT_SIZE * 2, DESC_FONT_SIZE * 2 size: DESC_FONT_SIZE * 2, DESC_FONT_SIZE * 2
@@ -78,17 +81,22 @@
<ConfigPopup>: <ConfigPopup>:
configfile: configfile configfile: configfile
BoxLayout:
size_hint: 1,1
orientation: 'horizontal'
DescriptionLabel:
text: i18n._('general settings')
font_size: DESC_FONT_SIZE * 1.5
DescriptionLabel:
font_size: DESC_FONT_SIZE * 1.5
text: i18n._('engine settings')
BoxLayout: BoxLayout:
size_hint: 1,9 size_hint: 1,9
orientation: 'horizontal' orientation: 'horizontal'
GridLayout: GridLayout:
cols: 2 cols: 2
rows: 8 rows: 7
spacing: CP_SPACING spacing: CP_SPACING
Label:
DescriptionLabel:
text: i18n._('general settings')
font_size: DESC_FONT_SIZE * 1.5
DescriptionLabel: DescriptionLabel:
text: i18n._("general:sgf_load") text: i18n._("general:sgf_load")
AnchorLayout: AnchorLayout:
@@ -126,12 +134,8 @@
width: 2 width: 2
GridLayout: GridLayout:
cols: 2 cols: 2
rows: 8 rows: 7
spacing: CP_SPACING spacing: CP_SPACING
Label:
DescriptionLabel:
font_size: DESC_FONT_SIZE * 1.5
text: i18n._('engine settings')
DescriptionLabel: DescriptionLabel:
text: i18n._("engine:katago") text: i18n._("engine:katago")
AnchorLayout: AnchorLayout: