1.12 (#557)
* add numpy * fix rotation on new game * fix wide root zero * edit INSTALL.md * force hints on only if starting pondering * readme * edit README.md * Check that the platform is Windows, and the user32 dll has the SetProcessDpiAwarenessContext function (should exist for all versions >= Vista). Prevents blurry Kivy windows on high dpi displays. Reference: https://stackoverflow.com/questions/71704354/kivy-app-is-blurry-on-windows-with-high-resolution-screen * Prevents blurry Kivy windows on high dpi displays. Check that the platform is Windows, and the user32 dll has the SetProcessDpiAwarenessContext function (should exist for all versions >= Vista). Reference: https://stackoverflow.com/questions/71704354/kivy-app-is-blurry-on-windows-with-high-resolution-screen * Use kivy.platform instead of the platform package. * Fix bug when the board size is not square. * Set log level back to "warning" (was changed by mistake in a previous commit). * change pyinstaller icon file paths from fully qualified to relative, pointing to the ico file in the repository * add VSVersionInfo metadata to Windows executable file from PyInstaller * replace importLib modules with path modification and regular imports * Initial code for analysis in move range (not working yet). * Initial working code for move range analysis. Work in progress, i18n not done. * Initial working code for move range analysis. Work in progress, i18n not done. * Get rid of unnecessary code for getting dialog subwidgets. * Implemented greyed-out inputs when the move range checkbox is not selected. * Fix spacing which was changed by accident. * edit katrain/popups.kv, edit katrain/gui/popups.py * Fix invocation of game analysis. * Added i18n for the move range analysis. i18n done for Russian. * TODO removed for Russian. * Translated remaining Russian strings which were TODO:. * French strings for move range analysis. * Change the way analyze_extra is called when move range is specified. * scale=0 turns off * allow 3.10 * edit .github/workflows/test.yaml * Blended territory display (#556) * Check that the platform is Windows, and the user32 dll has the SetProcessDpiAwarenessContext function (should exist for all versions >= Vista). Prevents blurry Kivy windows on high dpi displays. Reference: https://stackoverflow.com/questions/71704354/kivy-app-is-blurry-on-windows-with-high-resolution-screen * Prevents blurry Kivy windows on high dpi displays. Check that the platform is Windows, and the user32 dll has the SetProcessDpiAwarenessContext function (should exist for all versions >= Vista). Reference: https://stackoverflow.com/questions/71704354/kivy-app-is-blurry-on-windows-with-high-resolution-screen * Changed territory display to be smoothly blended, for player expected territories and for loss in teaching games. Added an ownership mark to stones while displaying territory; the color of the mark indicates expected ownership, and the size of the mark is proportional to certainty. * Fixed bug: blended territory display does not work with rotation. * Revert "Prevents blurry Kivy windows on high dpi displays." This reverts commit 1cff741e3f7b6bcd8fe7d25cdc9a3fddbcaea242. * Revert "Check that the platform is Windows, and the user32 dll has the SetProcessDpiAwarenessContext function (should exist for all versions >= Vista)." This reverts commit c9ae4f6432efe566bed11eb4a9575b19df0a0db2. * Changed territory display to be smoothly blended, for player expected territories and for loss in teaching games. Added an ownership mark to stones while displaying territory; the color of the mark indicates expected ownership, and the size of the mark is proportional to certainty. Rebased blended territory display on the 1.12 branch. * Fixed bug: blended territory display does not work with rotation. Fixed for non-square board and rebased. * Revert "Prevents blurry Kivy windows on high dpi displays." This reverts commit 1cff741e3f7b6bcd8fe7d25cdc9a3fddbcaea242. * Revert "Check that the platform is Windows, and the user32 dll has the SetProcessDpiAwarenessContext function (should exist for all versions >= Vista)." This reverts commit c9ae4f6432efe566bed11eb4a9575b19df0a0db2. * Make blended territory work with non-square boards and their rotations. * Whitespace. * Make blended territory work with non-square boards and their rotations. * Change marks on stones from circles to squares. * Add Theme settings enabling different types of territory and stone marks displays. * Add Theme settings enabling different types of territory and stone marks displays. * Reformatted with black -l 120. * Documentation for territory display styles and themes. * Added acknowledgement for game used in screenshots. * spacing * Some cleanup of Theme variables. * Switch default mode back to "blended". * Added screenshot of blended style - weak stone marks. * Fix bold text. Co-authored-by: Jacob Minsky <jacob.minsky@gmail.com> * fix test yaml * edit spec/file_version.py, edit katrain/core/game.py and 3 other changes * edit katrain/core/constants.py * Fixes and enhancements for the blended territory feature. (#564) * Fix newly placed stone getting transparency before ownership is updated. * Set black and white territory colors separately. * Try dimming board when territory display is active. * Change tint of the board when in territory estimate mode and style is "blended". * Fix bug when loading SGF with initial position setup. * Revert "Change tint of the board when in territory estimate mode and style is "blended"." This reverts commit d5b46c8966d02b8660a2fe88e4c8d882af43f51f. * Marks on stone should be stone colors, not ownership colors - which look bad. * Version with new board texture (wood6.jpg from https://github.com/waltheri/wgo.js/tree/master/textures) and changed territory color parameters. * Added gamma-correction to territory coloring. * Add acknowledgement for board texture. Co-authored-by: rzcp66 <jacob.minsky@gm.com> * Remove numpy (#570) * Fix newly placed stone getting transparency before ownership is updated. * Set black and white territory colors separately. * Try dimming board when territory display is active. * Change tint of the board when in territory estimate mode and style is "blended". * Fix bug when loading SGF with initial position setup. * Revert "Change tint of the board when in territory estimate mode and style is "blended"." This reverts commit d5b46c8966d02b8660a2fe88e4c8d882af43f51f. * Marks on stone should be stone colors, not ownership colors - which look bad. * Version with new board texture (wood6.jpg from https://github.com/waltheri/wgo.js/tree/master/textures) and changed territory color parameters. * Added gamma-correction to territory coloring. * Eliminates numpy; working version with rotation without numpy. * Fixed stuff related to hover content and board rotation. * Bug fix for roi selection without numpy. * Added comment for rot90 implementation with lists. * Use reversed() instead of [::-1] for clarity. Co-authored-by: rzcp66 <jacob.minsky@gm.com> * Fix region of interest display when board is rotated. (#572) * themes Co-authored-by: Sander Land <sander@chatdesk.com> Co-authored-by: Sander Land <sander.land@cognite.com> Co-authored-by: Jacob Minsky <jacob.minsky@gmail.com> Co-authored-by: ulty4life <ulty4life@gmail.com> Co-authored-by: Jacob Minsky <35696962+jacobm-tech@users.noreply.github.com> Co-authored-by: rzcp66 <jacob.minsky@gm.com>
This commit is contained in:
47 files changed
+712
-205
No files matched your search
+15
-6
@@ -5,6 +5,14 @@ import sys
|
||||
|
||||
os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning")
|
||||
|
||||
from kivy.utils import platform as kivy_platform
|
||||
|
||||
if kivy_platform == "win":
|
||||
from ctypes import windll, c_int64
|
||||
|
||||
if hasattr(windll.user32, "SetProcessDpiAwarenessContext"):
|
||||
windll.user32.SetProcessDpiAwarenessContext(c_int64(-4))
|
||||
|
||||
import kivy
|
||||
|
||||
kivy.require("2.0.0")
|
||||
@@ -12,7 +20,6 @@ kivy.require("2.0.0")
|
||||
# next, icon
|
||||
from katrain.core.utils import find_package_resource, PATHS
|
||||
from kivy.config import Config
|
||||
from kivy.utils import platform
|
||||
|
||||
ICON = find_package_resource("katrain/img/icon.ico")
|
||||
Config.set("kivy", "window_icon", ICON)
|
||||
@@ -52,7 +59,6 @@ from kivy.properties import NumericProperty, ObjectProperty, StringProperty
|
||||
from kivy.clock import Clock
|
||||
from kivy.metrics import dp
|
||||
from katrain.core.ai import generate_ai_move
|
||||
from kivy.utils import platform as kivy_platform
|
||||
|
||||
from katrain.core.lang import DEFAULT_LANGUAGE, i18n
|
||||
from katrain.core.constants import (
|
||||
@@ -156,12 +162,14 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
def play_analyze_mode(self):
|
||||
return self.play_mode.mode
|
||||
|
||||
def toggle_continuous_analysis(self):
|
||||
def toggle_continuous_analysis(self, quiet=False):
|
||||
if self.contributing:
|
||||
self.animate_contributing = not self.animate_contributing
|
||||
else:
|
||||
if self.pondering:
|
||||
self.controls.set_status("", STATUS_INFO)
|
||||
elif not quiet: # See #549
|
||||
Clock.schedule_once(self.analysis_controls.hints.activate, 0)
|
||||
self.pondering = not self.pondering
|
||||
self.update_state()
|
||||
|
||||
@@ -337,7 +345,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
if (move_tree is not None and mode == MODE_PLAY) or (move_tree is None and mode == MODE_ANALYZE):
|
||||
self.play_mode.switch_ui_mode() # for new game, go to play, for loaded, analyze
|
||||
self.board_gui.animating_pv = None
|
||||
self.board_gui.rotation_degree = 0
|
||||
self.board_gui.reset_rotation()
|
||||
self.engine.on_new_game() # clear queries
|
||||
self.game = Game(
|
||||
self,
|
||||
@@ -401,6 +409,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
def _do_redo(self, n_times=1):
|
||||
self.board_gui.animating_pv = None
|
||||
self.game.redo(n_times)
|
||||
|
||||
def _do_rotate(self):
|
||||
self.board_gui.rotate_gridpos()
|
||||
|
||||
@@ -412,7 +421,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
self.board_gui.animating_pv = None
|
||||
self.controls.move_tree.switch_branch(*args)
|
||||
|
||||
def _play_stone_sound(self,_dt=None):
|
||||
def _play_stone_sound(self, _dt=None):
|
||||
play_sound(random.choice(Theme.STONE_SOUNDS))
|
||||
|
||||
def _do_play(self, coords):
|
||||
@@ -730,7 +739,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
return
|
||||
|
||||
if keycode[1] == Theme.KEY_TOGGLE_CONTINUOUS_ANALYSIS:
|
||||
self.toggle_continuous_analysis()
|
||||
self.toggle_continuous_analysis(quiet=shift_pressed)
|
||||
elif keycode[1] == Theme.KEY_TOGGLE_COORDINATES:
|
||||
self.board_gui.toggle_coordinates()
|
||||
elif keycode[1] in Theme.KEY_PAUSE_TIMER and not ctrl_pressed:
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
"anim_pv_time": 0.5,
|
||||
"debug_level": 0,
|
||||
"lang": "en",
|
||||
"version": "1.11.2",
|
||||
"version": "1.11.3",
|
||||
"load_fast_analysis": false,
|
||||
"load_sgf_rewind": true
|
||||
},
|
||||
|
||||
+1
-1
@@ -244,7 +244,7 @@ def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Optio
|
||||
extra_settings=extra_settings,
|
||||
)
|
||||
while not (error or analysis):
|
||||
time.sleep(0.01) # TODO: prevent deadlock if esc, check node in queries?
|
||||
time.sleep(0.01) # TODO: prevent deadlock if esc, check node in queries?
|
||||
engine.check_alive(exception_if_dead=True)
|
||||
return analysis
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PROGRAM_NAME = "KaTrain"
|
||||
VERSION = "1.11.2"
|
||||
VERSION = "1.12.0"
|
||||
HOMEPAGE = "https://github.com/sanderland/katrain"
|
||||
CONFIG_MIN_VERSION = "1.11.0" # keep config files from this version
|
||||
ANALYSIS_FORMAT_VERSION = "1.0"
|
||||
|
||||
@@ -344,15 +344,11 @@ class KataGoEngine(BaseEngine):
|
||||
if pq.get(k) != query.get(k)
|
||||
}
|
||||
if differences:
|
||||
# TODO:remove
|
||||
self.katrain.log(f"Found differences in ponder check: {differences}", OUTPUT_EXTRA_DEBUG)
|
||||
self.stop_pondering()
|
||||
query["maxVisits"] = 1_000_000
|
||||
query["maxVisits"] = 10_000_000
|
||||
query["reportDuringSearchEvery"] = PONDERING_REPORT_DT
|
||||
self.ponder_query = query
|
||||
else:
|
||||
# TODO:remove
|
||||
self.katrain.log("Found no differences in ponder check, skipping", OUTPUT_EXTRA_DEBUG)
|
||||
continue
|
||||
|
||||
terminate = query.get("action") == "terminate"
|
||||
@@ -434,10 +430,10 @@ class KataGoEngine(BaseEngine):
|
||||
avoid = []
|
||||
|
||||
settings = copy.copy(self.override_settings)
|
||||
settings["wideRootNoise"] = self.config["wide_root_noise"]
|
||||
if time_limit:
|
||||
settings["maxTime"] = self.config["max_time"]
|
||||
if self.config.get("wide_root_noise", 0.0) > 0.0: # don't send if 0.0, so older versions don't error
|
||||
settings["wideRootNoise"] = self.config["wide_root_noise"]
|
||||
|
||||
query = {
|
||||
"rules": self.get_rules(analysis_node.ruleset),
|
||||
"priority": self.base_priority + priority,
|
||||
|
||||
+18
-4
@@ -575,7 +575,6 @@ class Game(BaseGame):
|
||||
return
|
||||
|
||||
engine = self.engines[cn.next_player]
|
||||
Clock.schedule_once(self.katrain.analysis_controls.hints.activate, 0)
|
||||
|
||||
if mode == "ponder":
|
||||
cn.analyze(
|
||||
@@ -602,6 +601,10 @@ class Game(BaseGame):
|
||||
if mode == "game":
|
||||
nodes = self.root.nodes_in_tree
|
||||
only_mistakes = kwargs.get("mistakes_only", False)
|
||||
move_range = kwargs.get("move_range", None)
|
||||
if move_range:
|
||||
if move_range[1] < move_range[0]:
|
||||
move_range = reversed(move_range)
|
||||
threshold = self.katrain.config("trainer/eval_thresholds")[-4]
|
||||
if "visits" in kwargs:
|
||||
visits = kwargs["visits"]
|
||||
@@ -610,9 +613,20 @@ class Game(BaseGame):
|
||||
visits = min_visits + engine.config["max_visits"]
|
||||
for node in nodes:
|
||||
max_point_loss = max(c.points_lost or 0 for c in [node] + node.children)
|
||||
if not only_mistakes or max_point_loss > threshold:
|
||||
node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False, report_every=None)
|
||||
self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||
if only_mistakes and max_point_loss <= threshold:
|
||||
continue
|
||||
if move_range and (not node.depth - 1 in range(move_range[0], move_range[1] + 1)):
|
||||
continue
|
||||
node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False, report_every=None)
|
||||
if not move_range:
|
||||
self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||
else:
|
||||
self.katrain.controls.set_status(
|
||||
i18n._("move range analysis").format(
|
||||
start_move=move_range[0], end_move=move_range[1], visits=visits
|
||||
),
|
||||
STATUS_ANALYSIS,
|
||||
)
|
||||
return
|
||||
|
||||
elif mode == "sweep":
|
||||
|
||||
@@ -374,7 +374,7 @@ class SGFNode:
|
||||
def place_handicap_stones(self, n_handicaps, tygem=False):
|
||||
board_size_x, board_size_y = self.board_size
|
||||
if min(board_size_x, board_size_y) < 3:
|
||||
return # No
|
||||
return # No
|
||||
near_x = 3 if board_size_x >= 13 else min(2, board_size_x - 1)
|
||||
near_y = 3 if board_size_y >= 13 else min(2, board_size_y - 1)
|
||||
far_x = board_size_x - 1 - near_x
|
||||
|
||||
@@ -736,6 +736,7 @@
|
||||
size_hint: None, 0.7
|
||||
pos_hint: {'center_y':0.5}
|
||||
checkbox: checkbox
|
||||
label: label
|
||||
ClickableLabel:
|
||||
id: label
|
||||
text: root.text
|
||||
|
||||
+312
-118
@@ -1,11 +1,12 @@
|
||||
import math
|
||||
import time
|
||||
from typing import List, Optional
|
||||
import numpy as np
|
||||
import copy
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.graphics.context_instructions import Color
|
||||
from kivy.graphics.texture import Texture
|
||||
from kivy.graphics.context_instructions import Color, Rotate, Translate, PushMatrix, PopMatrix
|
||||
from kivy.graphics.vertex_instructions import Ellipse, Line, Rectangle
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import BooleanProperty, ListProperty, NumericProperty, ObjectProperty
|
||||
@@ -41,7 +42,7 @@ class BadukPanWidget(Widget):
|
||||
super(BadukPanWidget, self).__init__(**kwargs)
|
||||
self.trainer_config = {}
|
||||
self.ghost_stone = []
|
||||
self.gridpos = np.zeros((0,0,2))
|
||||
self.gridpos = None
|
||||
self.initial_gridpos_x = []
|
||||
self.initial_gridpos_y = []
|
||||
self.rotation_degree = 0
|
||||
@@ -62,6 +63,10 @@ class BadukPanWidget(Widget):
|
||||
self.bind(size=self.redraw_trigger, pos=self.redraw_trigger)
|
||||
Clock.schedule_interval(self.animate_pv, 0.1)
|
||||
|
||||
def reset_rotation(self):
|
||||
while self.rotation_degree:
|
||||
self.rotate_gridpos()
|
||||
|
||||
def toggle_coordinates(self):
|
||||
self.draw_coords_enabled = not self.draw_coords_enabled
|
||||
self.redraw_trigger()
|
||||
@@ -72,16 +77,16 @@ class BadukPanWidget(Widget):
|
||||
|
||||
# stone placement functions
|
||||
def _find_closest(self, pos_x, pos_y):
|
||||
xd = abs(self.gridpos[0,0,0] - pos_x)
|
||||
xd = abs(self.gridpos[0][0][0] - pos_x)
|
||||
xp = 0
|
||||
yd = abs(self.gridpos[0,0,1] - pos_y)
|
||||
yd = abs(self.gridpos[0][0][1] - pos_y)
|
||||
yp = 0
|
||||
for y in range(0, len(self.gridpos)):
|
||||
for x in range(0, len(self.gridpos[0])):
|
||||
if abs(self.gridpos[y,x,0] - pos_x) <= xd and abs(self.gridpos[y,x,1] - pos_y) <= yd:
|
||||
xd = abs(self.gridpos[y,x,0] - pos_x)
|
||||
if abs(self.gridpos[y][x][0] - pos_x) <= xd and abs(self.gridpos[y][x][1] - pos_y) <= yd:
|
||||
xd = abs(self.gridpos[y][x][0] - pos_x)
|
||||
xp = x
|
||||
yd = abs(self.gridpos[y,x,1] - pos_y)
|
||||
yd = abs(self.gridpos[y][x][1] - pos_y)
|
||||
yp = y
|
||||
return xd, xp, yd, yp
|
||||
|
||||
@@ -147,9 +152,9 @@ class BadukPanWidget(Widget):
|
||||
(pv, node)
|
||||
for move, pv, node in self.active_pv_moves
|
||||
if move[0] < len(self.gridpos[0])
|
||||
and move[1] < len(self.gridpos)
|
||||
and abs(rel_pos[0] - self.gridpos[move[1],move[0],0]) < self.grid_size / 2
|
||||
and abs(rel_pos[1] - self.gridpos[move[1],move[0],1]) < self.grid_size / 2
|
||||
and move[1] < len(self.gridpos)
|
||||
and abs(rel_pos[0] - self.gridpos[move[1]][move[0]][0]) < self.grid_size / 2
|
||||
and abs(rel_pos[1] - self.gridpos[move[1]][move[0]][1]) < self.grid_size / 2
|
||||
]
|
||||
if near_move:
|
||||
self.set_animating_pv(near_move[0][0], near_move[0][1])
|
||||
@@ -221,14 +226,54 @@ class BadukPanWidget(Widget):
|
||||
self.draw_board()
|
||||
self.draw_board_contents()
|
||||
|
||||
def draw_stone(self, x, y, player, alpha=1, innercol=None, evalcol=None, evalscale=1.0, scale=1.0):
|
||||
def draw_stone(
|
||||
self, x, y, player, alpha=1, innercol=None, evalcol=None, evalscale=1.0, scale=1.0, ownership=None, loss=None
|
||||
):
|
||||
stone_size = self.stone_size * scale
|
||||
if ownership is not None:
|
||||
(owner, other) = ("B", "W") if ownership > 0 else ("W", "B")
|
||||
if Theme.TERRITORY_DISPLAY != "marks":
|
||||
if player == owner:
|
||||
alpha = Theme.STONE_MIN_ALPHA + (1.0 - Theme.STONE_MIN_ALPHA) * abs(ownership)
|
||||
else:
|
||||
alpha = Theme.STONE_MIN_ALPHA
|
||||
Color(1, 1, 1, alpha)
|
||||
Rectangle(
|
||||
pos=(self.gridpos[y,x,0] - stone_size, self.gridpos[y,x,1] - stone_size),
|
||||
pos=(self.gridpos[y][x][0] - stone_size, self.gridpos[y][x][1] - stone_size),
|
||||
size=(2 * stone_size, 2 * stone_size),
|
||||
texture=cached_texture(Theme.STONE_TEXTURE[player]),
|
||||
)
|
||||
# Draw ownership marks on stones; the mark is a square with an outline.
|
||||
if (ownership is not None or loss is not None) and (
|
||||
Theme.STONE_MARKS == "all" or (Theme.STONE_MARKS == "weak" and player != owner)
|
||||
):
|
||||
if ownership is not None:
|
||||
mark_color = *Theme.STONE_COLORS[owner][:3], 1.0
|
||||
other_color = *Theme.STONE_COLORS[other][:3], 1.0
|
||||
outline_color = tuple(map(lambda y: sum(y) / float(len(y)), zip(*(mark_color, other_color))))
|
||||
if loss is not None:
|
||||
mark_color = *Theme.EVAL_COLORS[self.trainer_config["theme"]][1][:3], loss
|
||||
outline_color = mark_color
|
||||
|
||||
mark_size = Theme.MARK_SIZE * abs(ownership if ownership else loss) * self.stone_size * 2.0
|
||||
Color(*mark_color)
|
||||
Rectangle(
|
||||
pos=(
|
||||
self.gridpos[y][x][0] - mark_size / 2,
|
||||
self.gridpos[y][x][1] - mark_size / 2,
|
||||
),
|
||||
size=(mark_size, mark_size),
|
||||
)
|
||||
Color(*outline_color)
|
||||
Line(
|
||||
rectangle=(
|
||||
self.gridpos[y][x][0] - mark_size / 2,
|
||||
self.gridpos[y][x][1] - mark_size / 2,
|
||||
mark_size,
|
||||
mark_size,
|
||||
),
|
||||
width=1.0,
|
||||
)
|
||||
if evalcol:
|
||||
eval_radius = math.sqrt(evalscale) # scale area by evalscale
|
||||
evalsize = self.stone_size * (
|
||||
@@ -236,7 +281,7 @@ class BadukPanWidget(Widget):
|
||||
)
|
||||
Color(*evalcol)
|
||||
Rectangle(
|
||||
pos=(self.gridpos[y,x,0] - evalsize, self.gridpos[y,x,1] - evalsize),
|
||||
pos=(self.gridpos[y][x][0] - evalsize, self.gridpos[y][x][1] - evalsize),
|
||||
size=(2 * evalsize, 2 * evalsize),
|
||||
texture=cached_texture(Theme.EVAL_DOT_TEXTURE),
|
||||
)
|
||||
@@ -244,7 +289,7 @@ class BadukPanWidget(Widget):
|
||||
Color(*innercol)
|
||||
inner_size = stone_size * 0.8
|
||||
Rectangle(
|
||||
pos=(self.gridpos[y,x,0] - inner_size, self.gridpos[y,x,1] - inner_size),
|
||||
pos=(self.gridpos[y][x][0] - inner_size, self.gridpos[y][x][1] - inner_size),
|
||||
size=(2 * inner_size, 2 * inner_size),
|
||||
texture=cached_texture(Theme.LAST_MOVE_TEXTURE),
|
||||
)
|
||||
@@ -267,23 +312,45 @@ class BadukPanWidget(Widget):
|
||||
grid_spaces_margin_x, grid_spaces_margin_y = self.get_grid_spaces_margins()
|
||||
h = round(self.height, 4)
|
||||
w = round(self.width, 4)
|
||||
x_grid_spaces, y_grid_spaces = self.calculate_grid_spaces(board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y)
|
||||
x_grid_spaces, y_grid_spaces = self.calculate_grid_spaces(
|
||||
board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y
|
||||
)
|
||||
self.grid_size = self.calculate_grid_size(w, h, x_grid_spaces, y_grid_spaces)
|
||||
board_width_with_margins, board_height_with_margins = self.calculate_board_margins(x_grid_spaces, y_grid_spaces, self.grid_size)
|
||||
extra_px_margin_x, extra_px_margin_y = self.calculate_extra_px_margins(w, h, board_width_with_margins, board_height_with_margins)
|
||||
board_width_with_margins, board_height_with_margins = self.calculate_board_margins(
|
||||
x_grid_spaces, y_grid_spaces, self.grid_size
|
||||
)
|
||||
extra_px_margin_x, extra_px_margin_y = self.calculate_extra_px_margins(
|
||||
w, h, board_width_with_margins, board_height_with_margins
|
||||
)
|
||||
self.stone_size = self.calculate_stone_size(self.grid_size)
|
||||
# if not initiated or if changed
|
||||
if (
|
||||
(len(self.gridpos) == 0 or abs(self.pos[0] + extra_px_margin_x
|
||||
+ math.floor(grid_spaces_margin_x[0] * self.grid_size + 0.5) - self.initial_gridpos_x[0]) > 0.001)
|
||||
self.gridpos is None
|
||||
or abs(
|
||||
self.pos[0]
|
||||
+ extra_px_margin_x
|
||||
+ math.floor(grid_spaces_margin_x[0] * self.grid_size + 0.5)
|
||||
- self.initial_gridpos_x[0]
|
||||
)
|
||||
> 0.001
|
||||
):
|
||||
self.initial_gridpos_x, self.initial_gridpos_y = self.initialize_gridpos_x_y(board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y, extra_px_margin_x, extra_px_margin_y, self.grid_size)
|
||||
self.initial_gridpos_x, self.initial_gridpos_y = self.initialize_gridpos_x_y(
|
||||
board_size_x,
|
||||
board_size_y,
|
||||
grid_spaces_margin_x,
|
||||
grid_spaces_margin_y,
|
||||
extra_px_margin_x,
|
||||
extra_px_margin_y,
|
||||
self.grid_size,
|
||||
)
|
||||
if self.rotation_degree == 0:
|
||||
self.initialize_gridpos()
|
||||
|
||||
if (self.rotation_degree == 90 or self.rotation_degree == 270) and board_size_x != board_size_y:
|
||||
board_size_y, board_size_x = katrain.game.board_size
|
||||
x_grid_spaces, y_grid_spaces = self.calculate_grid_spaces(board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y)
|
||||
# Note that we use the board_size_y, board_size_x order for this rotation.
|
||||
x_grid_spaces, y_grid_spaces = self.calculate_grid_spaces(
|
||||
board_size_y, board_size_x, grid_spaces_margin_x, grid_spaces_margin_y
|
||||
)
|
||||
self.grid_size = self.calculate_grid_size(w, h, x_grid_spaces, y_grid_spaces)
|
||||
self.stone_size = self.calculate_stone_size(self.grid_size)
|
||||
current_gridpos_x, current_gridpos_y = self.calculate_rotated_gridpos()
|
||||
@@ -293,18 +360,28 @@ class BadukPanWidget(Widget):
|
||||
|
||||
# if window size got changed
|
||||
if (
|
||||
(self.gridpos[0,0,0] not in current_gridpos_x or self.gridpos[0,0,1] not in current_gridpos_y
|
||||
or (self.gridpos[len(self.gridpos) - 1,len(self.gridpos[0]) - 1,0] in current_gridpos_x
|
||||
or self.gridpos[len(self.gridpos) - 1,len(self.gridpos[0]) - 1,1] in current_gridpos_y))
|
||||
self.gridpos[0][0][0] not in current_gridpos_x
|
||||
or self.gridpos[0][0][1] not in current_gridpos_y
|
||||
or (
|
||||
self.gridpos[len(self.gridpos) - 1][len(self.gridpos[0]) - 1][0] in current_gridpos_x
|
||||
or self.gridpos[len(self.gridpos) - 1][len(self.gridpos[0]) - 1][1] in current_gridpos_y
|
||||
)
|
||||
):
|
||||
self.resize_board()
|
||||
|
||||
self.draw_board_background(katrain, current_gridpos_x, current_gridpos_y, x_grid_spaces, y_grid_spaces, grid_spaces_margin_x, grid_spaces_margin_y)
|
||||
self.draw_board_background(
|
||||
katrain,
|
||||
current_gridpos_x,
|
||||
current_gridpos_y,
|
||||
x_grid_spaces,
|
||||
y_grid_spaces,
|
||||
grid_spaces_margin_x,
|
||||
grid_spaces_margin_y,
|
||||
)
|
||||
self.draw_lines(current_gridpos_x, current_gridpos_y)
|
||||
self.draw_star_points(board_size_x, board_size_y)
|
||||
self.draw_coordinates(current_gridpos_x, current_gridpos_y)
|
||||
|
||||
|
||||
def get_grid_spaces_margins(self):
|
||||
if self.draw_coords_enabled:
|
||||
grid_spaces_margin_x = [1.5, 0.75] # left, right
|
||||
@@ -314,46 +391,49 @@ class BadukPanWidget(Widget):
|
||||
grid_spaces_margin_y = [0.75, 0.75] # bottom, top
|
||||
return grid_spaces_margin_x, grid_spaces_margin_y
|
||||
|
||||
|
||||
def calculate_grid_spaces(self, board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y):
|
||||
x_grid_spaces = board_size_x - 1 + sum(grid_spaces_margin_x)
|
||||
y_grid_spaces = board_size_y - 1 + sum(grid_spaces_margin_y)
|
||||
|
||||
return x_grid_spaces, y_grid_spaces
|
||||
|
||||
|
||||
def calculate_grid_size(self, width, height, x_grid_spaces, y_grid_spaces):
|
||||
# grid size is rounded to an integer to avoid rounding errors that
|
||||
# produce tiny gaps between shaded grid squares
|
||||
return math.floor(min(width / x_grid_spaces, height / y_grid_spaces) + 0.1)
|
||||
|
||||
|
||||
def calculate_board_margins(self, x_grid_spaces, y_grid_spaces, grid_size):
|
||||
board_width_with_margins = x_grid_spaces * grid_size
|
||||
board_height_with_margins = y_grid_spaces * grid_size
|
||||
|
||||
return board_width_with_margins, board_height_with_margins
|
||||
|
||||
|
||||
def calculate_extra_px_margins(self, width, height, board_width_with_margins, board_height_with_margins):
|
||||
extra_px_margin_x = round((width - board_width_with_margins) / 2, 4)
|
||||
extra_px_margin_y = round((height - board_height_with_margins) / 2, 4)
|
||||
|
||||
return extra_px_margin_x, extra_px_margin_y
|
||||
|
||||
|
||||
def calculate_stone_size(self, grid_size):
|
||||
return grid_size * Theme.STONE_SIZE
|
||||
|
||||
|
||||
def initialize_gridpos(self):
|
||||
self.gridpos = np.zeros((len(self.initial_gridpos_y), len(self.initial_gridpos_x),2))
|
||||
self.gridpos = [[None for x in range(len(self.initial_gridpos_x))] for y in range(len(self.initial_gridpos_y))]
|
||||
#[[None]*len(self.initial_gridpos_x)]*len(self.initial_gridpos_y)
|
||||
for y in range(len(self.initial_gridpos_y)):
|
||||
for x in range(len(self.initial_gridpos_x)):
|
||||
self.gridpos[y][x] = [self.initial_gridpos_x[x], self.initial_gridpos_y[y]]
|
||||
|
||||
|
||||
def initialize_gridpos_x_y(self, board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y, extra_px_margin_x, extra_px_margin_y, grid_size):
|
||||
def initialize_gridpos_x_y(
|
||||
self,
|
||||
board_size_x,
|
||||
board_size_y,
|
||||
grid_spaces_margin_x,
|
||||
grid_spaces_margin_y,
|
||||
extra_px_margin_x,
|
||||
extra_px_margin_y,
|
||||
grid_size,
|
||||
):
|
||||
gridpos_x = [
|
||||
round(self.pos[0] + extra_px_margin_x + math.floor((grid_spaces_margin_x[0] + i) * grid_size + 0.5), 4)
|
||||
for i in range(board_size_x)
|
||||
@@ -365,20 +445,32 @@ class BadukPanWidget(Widget):
|
||||
|
||||
return gridpos_x, gridpos_y
|
||||
|
||||
|
||||
def calculate_rotated_gridpos(self):
|
||||
board_size_y, board_size_x = self.katrain.game.board_size
|
||||
grid_spaces_margin_x, grid_spaces_margin_y = self.get_grid_spaces_margins()
|
||||
h = round(self.height, 4)
|
||||
w = round(self.width, 4)
|
||||
|
||||
x_grid_spaces, y_grid_spaces = self.calculate_grid_spaces(board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y)
|
||||
grid_size = self.calculate_grid_size(w, h , x_grid_spaces, y_grid_spaces)
|
||||
board_width_with_margins, board_height_with_margins = self.calculate_board_margins(x_grid_spaces, y_grid_spaces, grid_size)
|
||||
extra_px_margin_x, extra_px_margin_y = self.calculate_extra_px_margins(w, h, board_width_with_margins, board_height_with_margins)
|
||||
|
||||
return self.initialize_gridpos_x_y(board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y, extra_px_margin_x, extra_px_margin_y, grid_size)
|
||||
x_grid_spaces, y_grid_spaces = self.calculate_grid_spaces(
|
||||
board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y
|
||||
)
|
||||
grid_size = self.calculate_grid_size(w, h, x_grid_spaces, y_grid_spaces)
|
||||
board_width_with_margins, board_height_with_margins = self.calculate_board_margins(
|
||||
x_grid_spaces, y_grid_spaces, grid_size
|
||||
)
|
||||
extra_px_margin_x, extra_px_margin_y = self.calculate_extra_px_margins(
|
||||
w, h, board_width_with_margins, board_height_with_margins
|
||||
)
|
||||
|
||||
return self.initialize_gridpos_x_y(
|
||||
board_size_x,
|
||||
board_size_y,
|
||||
grid_spaces_margin_x,
|
||||
grid_spaces_margin_y,
|
||||
extra_px_margin_x,
|
||||
extra_px_margin_y,
|
||||
grid_size,
|
||||
)
|
||||
|
||||
def resize_board(self):
|
||||
rotated_gridpos_x, rotated_gridpos_y = self.calculate_rotated_gridpos()
|
||||
@@ -388,8 +480,8 @@ class BadukPanWidget(Widget):
|
||||
|
||||
for yi in range(len(self.gridpos)):
|
||||
for xi in range(len(self.gridpos[0])):
|
||||
current_gridpos_x.append(self.gridpos[yi,xi,0])
|
||||
current_gridpos_y.append(self.gridpos[yi,xi,1])
|
||||
current_gridpos_x.append(self.gridpos[yi][xi][0])
|
||||
current_gridpos_y.append(self.gridpos[yi][xi][1])
|
||||
sorted_current_gridpos_x = list(set(current_gridpos_x))
|
||||
sorted_current_gridpos_x.sort()
|
||||
sorted_current_gridpos_y = list(set(current_gridpos_y))
|
||||
@@ -397,15 +489,16 @@ class BadukPanWidget(Widget):
|
||||
|
||||
for yi in range(len(self.gridpos)):
|
||||
for xi in range(len(self.gridpos[0])):
|
||||
index_x = sorted_current_gridpos_x.index(self.gridpos[yi,xi,0])
|
||||
index_y = sorted_current_gridpos_y.index(self.gridpos[yi,xi,1])
|
||||
index_x = sorted_current_gridpos_x.index(self.gridpos[yi][xi][0])
|
||||
index_y = sorted_current_gridpos_y.index(self.gridpos[yi][xi][1])
|
||||
if self.rotation_degree == 90 or self.rotation_degree == 270:
|
||||
self.gridpos[yi][xi] = [rotated_gridpos_x[index_x], rotated_gridpos_y[index_y]]
|
||||
else:
|
||||
self.gridpos[yi][xi] = [self.initial_gridpos_x[index_x], self.initial_gridpos_y[index_y]]
|
||||
|
||||
|
||||
def draw_board_background(self, katrain, gridpos_x, gridpos_y, x_grid_spaces, y_grid_spaces, grid_spaces_margin_x, grid_spaces_margin_y):
|
||||
def draw_board_background(
|
||||
self, katrain, gridpos_x, gridpos_y, x_grid_spaces, y_grid_spaces, grid_spaces_margin_x, grid_spaces_margin_y
|
||||
):
|
||||
if katrain.game.insert_mode:
|
||||
Color(*Theme.INSERT_BOARD_COLOR_TINT) # dreamy
|
||||
else:
|
||||
@@ -426,21 +519,17 @@ class BadukPanWidget(Widget):
|
||||
for i in range(len(gridpos_y)):
|
||||
Line(points=[(gridpos_x[0], gridpos_y[i]), (gridpos_x[-1], gridpos_y[i])])
|
||||
|
||||
|
||||
def draw_star_points(self, board_size_x, board_size_y):
|
||||
def star_point_coords(size):
|
||||
star_point_pos = 3 if size <= 11 else 4
|
||||
if size < 7:
|
||||
return []
|
||||
return [star_point_pos - 1, size - star_point_pos] + (
|
||||
[int(size / 2)] if size % 2 == 1 and size > 7 else []
|
||||
)
|
||||
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 * Theme.STARPOINT_SIZE
|
||||
for x in star_point_coords(board_size_x):
|
||||
for y in star_point_coords(board_size_y):
|
||||
draw_circle((self.gridpos[y,x,0], self.gridpos[y,x,1]), starpt_size, Theme.LINE_COLOR)
|
||||
|
||||
draw_circle((self.gridpos[y][x][0], self.gridpos[y][x][1]), starpt_size, Theme.LINE_COLOR)
|
||||
|
||||
def draw_coordinates(self, gridpos_x, gridpos_y):
|
||||
if self.draw_coords_enabled:
|
||||
@@ -466,38 +555,37 @@ class BadukPanWidget(Widget):
|
||||
font_name="Roboto",
|
||||
)
|
||||
|
||||
|
||||
def get_x_coordinate_text(self, i, board_size_x):
|
||||
x_text = Move.GTP_COORD[i]
|
||||
if (self.rotation_degree == 90):
|
||||
if self.rotation_degree == 90:
|
||||
x_text = str(i + 1)
|
||||
elif (self.rotation_degree == 180):
|
||||
elif self.rotation_degree == 180:
|
||||
x_text = Move.GTP_COORD[board_size_x - i - 1]
|
||||
elif (self.rotation_degree == 270):
|
||||
elif self.rotation_degree == 270:
|
||||
x_text = str(board_size_x - i)
|
||||
return x_text
|
||||
|
||||
|
||||
def get_y_coordinate_text(self, i, board_size_y):
|
||||
y_text = str(i + 1)
|
||||
if (self.rotation_degree == 90):
|
||||
if self.rotation_degree == 90:
|
||||
y_text = Move.GTP_COORD[board_size_y - i - 1]
|
||||
elif (self.rotation_degree == 180):
|
||||
elif self.rotation_degree == 180:
|
||||
|
||||
y_text = str(board_size_y - i)
|
||||
elif (self.rotation_degree == 270):
|
||||
elif self.rotation_degree == 270:
|
||||
y_text = Move.GTP_COORD[i]
|
||||
return y_text
|
||||
|
||||
|
||||
def draw_board_contents(self, *_args):
|
||||
if not (self.katrain and self.katrain.game):
|
||||
return
|
||||
katrain = self.katrain
|
||||
board_size_x, board_size_y = katrain.game.board_size
|
||||
if self.gridpos.shape[0] < board_size_x or self.gridpos.shape[1] < board_size_y:
|
||||
if self.gridpos is None or len(self.gridpos) < board_size_y or len(self.gridpos[0]) < board_size_x:
|
||||
return # race condition
|
||||
show_n_eval = self.trainer_config.get("eval_on_show_last", 3)
|
||||
ownership_grid = None
|
||||
loss_grid = None
|
||||
|
||||
with self.canvas:
|
||||
self.canvas.clear()
|
||||
@@ -517,32 +605,15 @@ class BadukPanWidget(Widget):
|
||||
[a - b for a, b in zip(current_node.children[-1].ownership, ownership)],
|
||||
(board_size_x, board_size_y),
|
||||
)
|
||||
|
||||
for y in range(board_size_y - 1, -1, -1):
|
||||
for x in range(board_size_x):
|
||||
loss = max(0, (-1 if current_node.children[-1].move.player == "B" else 1) * loss_grid[y][x])
|
||||
if loss > 0:
|
||||
Color(*Theme.EVAL_COLORS[self.trainer_config["theme"]][1][:3], loss)
|
||||
Rectangle(
|
||||
pos=(
|
||||
self.gridpos[y,x,0] - self.grid_size / 2,
|
||||
self.gridpos[y,x,1] - self.grid_size / 2,
|
||||
),
|
||||
size=(self.grid_size, self.grid_size),
|
||||
)
|
||||
loss_grid[y][x] = max(
|
||||
0, (-1 if current_node.children[-1].move.player == "B" else 1) * loss_grid[y][x]
|
||||
)
|
||||
self.draw_territory(loss_grid, Theme.EVAL_COLORS[self.trainer_config["theme"]][1][:3])
|
||||
else:
|
||||
ownership_grid = var_to_grid(ownership, (board_size_x, board_size_y))
|
||||
for y in range(board_size_y - 1, -1, -1):
|
||||
for x in range(board_size_x):
|
||||
ix_owner = "B" if ownership_grid[y][x] > 0 else "W"
|
||||
Color(
|
||||
*Theme.STONE_COLORS[ix_owner][:3], abs(ownership_grid[y][x]) * Theme.OWNERSHIP_MAX_ALPHA
|
||||
)
|
||||
Rectangle(
|
||||
pos=(self.gridpos[y,x,0] - self.grid_size / 2, self.gridpos[y,x,1] - self.grid_size / 2),
|
||||
size=(self.grid_size, self.grid_size),
|
||||
)
|
||||
|
||||
self.draw_territory(ownership_grid)
|
||||
# stones
|
||||
all_dots_off = not katrain.analysis_controls.eval.active
|
||||
has_stone = {}
|
||||
@@ -567,6 +638,7 @@ class BadukPanWidget(Widget):
|
||||
evalscale = min(1, max(0, realized_points_lost / points_lost))
|
||||
placements = node.placements
|
||||
for m in node.moves + placements:
|
||||
new_move = (current_node.move and m.coords == current_node.move.coords) and not current_node.ownership
|
||||
if has_stone.get(m.coords) and not drawn_stone.get(m.coords): # skip captures, last only for
|
||||
move_eval_on = not all_dots_off and show_dots_for.get(m.player) and i < show_n_eval
|
||||
if move_eval_on and points_lost is not None:
|
||||
@@ -582,6 +654,10 @@ class BadukPanWidget(Widget):
|
||||
innercol=inner,
|
||||
evalcol=evalcol,
|
||||
evalscale=evalscale,
|
||||
ownership=ownership_grid[m.coords[1]][m.coords[0]]
|
||||
if ownership_grid and not loss_grid and not new_move
|
||||
else None,
|
||||
loss=loss_grid[m.coords[1]][m.coords[0]] if loss_grid else None,
|
||||
)
|
||||
realized_points_lost = node.parent_realized_points_lost
|
||||
|
||||
@@ -620,7 +696,7 @@ class BadukPanWidget(Widget):
|
||||
pol_order = max(0, 5 + int(math.log10(max(1e-9, move_policy - 1e-9))))
|
||||
if move_policy > text_lb:
|
||||
draw_circle(
|
||||
(self.gridpos[y,x,0], self.gridpos[y,x,1]),
|
||||
(self.gridpos[y][x][0], self.gridpos[y][x][1]),
|
||||
self.stone_size * Theme.HINT_SCALE * 0.98,
|
||||
Theme.APPROX_BOARD_COLOR,
|
||||
)
|
||||
@@ -628,14 +704,14 @@ class BadukPanWidget(Widget):
|
||||
else:
|
||||
scale = 0.5
|
||||
draw_circle(
|
||||
(self.gridpos[y,x,0], self.gridpos[y,x,1]),
|
||||
(self.gridpos[y][x][0], self.gridpos[y][x][1]),
|
||||
Theme.HINT_SCALE * self.stone_size * scale,
|
||||
(*colors[pol_order][:3], Theme.POLICY_ALPHA),
|
||||
)
|
||||
if move_policy > text_lb:
|
||||
Color(*Theme.HINT_TEXT_COLOR)
|
||||
draw_text(
|
||||
pos=(self.gridpos[y,x,0], self.gridpos[y,x,1]),
|
||||
pos=(self.gridpos[y][x][0], self.gridpos[y][x][1]),
|
||||
text=f"{100 * move_policy :.2f}"[:4] + "%",
|
||||
font_name="Roboto",
|
||||
font_size=self.grid_size / 4,
|
||||
@@ -645,8 +721,8 @@ class BadukPanWidget(Widget):
|
||||
Color(*Theme.TOP_MOVE_BORDER_COLOR[:3], Theme.POLICY_ALPHA)
|
||||
Line(
|
||||
circle=(
|
||||
self.gridpos[y,x,0],
|
||||
self.gridpos[y,x,1],
|
||||
self.gridpos[y][x][0],
|
||||
self.gridpos[y][x][1],
|
||||
self.stone_size - dp(1.2),
|
||||
),
|
||||
width=dp(2),
|
||||
@@ -664,15 +740,116 @@ class BadukPanWidget(Widget):
|
||||
|
||||
self.redraw_hover_contents_trigger()
|
||||
|
||||
def draw_roi_box(self, region_of_interest, width:float=2):
|
||||
xmin, xmax, ymin, ymax = region_of_interest
|
||||
def draw_territory(self, grid, loss_color=None):
|
||||
if Theme.TERRITORY_DISPLAY == "marks":
|
||||
self.draw_territory_marks(grid, loss_color=None)
|
||||
else:
|
||||
self.draw_territory_color(grid, loss_color=None)
|
||||
|
||||
def draw_territory_marks(self, grid, loss_color=None):
|
||||
board_size_x, board_size_y = self.katrain.game.board_size
|
||||
for y in range(board_size_y - 1, -1, -1):
|
||||
for x in range(board_size_x):
|
||||
if abs(grid[y][x]) < 0.01:
|
||||
continue
|
||||
(ix_owner, other) = ("B", "W") if grid[y][x] > 0 else ("W", "B")
|
||||
Color(
|
||||
*Theme.STONE_COLORS[ix_owner][:3],
|
||||
1.0
|
||||
)
|
||||
rect_size = Theme.MARK_SIZE * abs(grid[y][x]) * self.stone_size * 2.0
|
||||
Rectangle(
|
||||
pos=(
|
||||
self.gridpos[y][x][0] - rect_size / 2,
|
||||
self.gridpos[y][x][1] - rect_size / 2,
|
||||
),
|
||||
# radius=[rect_size / 4],
|
||||
size=(rect_size, rect_size),
|
||||
)
|
||||
|
||||
def draw_territory_color(self, grid, loss_color=None):
|
||||
# This draws the expected black and white territories, or the loss during a teching game.
|
||||
# We draw a blended territory by creating a small texture of size 19x19 (more precisely board_size)
|
||||
# and painting it over the whole board. This causes Kivy to produce a smooth texture.
|
||||
|
||||
# We add extra rows and columns (so the texture for a 19x19 board is actually 21x21)
|
||||
# in order to ensure smooth rolloff of the painted area at the edges. The alpha in the
|
||||
# extra rows is 0.
|
||||
|
||||
board_size_x, board_size_y = self.katrain.game.board_size
|
||||
texture = Texture.create(size=(board_size_x + 2, board_size_y + 2), colorfmt="rgba")
|
||||
bytes = bytearray(4 * (board_size_y + 2) * (board_size_x + 2))
|
||||
for y in range(board_size_y + 2):
|
||||
for x in range(board_size_x + 2):
|
||||
x_coord = x - 1
|
||||
y_coord = y - 1
|
||||
|
||||
if x_coord < 0 or x_coord > board_size_x - 1 or y_coord < 0 or y_coord > board_size_y - 1:
|
||||
# We're in the extra rows/columns outside the board
|
||||
alpha = 0
|
||||
else:
|
||||
alpha = abs(grid[y_coord][x_coord])
|
||||
if Theme.TERRITORY_DISPLAY == "blocks":
|
||||
alpha = 1 if alpha > Theme.BLOCKS_THRESHOLD else 0
|
||||
alpha = alpha**(1.0/Theme.OWNERSHIP_GAMMA)
|
||||
|
||||
x_coord = max(0, min(x_coord, board_size_x - 1))
|
||||
y_coord = max(0, min(y_coord, board_size_y - 1))
|
||||
|
||||
ix_owner = "B" if grid[y_coord][x_coord] > 0 else "W"
|
||||
if loss_color is None:
|
||||
pixel = Theme.OWNERSHIP_COLORS[ix_owner][:4]
|
||||
pixel[3] *= alpha
|
||||
else:
|
||||
pixel = *loss_color, min(1.0, alpha)
|
||||
pixel = tuple(map(lambda p: int(p * 255), pixel))
|
||||
idx = 4 * y * (board_size_x + 2) + x * 4
|
||||
bytes[idx : idx + 4] = pixel
|
||||
|
||||
if Theme.TERRITORY_DISPLAY == "blocks":
|
||||
texture.mag_filter = "nearest"
|
||||
texture.blit_buffer(bytes, colorfmt="rgba", bufferfmt="ubyte")
|
||||
Color(1, 1, 1, 1)
|
||||
lx = board_size_x - 1
|
||||
ly = board_size_y - 1
|
||||
left = min(self.gridpos[0][0][1], self.gridpos[ly][lx][1])
|
||||
bottom = min(self.gridpos[0][0][0], self.gridpos[ly][lx][0])
|
||||
|
||||
# Our texture is 3 squares larger than the grid of lines: we added 2 rows/columns
|
||||
# for the edge blending, and the additional 1 is because the grid of
|
||||
# intersections is 1 smaller than the board state. We will shift the texture by 3/2 square
|
||||
# to align it.
|
||||
left = left - self.grid_size * 3 / 2
|
||||
bottom = bottom - self.grid_size * 3 / 2
|
||||
|
||||
PushMatrix()
|
||||
|
||||
Rotate(origin=(bottom, left), axis=(0, 0, 1), angle=-self.rotation_degree)
|
||||
if self.rotation_degree in (90, 180):
|
||||
Translate(-self.grid_size * (board_size_x + 2), 0, 0)
|
||||
if self.rotation_degree in (180, 270):
|
||||
Translate(0, -self.grid_size * (board_size_y + 2), 0)
|
||||
|
||||
Rectangle(
|
||||
pos=(bottom, left),
|
||||
size=(self.grid_size * (board_size_x + 2), self.grid_size * (board_size_y + 2)),
|
||||
texture=texture,
|
||||
)
|
||||
|
||||
PopMatrix()
|
||||
|
||||
def draw_roi_box(self, region_of_interest, width: float = 2):
|
||||
x1, x2, y1, y2 = region_of_interest
|
||||
x_start, y_start = self.gridpos[y1][x1]
|
||||
x_end, y_end = self.gridpos[y2][x2]
|
||||
|
||||
Color(*Theme.REGION_BORDER_COLOR)
|
||||
Line(
|
||||
rectangle=(
|
||||
self.gridpos[ymin,xmin,0] - self.grid_size / 3,
|
||||
self.gridpos[ymin,xmin,1] - self.grid_size / 3,
|
||||
(xmax - xmin + 2 / 3) * self.grid_size,
|
||||
(ymax - ymin + 2 / 3) * self.grid_size,
|
||||
min(x_start, x_end) - self.grid_size / 3,
|
||||
min(y_start, y_end) - self.grid_size / 3,
|
||||
abs(x_start - x_end) + (2 / 3) * self.grid_size,
|
||||
abs(y_start - y_end) + (2 / 3) * self.grid_size,
|
||||
),
|
||||
width=width,
|
||||
)
|
||||
@@ -746,6 +923,9 @@ class BadukPanWidget(Widget):
|
||||
text_on = False
|
||||
alpha = Theme.HINTS_LO_ALPHA
|
||||
|
||||
if scale <= 0: # if theme turns hints off, do not draw them
|
||||
continue
|
||||
|
||||
if "pv" in move_dict:
|
||||
self.active_pv_moves.append((move.coords, move_dict["pv"], current_node))
|
||||
else:
|
||||
@@ -754,14 +934,20 @@ class BadukPanWidget(Widget):
|
||||
evalcol = self.eval_color(move_dict["pointsLost"])
|
||||
if text_on and top_moves_show: # remove grid lines using a board colored circle
|
||||
draw_circle(
|
||||
(self.gridpos[move.coords[1],move.coords[0],0], self.gridpos[move.coords[1],move.coords[0],1]),
|
||||
(
|
||||
self.gridpos[move.coords[1]][move.coords[0]][0],
|
||||
self.gridpos[move.coords[1]][move.coords[0]][1],
|
||||
),
|
||||
self.stone_size * scale * 0.98,
|
||||
Theme.APPROX_BOARD_COLOR,
|
||||
)
|
||||
|
||||
Color(*evalcol[:3], alpha)
|
||||
Rectangle(
|
||||
pos=(self.gridpos[move.coords[1],move.coords[0],0] - evalsize, self.gridpos[move.coords[1],move.coords[0],1] - evalsize),
|
||||
pos=(
|
||||
self.gridpos[move.coords[1]][move.coords[0]][0] - evalsize,
|
||||
self.gridpos[move.coords[1]][move.coords[0]][1] - evalsize,
|
||||
),
|
||||
size=(2 * evalsize, 2 * evalsize),
|
||||
texture=cached_texture(Theme.TOP_MOVE_TEXTURE),
|
||||
)
|
||||
@@ -791,7 +977,10 @@ class BadukPanWidget(Widget):
|
||||
|
||||
Color(*Theme.HINT_TEXT_COLOR)
|
||||
draw_text(
|
||||
pos=(self.gridpos[move.coords[1],move.coords[0],0], self.gridpos[move.coords[1],move.coords[0],1]),
|
||||
pos=(
|
||||
self.gridpos[move.coords[1]][move.coords[0]][0],
|
||||
self.gridpos[move.coords[1]][move.coords[0]][1],
|
||||
),
|
||||
text=fmt.format(**keys),
|
||||
font_name="Roboto",
|
||||
markup=True,
|
||||
@@ -804,8 +993,8 @@ class BadukPanWidget(Widget):
|
||||
Color(*Theme.TOP_MOVE_BORDER_COLOR)
|
||||
Line(
|
||||
circle=(
|
||||
self.gridpos[move.coords[1],move.coords[0],0],
|
||||
self.gridpos[move.coords[1],move.coords[0],1],
|
||||
self.gridpos[move.coords[1]][move.coords[0]][0],
|
||||
self.gridpos[move.coords[1]][move.coords[0]][1],
|
||||
self.stone_size - dp(1.2),
|
||||
),
|
||||
width=dp(1.2),
|
||||
@@ -826,8 +1015,8 @@ class BadukPanWidget(Widget):
|
||||
Color(*Theme.NEXT_MOVE_DASH_CONTRAST_COLORS[child_node.player])
|
||||
Line(
|
||||
circle=(
|
||||
self.gridpos[move.coords[1],move.coords[0],0],
|
||||
self.gridpos[move.coords[1],move.coords[0],1],
|
||||
self.gridpos[move.coords[1]][move.coords[0]][0],
|
||||
self.gridpos[move.coords[1]][move.coords[0]][1],
|
||||
self.stone_size - dp(1.2),
|
||||
),
|
||||
width=dp(1.2),
|
||||
@@ -838,8 +1027,8 @@ class BadukPanWidget(Widget):
|
||||
for s in range(0, 360, 30):
|
||||
Line(
|
||||
circle=(
|
||||
self.gridpos[move.coords[1],move.coords[0],0],
|
||||
self.gridpos[move.coords[1],move.coords[0],1],
|
||||
self.gridpos[move.coords[1]][move.coords[0]][0],
|
||||
self.gridpos[move.coords[1]][move.coords[0]][1],
|
||||
self.stone_size - dp(1.2),
|
||||
s,
|
||||
s + dashed_width,
|
||||
@@ -848,8 +1037,7 @@ class BadukPanWidget(Widget):
|
||||
)
|
||||
|
||||
if self.selecting_region_of_interest and len(self.region_of_interest) == 4:
|
||||
x1, x2, y1, y2 = self.region_of_interest
|
||||
self.draw_roi_box([min(x1, x2), max(x1, x2), min(y1, y2), max(y1, y2)], width=dp(2))
|
||||
self.draw_roi_box(self.region_of_interest, width=dp(2))
|
||||
else:
|
||||
# hover next move ghost stone
|
||||
if self.ghost_stone:
|
||||
@@ -890,7 +1078,10 @@ class BadukPanWidget(Widget):
|
||||
hide_node = cn
|
||||
while hide_node and hide_node.move and hide_node != node:
|
||||
if not hide_node.move.is_pass:
|
||||
pos = (self.gridpos[hide_node.move.coords[1],hide_node.move.coords[0],0], self.gridpos[hide_node.move.coords[1],hide_node.move.coords[0],1])
|
||||
pos = (
|
||||
self.gridpos[hide_node.move.coords[1]][hide_node.move.coords[0]][0],
|
||||
self.gridpos[hide_node.move.coords[1]][hide_node.move.coords[0]][1],
|
||||
)
|
||||
draw_circle(pos, self.stone_size, [0.85, 0.68, 0.40, 0.8])
|
||||
hide_node = hide_node.parent
|
||||
for i, gtpmove in enumerate(pv):
|
||||
@@ -907,7 +1098,7 @@ class BadukPanWidget(Widget):
|
||||
katrain.board_controls.pass_btn.pos[1] + katrain.board_controls.pass_btn.size[1] / 2,
|
||||
]
|
||||
else:
|
||||
board_coords = (self.gridpos[coords[1],coords[0],0], self.gridpos[coords[1],coords[0],1])
|
||||
board_coords = (self.gridpos[coords[1]][coords[0]][0], self.gridpos[coords[1]][coords[0]][1])
|
||||
sizefac = 1
|
||||
|
||||
stone_size = self.stone_size * sizefac
|
||||
@@ -957,10 +1148,10 @@ class BadukPanWidget(Widget):
|
||||
x0 = self.initial_gridpos_x[0]
|
||||
y0 = self.initial_gridpos_y[0]
|
||||
|
||||
pos = self.gridpos.tolist()
|
||||
pos = copy.deepcopy(self.gridpos)
|
||||
for yi in range(len(self.gridpos)):
|
||||
for xi in range(len(self.gridpos[0])):
|
||||
if (self.rotation_degree == 90 or self.rotation_degree == 270):
|
||||
if self.rotation_degree == 90 or self.rotation_degree == 270:
|
||||
gridpos_x = pos[len(self.gridpos) - 1 - yi][:]
|
||||
else:
|
||||
gridpos_x = pos[yi][:]
|
||||
@@ -973,9 +1164,11 @@ class BadukPanWidget(Widget):
|
||||
elif y0 > x0:
|
||||
x = round(x - diff, 4)
|
||||
y = round(y + diff, 4)
|
||||
self.gridpos[yi][xi] = (x, y)
|
||||
self.gridpos[yi][xi] = [x, y]
|
||||
else:
|
||||
self.gridpos = np.rot90(self.gridpos, -1, (0, 1))
|
||||
# This is a rot90 for list of lists. Based on the code found in
|
||||
# stackoverflow.com/questions/8421337/rotating-a-two-dimensional-array-in-python
|
||||
self.gridpos = list(list(x) for x in zip(*reversed(self.gridpos)))
|
||||
|
||||
self.rotation_degree += 90
|
||||
if self.rotation_degree == 360:
|
||||
@@ -990,9 +1183,10 @@ class BadukPanWidget(Widget):
|
||||
|
||||
class AnalysisDropDown(DropDown):
|
||||
def open_game_analysis_popup(self, *_args):
|
||||
analysis_popup = I18NPopup(title_key="analysis:game", size=[dp(500), dp(350)], content=ReAnalyzeGamePopup())
|
||||
analysis_popup = I18NPopup(
|
||||
title_key="analysis:game", size=[dp(500), dp(350)], content=ReAnalyzeGamePopup(MDApp.get_running_app().gui)
|
||||
)
|
||||
analysis_popup.content.popup = analysis_popup
|
||||
analysis_popup.content.katrain = MDApp.get_running_app().gui
|
||||
analysis_popup.open()
|
||||
|
||||
def open_report_popup(self, *_args):
|
||||
|
||||
+19
-1
@@ -839,9 +839,27 @@ class SaveSGFPopup(BoxLayout):
|
||||
|
||||
|
||||
class ReAnalyzeGamePopup(BoxLayout):
|
||||
katrain = ObjectProperty(None)
|
||||
popup = ObjectProperty(None)
|
||||
|
||||
def on_checkbox_active(self, checkbox, value):
|
||||
self.start_move.opacity = 1.0 if value else 0.3
|
||||
self.end_move.opacity = 1.0 if value else 0.3
|
||||
self.start_move.disabled = not value
|
||||
self.end_move.disabled = not value
|
||||
|
||||
def __init__(self, katrain, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.katrain = katrain
|
||||
self.move_range.bind(active=self.on_checkbox_active)
|
||||
|
||||
self.start_move.disabled = True
|
||||
self.end_move.disabled = True
|
||||
self.start_move.opacity = 0.3
|
||||
self.end_move.opacity = 0.3
|
||||
|
||||
self.start_move.text = str(katrain.game.current_node.depth)
|
||||
|
||||
def on_submit(self):
|
||||
self.button.trigger_action(duration=0)
|
||||
|
||||
|
||||
+14
-3
@@ -65,6 +65,7 @@ class Theme:
|
||||
LAST_MOVE_TEXTURE = "inner.png"
|
||||
TOP_MOVE_TEXTURE = "topmove.png"
|
||||
BOARD_TEXTURE = "board.png"
|
||||
BOARD_TEXTURE = "wood6.jpg"
|
||||
GRAPH_TEXTURE = "graph_bg.png"
|
||||
# sounds
|
||||
STONE_SOUNDS = [f"stone{i}.wav" for i in [1, 2, 3, 4, 5]]
|
||||
@@ -98,12 +99,12 @@ class Theme:
|
||||
|
||||
# board theme
|
||||
APPROX_BOARD_COLOR = [0.95, 0.75, 0.47, 1] # for drawing on top of / hiding what's under it
|
||||
BOARD_COLOR_TINT = [1, 0.95, 0.8, 1] # multiplied by texture
|
||||
BOARD_COLOR_TINT = [1, 1, 1, 1] # multiplied by texture
|
||||
|
||||
HINT_TEXT_COLOR = BLACK
|
||||
|
||||
REGION_BORDER_COLOR = LIGHTER_BACKGROUND_COLOR
|
||||
INSERT_BOARD_COLOR_TINT = [1, 1, 1, 0.75]
|
||||
INSERT_BOARD_COLOR_TINT = [1, 1, 1, 0.6]
|
||||
PASS_CIRCLE_COLOR = [0.45, 0.05, 0.45, 0.7]
|
||||
PASS_CIRCLE_TEXT_COLOR = [0.85, 0.85, 0.85]
|
||||
|
||||
@@ -120,7 +121,17 @@ class Theme:
|
||||
|
||||
GHOST_ALPHA = 0.6
|
||||
POLICY_ALPHA = 0.5
|
||||
OWNERSHIP_MAX_ALPHA = 0.7 # max ownership brightness
|
||||
OWNERSHIP_COLORS = {"B": [0.0, 0.0, 0.10, 0.75], "W": [0.92, 0.92, 1.0, 0.800]}
|
||||
OWNERSHIP_GAMMA = 1.33
|
||||
STONE_MIN_ALPHA = 0.7 # the minimal alpha for dead/weak stones
|
||||
|
||||
TERRITORY_DISPLAY = "blended" # other possibilities are "marks" and "blocks"
|
||||
BLOCKS_THRESHOLD = 0.3 # in "blocks" mode, territory which is this likely to be
|
||||
# a certain player's gets his color
|
||||
STONE_MARKS = "all" # all: always display marks on stones
|
||||
# none: no marks on stones, indicate ownership by transparency only
|
||||
# weak: draw marks only on stones likely (>50%) to be captured
|
||||
MARK_SIZE = 0.42 # stone mark size as fraction of stone size
|
||||
|
||||
HINTS_LO_ALPHA = 0.6
|
||||
HINTS_ALPHA = 0.8
|
||||
|
||||
Binary file not shown.
@@ -945,3 +945,20 @@ msgstr "Ko allowed?"
|
||||
#. TODO - Menu option for tsumego frame around position
|
||||
msgid "analysis:tsumegoframe"
|
||||
msgstr "Tsumego Frame"
|
||||
|
||||
#. TODO
|
||||
msgid "from move"
|
||||
msgstr "from move"
|
||||
|
||||
#. TODO - status message
|
||||
msgid "move range analysis"
|
||||
msgstr ""
|
||||
"Re-analyzing moves from {start_move} to {end_move} using {visits} visits"
|
||||
|
||||
#. TODO
|
||||
msgid "to move"
|
||||
msgstr "to move"
|
||||
|
||||
#. TODO - Restrict analysis to move range
|
||||
msgid "limit to moves"
|
||||
msgstr "Limit to moves"
|
||||
Binary file not shown.
@@ -1014,3 +1014,20 @@ msgstr "Ko allowed?"
|
||||
#. TODO - Menu option for tsumego frame around position
|
||||
msgid "analysis:tsumegoframe"
|
||||
msgstr "Tsumego Frame"
|
||||
|
||||
#. TODO
|
||||
msgid "from move"
|
||||
msgstr "from move"
|
||||
|
||||
#. TODO - status message
|
||||
msgid "move range analysis"
|
||||
msgstr ""
|
||||
"Re-analyzing moves from {start_move} to {end_move} using {visits} visits"
|
||||
|
||||
#. TODO
|
||||
msgid "to move"
|
||||
msgstr "to move"
|
||||
|
||||
#. TODO - Restrict analysis to move range
|
||||
msgid "limit to moves"
|
||||
msgstr "Limit to moves"
|
||||
Binary file not shown.
@@ -105,6 +105,16 @@ msgstr "Fast analysis of all moves"
|
||||
msgid "analysis:game"
|
||||
msgstr "Deeper full game analysis"
|
||||
|
||||
#. Restrict analysis to move range
|
||||
msgid "limit to moves"
|
||||
msgstr "Limit to moves"
|
||||
|
||||
msgid "from move"
|
||||
msgstr "from move"
|
||||
|
||||
msgid "to move"
|
||||
msgstr "to move"
|
||||
|
||||
#. Menu option for tsumego frame around position
|
||||
msgid "analysis:tsumegoframe"
|
||||
msgstr "Tsumego Frame"
|
||||
@@ -210,6 +220,11 @@ msgstr "Equalizing analysis of candidate moves to {visits} visits"
|
||||
msgid "game re-analysis"
|
||||
msgstr "Re-analyzing entire game using {visits} visits"
|
||||
|
||||
#. status message
|
||||
msgid "move range analysis"
|
||||
msgstr ""
|
||||
"Re-analyzing moves from {start_move} to {end_move} using {visits} visits"
|
||||
|
||||
#. right panel buttons and below board
|
||||
msgid "pass-button-text"
|
||||
msgstr "Pass"
|
||||
|
||||
Binary file not shown.
@@ -984,3 +984,19 @@ msgstr "Permettre les kos"
|
||||
|
||||
msgid "analysis:tsumegoframe"
|
||||
msgstr "Composer un problème"
|
||||
|
||||
#. Restrict analysis to move range
|
||||
msgid "limit to moves"
|
||||
msgstr "Limiter aux coups"
|
||||
|
||||
msgid "from move"
|
||||
msgstr "du coup"
|
||||
|
||||
msgid "to move"
|
||||
msgstr "au coup"
|
||||
|
||||
#. status message
|
||||
msgid "move range analysis"
|
||||
msgstr ""
|
||||
"Analyse de partie approfondie du coup {start_move} au coup {end_move} "
|
||||
"({visits} visites/coup)."
|
||||
Binary file not shown.
@@ -986,3 +986,20 @@ msgstr "劫でもOK"
|
||||
#. Menu option for tsumego frame around position
|
||||
msgid "analysis:tsumegoframe"
|
||||
msgstr "詰碁用の外枠"
|
||||
|
||||
#. TODO
|
||||
msgid "from move"
|
||||
msgstr "from move"
|
||||
|
||||
#. TODO - status message
|
||||
msgid "move range analysis"
|
||||
msgstr ""
|
||||
"Re-analyzing moves from {start_move} to {end_move} using {visits} visits"
|
||||
|
||||
#. TODO
|
||||
msgid "to move"
|
||||
msgstr "to move"
|
||||
|
||||
#. TODO - Restrict analysis to move range
|
||||
msgid "limit to moves"
|
||||
msgstr "Limit to moves"
|
||||
Binary file not shown.
@@ -919,3 +919,20 @@ msgstr "패로 충분?"
|
||||
|
||||
msgid "analysis:tsumegoframe"
|
||||
msgstr "묘수풀이 틀"
|
||||
|
||||
#. TODO
|
||||
msgid "from move"
|
||||
msgstr "from move"
|
||||
|
||||
#. TODO - status message
|
||||
msgid "move range analysis"
|
||||
msgstr ""
|
||||
"Re-analyzing moves from {start_move} to {end_move} using {visits} visits"
|
||||
|
||||
#. TODO
|
||||
msgid "to move"
|
||||
msgstr "to move"
|
||||
|
||||
#. TODO - Restrict analysis to move range
|
||||
msgid "limit to moves"
|
||||
msgstr "Limit to moves"
|
||||
Binary file not shown.
@@ -722,9 +722,9 @@ msgstr "Изменение счёта"
|
||||
msgid "show stats if"
|
||||
msgstr "Числа только если посещений больше чем"
|
||||
|
||||
#. TODO - point loss precision options
|
||||
#. point loss precision options
|
||||
msgid "show two digits for point loss near zero"
|
||||
msgstr "Show two digits for point loss near zero"
|
||||
msgstr "Показазывать две цифры после запятой для малой потери очков"
|
||||
|
||||
msgid "cache analysis to sgf"
|
||||
msgstr "Кэшировать анализ в SGF"
|
||||
@@ -929,28 +929,40 @@ msgstr ""
|
||||
msgid "Entire Game"
|
||||
msgstr "Полная игра"
|
||||
|
||||
#. TODO
|
||||
msgid "ai:antimirror"
|
||||
msgstr "KataAntiMirror"
|
||||
msgstr "Противозеркальная"
|
||||
|
||||
#. TODO - on move tree editing, deletes all other branches splitting off from
|
||||
#. on move tree editing, deletes all other branches splitting off from
|
||||
#. the
|
||||
#. path UP TO this node
|
||||
msgid "Prune Branch"
|
||||
msgstr "Prune Branch"
|
||||
msgstr "Удалить ответвления"
|
||||
|
||||
#. TODO
|
||||
msgid "aihelp:antimirror"
|
||||
msgstr "Anti Mirror-go Mode of KataGo."
|
||||
msgstr "Режим противодействия зеркальной стратегии."
|
||||
|
||||
#. TODO - distance of wall to existing stones
|
||||
#. distance of wall to existing stones
|
||||
msgid "tsumego:walldistance"
|
||||
msgstr "Distance of wall"
|
||||
msgstr "Расстояние до стены"
|
||||
|
||||
#. TODO - tsumego: ko is good enough?
|
||||
#. tsumego: ko is good enough?
|
||||
msgid "tsumego:ko"
|
||||
msgstr "Ko allowed?"
|
||||
msgstr "Разрешить ко?"
|
||||
|
||||
#. TODO - Menu option for tsumego frame around position
|
||||
#. Menu option for tsumego frame around position
|
||||
msgid "analysis:tsumegoframe"
|
||||
msgstr "Tsumego Frame"
|
||||
msgstr "Рамка для цумэ-го"
|
||||
|
||||
#. Move range analysis messages
|
||||
msgid "from move"
|
||||
msgstr "от хода"
|
||||
|
||||
msgid "to move"
|
||||
msgstr "до хода"
|
||||
|
||||
msgid "limit to moves"
|
||||
msgstr "Только для ходов"
|
||||
|
||||
#. status message
|
||||
msgid "move range analysis"
|
||||
msgstr "Анализ ходов от {start_move} до {end_move} с {visits} посещениями"
|
||||
Binary file not shown.
@@ -1049,3 +1049,20 @@ msgstr "Tsumego Çerçevesi"
|
||||
#. TODO - point loss precision options
|
||||
msgid "show two digits for point loss near zero"
|
||||
msgstr "Show two digits for point loss near zero"
|
||||
|
||||
#. TODO
|
||||
msgid "from move"
|
||||
msgstr "from move"
|
||||
|
||||
#. TODO - status message
|
||||
msgid "move range analysis"
|
||||
msgstr ""
|
||||
"Re-analyzing moves from {start_move} to {end_move} using {visits} visits"
|
||||
|
||||
#. TODO
|
||||
msgid "to move"
|
||||
msgstr "to move"
|
||||
|
||||
#. TODO - Restrict analysis to move range
|
||||
msgid "limit to moves"
|
||||
msgstr "Limit to moves"
|
||||
Binary file not shown.
@@ -968,3 +968,20 @@ msgstr "允許打劫?"
|
||||
#. Menu option for tsumego frame around position
|
||||
msgid "analysis:tsumegoframe"
|
||||
msgstr "詰碁框"
|
||||
|
||||
#. TODO
|
||||
msgid "from move"
|
||||
msgstr "from move"
|
||||
|
||||
#. TODO - status message
|
||||
msgid "move range analysis"
|
||||
msgstr ""
|
||||
"Re-analyzing moves from {start_move} to {end_move} using {visits} visits"
|
||||
|
||||
#. TODO
|
||||
msgid "to move"
|
||||
msgstr "to move"
|
||||
|
||||
#. TODO - Restrict analysis to move range
|
||||
msgid "limit to moves"
|
||||
msgstr "Limit to moves"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
+60
-24
@@ -811,37 +811,73 @@
|
||||
on_ref_press: app.webbrowser('engine:help') if args[1]=='engine_help' else app.gui('config-popup')
|
||||
|
||||
|
||||
|
||||
|
||||
<ReAnalyzeGamePopup>:
|
||||
visits: visits
|
||||
orientation: 'vertical'
|
||||
button: button
|
||||
move_range: move_range
|
||||
start_move: start_move
|
||||
end_move: end_move
|
||||
mistakes: mistakes
|
||||
BoxLayout:
|
||||
size_hint: 1, 1.5
|
||||
orientation: 'horizontal'
|
||||
DescriptionLabel:
|
||||
text: i18n._('reanalyze max visits')
|
||||
orientation: 'vertical'
|
||||
GridLayout:
|
||||
cols: 2
|
||||
rows: 2
|
||||
size_hint: 1, 2
|
||||
DescriptionLabel:
|
||||
text: i18n._('reanalyze max visits')
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
id: visits
|
||||
text: '2500'
|
||||
BoxLayout:
|
||||
AnchorLayout:
|
||||
size_hint: 0.3333, 1
|
||||
LabelledCheckBox:
|
||||
id: move_range
|
||||
default_active: False
|
||||
DescriptionLabel:
|
||||
size_hint: 0.6667, 1
|
||||
text: i18n._('limit to moves')
|
||||
halign: 'left'
|
||||
BoxLayout:
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
helper_text: i18n._('from move')
|
||||
helper_text_mode: "persistent"
|
||||
id: start_move
|
||||
text: '0'
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
helper_text: i18n._('to move')
|
||||
helper_text_mode: "persistent"
|
||||
id: end_move
|
||||
text: '999'
|
||||
BoxLayout:
|
||||
size_hint: 1, 1
|
||||
AnchorLayout:
|
||||
size_hint: 0.16666, 1
|
||||
LabelledCheckBox:
|
||||
id: mistakes
|
||||
default_active: False
|
||||
DescriptionLabel:
|
||||
size_hint: 0.833333, 1
|
||||
text: i18n._('reanalyze mistakes only')
|
||||
halign: 'left'
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
id: visits
|
||||
text: '2500'
|
||||
BoxLayout:
|
||||
size_hint: 1, 1.5
|
||||
orientation: 'horizontal'
|
||||
DescriptionLabel:
|
||||
text: i18n._('reanalyze mistakes only')
|
||||
AnchorLayout:
|
||||
LabelledCheckBox:
|
||||
id: mistakes
|
||||
AnchorLayout:
|
||||
size_hint: 1, 1
|
||||
AutoSizedRoundedRectangleButton:
|
||||
id: button
|
||||
padding_x: 15
|
||||
size_hint: None,0.66
|
||||
text: i18n._("analysis:game") # re-use string
|
||||
on_press:
|
||||
root.katrain("analyze-extra", "game", visits=visits.input_value, mistakes_only=mistakes.input_value)
|
||||
root.popup.dismiss()
|
||||
size_hint: 1, 0.66
|
||||
AutoSizedRoundedRectangleButton:
|
||||
id: button
|
||||
padding_x: 15
|
||||
size_hint: None,0.66
|
||||
text: i18n._("analysis:game") # re-use string
|
||||
on_press:
|
||||
root.katrain("analyze-extra", "game", visits=visits.input_value, move_range=(start_move.input_value, end_move.input_value) if move_range.input_value else None, mistakes_only=mistakes.input_value)
|
||||
root.popup.dismiss()
|
||||
|
||||
<TsumegoFramePopup>:
|
||||
ko: ko
|
||||
|
||||
Reference in new issue
Block a user