40 files changed
+425
-103
No files matched your search
+5
-2
@@ -1,7 +1,8 @@
|
||||
# Contributing
|
||||
|
||||
If you are a new contributor, please first discuss the change you wish to make via
|
||||
an issue, reddit or discord before making a change.
|
||||
If you are a new contributor wanting to make a larger contribution,
|
||||
please first discuss the change you wish to make via
|
||||
an issue, reddit or discord before making a pull request.
|
||||
|
||||
## Python contributions
|
||||
|
||||
@@ -49,6 +50,8 @@ Many thanks to these additional authors:
|
||||
* "nimets123" for the timer sound effects and board/stone graphics.
|
||||
* Jordan Seaward for the stone sound effects.
|
||||
* "fohristiwhirl" for the Gibo and NGF formats parsing code.
|
||||
* "kaorahi" for bug fixes and SGF parser improvements.
|
||||
* "ajkenny84" for the red-green colourblind theme.
|
||||
|
||||
## Translators
|
||||
|
||||
|
||||
@@ -7,18 +7,16 @@ For on related licenses for these binaries and libraries see https://github.com/
|
||||
- Equalize icon and Thrash Icon: derived from work by bqlqn from www.flaticon.com
|
||||
- Other Menu icons and Flag icons: derived from work by Freepik from www.flaticon.com
|
||||
|
||||
|
||||
3. The True Type Font DIGITAL-7 version 1.02 by Sizenko Alexander, which is free for non-commercial use.
|
||||
|
||||
4. The Noto Sans fonts from google which are covered by the SIL open font license v1.1 included in the katrain/fonts directory.
|
||||
|
||||
|
||||
-----------------------------------------------------------------------------------------
|
||||
Aside from the above, the license for all other content in this repository is as follows:
|
||||
-----------------------------------------------------------------------------------------
|
||||
|
||||
Copyright 2020 Sander Land and/or other authors of the content in this repository.
|
||||
(See 'CONTRIBUTORS' file for a list of authors as well as other indirect contributors).
|
||||
(See 'CONTRIBUTIONS.md' file for a list of authors as well as other indirect contributors).
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[](https://en.wikipedia.org/wiki/MIT_License)
|
||||
[](https://github.com/sanderland/katrain/releases)
|
||||
[](https://pepy.tech/project/katrain)
|
||||
[](https://github.com/sponsors/sanderland)
|
||||
[](https://github.com/sponsors/sanderland)
|
||||
[](https://discord.com/channels/417022162348802048/629446365688365067)
|
||||
|
||||
<table>
|
||||
@@ -191,7 +191,7 @@ In addition to shortcuts mentioned above and those shown in the main menu:
|
||||
[](https://github.com/sanderland/katrain/issues)
|
||||
[](CONTRIBUTIONS.md)
|
||||
[](https://liberapay.com/sanderbaduk/)
|
||||
[](https://github.com/sponsors/sanderland)
|
||||
[](https://github.com/sponsors/sanderland)
|
||||
|
||||
* Ideas, feedback, and contributions to code or translations are all very welcome.
|
||||
* For suggestions and planned improvements, see [open issues](https://github.com/sanderland/katrain/issues) on github to check if the functionality is already planned.
|
||||
|
||||
+7
-7
@@ -4,7 +4,7 @@ import os
|
||||
|
||||
os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning")
|
||||
if "KIVY_AUDIO" not in os.environ:
|
||||
os.environ["KIVY_AUDIO"] = "sdl2" # seems to be most stable / some players hard crash
|
||||
os.environ["KIVY_AUDIO"] = "sdl2" # some backends hard crash / this seems to be most stable
|
||||
|
||||
# next, icon
|
||||
from katrain.core.utils import find_package_resource, PATHS
|
||||
@@ -365,15 +365,15 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
).__self__
|
||||
|
||||
def readfile(*_args):
|
||||
files = popup_contents.filesel.selection
|
||||
filename = popup_contents.filesel.filename
|
||||
self.fileselect_popup.dismiss()
|
||||
path, file = os.path.split(files[0])
|
||||
path, file = os.path.split(filename)
|
||||
settings_path = self.config("general/sgf_load")
|
||||
if path != settings_path:
|
||||
self.log(f"Updating sgf load path default to {path}", OUTPUT_INFO)
|
||||
self._config["general"]["sgf_load"] = path
|
||||
self.save_config("general")
|
||||
self.load_sgf_file(files[0], popup_contents.fast.active, popup_contents.rewind.active)
|
||||
self.load_sgf_file(filename, popup_contents.fast.active, popup_contents.rewind.active)
|
||||
|
||||
popup_contents.filesel.on_success = readfile
|
||||
popup_contents.filesel.on_submit = readfile
|
||||
@@ -382,18 +382,18 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
def _do_output_sgf(self):
|
||||
msg = self.game.write_sgf(self.config("general/sgf_save"))
|
||||
self.log(msg, OUTPUT_INFO)
|
||||
self.controls.set_status(msg, OUTPUT_INFO)
|
||||
self.controls.set_status(msg, STATUS_INFO)
|
||||
|
||||
def load_sgf_from_clipboard(self):
|
||||
clipboard = Clipboard.paste()
|
||||
if not clipboard:
|
||||
self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.", STATUS_ERROR)
|
||||
self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.", STATUS_INFO)
|
||||
return
|
||||
try:
|
||||
move_tree = KaTrainSGF.parse_sgf(clipboard)
|
||||
except Exception as exc:
|
||||
self.controls.set_status(
|
||||
i18n._("Failed to import from clipboard").format(error=exc, contents=clipboard[:50])
|
||||
i18n._("Failed to import from clipboard").format(error=exc, contents=clipboard[:50]), STATUS_INFO
|
||||
)
|
||||
return
|
||||
move_tree.nodes_in_tree[-1].analyze(
|
||||
|
||||
+2
-1
@@ -16,7 +16,7 @@
|
||||
"anim_pv_time": 0.5,
|
||||
"debug_level": 0,
|
||||
"lang": "en",
|
||||
"version": "1.3.0"
|
||||
"version": "1.3.3"
|
||||
},
|
||||
"timer": {
|
||||
"byo_length": 30,
|
||||
@@ -31,6 +31,7 @@
|
||||
"clear_cache": false
|
||||
},
|
||||
"trainer": {
|
||||
"theme": "theme:normal",
|
||||
"num_undo_prompts": [
|
||||
1,
|
||||
1,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
VERSION = "1.3.2"
|
||||
VERSION = "1.3.3"
|
||||
HOMEPAGE = "https://github.com/sanderland/katrain"
|
||||
CONFIG_MIN_VERSION = "1.3.0" # keep config files from this version
|
||||
CONFIG_MIN_VERSION = "1.3.3" # keep config files from this version
|
||||
|
||||
OUTPUT_ERROR = -1
|
||||
OUTPUT_KATAGO_STDERR = -0.5
|
||||
@@ -34,7 +34,7 @@ AI_INFLUENCE = "ai:p:influence"
|
||||
AI_TERRITORY = "ai:p:territory"
|
||||
AI_RANK = "ai:p:rank"
|
||||
|
||||
AI_CONFIG_DEFAULT = AI_SCORELOSS
|
||||
AI_CONFIG_DEFAULT = AI_RANK
|
||||
|
||||
AI_STRATEGIES_ENGINE = [AI_DEFAULT, AI_HANDICAP, AI_SCORELOSS, AI_JIGO]
|
||||
AI_STRATEGIES_PICK = [AI_PICK, AI_LOCAL, AI_TENUKI, AI_INFLUENCE, AI_TERRITORY, AI_RANK]
|
||||
|
||||
+10
-8
@@ -5,15 +5,15 @@ import subprocess
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from typing import Callable, Optional, Dict
|
||||
from typing import Callable, Dict, Optional
|
||||
|
||||
from kivy.utils import platform
|
||||
|
||||
from katrain.core.constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR
|
||||
from katrain.core.game_node import GameNode
|
||||
from katrain.core.lang import i18n
|
||||
from katrain.core.utils import find_package_resource
|
||||
|
||||
from kivy.utils import platform
|
||||
|
||||
|
||||
class EngineDiedException(Exception):
|
||||
pass
|
||||
@@ -164,10 +164,13 @@ class KataGoEngine:
|
||||
continue
|
||||
try:
|
||||
analysis = json.loads(line)
|
||||
if analysis["id"] not in self.queries:
|
||||
self.katrain.log(f"Query result {analysis['id']} discarded -- recent new game?", OUTPUT_DEBUG)
|
||||
if "id" not in analysis:
|
||||
self.katrain.log(f"Error without ID {analysis} received from KataGo", OUTPUT_ERROR)
|
||||
continue
|
||||
query_id = analysis["id"]
|
||||
if query_id not in self.queries:
|
||||
self.katrain.log(f"Query result {query_id} discarded -- recent new game?", OUTPUT_DEBUG)
|
||||
continue
|
||||
callback, error_callback, start_time, next_move = self.queries[query_id]
|
||||
if "error" in analysis:
|
||||
del self.queries[query_id]
|
||||
@@ -181,8 +184,7 @@ class KataGoEngine:
|
||||
del self.queries[query_id]
|
||||
time_taken = time.time() - start_time
|
||||
self.katrain.log(
|
||||
f"[{time_taken:.1f}][{analysis['id']}] KataGo Analysis Received: {analysis.keys()}",
|
||||
OUTPUT_DEBUG,
|
||||
f"[{time_taken:.1f}][{query_id}] KataGo Analysis Received: {analysis.keys()}", OUTPUT_DEBUG,
|
||||
)
|
||||
self.katrain.log(line, OUTPUT_EXTRA_DEBUG)
|
||||
try:
|
||||
@@ -192,8 +194,8 @@ class KataGoEngine:
|
||||
if getattr(self.katrain, "update_state", None): # easier mocking etc
|
||||
self.katrain.update_state()
|
||||
except Exception as e:
|
||||
traceback.print_exc(e)
|
||||
self.katrain.log(f"Unexpected exception {e} while processing KataGo output {line}", OUTPUT_ERROR)
|
||||
traceback.print_exc()
|
||||
|
||||
def send_query(self, query, callback, error_callback, next_move=None):
|
||||
with self._lock:
|
||||
|
||||
@@ -11,10 +11,10 @@ from katrain.core.constants import (
|
||||
HOMEPAGE,
|
||||
OUTPUT_DEBUG,
|
||||
OUTPUT_INFO,
|
||||
PLAYER_AI,
|
||||
STATUS_ANALYSIS,
|
||||
STATUS_INFO,
|
||||
STATUS_TEACHING,
|
||||
PLAYER_AI,
|
||||
)
|
||||
from katrain.core.engine import KataGoEngine
|
||||
from katrain.core.game_node import GameNode
|
||||
@@ -55,7 +55,7 @@ class Game:
|
||||
self.root = move_tree
|
||||
self.komi = self.root.komi
|
||||
handicap = int(self.root.get_property("HA", 0))
|
||||
if handicap and not self.root.placements:
|
||||
if handicap >= 2 and not self.root.placements:
|
||||
self.root.place_handicap_stones(handicap)
|
||||
else:
|
||||
board_size = katrain.config("game/size")
|
||||
|
||||
@@ -32,12 +32,11 @@ class GameNode(SGFNode):
|
||||
else:
|
||||
show_class = False
|
||||
if (
|
||||
(save_comments_player or {}).get(self.player, False)
|
||||
and show_class
|
||||
and self.analysis_ready
|
||||
and self.parent
|
||||
self.parent
|
||||
and self.parent.analysis_ready
|
||||
) or note:
|
||||
and self.analysis_ready
|
||||
and (note or ((save_comments_player or {}).get(self.player, False) and show_class))
|
||||
):
|
||||
candidate_moves = self.parent.candidate_moves
|
||||
top_x = Move.from_gtp(candidate_moves[0]["move"]).sgf(self.board_size)
|
||||
best_sq = [
|
||||
@@ -194,6 +193,8 @@ class GameNode(SGFNode):
|
||||
text += "\n" + i18n._("Info:AI thoughts").format(thoughts=self.ai_thoughts)
|
||||
else:
|
||||
text = i18n._("No analysis available") if sgf else i18n._("Analyzing move...")
|
||||
if "C" in self.properties:
|
||||
text += "\n[u]SGF Comments:[/u]\n" + "\n".join(self.properties["C"])
|
||||
return text
|
||||
|
||||
@property
|
||||
|
||||
@@ -11,11 +11,7 @@ from katrain.gui.style import DEFAULT_FONT
|
||||
class Lang(Observable):
|
||||
observers = []
|
||||
callbacks = []
|
||||
FONTS = {
|
||||
"ko": "fonts/NotoSansKR-Regular.otf",
|
||||
"cn": "fonts/NotoSansCJKsc-Regular.otf",
|
||||
"jp": "fonts/NotoSansJP-Regular.otf",
|
||||
}
|
||||
FONTS = {"jp": "fonts/NotoSansJP-Regular.otf"}
|
||||
|
||||
def __init__(self, lang):
|
||||
super(Lang, self).__init__()
|
||||
|
||||
@@ -132,7 +132,9 @@ class SGFNode:
|
||||
|
||||
def add_list_property(self, property: str, values: List):
|
||||
"""Add some values to the property list."""
|
||||
self.properties[property] += values
|
||||
# SiZe[19] ==> SZ[19] etc. for old SGF
|
||||
normalized_property = re.sub("[a-z]", "", property)
|
||||
self.properties[normalized_property] += values
|
||||
|
||||
def get_list_property(self, property, default=None) -> Any:
|
||||
"""Get the list of values for a property."""
|
||||
@@ -323,11 +325,14 @@ class SGF:
|
||||
_NODE_CLASS = SGFNode # Class used for SGF Nodes, can change this to something that inherits from SGFNode
|
||||
# https://xkcd.com/1171/
|
||||
SGFPROP_PAT = re.compile(r"\s*(?:\(|\)|;|(\w+)((\s*\[([^\]\\]|\\.)*\])+))", flags=re.DOTALL)
|
||||
SGF_PAT = re.compile(r"\(;.*\)", flags=re.DOTALL)
|
||||
|
||||
@classmethod
|
||||
def parse_sgf(cls, input_str) -> SGFNode:
|
||||
"""Parse a string as SGF."""
|
||||
return cls(input_str).root
|
||||
match = re.search(cls.SGF_PAT, input_str)
|
||||
clipped_str = match.group() if match else input_str
|
||||
return cls(clipped_str).root
|
||||
|
||||
@classmethod
|
||||
def parse_file(cls, filename, encoding=None) -> SGFNode:
|
||||
@@ -375,7 +380,10 @@ class SGF:
|
||||
if matched_item == "(":
|
||||
self._parse_branch(self._NODE_CLASS(parent=current_move))
|
||||
elif matched_item == ";":
|
||||
if not current_move.empty: # ignore ; that generate empty nodes
|
||||
# ignore ;) for old SGF
|
||||
useless = self.ix < len(self.contents) and self.contents[self.ix] == ")"
|
||||
# ignore ; that generate empty nodes
|
||||
if not (current_move.empty or useless):
|
||||
current_move = self._NODE_CLASS(parent=current_move)
|
||||
else:
|
||||
property, value = match[1], match[2].strip()[1:-1]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+4
-1
@@ -146,6 +146,9 @@
|
||||
background_color: [*[c*255/88 for c in BACKGROUND_COLOR[:3]], 1] # compensate for texture
|
||||
option_cls: 'I18NSpinnerOption'
|
||||
|
||||
<Button>:
|
||||
track_lang: i18n._('')
|
||||
|
||||
<I18NSpinner>
|
||||
sync_height_frac: 1.0
|
||||
|
||||
@@ -626,7 +629,7 @@
|
||||
padding: 0
|
||||
id: notebox
|
||||
size_hint: 1,0
|
||||
MDTextField:
|
||||
IMETextField:
|
||||
id: note
|
||||
font_size: NOTES_FONT_SIZE
|
||||
multiline: True
|
||||
|
||||
@@ -164,8 +164,9 @@ class BadukPanWidget(Widget):
|
||||
|
||||
def eval_color(self, points_lost, show_dots_for_class: List[bool] = None) -> Optional[List[float]]:
|
||||
i = evaluation_class(points_lost, self.trainer_config["eval_thresholds"])
|
||||
colors = EVAL_COLORS[self.trainer_config["theme"]]
|
||||
if show_dots_for_class is None or show_dots_for_class[i]:
|
||||
return EVAL_COLORS[i]
|
||||
return colors[i]
|
||||
|
||||
def draw_board(self, *_args):
|
||||
if not (self.katrain and self.katrain.game):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.text import Label as CoreLabel
|
||||
from kivy.core.window import Window
|
||||
from kivy.graphics import *
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
@@ -9,18 +10,20 @@ from kivy.properties import (
|
||||
OptionProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from kivy.uix.behaviors import ButtonBehavior, ToggleButtonBehavior
|
||||
from kivy.uix.behaviors import ButtonBehavior, ToggleButtonBehavior, FocusBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.button import Button
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.scrollview import ScrollView
|
||||
from kivy.uix.spinner import Spinner
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.uix.widget import Widget
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.behaviors import CircularRippleBehavior, RectangularRippleBehavior
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.button import BaseFlatButton, BasePressedButton
|
||||
from kivymd.uix.navigationdrawer import MDNavigationDrawer
|
||||
from kivymd.uix.textfield import MDTextField
|
||||
|
||||
from katrain.core.constants import AI_STRATEGIES_RECOMMENDED_ORDER, GAME_TYPES, MODE_PLAY, PLAYER_AI
|
||||
from katrain.core.lang import i18n
|
||||
@@ -163,6 +166,45 @@ class BGBoxLayout(BoxLayout, BackgroundMixin):
|
||||
# -- gui elements
|
||||
|
||||
|
||||
class IMETextField(MDTextField):
|
||||
_imo_composition = StringProperty("")
|
||||
_imo_cursor = ListProperty(None, allownone=True)
|
||||
|
||||
def _bind_keyboard(self):
|
||||
super()._bind_keyboard()
|
||||
Window.bind(on_textedit=self.window_on_textedit)
|
||||
|
||||
def _unbind_keyboard(self):
|
||||
super()._unbind_keyboard()
|
||||
Window.unbind(on_textedit=self.window_on_textedit)
|
||||
|
||||
def do_backspace(self, from_undo=False, mode="bkspc"):
|
||||
if self._imo_composition == "": # IMO handles sub-character backspaces
|
||||
return super().do_backspace(from_undo, mode)
|
||||
|
||||
def window_on_textedit(self, window, imo_input):
|
||||
text_lines = self._lines
|
||||
if self._imo_composition:
|
||||
pcc, pcr = self._imo_cursor
|
||||
text = text_lines[pcr]
|
||||
if text[pcc - len(self._imo_composition) : pcc] == self._imo_composition: # should always be true
|
||||
remove_old_imo_text = text[: pcc - len(self._imo_composition)] + text[pcc:]
|
||||
ci = self.cursor_index()
|
||||
self._refresh_text_from_property("insert", *self._get_line_from_cursor(pcr, remove_old_imo_text))
|
||||
self.cursor = self.get_cursor_from_index(ci - len(self._imo_composition))
|
||||
|
||||
if imo_input:
|
||||
if self._selection:
|
||||
self.delete_selection()
|
||||
cc, cr = self.cursor
|
||||
text = text_lines[cr]
|
||||
new_text = text[:cc] + imo_input + text[cc:]
|
||||
self._refresh_text_from_property("insert", *self._get_line_from_cursor(cr, new_text))
|
||||
self.cursor = self.get_cursor_from_index(self.cursor_index() + len(imo_input))
|
||||
self._imo_composition = imo_input
|
||||
self._imo_cursor = self.cursor
|
||||
|
||||
|
||||
class I18NSpinner(Spinner):
|
||||
__events__ = ["on_select"]
|
||||
sync_height_frac = NumericProperty(1.0)
|
||||
|
||||
+11
-21
@@ -19,13 +19,13 @@ from katrain.core.ai import ai_rank_estimation
|
||||
from katrain.core.constants import (
|
||||
AI_CONFIG_DEFAULT,
|
||||
AI_DEFAULT,
|
||||
AI_KEY_PROPERTIES,
|
||||
AI_OPTION_VALUES,
|
||||
AI_STRATEGIES_RECOMMENDED_ORDER,
|
||||
OUTPUT_DEBUG,
|
||||
OUTPUT_ERROR,
|
||||
OUTPUT_INFO,
|
||||
STATUS_INFO,
|
||||
AI_KEY_PROPERTIES,
|
||||
)
|
||||
from katrain.core.engine import KataGoEngine
|
||||
from katrain.core.lang import i18n, rank_label
|
||||
@@ -104,21 +104,7 @@ class LabelledSpinner(I18NSpinner):
|
||||
|
||||
|
||||
class LabelledFloatInput(LabelledTextInput):
|
||||
signed = BooleanProperty(True)
|
||||
pat = re.compile("[^0-9-]")
|
||||
|
||||
def insert_text(self, substring, from_undo=False):
|
||||
pat = self.pat
|
||||
if "." in self.text:
|
||||
s = re.sub(pat, "", substring)
|
||||
else:
|
||||
s = ".".join([re.sub(pat, "", s) for s in substring.split(".", 1)])
|
||||
r = super().insert_text(s, from_undo=from_undo)
|
||||
if not self.signed and "-" in self.text:
|
||||
self.text = self.text.replace("-", "")
|
||||
elif self.text and "-" in self.text[1:]:
|
||||
self.text = self.text[0] + self.text[1:].replace("-", "")
|
||||
return r
|
||||
input_filter = ObjectProperty("float")
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
@@ -126,10 +112,7 @@ class LabelledFloatInput(LabelledTextInput):
|
||||
|
||||
|
||||
class LabelledIntInput(LabelledTextInput):
|
||||
pat = re.compile("[^0-9]")
|
||||
|
||||
def insert_text(self, substring, from_undo=False):
|
||||
return super().insert_text(re.sub(self.pat, "", substring), from_undo=from_undo)
|
||||
input_filter = ObjectProperty("int")
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
@@ -286,13 +269,16 @@ class ConfigTeacherPopup(QuickConfigGui):
|
||||
self.options_grid.add_widget(wrap_anchor(widget))
|
||||
|
||||
def build_and_set_properties(self, *_args):
|
||||
theme = self.katrain.config("trainer/theme")
|
||||
undos = self.katrain.config("trainer/num_undo_prompts")
|
||||
thresholds = self.katrain.config("trainer/eval_thresholds")
|
||||
savesgfs = self.katrain.config("trainer/save_feedback")
|
||||
show_dots = self.katrain.config("trainer/show_dots")
|
||||
|
||||
self.themes_spinner.value_refs = list(EVAL_COLORS.keys())
|
||||
self.options_grid.clear_widgets()
|
||||
for i, (color, threshold, undo, show_dot, savesgf) in enumerate(
|
||||
zip(EVAL_COLORS, thresholds, undos, show_dots, savesgfs)
|
||||
zip(EVAL_COLORS[theme], thresholds, undos, show_dots, savesgfs)
|
||||
):
|
||||
self.add_option_widgets(
|
||||
[
|
||||
@@ -305,6 +291,10 @@ class ConfigTeacherPopup(QuickConfigGui):
|
||||
)
|
||||
super().build_and_set_properties()
|
||||
|
||||
def update_config(self, save_to_file=True):
|
||||
super().update_config(save_to_file=save_to_file)
|
||||
self.build_and_set_properties()
|
||||
|
||||
|
||||
class DescriptionLabel(Label):
|
||||
pass
|
||||
|
||||
+24
-12
@@ -2,7 +2,7 @@ def to_hexcol(kivycol):
|
||||
return "#" + "".join(f"{round(c * 255):02x}" for c in kivycol[:3])
|
||||
|
||||
|
||||
DEFAULT_FONT = "fonts/NotoSans-Regular.ttf"
|
||||
DEFAULT_FONT = "fonts/NotoSansCJKsc-Regular.otf"
|
||||
|
||||
# basic color definitions
|
||||
WHITE = [0.95, 0.95, 0.95, 1]
|
||||
@@ -26,14 +26,26 @@ WINRATE_COLOR = GREEN
|
||||
POINTLOSS_COLOR = YELLOW
|
||||
|
||||
# eval dots
|
||||
EVAL_COLORS = [
|
||||
[0.447, 0.129, 0.42, 1],
|
||||
[0.8, 0, 0, 1],
|
||||
[0.9, 0.4, 0.1, 1],
|
||||
[0.85, 0.89, 0.3, 1],
|
||||
[0.67, 0.9, 0.18, 1],
|
||||
[0.117, 0.588, 0, 1],
|
||||
]
|
||||
EVAL_COLORS = {
|
||||
"theme:normal": [
|
||||
[0.447, 0.129, 0.42, 1],
|
||||
[0.8, 0, 0, 1],
|
||||
[0.9, 0.4, 0.1, 1],
|
||||
[0.85, 0.89, 0.3, 1],
|
||||
[0.67, 0.9, 0.18, 1],
|
||||
[0.117, 0.588, 0, 1],
|
||||
],
|
||||
"theme:red-green-colourblind": [
|
||||
[1, 0, 1, 1],
|
||||
[1, 0, 0, 1],
|
||||
[1, 0.5, 0, 1],
|
||||
[0.85, 0.89, 0.3, 1],
|
||||
[0, 1, 1, 1],
|
||||
[0, 0, 1, 1],
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
EVAL_DOT_MAX_SIZE = 0.5
|
||||
EVAL_DOT_MIN_SIZE = 0.25
|
||||
|
||||
@@ -55,9 +67,9 @@ TOP_MOVE_BORDER_COLOR = [46 / 255, 65 / 255, 88 / 255, 0.9]
|
||||
CHILD_SCALE = 0.95
|
||||
|
||||
# ponder light
|
||||
ENGINE_DOWN_COL = EVAL_COLORS[1]
|
||||
ENGINE_BUSY_COL = EVAL_COLORS[2]
|
||||
ENGINE_READY_COL = EVAL_COLORS[-1]
|
||||
ENGINE_DOWN_COL = EVAL_COLORS["theme:normal"][1]
|
||||
ENGINE_BUSY_COL = EVAL_COLORS["theme:normal"][2]
|
||||
ENGINE_READY_COL = EVAL_COLORS["theme:normal"][-1]
|
||||
|
||||
# info
|
||||
INFO_PV_COLOR = to_hexcol(YELLOW)
|
||||
@@ -165,9 +165,6 @@ Builder.load_string(
|
||||
#:import abspath os.path.abspath
|
||||
#:import i18n katrain.core.lang.i18n
|
||||
|
||||
<Button>:
|
||||
track_lang: i18n._('')
|
||||
|
||||
<TreeLabel>:
|
||||
on_touch_down:
|
||||
self.parent.browser.path = self.path if self.collide_point(*args[1].pos) and self.path else self.parent.browser.path
|
||||
|
||||
Binary file not shown.
@@ -595,3 +595,15 @@ msgstr "每步棋计算深度"
|
||||
#. TODO - on move tree editing
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:red-green-colourblind"
|
||||
msgstr "Red-Green Colourblind"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:normal"
|
||||
msgstr "Default Theme"
|
||||
|
||||
#. TODO - theme label
|
||||
msgid "theme"
|
||||
msgstr "Colour Theme"
|
||||
Binary file not shown.
@@ -559,9 +559,9 @@ msgstr ""
|
||||
|
||||
msgid "aihelp:p:rank"
|
||||
msgstr ""
|
||||
"Wählt zufällige Züge aus einer beschränkten Auswahl an Zügen und spieltden "
|
||||
"Besten. Stärkere Einstellungen wählen den besten Zug aus einer grösseren "
|
||||
"Auswahl. Da es keinen 0 Kyu/Dan gibt, ist 3 Dan = -2 Kyu."
|
||||
"Wählt eine begrenzte Anzahl zufälliger Züge und spielt den Besten. Stärkere "
|
||||
"Einstellungen wählen den besten Zug aus einer grösseren Auswahl. Da es "
|
||||
"keinen 0 Kyu/Dan gibt, ist 3 Dan = -2 Kyu."
|
||||
|
||||
msgid "ai:p:rank"
|
||||
msgstr "Kalibrierter Rang"
|
||||
@@ -639,3 +639,15 @@ msgstr "Anzahl der Visits pro Zug"
|
||||
#. TODO - on move tree editing
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:red-green-colourblind"
|
||||
msgstr "Red-Green Colourblind"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:normal"
|
||||
msgstr "Default Theme"
|
||||
|
||||
#. TODO - theme label
|
||||
msgid "theme"
|
||||
msgstr "Colour Theme"
|
||||
Binary file not shown.
@@ -616,9 +616,9 @@ msgstr "Calibrated Rank"
|
||||
|
||||
msgid "aihelp:p:rank"
|
||||
msgstr ""
|
||||
"Picks moves at random from a limited selection of moves and plays the best "
|
||||
"one. Stronger settings select the best move from a larger selection. Since "
|
||||
"there is no 0 kyu/dan, 3 dan = -2 kyu."
|
||||
"Picks a limited number of legal moves at random and plays the best one. "
|
||||
"Stronger settings select the best move from a larger selection. Since there "
|
||||
"is no 0 kyu/dan, 3 dan = -2 kyu."
|
||||
|
||||
#. in AI settings
|
||||
msgid "estimated strength"
|
||||
@@ -643,3 +643,15 @@ msgstr "Number of visits per move"
|
||||
#. on move tree editing
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
|
||||
#. theme label
|
||||
msgid "theme"
|
||||
msgstr "Colour Theme"
|
||||
|
||||
#. theme name
|
||||
msgid "theme:normal"
|
||||
msgstr "Default Theme"
|
||||
|
||||
#. theme name
|
||||
msgid "theme:red-green-colourblind"
|
||||
msgstr "Red-Green Colourblind"
|
||||
Binary file not shown.
@@ -557,9 +557,9 @@ msgstr ""
|
||||
|
||||
msgid "aihelp:p:rank"
|
||||
msgstr ""
|
||||
"Seleciona jugadas al azar de una limitada selección y juega la mejor. "
|
||||
"Configuraciones más fuertes toma la mejor jugada de una selección más amplia"
|
||||
" de jugadas. Como no hay 0 kyu/dan, 3 dan = -2 kyu."
|
||||
"Elige una selección limitada de jugadas y juega la mejor. Configuraciones "
|
||||
"más fuertes toma la mejor jugada de una selección más amplia de jugadas. "
|
||||
"Como no hay 0 kyu/dan, 3 dan = -2 kyu."
|
||||
|
||||
msgid "Kata model not found"
|
||||
msgstr "El modelo KataGo {model} no existe, revisar conf general."
|
||||
@@ -635,3 +635,15 @@ msgstr "Número de visitas por jugada"
|
||||
#. TODO - on move tree editing
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:red-green-colourblind"
|
||||
msgstr "Red-Green Colourblind"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:normal"
|
||||
msgstr "Default Theme"
|
||||
|
||||
#. TODO - theme label
|
||||
msgid "theme"
|
||||
msgstr "Colour Theme"
|
||||
Binary file not shown.
@@ -676,3 +676,15 @@ msgstr ""
|
||||
#. TODO - on move tree editing
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:red-green-colourblind"
|
||||
msgstr "Red-Green Colourblind"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:normal"
|
||||
msgstr "Default Theme"
|
||||
|
||||
#. TODO - theme label
|
||||
msgid "theme"
|
||||
msgstr "Colour Theme"
|
||||
Binary file not shown.
@@ -541,9 +541,10 @@ msgstr "第一感(比重)"
|
||||
|
||||
msgid "aihelp:p:weighted"
|
||||
msgstr ""
|
||||
"第一感での確率にしたがって手を選びます. `weaken_fac`は弱そうな手を選ぶ度合を調整します. これを非常に小さな値にすれば`Policy` "
|
||||
"AIと似たような打ち方になります. なお, 最有力手の第一感での確率が`pick_override`より大きい場合はその手が無条件で選ばれます. 逆に,"
|
||||
" 第一感での確率が`lower_bound`より小さい手は決して選ばれません."
|
||||
"第一感での確率にしたがって手を選びます. `weaken_fac`は弱そうな手を選ぶ度合を調整します. これを非常に小さな値にすれば, "
|
||||
"本来の「第一感」と似たような打ち方になります. なお, "
|
||||
"最有力手の第一感での確率が`pick_override`より大きい場合はその手が無条件で選ばれます. 逆に, "
|
||||
"第一感での確率が`lower_bound`より小さい手は決して選ばれません."
|
||||
|
||||
msgid "ai:p:pick"
|
||||
msgstr "第一感(盲点)"
|
||||
@@ -551,7 +552,7 @@ msgstr "第一感(盲点)"
|
||||
msgid "aihelp:p:pick"
|
||||
msgstr ""
|
||||
"`pick_n + pick_frac * <合法手の個数>`個の手をランダムに選んだ上で, その中での最善手を打ちます. "
|
||||
"`pick_frac=1`にすれば`Policy` AIと同じような打ち方になります. なお, 明らかなミスを避けるために, "
|
||||
"`pick_frac=1`にすれば, 本来の「第一感」と同じような打ち方になります. なお, 明らかなミスを避けるために, "
|
||||
"最有力手の第一感での確率が`pick_override`より大きい場合は無条件でその手を打ちます."
|
||||
|
||||
msgid "ai:p:local"
|
||||
@@ -595,8 +596,8 @@ msgstr "段級位相応"
|
||||
|
||||
msgid "aihelp:p:rank"
|
||||
msgstr ""
|
||||
"制限された手のうちからランダムに何手か選んだ上で, その中での最善手を打ちます. 強い設定にするほどより広い候補の中から最善手を選ぶようになります. "
|
||||
"なお, 3段は-2 kyuに対応します(0級/段はないので数字がずれる). "
|
||||
"すべての合法手の中からランダムに何個かの候補手を選んで, その中での再有力手を打ちます. "
|
||||
"強い設定にするほどより多くの候補手の中から再有力手を選ぶようになります. なお, 3段は-2 kyuに対応します(0級/段はないので数字がずれる). "
|
||||
|
||||
#. in AI settings
|
||||
msgid "estimated strength"
|
||||
@@ -620,4 +621,16 @@ msgstr "着手毎の読み手数"
|
||||
|
||||
#. TODO - on move tree editing
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
msgstr "この手以降を削除"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:red-green-colourblind"
|
||||
msgstr "赤緑色弱"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:normal"
|
||||
msgstr "デフォルト"
|
||||
|
||||
#. TODO - theme label
|
||||
msgid "theme"
|
||||
msgstr "カラーテーマ"
|
||||
Binary file not shown.
@@ -611,3 +611,15 @@ msgstr "한 수당 visit 수"
|
||||
#. TODO - on move tree editing
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:red-green-colourblind"
|
||||
msgstr "Red-Green Colourblind"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:normal"
|
||||
msgstr "Default Theme"
|
||||
|
||||
#. TODO - theme label
|
||||
msgid "theme"
|
||||
msgstr "Colour Theme"
|
||||
Binary file not shown.
@@ -636,3 +636,15 @@ msgstr "Количество посещений на ход"
|
||||
#. TODO - on move tree editing
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:red-green-colourblind"
|
||||
msgstr "Red-Green Colourblind"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:normal"
|
||||
msgstr "Default Theme"
|
||||
|
||||
#. TODO - theme label
|
||||
msgid "theme"
|
||||
msgstr "Colour Theme"
|
||||
+14
-6
@@ -213,6 +213,7 @@
|
||||
|
||||
<ConfigTeacherPopup>
|
||||
options_grid: options_grid
|
||||
themes_spinner: themes_spinner
|
||||
GridLayout:
|
||||
cols: 5
|
||||
rows: 7
|
||||
@@ -229,22 +230,29 @@
|
||||
DescriptionLabel:
|
||||
text: i18n._("save dots")
|
||||
AnchorLayout:
|
||||
size_hint: 1, 2
|
||||
size_hint: 1, 3
|
||||
GridLayout:
|
||||
cols: 2
|
||||
rows: 3
|
||||
rows: 4
|
||||
padding: 4
|
||||
size_hint: 0.66, 1
|
||||
size_hint: 0.8, 1
|
||||
DescriptionLabel:
|
||||
font_size: DESC_FONT_SIZE * 0.8
|
||||
text: i18n._("theme")
|
||||
AnchorLayout:
|
||||
LabelledSpinner:
|
||||
size_hint: 0.8,0.8
|
||||
input_property: 'trainer/theme'
|
||||
font_size: DESC_FONT_SIZE * 0.8
|
||||
id: themes_spinner
|
||||
DescriptionLabel:
|
||||
font_size: DESC_FONT_SIZE * 0.8
|
||||
text: i18n._("show last n dots")
|
||||
size_hint: 2,1
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
size_hint_x: 0.33
|
||||
size_hint_x: 0.15
|
||||
input_property: "trainer/eval_off_show_last"
|
||||
DescriptionLabel:
|
||||
size_hint: 2,1
|
||||
font_size: DESC_FONT_SIZE * 0.8
|
||||
text: i18n._("show ai dots")
|
||||
AnchorLayout:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
(;GM[1]FF[4] SZ[19] GN[] DT[2020-07-20] PB[whatiskale] PW[V259081699] BR[15级] WR[14级] KM[0]HA[1]RU[Japanese]AP[GNU Go:3.8]RE[W+R]TM[1200]TC[3]TT[30]AP[foxwq]RL[0] ;B[pd];W[dp];B[pq];W[dd];B[fc];W[qo];B[np];W[nc];B[qf];W[pc];B[qc];W[qb];B[rc];W[od];B[pb];W[oc];B[rb];W[jd];B[cc];W[dc];B[cd];W[de];B[db];W[eb];B[cb];W[fb];B[gc];W[ec];B[gb];W[ce];B[be];W[bf];B[bd];W[ge];B[cf];W[dg];B[bg];W[kc];B[df];W[ef];B[cg];W[eg];B[fe];W[ff];B[fq];W[hq];B[cq];W[dq];B[cp];W[do];B[dr];W[er];B[cr];W[fr];B[eq];W[gq];B[fp];W[co];B[bo];W[bn];B[bp];W[go];B[fo];W[fn];B[cn];W[cm];B[gn];W[fm];B[qm];W[ki];B[oe];W[pe];B[qd];W[ne];B[of];W[nf];B[ng];W[og];B[pf];W[mg];B[nh];W[mi];B[ni];W[nj];B[mj];W[mh];B[ph];W[oj];B[pi];W[mk];B[lj];W[kl];B[nk];W[ok];B[nl];W[ol];B[ll];W[ml];B[nm];W[mm];B[lm];W[mn];B[nn];W[lk];B[kk];W[kj];B[jk];W[mo];B[ln];W[lo];B[jm];W[gm];B[hn];W[hm];B[in];W[jo];B[gp];W[hp];B[ho];W[lq];B[jj];W[hj];B[hk];W[gj];B[gk];W[fj];B[ji];W[dj];B[dh];W[ch];B[ci];W[ag];B[bh];W[rq];B[rn];W[pn];B[om];W[pm];B[no];W[ro];B[pl];W[ql];B[pk];W[pj];B[qk];W[qj];B[rk];W[qr];B[pr];W[mr];B[so];W[sp];B[sn];W[qq];B[ip];W[iq];B[jp];W[jq];B[lp];W[ko];B[mp];W[qn];B[rl];W[ob];B[qa];W[dn];B[jb];W[kb];B[fd];W[rj];B[ee];W[rh];B[rg];W[qh];B[sh];W[qg];B[pg];W[rf];B[sg];W[si];B[qi];W[sf];B[sg];W[re];B[qe];W[li])
|
||||
@@ -0,0 +1 @@
|
||||
(;GM[1]FF[4] SZ[19] GN[] DT[2020-07-25] PB[whatiskale] PW[注号用名] BR[15级] WR[15级] KM[0]HA[0]RU[Japanese]AP[GNU Go:3.8]RE[B+R]TM[1200]TC[3]TT[30]AP[foxwq]RL[0] ;B[pd];W[dp];B[pp];W[dd];B[fc];W[qq];B[pq];W[qp];B[qn];W[qo];B[pn];W[po];B[oo];W[rn];B[rm];W[sn];B[ql];W[qf];B[nc];W[qi];B[fq];W[hq];B[fo];W[lq];B[cn];W[dn];B[dm];W[en];B[dq];W[cp];B[cq];W[bp];B[bq];W[bn];B[cm];W[bm];B[ck];W[fn];B[cf];W[ee];B[cc];W[dc];B[cd];W[db];B[id];W[hd];B[hc];W[he];B[jq];W[fp];B[ep];W[gp];B[eo];W[do];B[go];W[ho];B[gn];W[fl];B[jo];W[lo];B[hn];W[er];B[fr];W[jn];B[io];W[hp];B[kn];W[jm];B[ln];W[ko];B[mn];W[jp];B[ip];W[kp];B[iq];W[hr];B[ir];W[is];B[js];W[hs];B[gs];W[in];B[gr];W[kr];B[mr];W[jr];B[gq];W[ks];B[mq];W[pr];B[or];W[mp])
|
||||
@@ -0,0 +1,133 @@
|
||||
(
|
||||
;
|
||||
FileFormat[1]GaMe[1]
|
||||
VieW[]
|
||||
SiZe[9]
|
||||
PlayerBlack[hi]
|
||||
PlayerWhite[ka]
|
||||
KoMi[-1.5]
|
||||
Black[ff]
|
||||
;
|
||||
White[gd]
|
||||
;
|
||||
Black[fd]
|
||||
;
|
||||
White[fe]
|
||||
;
|
||||
Black[ge]
|
||||
;
|
||||
White[ee]
|
||||
;
|
||||
Black[gc]
|
||||
;
|
||||
White[hd]
|
||||
;
|
||||
Black[hc]
|
||||
;
|
||||
White[he]
|
||||
;
|
||||
Black[gf]
|
||||
;
|
||||
White[hf]
|
||||
;
|
||||
Black[ed]
|
||||
;
|
||||
White[ef]
|
||||
;
|
||||
Black[eg]
|
||||
;
|
||||
White[dg]
|
||||
;
|
||||
Black[fg]
|
||||
;
|
||||
White[gh]
|
||||
;
|
||||
Black[cg]
|
||||
;
|
||||
White[dh]
|
||||
;
|
||||
Black[df]
|
||||
;
|
||||
White[de]
|
||||
;
|
||||
Black[cf]
|
||||
;
|
||||
White[dd]
|
||||
;
|
||||
Black[dc]
|
||||
;
|
||||
White[ec]
|
||||
;
|
||||
Black[fc]
|
||||
;
|
||||
White[cc]
|
||||
;
|
||||
Black[eb]
|
||||
;
|
||||
White[be]
|
||||
;
|
||||
Black[bf]
|
||||
;
|
||||
White[ce]
|
||||
;
|
||||
Black[bd]
|
||||
;
|
||||
White[ae]
|
||||
;
|
||||
Black[af]
|
||||
;
|
||||
White[db]
|
||||
;
|
||||
Black[ec]
|
||||
;
|
||||
White[bc]
|
||||
;
|
||||
Black[eh]
|
||||
;
|
||||
White[gg]
|
||||
;
|
||||
Black[ch]
|
||||
;
|
||||
White[fh]
|
||||
;
|
||||
Black[di]
|
||||
;
|
||||
White[ic]
|
||||
;
|
||||
Black[cb]
|
||||
;
|
||||
White[bb]
|
||||
;
|
||||
Black[da]
|
||||
;
|
||||
White[hb]
|
||||
;
|
||||
Black[gb]
|
||||
;
|
||||
White[ba]
|
||||
;
|
||||
Black[ca]
|
||||
;
|
||||
White[fi]
|
||||
;
|
||||
Black[id]
|
||||
;
|
||||
White[ib]
|
||||
;
|
||||
Black[ha]
|
||||
;
|
||||
White[ie]
|
||||
;
|
||||
Black[ga]
|
||||
;
|
||||
White[hg]
|
||||
;
|
||||
Black[ia]
|
||||
;
|
||||
Comment[Black: 13 + 4 = 17
|
||||
|
||||
White: 13 + 2 + -1.5 = 13.5
|
||||
|
||||
Black wins by 3.5]
|
||||
White[id]
|
||||
)
|
||||
+29
-1
@@ -1,5 +1,9 @@
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from katrain.core.base_katrain import KaTrainBase
|
||||
from katrain.core.engine import KataGoEngine
|
||||
from katrain.core.game import Game, KaTrainSGF
|
||||
from katrain.core.sgf_parser import SGF, SGFNode
|
||||
|
||||
|
||||
@@ -59,7 +63,7 @@ def test_pandanet():
|
||||
"GM",
|
||||
"EV",
|
||||
"US",
|
||||
"CoPyright",
|
||||
"CP",
|
||||
"GN",
|
||||
"RE",
|
||||
"PW",
|
||||
@@ -91,6 +95,16 @@ def test_pandanet():
|
||||
assert move is root
|
||||
|
||||
|
||||
def test_old_long_properties():
|
||||
file = os.path.join(os.path.dirname(__file__), "data/xmgt97.sgf")
|
||||
SGF.parse_file(file)
|
||||
|
||||
|
||||
def test_old_server_style():
|
||||
input_sgf = "... 01:23:45 +0900 (JST) ... (;SZ[19];B[aa];W[ba];)"
|
||||
SGF.parse_sgf(input_sgf)
|
||||
|
||||
|
||||
def test_ogs():
|
||||
file = os.path.join(os.path.dirname(__file__), "data/ogs.sgf")
|
||||
tree = SGF.parse_file(file)
|
||||
@@ -125,3 +139,17 @@ def test_ngf():
|
||||
"SZ": [19],
|
||||
} == root.properties
|
||||
assert "pq" == root.children[0].get_property("W")
|
||||
|
||||
|
||||
def test_foxwq():
|
||||
for sgf in ["data/fox sgf error.sgf", "data/fox sgf works.sgf"]:
|
||||
file = os.path.join(os.path.dirname(__file__), sgf)
|
||||
move_tree = KaTrainSGF.parse_file(file)
|
||||
katrain = KaTrainBase(force_package_config=True, debug_level=0)
|
||||
game = Game(katrain, MagicMock(), move_tree)
|
||||
|
||||
assert [] == move_tree.placements
|
||||
assert [] == game.root.placements
|
||||
while game.current_node.children:
|
||||
assert 1 == len(game.current_node.children)
|
||||
game.redo(1)
|
||||
Reference in new issue
Block a user