tweak
This commit is contained in:
1 parent
a646c32939
commit
40ae451bef
12 files changed
+283
-326
No files matched your search
@@ -4,7 +4,7 @@ from collections import defaultdict
|
||||
|
||||
localedir = "katrain/i18n/locales"
|
||||
locales = set(os.listdir(localedir))
|
||||
print("locales found:",locales)
|
||||
print("locales found:", locales)
|
||||
|
||||
strings_to_langs = defaultdict(dict)
|
||||
lang_to_strings = defaultdict(set)
|
||||
@@ -14,11 +14,11 @@ DEFAULT_LANG = "en"
|
||||
po = {}
|
||||
pofile = {}
|
||||
for lang in locales:
|
||||
pofile[lang] = os.path.join(localedir, lang, 'LC_MESSAGES', 'katrain.po')
|
||||
pofile[lang] = os.path.join(localedir, lang, "LC_MESSAGES", "katrain.po")
|
||||
po[lang] = polib.pofile(pofile[lang])
|
||||
for entry in po[lang].translated_entries():
|
||||
if 'TODO' in entry.comment:
|
||||
print(lang,'/',entry.msgid,'is TODO')
|
||||
if "TODO" in entry.comment:
|
||||
print(lang, "/", entry.msgid, "is TODO")
|
||||
else:
|
||||
strings_to_langs[entry.msgid][lang] = entry.msgstr
|
||||
lang_to_strings[lang].add(entry.msgid)
|
||||
@@ -26,16 +26,15 @@ for lang in locales:
|
||||
|
||||
for lang in locales:
|
||||
for msgid in strings_to_langs.keys() - lang_to_strings[lang]:
|
||||
if lang==DEFAULT_LANG:
|
||||
print("Message id",msgid,"found as ",strings_to_langs[msgid],"but missing in default",DEFAULT_LANG)
|
||||
if lang == DEFAULT_LANG:
|
||||
print("Message id", msgid, "found as ", strings_to_langs[msgid], "but missing in default", DEFAULT_LANG)
|
||||
elif DEFAULT_LANG in strings_to_langs[msgid]:
|
||||
print("Message id", msgid, "missing in ",lang,'-> Adding it from',DEFAULT_LANG)
|
||||
entry = polib.POEntry(msgid=msgid,msgstr=strings_to_langs[msgid][DEFAULT_LANG],comment="TODO")
|
||||
print("Message id", msgid, "missing in ", lang, "-> Adding it from", DEFAULT_LANG)
|
||||
entry = polib.POEntry(msgid=msgid, msgstr=strings_to_langs[msgid][DEFAULT_LANG], comment="TODO")
|
||||
po[lang].append(entry)
|
||||
else:
|
||||
print(f"MISSING IN DEFAULT AND {lang}",strings_to_langs[msgid])
|
||||
print(f"MISSING IN DEFAULT AND {lang}", strings_to_langs[msgid])
|
||||
po[lang].save(pofile[lang])
|
||||
mofile = pofile[lang].replace('.po', '.mo')
|
||||
mofile = pofile[lang].replace(".po", ".mo")
|
||||
po[lang].save_as_mofile(mofile)
|
||||
print('Fixed',pofile[lang],'and converted ->',mofile)
|
||||
|
||||
print("Fixed", pofile[lang], "and converted ->", mofile)
|
||||
+29
-23
@@ -1,38 +1,46 @@
|
||||
from kivy.config import Config # isort:skip
|
||||
from kivy.properties import ObjectProperty
|
||||
|
||||
ICON = "img/icon.png"
|
||||
Config.set("kivy", "window_icon", ICON) # isort:skip # set icon
|
||||
Config.set("input", "mouse", "mouse,multitouch_on_demand") # isort:skip # no red dots on right click
|
||||
|
||||
import signal
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
from queue import Queue
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.resources import resource_add_path
|
||||
|
||||
from kivy.app import App
|
||||
from kivymd.app import MDApp
|
||||
from kivy.core.clipboard import Clipboard
|
||||
from kivy.lang import Builder
|
||||
from kivy.properties import ObjectProperty
|
||||
from kivy.resources import resource_add_path
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.uix.screenmanager import Screen
|
||||
from kivymd.app import MDApp
|
||||
|
||||
from katrain.core.ai import ai_move
|
||||
from katrain.core.common import OUTPUT_INFO, OUTPUT_ERROR, OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR, find_package_resource, LANGUAGE
|
||||
from katrain.core.common import (
|
||||
LANGUAGE,
|
||||
OUTPUT_DEBUG,
|
||||
OUTPUT_ERROR,
|
||||
OUTPUT_EXTRA_DEBUG,
|
||||
OUTPUT_INFO,
|
||||
OUTPUT_KATAGO_STDERR,
|
||||
Lang,
|
||||
find_package_resource,
|
||||
)
|
||||
from katrain.core.engine import KataGoEngine
|
||||
from katrain.core.game import Game, IllegalMoveException, KaTrainSGF
|
||||
from katrain.core.sgf_parser import Move, ParseError
|
||||
from katrain.gui.kivyutils import *
|
||||
from katrain.gui.graph import ScoreGraph
|
||||
from katrain.gui.badukpan import BadukPanWidget, AnalysisControls, BadukPanControls
|
||||
from katrain.gui.badukpan import AnalysisControls, BadukPanControls, BadukPanWidget
|
||||
from katrain.gui.controlspanel import ControlsPanel, RightButtonControls
|
||||
from katrain.gui.popups import NewGamePopup, ConfigPopup, LoadSGFPopup
|
||||
from katrain.core.common import Lang
|
||||
from katrain.gui.graph import ScoreGraph
|
||||
from katrain.gui.kivyutils import *
|
||||
from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup
|
||||
from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL
|
||||
|
||||
ICON = "img/icon.png"
|
||||
Config.set("kivy", "window_icon", ICON) # isort:skip # set icon
|
||||
Config.set("input", "mouse", "mouse,multitouch_on_demand") # isort:skip # no red dots on right click
|
||||
|
||||
|
||||
__version__ = "1.1.0"
|
||||
|
||||
@@ -105,7 +113,6 @@ class KaTrainGui(Screen):
|
||||
if self.engine:
|
||||
return
|
||||
self.board_gui.trainer_config = self.config("trainer")
|
||||
self.board_gui.ui_config = self.config("board_ui")
|
||||
self.engine = KataGoEngine(self, self.config("engine"))
|
||||
threading.Thread(target=self._message_loop_thread, daemon=True).start()
|
||||
self._do_new_game()
|
||||
@@ -139,11 +146,11 @@ class KaTrainGui(Screen):
|
||||
|
||||
# update engine status dot
|
||||
if not self.engine or not self.engine.katago_process or self.engine.katago_process.poll() is not None:
|
||||
self.controls.engine_status_col = self.config("board_ui/engine_down_col")
|
||||
self.controls.engine_status_col = ENGINE_DOWN_COL
|
||||
elif len(self.engine.queries) == 0:
|
||||
self.controls.engine_status_col = self.config("board_ui/engine_ready_col")
|
||||
self.controls.engine_status_col = ENGINE_READY_COL
|
||||
else:
|
||||
self.controls.engine_status_col = self.config("board_ui/engine_busy_col")
|
||||
self.controls.engine_status_col = ENGINE_BUSY_COL
|
||||
|
||||
# redraw
|
||||
if redraw_board:
|
||||
@@ -203,7 +210,7 @@ class KaTrainGui(Screen):
|
||||
def _do_play(self, coords):
|
||||
self.board_gui.animating_pv = None
|
||||
try:
|
||||
self.game.play(Move(coords, player=self.game.next_player))
|
||||
self.game.play(Move(coords, player=self.game.next_player.player))
|
||||
except IllegalMoveException as e:
|
||||
self.controls.set_status(f"Illegal Move: {str(e)}")
|
||||
|
||||
@@ -331,7 +338,6 @@ class KaTrainGui(Screen):
|
||||
return True
|
||||
|
||||
|
||||
|
||||
class KaTrainApp(MDApp):
|
||||
gui = ObjectProperty(None)
|
||||
language = StringProperty(LANGUAGE)
|
||||
|
||||
+2
-117
@@ -51,7 +51,8 @@
|
||||
],
|
||||
"eval_off_show_last": 3,
|
||||
"eval_show_ai": true,
|
||||
"lock_ai": false
|
||||
"lock_ai": false,
|
||||
"anim_pv_time": 0.5
|
||||
},
|
||||
"ai": {
|
||||
"Default": {
|
||||
@@ -133,122 +134,6 @@
|
||||
"_help_right": "Increase `line_weight` to penalize moves closer to the center more. Stops strategy after the 'endgame' fraction of the board is filled."
|
||||
}
|
||||
},
|
||||
"board_ui": {
|
||||
"anim_pv_time": 0.5,
|
||||
"engine_down_col": [
|
||||
0.8,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"engine_busy_col": [
|
||||
0.9,
|
||||
0.4,
|
||||
0.1,
|
||||
1
|
||||
],
|
||||
"engine_little_busy_col": [
|
||||
0.85,
|
||||
0.89,
|
||||
0.3,
|
||||
1
|
||||
],
|
||||
"engine_almost_done_col": [
|
||||
0.67,
|
||||
0.9,
|
||||
0.18,
|
||||
1
|
||||
],
|
||||
"engine_ready_col": [
|
||||
0.117,
|
||||
0.588,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"board_color": [0.85, 0.68, 0.40,1],
|
||||
"starpoint_size": 0.1,
|
||||
"stone_size": 0.475,
|
||||
"eval_dot_max_size": 0.5,
|
||||
"eval_dot_min_size": 0.25,
|
||||
"visit_frac_small": 0.1,
|
||||
"stones": {
|
||||
"B": [
|
||||
0.05,
|
||||
0.05,
|
||||
0.05
|
||||
],
|
||||
"W": [
|
||||
0.95,
|
||||
0.95,
|
||||
0.95
|
||||
]
|
||||
},
|
||||
"outline": {
|
||||
"B": [
|
||||
0.3,
|
||||
0.3,
|
||||
0.3,
|
||||
0.5
|
||||
],
|
||||
"W": [
|
||||
0.7,
|
||||
0.7,
|
||||
0.7,
|
||||
0.5
|
||||
]
|
||||
},
|
||||
"ghost_alpha": 0.5,
|
||||
"top_move_x_alpha": 0.3,
|
||||
"child_scale": 0.95,
|
||||
"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
|
||||
],
|
||||
[
|
||||
0.117,
|
||||
0.588,
|
||||
0,
|
||||
1.0
|
||||
]
|
||||
],
|
||||
"line_color": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"policy_color": [
|
||||
0.9,
|
||||
0.2,
|
||||
0.8
|
||||
]
|
||||
},
|
||||
"debug": {
|
||||
"level": 1,
|
||||
"_hint_level": "0: Little console output.\n1: Some extra output\nfor error reporting."
|
||||
|
||||
+10
-7
@@ -17,9 +17,7 @@ OUTPUT_INFO = 0
|
||||
OUTPUT_DEBUG = 1
|
||||
OUTPUT_EXTRA_DEBUG = 2
|
||||
|
||||
PLAYER_HUMAN = 0
|
||||
PLAYER_HUMAN_TEACHING = 1
|
||||
PLAYER_AI = 2
|
||||
MODE_PLAY, MODE_ANALYZE = "play", "analyze"
|
||||
|
||||
|
||||
def var_to_grid(array_var: List[Any], size: Tuple[int, int]) -> List[List[Any]]:
|
||||
@@ -63,7 +61,8 @@ class Lang(Observable):
|
||||
self.switch_lang(self.lang)
|
||||
|
||||
def _(self, text):
|
||||
return self.ugettext(text)
|
||||
str = self.ugettext(text)
|
||||
return str
|
||||
|
||||
def fbind(self, name, func, *args, **kwargs):
|
||||
if name == "_":
|
||||
@@ -74,7 +73,7 @@ class Lang(Observable):
|
||||
def funbind(self, name, func, *args, **kwargs):
|
||||
if name == "_":
|
||||
key = (func, args, kwargs)
|
||||
print('funbind',key in self.observers)
|
||||
print("funbind", key in self.observers)
|
||||
if key in self.observers:
|
||||
self.observers.remove(key)
|
||||
else:
|
||||
@@ -92,16 +91,20 @@ class Lang(Observable):
|
||||
try:
|
||||
func(args[0], None, None)
|
||||
except ReferenceError:
|
||||
pass # proxy no longer exists
|
||||
pass # proxy no longer exists
|
||||
|
||||
|
||||
LANGUAGE = "nl"
|
||||
i18n = Lang(LANGUAGE)
|
||||
|
||||
|
||||
class I18NLabel(Label):
|
||||
pass
|
||||
|
||||
|
||||
class I18NCoreLabel(CoreLabel):
|
||||
pass
|
||||
|
||||
|
||||
class I18NTextInput(TextInput):
|
||||
pass
|
||||
pass
|
||||
+43
-5
@@ -19,15 +19,47 @@ class KaTrainSGF(SGF):
|
||||
_NODE_CLASS = GameNode
|
||||
|
||||
|
||||
PLAYER_HUMAN, PLAYER_AI = "Player", "AI"
|
||||
PLAYING_NORMAL, PLAYING_TEACHING = "Normal game", "Teaching game"
|
||||
AI_DEFAULT = "default"
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, player="B", player_type=PLAYER_HUMAN, player_subtype=PLAYING_NORMAL, periods_used=0):
|
||||
self.player = player
|
||||
self.player_type = player_type
|
||||
self.player_subtype = player_subtype
|
||||
self.periods_used = periods_used
|
||||
|
||||
@property
|
||||
def ai(self):
|
||||
return self.player_type == PLAYER_AI
|
||||
|
||||
@property
|
||||
def human(self):
|
||||
return self.player_type == PLAYER_HUMAN
|
||||
|
||||
@property
|
||||
def being_taught(self):
|
||||
return self.player_type == PLAYER_HUMAN and self.player_subtype == PLAYING_TEACHING
|
||||
|
||||
def strategy(self):
|
||||
return self.player_subtype if self.ai else AI_DEFAULT
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.player_type} ({self.player_subtype})"
|
||||
|
||||
|
||||
class Game:
|
||||
"""Represents a game of go, including an implementation of capture rules."""
|
||||
|
||||
DEFAULT_PROPERTIES = {"GM": 1, "FF": 4, "RU": "JP", "AP": "KaTrain:https://github.com/sanderland/katrain"}
|
||||
|
||||
def __init__(self, katrain, engine: Union[Dict, KataGoEngine], config: Dict, move_tree: GameNode = None, analyze_fast=False):
|
||||
def __init__(self, katrain, engine: Union[Dict, KataGoEngine], config: Dict, move_tree: GameNode = None, players: Dict = None, analyze_fast=False):
|
||||
self.katrain = katrain
|
||||
if not isinstance(engine, Dict):
|
||||
engine = {"B": engine, "W": engine}
|
||||
self.players = players or {"B": Player("B"), "W": Player("W")}
|
||||
self.engines = engine
|
||||
self.config = config
|
||||
self.game_id = datetime.strftime(datetime.now(), "%Y-%m-%d %H %M %S")
|
||||
@@ -42,10 +74,20 @@ class Game:
|
||||
board_size = config.get("init_size", 19)
|
||||
self.komi = self.config.get("init_komi", 6.5)
|
||||
self.root = GameNode(properties={**Game.DEFAULT_PROPERTIES, **{"SZ": board_size, "KM": self.komi, "DT": self.game_id}})
|
||||
self.root.set_property("PW", str(self.players["W"]))
|
||||
self.root.set_property("PB", str(self.players["B"]))
|
||||
|
||||
self.set_current_node(self.root)
|
||||
threading.Thread(target=lambda: self.analyze_all_nodes(-1_000_000, analyze_fast=analyze_fast), daemon=True).start() # return faster, but bypass Kivy Clock
|
||||
|
||||
@property
|
||||
def last_player(self) -> Player:
|
||||
return self.players[self.current_node.player]
|
||||
|
||||
@property
|
||||
def next_player(self) -> Player:
|
||||
return self.players[self.current_node.next_player]
|
||||
|
||||
def analyze_all_nodes(self, priority=0, analyze_fast=False):
|
||||
for node in self.root.nodes_in_tree:
|
||||
node.analyze(self.engines[node.next_player], priority=priority, analyze_fast=analyze_fast)
|
||||
@@ -181,10 +223,6 @@ class Game:
|
||||
def board_size(self):
|
||||
return self.root.board_size
|
||||
|
||||
@property
|
||||
def next_player(self):
|
||||
return self.current_node.next_player
|
||||
|
||||
@property
|
||||
def stones(self):
|
||||
return sum(self.chains, [])
|
||||
|
||||
@@ -94,7 +94,6 @@ class GameNode(SGFNode):
|
||||
def analysis_ready(self):
|
||||
return self.analysis["root"] is not None
|
||||
|
||||
|
||||
@property
|
||||
def score(self) -> Optional[float]:
|
||||
if self.analysis_ready:
|
||||
@@ -179,7 +178,6 @@ class GameNode(SGFNode):
|
||||
score = self.score
|
||||
return self.player_sign(single_move.player) * (score - parent_parent_score)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def player_sign(player):
|
||||
return {"B": 1, "W": -1, None: 0}[player]
|
||||
|
||||
+27
-32
@@ -30,9 +30,6 @@
|
||||
<I18NLabel>:
|
||||
font_name: i18n._('Roboto')
|
||||
|
||||
<I18NCoreLabel>:
|
||||
font_name: i18n._('Roboto')
|
||||
|
||||
<I18NTextInput>:
|
||||
font_name: i18n._('Roboto')
|
||||
|
||||
@@ -100,11 +97,11 @@
|
||||
|
||||
<AutoSizedMDFlatRectangleButton>: # only wants height
|
||||
width: self.label.texture_size[0] + self.hor_padding * 2
|
||||
font_size: 0.75 * self.height / len(self.label.text.split('\n'))
|
||||
font_size: 0.8 * self.height / len(self.label.text.split('\n'))
|
||||
|
||||
<AutoSizedMDFlatRectangleToggleButton>: # only wants height
|
||||
width: self.label.texture_size[0] + self.hor_padding * 2
|
||||
font_size: 0.75 * self.height / len(self.label.text.split('\n'))
|
||||
font_size: 0.8 * self.height / len(self.label.text.split('\n'))
|
||||
|
||||
<BGMDBoxLayout@MDBoxLayout+BackgroundColor>:
|
||||
background_color: BOX_BACKGROUND_COLOR
|
||||
@@ -253,19 +250,19 @@
|
||||
<CircleWithText>:
|
||||
min_size: min(self.height,self.width)
|
||||
Image:
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
|
||||
id: image
|
||||
pos: (root.pos[0] + (root.width - root.min_size)/2,root.pos[1] + (root.height - root.min_size)/2)
|
||||
size: (root.min_size,root.min_size)
|
||||
source: 'img/white.png' if root.player=='White' else 'img/black.png'
|
||||
mipmap: True
|
||||
I18NLabel:
|
||||
Label:
|
||||
id: lbl
|
||||
text: root.text
|
||||
bold: True
|
||||
color: BLACK if root.player=='White' else WHITE
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
|
||||
size: (root.min_size,root.min_size)
|
||||
halign: 'center'
|
||||
valign: 'middle'
|
||||
font_size: root.min_size * 1.2 / (1 + 1 * len(self.text)) # TODO: easier?
|
||||
pos: (image.x, image.y+2)
|
||||
size: image.size
|
||||
font_size: root.min_size * 1.2 / (1 + 1 * len(self.text))
|
||||
|
||||
# UI components
|
||||
|
||||
@@ -387,8 +384,6 @@
|
||||
|
||||
# ---------- components of right pane -------
|
||||
|
||||
|
||||
|
||||
<PlayerInfo@MDBoxLayout>:
|
||||
captures: 0
|
||||
name: 'Player'
|
||||
@@ -548,26 +543,24 @@
|
||||
opacity: int(root.show_winrate)
|
||||
|
||||
|
||||
<StatsBox@BoxLayout+BackgroundColor>
|
||||
<StatsBox>
|
||||
orientation: 'vertical'
|
||||
background_color: BOX_BACKGROUND_COLOR
|
||||
score: score
|
||||
winrate: winrate
|
||||
score_change: score_change
|
||||
labels: {'score':score_label,'winrate':winrate_label,'points':pointloss_label}
|
||||
CensorableLabel:
|
||||
id: winrate
|
||||
id: winrate_label
|
||||
label: 'Win rate'
|
||||
text: ''
|
||||
text: root.winrate
|
||||
color: WINRATE_COLOR
|
||||
CensorableLabel:
|
||||
id: score
|
||||
id: score_label
|
||||
label: 'Score'
|
||||
text: ''
|
||||
text: root.score
|
||||
color: SCORE_COLOR
|
||||
CensorableLabel:
|
||||
id: score_change
|
||||
label: 'Points Lost'
|
||||
text: ''
|
||||
id: pointloss_label
|
||||
label: 'Points Lost' if root.points_lost is None or root.points_lost > 0 else 'Points gained'
|
||||
text: '{}: {:.1f}'.format(root.player,abs(root.points_lost)) if root.points_lost is not None else '...'
|
||||
color: POINTLOSS_COLOR
|
||||
|
||||
<ScrollableLabel>:
|
||||
@@ -678,7 +671,7 @@
|
||||
on_ref_press: root.katrain.board_gui.show_pv_from_comments(args[1])
|
||||
size_hint: 1, None
|
||||
height: self.parent.height - status.height - note.height - 1
|
||||
TextInput:
|
||||
I18NTextInput:
|
||||
id: note
|
||||
size_hint: 1, None
|
||||
on_text: root.katrain.set_note(self.text)
|
||||
@@ -765,6 +758,11 @@
|
||||
font_size: root.font_size
|
||||
color: GREY
|
||||
|
||||
|
||||
<LangButton@SizedMDFlatRectangleButton>
|
||||
size_hint: 1,1
|
||||
font_size: 0.4 * self.height
|
||||
|
||||
<ContentNavigationDrawer@MDBoxLayout+BackgroundColor>:
|
||||
katrain: app.gui
|
||||
background_color: BOX_BACKGROUND_COLOR
|
||||
@@ -783,18 +781,15 @@
|
||||
Line:
|
||||
rectangle: (*self.pos,*self.size)
|
||||
width: 1
|
||||
SizedMDFlatButton:
|
||||
LangButton:
|
||||
text: i18n._('English')
|
||||
on_press: app.language = 'en'
|
||||
size_hint: 1/3,1
|
||||
SizedMDFlatButton:
|
||||
LangButton:
|
||||
text: i18n._('Dutch')
|
||||
on_press: app.language = 'nl'
|
||||
size_hint: 1/3,1
|
||||
SizedMDFlatButton:
|
||||
LangButton:
|
||||
text: i18n._('Korean')
|
||||
on_press: app.language = 'ko'
|
||||
size_hint: 1/3,1
|
||||
Label:
|
||||
MDBoxLayout:
|
||||
canvas.before:
|
||||
|
||||
+50
-41
@@ -11,17 +11,33 @@ from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
from kivymd.uix.menu import MDDropdownMenu
|
||||
|
||||
from katrain.core.common import OUTPUT_DEBUG, evaluation_class, PLAYER_AI, i18n
|
||||
from katrain.core.common import OUTPUT_DEBUG, evaluation_class, i18n, MODE_PLAY
|
||||
from katrain.core.game import Move
|
||||
from katrain.gui.kivyutils import draw_circle, draw_text
|
||||
from katrain.core.common import var_to_grid
|
||||
from kivy.core.window import Window
|
||||
|
||||
from katrain.gui.style import (
|
||||
EVAL_COLORS,
|
||||
LINE_COLOR,
|
||||
POLICY_COLOR,
|
||||
GHOST_ALPHA,
|
||||
TOP_MOVE_ALPHA,
|
||||
STONE_COLORS,
|
||||
OUTLINE_COLORS,
|
||||
CHILD_SCALE,
|
||||
EVAL_DOT_MIN_SIZE,
|
||||
EVAL_DOT_MAX_SIZE,
|
||||
BOARD_COLOR,
|
||||
STARPOINT_SIZE,
|
||||
STONE_SIZE,
|
||||
VISITS_FRAC_SMALL,
|
||||
)
|
||||
|
||||
|
||||
class BadukPanWidget(Widget):
|
||||
def __init__(self, **kwargs):
|
||||
super(BadukPanWidget, self).__init__(**kwargs)
|
||||
self.ui_config = {}
|
||||
self.trainer_config = {}
|
||||
self.ghost_stone = []
|
||||
self.gridpos_x = []
|
||||
@@ -124,7 +140,7 @@ class BadukPanWidget(Widget):
|
||||
Line(circle=(self.gridpos_x[x], self.gridpos_y[y], stone_size), width=min(2, 0.035 * stone_size))
|
||||
if evalcol:
|
||||
eval_radius = math.sqrt(evalscale) # scale area by evalscale
|
||||
evalsize = self.stone_size * (self.ui_config["eval_dot_min_size"] + eval_radius * (self.ui_config["eval_dot_max_size"] - self.ui_config["eval_dot_min_size"]))
|
||||
evalsize = self.stone_size * (EVAL_DOT_MIN_SIZE + eval_radius * (EVAL_DOT_MAX_SIZE - EVAL_DOT_MIN_SIZE))
|
||||
draw_circle((self.gridpos_x[x], self.gridpos_y[y]), evalsize, evalcol)
|
||||
|
||||
if innercol:
|
||||
@@ -132,12 +148,12 @@ class BadukPanWidget(Widget):
|
||||
Line(circle=(self.gridpos_x[x], self.gridpos_y[y], stone_size * 0.475 / 0.85), width=0.1 * stone_size)
|
||||
|
||||
def eval_color(self, points_lost):
|
||||
colors = self.ui_config["eval_colors"]
|
||||
colors = EVAL_COLORS
|
||||
i = evaluation_class(points_lost, self.trainer_config["eval_thresholds"])
|
||||
return colors[i]
|
||||
|
||||
def draw_board(self, *_args):
|
||||
if not self.ui_config:
|
||||
if not (self.katrain and self.katrain.game):
|
||||
return
|
||||
katrain = self.katrain
|
||||
board_size_x, board_size_y = katrain.game.board_size
|
||||
@@ -154,18 +170,17 @@ class BadukPanWidget(Widget):
|
||||
board_height_with_margins = y_grid_spaces * self.grid_size
|
||||
extra_px_margin_x = (self.width - board_width_with_margins) / 2
|
||||
extra_px_margin_y = (self.height - board_height_with_margins) / 2
|
||||
self.stone_size = self.grid_size * self.ui_config["stone_size"]
|
||||
self.stone_size = self.grid_size * STONE_SIZE
|
||||
|
||||
self.gridpos_x = [self.pos[0] + extra_px_margin_x + math.floor((grid_spaces_margin_x[0] + i) * self.grid_size + 0.5) for i in range(board_size_x)]
|
||||
self.gridpos_y = [self.pos[1] + extra_px_margin_y + math.floor((grid_spaces_margin_y[0] + i) * self.grid_size + 0.5) for i in range(board_size_y)]
|
||||
|
||||
Color(*self.ui_config["board_color"])
|
||||
Color(*BOARD_COLOR)
|
||||
Rectangle(
|
||||
pos=(self.gridpos_x[0] - self.grid_size * 1.5, self.gridpos_y[0] - self.grid_size * 1.5), size=(self.grid_size * x_grid_spaces, self.grid_size * y_grid_spaces)
|
||||
)
|
||||
|
||||
line_color = self.ui_config["line_color"]
|
||||
Color(*line_color)
|
||||
Color(*LINE_COLOR)
|
||||
for i in range(board_size_x):
|
||||
Line(points=[(self.gridpos_x[i], self.gridpos_y[0]), (self.gridpos_x[i], self.gridpos_y[-1])])
|
||||
for i in range(board_size_y):
|
||||
@@ -178,10 +193,10 @@ class BadukPanWidget(Widget):
|
||||
return []
|
||||
return [star_point_pos - 1, size - star_point_pos] + ([int(size / 2)] if size % 2 == 1 and size > 7 else [])
|
||||
|
||||
starpt_size = self.grid_size * self.ui_config["starpoint_size"]
|
||||
starpt_size = self.grid_size * STARPOINT_SIZE
|
||||
for x in star_point_coords(board_size_x):
|
||||
for y in star_point_coords(board_size_y):
|
||||
draw_circle((self.gridpos_x[x], self.gridpos_y[y]), starpt_size, line_color)
|
||||
draw_circle((self.gridpos_x[x], self.gridpos_y[y]), starpt_size, LINE_COLOR)
|
||||
|
||||
# coordinates
|
||||
Color(0.25, 0.25, 0.25)
|
||||
@@ -192,10 +207,10 @@ class BadukPanWidget(Widget):
|
||||
draw_text(pos=(self.gridpos_x[0] - coord_offset, self.gridpos_y[i]), text=str(i + 1), font_size=self.grid_size / 1.5)
|
||||
|
||||
def draw_board_contents(self, *_args):
|
||||
if not self.ui_config:
|
||||
if not (self.katrain and self.katrain.game):
|
||||
return
|
||||
stone_color = self.ui_config["stones"]
|
||||
outline_color = self.ui_config["outline"]
|
||||
stone_color = STONE_COLORS
|
||||
outline_color = OUTLINE_COLORS
|
||||
katrain = self.katrain
|
||||
board_size_x, board_size_y = katrain.game.board_size
|
||||
lock_ai = self.trainer_config.get("lock_ai", False) and katrain.controls.teaching_mode_enabled()
|
||||
@@ -212,7 +227,7 @@ class BadukPanWidget(Widget):
|
||||
for m in katrain.game.stones:
|
||||
has_stone[m.coords] = m.player
|
||||
|
||||
show_dots_for = {p: self.trainer_config["eval_show_ai"] or katrain.controls.player_mode(p) != PLAYER_AI for p in Move.PLAYERS}
|
||||
show_dots_for = {p: self.trainer_config["eval_show_ai"] or katrain.game.players[p].human for p in Move.PLAYERS}
|
||||
nodes = katrain.game.current_node.nodes_from_root
|
||||
realized_points_lost = None
|
||||
for i, node in enumerate(nodes[::-1]): # reverse order!
|
||||
@@ -257,13 +272,7 @@ class BadukPanWidget(Widget):
|
||||
Rectangle(pos=(self.gridpos_x[x] - rsz / 2, self.gridpos_y[y] - rsz / 2), size=(rsz, rsz))
|
||||
|
||||
policy = current_node.policy
|
||||
if (
|
||||
not policy
|
||||
and current_node.parent
|
||||
and current_node.parent.policy
|
||||
and katrain.controls.player_mode("B") == PLAYER_AI
|
||||
and katrain.controls.player_mode("W") == PLAYER_AI
|
||||
):
|
||||
if not policy and current_node.parent and current_node.parent.policy and katrain.game.last_player.ai and katrain.game.next_player.ai:
|
||||
policy = current_node.parent.policy # in the case of AI self-play we allow the policy to be one step out of date
|
||||
|
||||
pass_btn = katrain.board_controls.pass_btn
|
||||
@@ -276,13 +285,13 @@ class BadukPanWidget(Widget):
|
||||
if policy_grid[y][x] > 0:
|
||||
polsize = 1.1 * math.sqrt(policy_grid[y][x])
|
||||
policy_circle_color = (
|
||||
*self.ui_config["policy_color"],
|
||||
self.ui_config["ghost_alpha"] + self.ui_config["top_move_x_alpha"] * (policy_grid[y][x] == best_move_policy),
|
||||
*POLICY_COLOR,
|
||||
GHOST_ALPHA + TOP_MOVE_ALPHA * (policy_grid[y][x] == best_move_policy),
|
||||
)
|
||||
self.draw_stone(x, y, policy_circle_color, scale=polsize)
|
||||
polsize = math.sqrt(policy[-1])
|
||||
with pass_btn.canvas.after:
|
||||
draw_circle((pass_btn.pos[0] + pass_btn.width / 2, pass_btn.pos[1] + pass_btn.height / 2), polsize * pass_btn.height / 2, self.ui_config["policy_color"])
|
||||
draw_circle((pass_btn.pos[0] + pass_btn.width / 2, pass_btn.pos[1] + pass_btn.height / 2), polsize * pass_btn.height / 2, POLICY_COLOR)
|
||||
|
||||
# pass circle
|
||||
passed = len(nodes) > 1 and current_node.is_pass
|
||||
@@ -301,21 +310,20 @@ class BadukPanWidget(Widget):
|
||||
self.draw_hover_contents()
|
||||
|
||||
def draw_hover_contents(self, *_args):
|
||||
ghost_alpha = self.ui_config["ghost_alpha"]
|
||||
ghost_alpha = GHOST_ALPHA
|
||||
katrain = self.katrain
|
||||
game_ended = katrain.game.ended
|
||||
current_node = katrain.game.current_node
|
||||
player, next_player = current_node.player, current_node.next_player
|
||||
stone_color = self.ui_config["stones"]
|
||||
teaching = katrain.controls.teaching_mode_enabled()
|
||||
lock_ai = self.trainer_config.get("lock_ai", False) and teaching
|
||||
stone_color = STONE_COLORS
|
||||
lock_ai = self.trainer_config.get("lock_ai", False) and self.katrain.controls.play_analyze_mode == MODE_PLAY
|
||||
|
||||
self.canvas.after.clear()
|
||||
with self.canvas.after:
|
||||
self.active_pv_moves = []
|
||||
|
||||
# children of current moves in undo / review
|
||||
alpha = self.ui_config["ghost_alpha"]
|
||||
alpha = GHOST_ALPHA
|
||||
if katrain.analysis_controls.show_children.active:
|
||||
for child_node in current_node.children:
|
||||
points_lost = child_node.points_lost
|
||||
@@ -326,10 +334,9 @@ class BadukPanWidget(Widget):
|
||||
else:
|
||||
evalcol = copy.copy(self.eval_color(points_lost))
|
||||
evalcol[3] = alpha
|
||||
# if ((teaching and child_node.auto_undo) or katrain.controls.play_analyze_mode == "analyze") and child_node.analysis_ready: # TODO:?
|
||||
if child_node.analysis_ready:
|
||||
self.active_pv_moves.append((move.coords, [move.gtp()] + child_node.candidate_moves[0]["pv"], current_node))
|
||||
scale = self.ui_config["child_scale"]
|
||||
scale = CHILD_SCALE
|
||||
self.draw_stone(move.coords[0], move.coords[1], (*stone_color[move.player][:3], alpha), None, None, evalcol, evalscale=scale, scale=scale)
|
||||
|
||||
# hints or PV
|
||||
@@ -338,10 +345,10 @@ class BadukPanWidget(Widget):
|
||||
for i, move_dict in enumerate(hint_moves):
|
||||
move = Move.from_gtp(move_dict["move"])
|
||||
if move.coords is not None:
|
||||
alpha, scale = self.ui_config["ghost_alpha"], 1.0
|
||||
alpha, scale = GHOST_ALPHA, 1.0
|
||||
if i == 0:
|
||||
alpha += self.ui_config["top_move_x_alpha"]
|
||||
elif move_dict["visits"] < self.ui_config["visit_frac_small"] * hint_moves[0]["visits"]:
|
||||
alpha += TOP_MOVE_ALPHA
|
||||
elif move_dict["visits"] < VISITS_FRAC_SMALL * hint_moves[0]["visits"]:
|
||||
scale = 0.8
|
||||
if "pv" in move_dict:
|
||||
self.active_pv_moves.append((move.coords, move_dict["pv"], current_node))
|
||||
@@ -356,7 +363,7 @@ class BadukPanWidget(Widget):
|
||||
animating_pv = self.animating_pv
|
||||
if animating_pv:
|
||||
pv, node, start_time, _ = animating_pv
|
||||
delay = self.ui_config.get("anim_pv_time", 0.5)
|
||||
delay = self.trainer_config.get("anim_pv_time", 0.5)
|
||||
up_to_move = (time.time() - start_time) / delay
|
||||
self.draw_pv(pv, node, up_to_move)
|
||||
|
||||
@@ -367,7 +374,7 @@ class BadukPanWidget(Widget):
|
||||
def draw_pv(self, pv, node, up_to_move):
|
||||
katrain = self.katrain
|
||||
next_last_player = [node.next_player, node.player]
|
||||
stone_color = self.ui_config["stones"]
|
||||
stone_color = STONE_COLORS
|
||||
cn = katrain.game.current_node
|
||||
if node != cn and node.parent != cn:
|
||||
hide_node = cn
|
||||
@@ -401,18 +408,20 @@ class BadukPanWidget(Widget):
|
||||
def show_pv_from_comments(self, pv_str):
|
||||
self.set_animating_pv(pv_str[1:].split(" "), self.katrain.controls.active_comment_node.parent)
|
||||
|
||||
|
||||
class AnalysisControls(MDBoxLayout):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.analysis_menu=None
|
||||
Clock.schedule_once(self.build_menu,0)
|
||||
self.analysis_menu = None
|
||||
Clock.schedule_once(self.build_menu, 0)
|
||||
|
||||
def build_menu(self,_dt):
|
||||
def build_menu(self, _dt):
|
||||
menu_items = [{"icon": "git", "text": f"Item {i}"} for i in range(5)]
|
||||
self.analysis_menu = MDDropdownMenu(caller=self.analysis_button, items=menu_items, width_mult=4)
|
||||
|
||||
def open_analysis_menu(self):
|
||||
self.analysis_menu.open()
|
||||
|
||||
|
||||
class BadukPanControls(MDFloatLayout):
|
||||
pass
|
||||
pass
|
||||
@@ -6,7 +6,7 @@ from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.popup import Popup
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
from katrain.core.common import PLAYER_HUMAN, PLAYER_AI, PLAYER_HUMAN_TEACHING
|
||||
from katrain.core.common import MODE_PLAY, MODE_ANALYZE
|
||||
from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup
|
||||
from katrain.gui.ai_settings import ConfigAIPopupContents
|
||||
|
||||
@@ -28,7 +28,6 @@ class ControlsPanel(BoxLayout):
|
||||
self.active_comment_node = None
|
||||
self.timer_settings_popup = None
|
||||
self.last_timer_update = (None, 0)
|
||||
self.periods_used = {"B": 0, "W": 0}
|
||||
Clock.schedule_interval(self.update_timer, 0.07)
|
||||
|
||||
def check_hide_show(self, *_args):
|
||||
@@ -42,66 +41,47 @@ class ControlsPanel(BoxLayout):
|
||||
|
||||
@property
|
||||
def play_analyze_mode(self):
|
||||
return "analyze" # ??
|
||||
|
||||
def player_mode(self, player):
|
||||
return PLAYER_HUMAN
|
||||
|
||||
def ai_mode(self, player):
|
||||
return self.ai_mode_groups[player].text
|
||||
|
||||
def teaching_mode_enabled(self):
|
||||
return PLAYER_HUMAN_TEACHING in [self.player_mode("B"), self.player_mode("W")]
|
||||
|
||||
def on_size(self, *args):
|
||||
self.update_evaluation()
|
||||
return MODE_ANALYZE # ??
|
||||
|
||||
# handles showing completed analysis and score graph
|
||||
def update_evaluation(self):
|
||||
katrain = self.katrain
|
||||
current_node = katrain and katrain.game and katrain.game.current_node
|
||||
|
||||
if current_node is not self.status_node and not (self.status is not None and self.status_node is None and current_node.is_root): # startup errors on root
|
||||
game = katrain and katrain.game
|
||||
if not game:
|
||||
return
|
||||
current_node, move = game.current_node, game.current_node.move
|
||||
if game.current_node is not self.status_node and not (self.status is not None and self.status_node is None and game.current_node.is_root): # startup errors on root
|
||||
self.status.text = ""
|
||||
self.status_node = None
|
||||
|
||||
info = ""
|
||||
both_players_are_robots = all(p.ai for p in game.players.values())
|
||||
last_player_was_ai_playing_human = game.last_player.ai and game.next_player.human
|
||||
|
||||
if current_node:
|
||||
hints = katrain.analysis_controls.hints.active
|
||||
move = current_node.move
|
||||
both_players_are_robots = self.player_mode(current_node.player) == PLAYER_AI and self.player_mode(current_node.next_player) == PLAYER_AI
|
||||
last_player_was_human_or_both_robots = current_node.player and (self.player_mode(current_node.player) != PLAYER_AI or both_players_are_robots)
|
||||
last_player_was_ai_playing_human = (
|
||||
current_node.player and self.player_mode(current_node.player) == PLAYER_AI and self.player_mode(current_node.next_player) != PLAYER_AI
|
||||
)
|
||||
if last_player_was_human_or_both_robots and not current_node.is_root and move:
|
||||
info += current_node.comment(teach=self.player_mode(current_node.player) == PLAYER_HUMAN_TEACHING, hints=hints)
|
||||
self.active_comment_node = current_node
|
||||
elif last_player_was_ai_playing_human and current_node.parent:
|
||||
info += current_node.parent.comment(teach=self.player_mode(current_node.next_player) == PLAYER_HUMAN_TEACHING, hints=hints)
|
||||
self.active_comment_node = current_node
|
||||
if self.play_analyze_mode == MODE_PLAY and last_player_was_ai_playing_human:
|
||||
if game.next_player.being_taught and current_node.children and current_node.children.auto_undo:
|
||||
self.active_comment_node = current_node.children[-1]
|
||||
elif current_node.parent:
|
||||
self.active_comment_node = current_node.parent
|
||||
|
||||
if current_node.analysis_ready:
|
||||
self.stats.score.text = current_node.format_score()
|
||||
self.stats.winrate.text = current_node.format_winrate()
|
||||
if move and last_player_was_human_or_both_robots: # don't immediately hide this when an ai moves comes in
|
||||
points_lost = current_node.points_lost
|
||||
self.stats.score_change.label = f"Points lost" if points_lost and points_lost > 0 else f"Points gained"
|
||||
self.stats.score_change.text = f"{move.player}: {abs(points_lost):.1f}" if points_lost else "-"
|
||||
elif not last_player_was_ai_playing_human:
|
||||
self.stats.score_change.label = f"Points lost"
|
||||
self.stats.score_change.text = "-"
|
||||
elif last_player_was_ai_playing_human and current_node.parent and current_node.parent.move:
|
||||
points_lost = current_node.parent.points_lost
|
||||
self.stats.score_change.label = f"Points lost" if points_lost and points_lost > 0 else f"Points gained"
|
||||
self.stats.score_change.text = f"{current_node.parent.move.player}: {abs(points_lost):.1f}" if points_lost else "-"
|
||||
elif both_players_are_robots and current_node.parent and current_node.parent.analysis_ready:
|
||||
self.stats.score.text = current_node.parent.format_score()
|
||||
self.stats.winrate.text = current_node.parent.format_winrate()
|
||||
hints = katrain.analysis_controls.hints.active
|
||||
info = ""
|
||||
if current_node.move and not current_node.is_root:
|
||||
info = self.active_comment_node.comment(teach=game.players[self.active_comment_node.player].being_taught, hints=hints)
|
||||
|
||||
self.graph.update_value(current_node)
|
||||
self.note.text = current_node.note
|
||||
if self.active_comment_node.analysis_ready:
|
||||
self.stats.score = self.active_comment_node.format_score() or ""
|
||||
self.stats.winrate = self.active_comment_node.format_winrate() or ""
|
||||
self.stats.points_lost = self.active_comment_node.points_lost
|
||||
self.stats.player = self.active_comment_node.player
|
||||
else:
|
||||
self.stats.score = ""
|
||||
self.stats.winrate = ""
|
||||
self.stats.points_lost = None
|
||||
self.stats.player = ""
|
||||
|
||||
self.graph.update_value(current_node)
|
||||
self.note.text = current_node.note
|
||||
self.info.text = info
|
||||
|
||||
def configure_ais(self):
|
||||
@@ -122,10 +102,10 @@ class ControlsPanel(BoxLayout):
|
||||
last_update_node, last_update_time = self.last_timer_update
|
||||
now = time.time()
|
||||
self.last_timer_update = (current_node, now)
|
||||
player = current_node.next_player
|
||||
byo_len = max(1, self.katrain.config("timer/byo_length"))
|
||||
byo_num = max(1, self.katrain.config("timer/byo_num"))
|
||||
ai = self.player_mode(player) == PLAYER_AI
|
||||
player = self.katrain.game.next_player
|
||||
ai = player.ai
|
||||
if not self.timer.paused and not ai:
|
||||
if last_update_node == current_node and not current_node.children:
|
||||
current_node.time_used += now - last_update_time
|
||||
@@ -135,9 +115,9 @@ class ControlsPanel(BoxLayout):
|
||||
while time_remaining < 0:
|
||||
current_node.time_used -= byo_len
|
||||
time_remaining += byo_len
|
||||
self.periods_used[player] += 1
|
||||
player.periods_used += 1
|
||||
time_remaining = byo_len - current_node.time_used
|
||||
periods_rem = byo_num - self.periods_used[player]
|
||||
periods_rem = byo_num - player.periods_used
|
||||
self.timer.state = (time_remaining, periods_rem, ai)
|
||||
|
||||
def configure_timer(self):
|
||||
|
||||
+11
-9
@@ -33,10 +33,10 @@ class ScoreGraph(BackgroundColor):
|
||||
self.nodes.append(node)
|
||||
self.highlighted_index = 0
|
||||
|
||||
def show_graphs(self,keys):
|
||||
self.show_score = keys['score']
|
||||
self.show_winrate = keys['winrate']
|
||||
self.show_pointloss = keys['points']
|
||||
def show_graphs(self, keys):
|
||||
self.show_score = keys["score"]
|
||||
self.show_winrate = keys["winrate"]
|
||||
self.show_pointloss = keys["points"]
|
||||
|
||||
def update_graph(self, *args):
|
||||
nodes = self.nodes
|
||||
@@ -45,21 +45,23 @@ class ScoreGraph(BackgroundColor):
|
||||
score_nn_values = [n.score for n in nodes if n and n.score]
|
||||
score_values_range = min(score_nn_values or [0]), max(score_nn_values or [0])
|
||||
|
||||
winrate_values = [(n.winrate-0.5)*100 if n and n.winrate else math.nan for n in nodes]
|
||||
winrate_nn_values = [(n.winrate-0.5)*100 for n in nodes if n and n.winrate]
|
||||
winrate_values = [(n.winrate - 0.5) * 100 if n and n.winrate else math.nan for n in nodes]
|
||||
winrate_nn_values = [(n.winrate - 0.5) * 100 for n in nodes if n and n.winrate]
|
||||
winrate_values_range = min(winrate_nn_values or [0]), max(winrate_nn_values or [0])
|
||||
|
||||
score_granularity = 5
|
||||
winrate_granularity = 10
|
||||
self.score_scale = max(math.ceil( max(-score_values_range[0], score_values_range[1]) / score_granularity),1) * score_granularity
|
||||
self.score_scale = max(math.ceil(max(-score_values_range[0], score_values_range[1]) / score_granularity), 1) * score_granularity
|
||||
self.winrate_scale = max(math.ceil(max(-winrate_values_range[0], winrate_values_range[1]) / winrate_granularity), 1) * winrate_granularity
|
||||
|
||||
xscale = self.width / max(len(score_values) - 1, 15)
|
||||
available_height = self.height
|
||||
score_line_points = [[self.pos[0] + i * xscale, self.pos[1] + self.height / 2 + available_height / 2 * (val / self.score_scale)] for i, val in enumerate(score_values)]
|
||||
winrate_line_points = [[self.pos[0] + i * xscale, self.pos[1] + self.height / 2 + available_height / 2 * (val / self.winrate_scale)] for i, val in enumerate(winrate_values)]
|
||||
winrate_line_points = [
|
||||
[self.pos[0] + i * xscale, self.pos[1] + self.height / 2 + available_height / 2 * (val / self.winrate_scale)] for i, val in enumerate(winrate_values)
|
||||
]
|
||||
self.score_points = sum(score_line_points, [])
|
||||
self.winrate_points = sum(winrate_line_points,[])
|
||||
self.winrate_points = sum(winrate_line_points, [])
|
||||
|
||||
if self.highlighted_index is not None:
|
||||
self.highlighted_index = min(self.highlighted_index, len(score_values) - 1)
|
||||
|
||||
+36
-23
@@ -23,13 +23,14 @@ from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.button import BasePressedButton, BaseFlatButton
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
from kivymd.uix.navigationdrawer import MDNavigationDrawer
|
||||
|
||||
from katrain.core.common import i18n, I18NLabel, I18NTextInput, I18NCoreLabel
|
||||
from kivy.core.text import Label as CoreLabel
|
||||
from katrain.core.common import i18n, I18NTextInput, I18NLabel
|
||||
|
||||
#
|
||||
|
||||
# --- new mixins
|
||||
|
||||
|
||||
class BackgroundColor(Widget):
|
||||
background_color = ListProperty([1, 1, 1, 0])
|
||||
|
||||
@@ -89,14 +90,16 @@ class SizedMDFlatRectangleToggleButton(SizedMDFlatRectangleButton, ToggleButtonB
|
||||
return self.state == "down"
|
||||
return self.state == "down"
|
||||
|
||||
|
||||
class AutoSizedMDFlatRectangleButton(SizedMDFlatRectangleButton):
|
||||
hor_padding = NumericProperty(3)
|
||||
|
||||
|
||||
class AutoSizedMDFlatRectangleToggleButton(SizedMDFlatRectangleToggleButton):
|
||||
hor_padding = NumericProperty(3)
|
||||
|
||||
|
||||
class TransparentIconButton(CircularRippleBehavior,Button):
|
||||
class TransparentIconButton(CircularRippleBehavior, Button):
|
||||
icon_size = ListProperty([25, 25])
|
||||
icon = StringProperty("")
|
||||
|
||||
@@ -123,7 +126,7 @@ class MyNavigationDrawer(MDNavigationDrawer): # in PR - closes NavDrawer on any
|
||||
return super().on_touch_up(touch)
|
||||
|
||||
|
||||
class CircleWithText(MDFloatLayout):
|
||||
class CircleWithText(Widget):
|
||||
text = StringProperty("0")
|
||||
player = OptionProperty("Black", options=["Black", "White"])
|
||||
min_size = NumericProperty(50)
|
||||
@@ -156,7 +159,7 @@ class CollapsablePanel(MDBoxLayout):
|
||||
option_labels = ListProperty([])
|
||||
option_default_active = ListProperty([])
|
||||
option_colors = ListProperty([])
|
||||
closed_label = StringProperty('Closed Panel')
|
||||
closed_label = StringProperty("Closed Panel")
|
||||
|
||||
size_hint_y_open = NumericProperty(1)
|
||||
height_open = NumericProperty(None)
|
||||
@@ -170,44 +173,48 @@ class CollapsablePanel(MDBoxLayout):
|
||||
self.option_buttons = []
|
||||
super().__init__(**kwargs)
|
||||
self.orientation = "vertical"
|
||||
self.bind(options=self.build_options, option_colors=self.build_options, options_height=self.build_options,
|
||||
option_default_active=self.build_options,options_left_padding=self.build_options)
|
||||
self.bind(state=self.build,size_hint_y_open=self.build,height_open=self.build)
|
||||
self.bind(
|
||||
options=self.build_options,
|
||||
option_colors=self.build_options,
|
||||
options_height=self.build_options,
|
||||
option_default_active=self.build_options,
|
||||
options_left_padding=self.build_options,
|
||||
)
|
||||
self.bind(state=self.build, size_hint_y_open=self.build, height_open=self.build)
|
||||
self.build_options()
|
||||
|
||||
def build_options(self, *args, **kwargs):
|
||||
self.header = MDBoxLayout(height=self.options_height,size_hint_y=None,
|
||||
padding=[self.options_left_padding, 0, 0, 0], spacing=3)
|
||||
self.header = MDBoxLayout(height=self.options_height, size_hint_y=None, padding=[self.options_left_padding, 0, 0, 0], spacing=3)
|
||||
self.option_buttons = []
|
||||
option_labels = self.option_labels or [i18n._(f"tab:{opt}") for opt in self.options]
|
||||
for lbl, opt_col, active in zip(option_labels, self.option_colors, self.option_default_active):
|
||||
button = AutoSizedMDFlatRectangleToggleButton(text=lbl,color=opt_col, height=self.options_height,
|
||||
on_press=self.trigger_select, state="down" if active else "normal")
|
||||
button = AutoSizedMDFlatRectangleToggleButton(text=lbl, color=opt_col, height=self.options_height, on_press=self.trigger_select, state="down" if active else "normal")
|
||||
self.option_buttons.append(button)
|
||||
self.open_close_button = TransparentIconButton( # << / >> collapse button
|
||||
self.open_close_button = TransparentIconButton( # << / >> collapse button
|
||||
icon=self.open_close_icon(),
|
||||
icon_size=[0.5 * self.options_height, 0.5 * self.options_height],
|
||||
width=0.75*self.options_height,size_hint_x=None,
|
||||
on_press=lambda *_args: self.set_state('toggle'),
|
||||
width=0.75 * self.options_height,
|
||||
size_hint_x=None,
|
||||
on_press=lambda *_args: self.set_state("toggle"),
|
||||
)
|
||||
self.bind(state=lambda *_args: self.open_close_button.setter("icon")(None,self.open_close_icon()))
|
||||
self.bind(state=lambda *_args: self.open_close_button.setter("icon")(None, self.open_close_icon()))
|
||||
self.build()
|
||||
|
||||
def build(self, *args, **kwargs):
|
||||
self.header.clear_widgets()
|
||||
if self.state=='open':
|
||||
if self.state == "open":
|
||||
for button in self.option_buttons:
|
||||
self.header.add_widget(button)
|
||||
self.header.add_widget(Label()) # spacer
|
||||
self.header.add_widget(Label()) # spacer
|
||||
self.trigger_select()
|
||||
else:
|
||||
self.header.add_widget(I18NLabel(text=i18n._(self.closed_I18NLabel),halign='right',height=self.options_height))
|
||||
self.header.add_widget(I18NLabel(text=i18n._(self.closed_label), halign="right", height=self.options_height))
|
||||
self.header.add_widget(self.open_close_button)
|
||||
|
||||
super().clear_widgets()
|
||||
super().add_widget(self.header)
|
||||
height, size_hint_y = 1, None
|
||||
if self.state=='open' and self.contents:
|
||||
if self.state == "open" and self.contents:
|
||||
super().add_widget(self.contents)
|
||||
if self.height_open:
|
||||
height = self.height_open
|
||||
@@ -215,7 +222,7 @@ class CollapsablePanel(MDBoxLayout):
|
||||
size_hint_y = self.size_hint_y_open
|
||||
else:
|
||||
height = self.header.height
|
||||
self.height, self.size_hint_y = height,size_hint_y
|
||||
self.height, self.size_hint_y = height, size_hint_y
|
||||
|
||||
def open_close_icon(self):
|
||||
return self.open_icon if self.state == "open" else self.close_icon
|
||||
@@ -236,12 +243,18 @@ class CollapsablePanel(MDBoxLayout):
|
||||
|
||||
def trigger_select(self, *_args):
|
||||
if self.state == "open":
|
||||
self.dispatch("on_option_state",{opt:btn.state == "down" for opt,btn in zip(self.options,self.option_buttons)})
|
||||
self.dispatch("on_option_state", {opt: btn.state == "down" for opt, btn in zip(self.options, self.option_buttons)})
|
||||
|
||||
def on_option_state(self, options):
|
||||
pass
|
||||
|
||||
|
||||
class StatsBox(MDBoxLayout, BackgroundColor):
|
||||
winrate = StringProperty("...")
|
||||
score = StringProperty("...")
|
||||
points_lost = NumericProperty(None, allownone=True)
|
||||
player = StringProperty("")
|
||||
|
||||
|
||||
# --- not checked
|
||||
|
||||
@@ -469,7 +482,7 @@ class LabelledIntInput(LabelledTextInput):
|
||||
|
||||
|
||||
def draw_text(pos, text, **kw):
|
||||
label = I18NCoreLabel(text=text, bold=True, **kw)
|
||||
label = CoreLabel(text=text, bold=True, font_name=i18n._("Roboto"), **kw) #
|
||||
label.refresh()
|
||||
Rectangle(texture=label.texture, pos=(pos[0] - label.texture.size[0] / 2, pos[1] - label.texture.size[1] / 2), size=label.texture.size)
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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],
|
||||
[0.117, 0.588, 0, 1.0],
|
||||
]
|
||||
EVAL_DOT_MAX_SIZE = 0.5
|
||||
EVAL_DOT_MIN_SIZE = 0.25
|
||||
|
||||
# board
|
||||
LINE_COLOR = [0, 0, 0]
|
||||
POLICY_COLOR = [0.9, 0.2, 0.8]
|
||||
STARPOINT_SIZE = 0.1
|
||||
BOARD_COLOR = [0.85, 0.68, 0.40, 1]
|
||||
STONE_SIZE = 0.475
|
||||
VISITS_FRAC_SMALL = 0.1
|
||||
STONE_COLORS = {"B": [0.05, 0.05, 0.05], "W": [0.95, 0.95, 0.95]}
|
||||
OUTLINE_COLORS = {"B": [0.3, 0.3, 0.3, 0.5], "W": [0.7, 0.7, 0.7, 0.5]}
|
||||
GHOST_ALPHA = 0.5
|
||||
TOP_MOVE_ALPHA = 0.3
|
||||
CHILD_SCALE = 0.95
|
||||
|
||||
# ponder dot
|
||||
ENGINE_DOWN_COL = [0.8, 0, 0, 1]
|
||||
ENGINE_BUSY_COL = [0.9, 0.4, 0.1, 1]
|
||||
ENGINE_READY_COL = [0.117, 0.588, 0, 1]
|
||||
Reference in new issue
Block a user