* 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:
authored and GitHub committed 2022-11-20 11:01:13 +01:00
1 parent 624b3485c3
commit 695f852f2f
47 files changed
+711 -204

No files matched your search

-12
View File
@@ -1,12 +0,0 @@
# These are supported funding model platforms
github: sanderland # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # sanderbaduk
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: [3.7, 3.8, 3.9] python-version: ['3.7', '3.8', '3.9', '3.10']
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
+2
View File
@@ -56,6 +56,8 @@ Many thanks to these additional authors:
* "electricRGB" for help with adding configurable keyboard shortcuts. * "electricRGB" for help with adding configurable keyboard shortcuts.
* "milescrawford" for work on restyling the territory estimate. * "milescrawford" for work on restyling the territory estimate.
* "Funkenschlag1" for capturing stones sound and implementation, and board rotation. * "Funkenschlag1" for capturing stones sound and implementation, and board rotation.
* "waltheri" for one of the wooden board textures.
* Jacob Minsky ("jacobm-tech") for various contributions including analysis move range and improvements to territory display.
## Translators ## Translators
+1 -1
View File
@@ -82,7 +82,7 @@ Simply download and run, everything is included.
If you have a working Python 3.6-3.8 available, you should be able to simply: If you have a working Python 3.6-3.8 available, you should be able to simply:
* Run `pip3 install -U katrain` * Run `pip3 install -U katrain` to install or upgrade.
* Run the program by executing `katrain` in a terminal. * Run the program by executing `katrain` in a terminal.
### <a name="LinuxSources"></a>Installation from sources ### <a name="LinuxSources"></a>Installation from sources
+2 -2
View File
@@ -4,7 +4,6 @@
[![License:MIT](http://img.shields.io/pypi/l/katrain)](http://en.wikipedia.org/wiki/MIT_License) [![License:MIT](http://img.shields.io/pypi/l/katrain)](http://en.wikipedia.org/wiki/MIT_License)
[![GitHub Downloads](http://img.shields.io/github/downloads/sanderland/katrain/total?color=%23336699&label=github%20downloads)](http://github.com/sanderland/katrain/releases) [![GitHub Downloads](http://img.shields.io/github/downloads/sanderland/katrain/total?color=%23336699&label=github%20downloads)](http://github.com/sanderland/katrain/releases)
[![PyPI Downloads](http://pepy.tech/badge/katrain)](http://pepy.tech/project/katrain) [![PyPI Downloads](http://pepy.tech/badge/katrain)](http://pepy.tech/project/katrain)
[![Github sponsors](http://img.shields.io/static/v1?label=sponsor&message=%E2%9D%A4&logo=GitHub&color=dcb424&link=http://github.com/sponsors/sanderland/)](http://github.com/sponsors/sanderland)
[![Discord](http://img.shields.io/discord/417022162348802048?logo=discord)](http://discord.com/channels/417022162348802048/629446365688365067) [![Discord](http://img.shields.io/discord/417022162348802048?logo=discord)](http://discord.com/channels/417022162348802048/629446365688365067)
KaTrain is a tool for analyzing games and playing go with AI feedback from KataGo: KaTrain is a tool for analyzing games and playing go with AI feedback from KataGo:
@@ -167,6 +166,7 @@ Keyboard shortcuts are shown with **[key]**.
* **[i]**: Start insertion mode. Allows you to insert moves, to improve analysis when both players ignore an important exchange or life and death situation. Press again to stop inserting and copy the rest of the branch. * **[i]**: Start insertion mode. Allows you to insert moves, to improve analysis when both players ignore an important exchange or life and death situation. Press again to stop inserting and copy the rest of the branch.
* **[l]**: Play out the game until the end and add as a collapsed branch, to visualize the potential effect of mistakes. This is done in the background, and can be started at several nodes at once when comparing the results at different starting positions. * **[l]**: Play out the game until the end and add as a collapsed branch, to visualize the potential effect of mistakes. This is done in the background, and can be started at several nodes at once when comparing the results at different starting positions.
* **[spacebar]**: Turn continuous analysis on/off. This will continuously improve analysis of the current position, similar to Lizzie's 'pondering', but only when there are no other queries going on. * **[spacebar]**: Turn continuous analysis on/off. This will continuously improve analysis of the current position, similar to Lizzie's 'pondering', but only when there are no other queries going on.
* **[shift+spacebar]**: As above, but does not turn 'top moves' hints on when it is off.
* **[enter]** AI move. Makes the AI move for the current player regardless of current player selection. * **[enter]** AI move. Makes the AI move for the current player regardless of current player selection.
* **[F2]**: Deeper full game analysis. Analyze the entire game to a higher number of visits. * **[F2]**: Deeper full game analysis. Analyze the entire game to a higher number of visits.
* **[F3]**: Performance report. Show an overview of performance statistics for both players. * **[F3]**: Performance report. Show an overview of performance statistics for both players.
@@ -245,7 +245,7 @@ See [these instructions](THEMES.md) for how to modify the look of any graphics o
* Ideas, feedback, and contributions to code or translations are all very welcome. * Ideas, feedback, and contributions to code or translations are all very welcome.
* For suggestions and planned improvements, see [open issues](http://github.com/sanderland/katrain/issues) on github to check if the functionality is already planned. * For suggestions and planned improvements, see [open issues](http://github.com/sanderland/katrain/issues) on github to check if the functionality is already planned.
* You can contact me on the [Leela Zero & Friends Discord](http://discord.gg/AjTPFpN) (use the #gui channel) to get help, discuss improvements, or simply show your appreciation. * You can join the [Computer Go Community Discord (formerly Leela Zero & Friends)](http://discord.gg/AjTPFpN) (use the #gui channel) to get help, discuss improvements, or simply show your appreciation. Please do not use github issues to ask for technical help, this is only for bugs, suggestions and discussing contributions.
+28
View File
@@ -16,6 +16,34 @@ Version 1.7 brings basic support for themes, and 1.9 extends it to include keybo
* All resources (including icons, which can not be renamed for now) will be looked up in `<home dir>/.katrain` first, so files with identical names there can be used to override sounds and images. * All resources (including icons, which can not be renamed for now) will be looked up in `<home dir>/.katrain` first, so files with identical names there can be used to override sounds and images.
* If variables are specified in multiple theme files, the *latest* alphabetically takes precedence. That is, each later theme file overwrites the settings from any previous one. * If variables are specified in multiple theme files, the *latest* alphabetically takes precedence. That is, each later theme file overwrites the settings from any previous one.
## Expected territory options
* KaTrain supports different styles of display of expected territory:
* Blended style colors the board with an intensity proportional to the likelihood of a player controlling that territory at the end of the game.
* In the Marks style, each point of the board is marked with a square of size which is proportional to ownership likelihood.
* The Blocks style divides the whole board into black, white, and neutral territory, based on a likelihood threshold. This style is appropriate as a counting aid, but may be misleading before endgame if much of the territory is unsettled.
* Marks can also appear on stones to indicate the likelihood of these stones living at the end of the game. Three styles are supported:
* All stones can be marked, with the color of the mark indicating the expected ownership and the size of the mark indicating certainty.
* Weak stones only - marks will appear only on stones which are over 50% likely to die before the end of the game.
* No stone marks.
| <img src="https://raw.githubusercontent.com/jacobm-tech/katrain/blended-territory-display/themes/blended-all.png" width="400px"/><br><span style="font-weight:normal">Blended style, all stones marked</span> | <img src="https://raw.githubusercontent.com/jacobm-tech/katrain/blended-territory-display/themes/marks-weak.png" width="400px"/><br><span style="font-weight:normal">Marks on intersections, weak stones marked</span> |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
| <img src="https://raw.githubusercontent.com/jacobm-tech/katrain/blended-territory-display/themes/blocks-none.png" width="400px"/><br>Territory blocks, no stones marked | <img src="https://raw.githubusercontent.com/jacobm-tech/katrain/blended-territory-display/themes/blended-weak.png" width="400px"/><br>Blended territory, weak stones marked |
<sup>The game used in the screenshots is [Albert Yen vs. Eric Yoder](https://www.usgo.org/news/2022/03/members-edition-midwest-open-round-2-the-broken-ladder-game).</sup>
The stone marks and territory style are independent; the table above presents 4 possible variants out of 9.
The relevant variables are:
```
TERRITORY_DISPLAY = "blended" | "marks" | "blocks"
STONE_MARKS = "all" | "weak" | "none"
OWNERSHIP_MAX_ALPHA = 0.7
BLOCKS_THRESHOLD = 0.6
MARK_SIZE = 0.42 # as fraction of stone size
```
## Installation ## Installation
* To install a theme, simply unzip the theme.zip to your .katrain folder. * To install a theme, simply unzip the theme.zip to your .katrain folder.
+15 -6
View File
@@ -5,6 +5,14 @@ import sys
os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning") 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 import kivy
kivy.require("2.0.0") kivy.require("2.0.0")
@@ -12,7 +20,6 @@ kivy.require("2.0.0")
# next, icon # next, icon
from katrain.core.utils import find_package_resource, PATHS from katrain.core.utils import find_package_resource, PATHS
from kivy.config import Config from kivy.config import Config
from kivy.utils import platform
ICON = find_package_resource("katrain/img/icon.ico") ICON = find_package_resource("katrain/img/icon.ico")
Config.set("kivy", "window_icon", ICON) Config.set("kivy", "window_icon", ICON)
@@ -52,7 +59,6 @@ from kivy.properties import NumericProperty, ObjectProperty, StringProperty
from kivy.clock import Clock from kivy.clock import Clock
from kivy.metrics import dp from kivy.metrics import dp
from katrain.core.ai import generate_ai_move 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.lang import DEFAULT_LANGUAGE, i18n
from katrain.core.constants import ( from katrain.core.constants import (
@@ -156,12 +162,14 @@ class KaTrainGui(Screen, KaTrainBase):
def play_analyze_mode(self): def play_analyze_mode(self):
return self.play_mode.mode return self.play_mode.mode
def toggle_continuous_analysis(self): def toggle_continuous_analysis(self, quiet=False):
if self.contributing: if self.contributing:
self.animate_contributing = not self.animate_contributing self.animate_contributing = not self.animate_contributing
else: else:
if self.pondering: if self.pondering:
self.controls.set_status("", STATUS_INFO) 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.pondering = not self.pondering
self.update_state() 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): 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.play_mode.switch_ui_mode() # for new game, go to play, for loaded, analyze
self.board_gui.animating_pv = None 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.engine.on_new_game() # clear queries
self.game = Game( self.game = Game(
self, self,
@@ -401,6 +409,7 @@ class KaTrainGui(Screen, KaTrainBase):
def _do_redo(self, n_times=1): def _do_redo(self, n_times=1):
self.board_gui.animating_pv = None self.board_gui.animating_pv = None
self.game.redo(n_times) self.game.redo(n_times)
def _do_rotate(self): def _do_rotate(self):
self.board_gui.rotate_gridpos() self.board_gui.rotate_gridpos()
@@ -412,7 +421,7 @@ class KaTrainGui(Screen, KaTrainBase):
self.board_gui.animating_pv = None self.board_gui.animating_pv = None
self.controls.move_tree.switch_branch(*args) 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)) play_sound(random.choice(Theme.STONE_SOUNDS))
def _do_play(self, coords): def _do_play(self, coords):
@@ -730,7 +739,7 @@ class KaTrainGui(Screen, KaTrainBase):
return return
if keycode[1] == Theme.KEY_TOGGLE_CONTINUOUS_ANALYSIS: 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: elif keycode[1] == Theme.KEY_TOGGLE_COORDINATES:
self.board_gui.toggle_coordinates() self.board_gui.toggle_coordinates()
elif keycode[1] in Theme.KEY_PAUSE_TIMER and not ctrl_pressed: elif keycode[1] in Theme.KEY_PAUSE_TIMER and not ctrl_pressed:
+1 -1
View File
@@ -28,7 +28,7 @@
"anim_pv_time": 0.5, "anim_pv_time": 0.5,
"debug_level": 0, "debug_level": 0,
"lang": "en", "lang": "en",
"version": "1.11.2", "version": "1.11.3",
"load_fast_analysis": false, "load_fast_analysis": false,
"load_sgf_rewind": true "load_sgf_rewind": true
}, },
+1 -1
View File
@@ -244,7 +244,7 @@ def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Optio
extra_settings=extra_settings, extra_settings=extra_settings,
) )
while not (error or analysis): 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) engine.check_alive(exception_if_dead=True)
return analysis return analysis
+1 -1
View File
@@ -1,5 +1,5 @@
PROGRAM_NAME = "KaTrain" PROGRAM_NAME = "KaTrain"
VERSION = "1.11.2" VERSION = "1.12.0"
HOMEPAGE = "https://github.com/sanderland/katrain" HOMEPAGE = "https://github.com/sanderland/katrain"
CONFIG_MIN_VERSION = "1.11.0" # keep config files from this version CONFIG_MIN_VERSION = "1.11.0" # keep config files from this version
ANALYSIS_FORMAT_VERSION = "1.0" ANALYSIS_FORMAT_VERSION = "1.0"
+3 -7
View File
@@ -344,15 +344,11 @@ class KataGoEngine(BaseEngine):
if pq.get(k) != query.get(k) if pq.get(k) != query.get(k)
} }
if differences: if differences:
# TODO:remove
self.katrain.log(f"Found differences in ponder check: {differences}", OUTPUT_EXTRA_DEBUG)
self.stop_pondering() self.stop_pondering()
query["maxVisits"] = 1_000_000 query["maxVisits"] = 10_000_000
query["reportDuringSearchEvery"] = PONDERING_REPORT_DT query["reportDuringSearchEvery"] = PONDERING_REPORT_DT
self.ponder_query = query self.ponder_query = query
else: else:
# TODO:remove
self.katrain.log("Found no differences in ponder check, skipping", OUTPUT_EXTRA_DEBUG)
continue continue
terminate = query.get("action") == "terminate" terminate = query.get("action") == "terminate"
@@ -434,10 +430,10 @@ class KataGoEngine(BaseEngine):
avoid = [] avoid = []
settings = copy.copy(self.override_settings) settings = copy.copy(self.override_settings)
settings["wideRootNoise"] = self.config["wide_root_noise"]
if time_limit: if time_limit:
settings["maxTime"] = self.config["max_time"] 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 = { query = {
"rules": self.get_rules(analysis_node.ruleset), "rules": self.get_rules(analysis_node.ruleset),
"priority": self.base_priority + priority, "priority": self.base_priority + priority,
+18 -4
View File
@@ -575,7 +575,6 @@ class Game(BaseGame):
return return
engine = self.engines[cn.next_player] engine = self.engines[cn.next_player]
Clock.schedule_once(self.katrain.analysis_controls.hints.activate, 0)
if mode == "ponder": if mode == "ponder":
cn.analyze( cn.analyze(
@@ -602,6 +601,10 @@ class Game(BaseGame):
if mode == "game": if mode == "game":
nodes = self.root.nodes_in_tree nodes = self.root.nodes_in_tree
only_mistakes = kwargs.get("mistakes_only", False) 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] threshold = self.katrain.config("trainer/eval_thresholds")[-4]
if "visits" in kwargs: if "visits" in kwargs:
visits = kwargs["visits"] visits = kwargs["visits"]
@@ -610,9 +613,20 @@ class Game(BaseGame):
visits = min_visits + engine.config["max_visits"] visits = min_visits + engine.config["max_visits"]
for node in nodes: for node in nodes:
max_point_loss = max(c.points_lost or 0 for c in [node] + node.children) max_point_loss = max(c.points_lost or 0 for c in [node] + node.children)
if not only_mistakes or max_point_loss > threshold: if only_mistakes and max_point_loss <= threshold:
node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False, report_every=None) continue
self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS) 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 return
elif mode == "sweep": elif mode == "sweep":
+1 -1
View File
@@ -374,7 +374,7 @@ class SGFNode:
def place_handicap_stones(self, n_handicaps, tygem=False): def place_handicap_stones(self, n_handicaps, tygem=False):
board_size_x, board_size_y = self.board_size board_size_x, board_size_y = self.board_size
if min(board_size_x, board_size_y) < 3: 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_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) near_y = 3 if board_size_y >= 13 else min(2, board_size_y - 1)
far_x = board_size_x - 1 - near_x far_x = board_size_x - 1 - near_x
+1
View File
@@ -736,6 +736,7 @@
size_hint: None, 0.7 size_hint: None, 0.7
pos_hint: {'center_y':0.5} pos_hint: {'center_y':0.5}
checkbox: checkbox checkbox: checkbox
label: label
ClickableLabel: ClickableLabel:
id: label id: label
text: root.text text: root.text
+312 -118
View File
@@ -1,11 +1,12 @@
import math import math
import time import time
from typing import List, Optional from typing import List, Optional
import numpy as np import copy
from kivy.clock import Clock from kivy.clock import Clock
from kivy.core.window import Window 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.graphics.vertex_instructions import Ellipse, Line, Rectangle
from kivy.metrics import dp from kivy.metrics import dp
from kivy.properties import BooleanProperty, ListProperty, NumericProperty, ObjectProperty from kivy.properties import BooleanProperty, ListProperty, NumericProperty, ObjectProperty
@@ -41,7 +42,7 @@ class BadukPanWidget(Widget):
super(BadukPanWidget, self).__init__(**kwargs) super(BadukPanWidget, self).__init__(**kwargs)
self.trainer_config = {} self.trainer_config = {}
self.ghost_stone = [] self.ghost_stone = []
self.gridpos = np.zeros((0,0,2)) self.gridpos = None
self.initial_gridpos_x = [] self.initial_gridpos_x = []
self.initial_gridpos_y = [] self.initial_gridpos_y = []
self.rotation_degree = 0 self.rotation_degree = 0
@@ -62,6 +63,10 @@ class BadukPanWidget(Widget):
self.bind(size=self.redraw_trigger, pos=self.redraw_trigger) self.bind(size=self.redraw_trigger, pos=self.redraw_trigger)
Clock.schedule_interval(self.animate_pv, 0.1) Clock.schedule_interval(self.animate_pv, 0.1)
def reset_rotation(self):
while self.rotation_degree:
self.rotate_gridpos()
def toggle_coordinates(self): def toggle_coordinates(self):
self.draw_coords_enabled = not self.draw_coords_enabled self.draw_coords_enabled = not self.draw_coords_enabled
self.redraw_trigger() self.redraw_trigger()
@@ -72,16 +77,16 @@ class BadukPanWidget(Widget):
# stone placement functions # stone placement functions
def _find_closest(self, pos_x, pos_y): 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 xp = 0
yd = abs(self.gridpos[0,0,1] - pos_y) yd = abs(self.gridpos[0][0][1] - pos_y)
yp = 0 yp = 0
for y in range(0, len(self.gridpos)): for y in range(0, len(self.gridpos)):
for x in range(0, len(self.gridpos[0])): 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: 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) xd = abs(self.gridpos[y][x][0] - pos_x)
xp = x xp = x
yd = abs(self.gridpos[y,x,1] - pos_y) yd = abs(self.gridpos[y][x][1] - pos_y)
yp = y yp = y
return xd, xp, yd, yp return xd, xp, yd, yp
@@ -147,9 +152,9 @@ class BadukPanWidget(Widget):
(pv, node) (pv, node)
for move, pv, node in self.active_pv_moves for move, pv, node in self.active_pv_moves
if move[0] < len(self.gridpos[0]) if move[0] < len(self.gridpos[0])
and move[1] < len(self.gridpos) 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[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 abs(rel_pos[1] - self.gridpos[move[1]][move[0]][1]) < self.grid_size / 2
] ]
if near_move: if near_move:
self.set_animating_pv(near_move[0][0], near_move[0][1]) 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()
self.draw_board_contents() 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 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) Color(1, 1, 1, alpha)
Rectangle( 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), size=(2 * stone_size, 2 * stone_size),
texture=cached_texture(Theme.STONE_TEXTURE[player]), 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: if evalcol:
eval_radius = math.sqrt(evalscale) # scale area by evalscale eval_radius = math.sqrt(evalscale) # scale area by evalscale
evalsize = self.stone_size * ( evalsize = self.stone_size * (
@@ -236,7 +281,7 @@ class BadukPanWidget(Widget):
) )
Color(*evalcol) Color(*evalcol)
Rectangle( 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), size=(2 * evalsize, 2 * evalsize),
texture=cached_texture(Theme.EVAL_DOT_TEXTURE), texture=cached_texture(Theme.EVAL_DOT_TEXTURE),
) )
@@ -244,7 +289,7 @@ class BadukPanWidget(Widget):
Color(*innercol) Color(*innercol)
inner_size = stone_size * 0.8 inner_size = stone_size * 0.8
Rectangle( 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), size=(2 * inner_size, 2 * inner_size),
texture=cached_texture(Theme.LAST_MOVE_TEXTURE), 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() grid_spaces_margin_x, grid_spaces_margin_y = self.get_grid_spaces_margins()
h = round(self.height, 4) h = round(self.height, 4)
w = round(self.width, 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) 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) board_width_with_margins, board_height_with_margins = self.calculate_board_margins(
extra_px_margin_x, extra_px_margin_y = self.calculate_extra_px_margins(w, h, board_width_with_margins, board_height_with_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) self.stone_size = self.calculate_stone_size(self.grid_size)
# if not initiated or if changed # if not initiated or if changed
if ( if (
(len(self.gridpos) == 0 or abs(self.pos[0] + extra_px_margin_x self.gridpos is None
+ math.floor(grid_spaces_margin_x[0] * self.grid_size + 0.5) - self.initial_gridpos_x[0]) > 0.001) 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: if self.rotation_degree == 0:
self.initialize_gridpos() self.initialize_gridpos()
if (self.rotation_degree == 90 or self.rotation_degree == 270) and board_size_x != board_size_y: 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 # 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_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y) 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.grid_size = self.calculate_grid_size(w, h, x_grid_spaces, y_grid_spaces)
self.stone_size = self.calculate_stone_size(self.grid_size) self.stone_size = self.calculate_stone_size(self.grid_size)
current_gridpos_x, current_gridpos_y = self.calculate_rotated_gridpos() current_gridpos_x, current_gridpos_y = self.calculate_rotated_gridpos()
@@ -293,18 +360,28 @@ class BadukPanWidget(Widget):
# if window size got changed # if window size got changed
if ( if (
(self.gridpos[0,0,0] not in current_gridpos_x or self.gridpos[0,0,1] not in current_gridpos_y self.gridpos[0][0][0] not in current_gridpos_x
or (self.gridpos[len(self.gridpos) - 1,len(self.gridpos[0]) - 1,0] 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,1] 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.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_lines(current_gridpos_x, current_gridpos_y)
self.draw_star_points(board_size_x, board_size_y) self.draw_star_points(board_size_x, board_size_y)
self.draw_coordinates(current_gridpos_x, current_gridpos_y) self.draw_coordinates(current_gridpos_x, current_gridpos_y)
def get_grid_spaces_margins(self): def get_grid_spaces_margins(self):
if self.draw_coords_enabled: if self.draw_coords_enabled:
grid_spaces_margin_x = [1.5, 0.75] # left, right 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 grid_spaces_margin_y = [0.75, 0.75] # bottom, top
return grid_spaces_margin_x, grid_spaces_margin_y 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): 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) x_grid_spaces = board_size_x - 1 + sum(grid_spaces_margin_x)
y_grid_spaces = board_size_y - 1 + sum(grid_spaces_margin_y) y_grid_spaces = board_size_y - 1 + sum(grid_spaces_margin_y)
return x_grid_spaces, y_grid_spaces return x_grid_spaces, y_grid_spaces
def calculate_grid_size(self, width, height, 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 # grid size is rounded to an integer to avoid rounding errors that
# produce tiny gaps between shaded grid squares # produce tiny gaps between shaded grid squares
return math.floor(min(width / x_grid_spaces, height / y_grid_spaces) + 0.1) 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): def calculate_board_margins(self, x_grid_spaces, y_grid_spaces, grid_size):
board_width_with_margins = x_grid_spaces * grid_size board_width_with_margins = x_grid_spaces * grid_size
board_height_with_margins = y_grid_spaces * grid_size board_height_with_margins = y_grid_spaces * grid_size
return board_width_with_margins, board_height_with_margins 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): 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_x = round((width - board_width_with_margins) / 2, 4)
extra_px_margin_y = round((height - board_height_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 return extra_px_margin_x, extra_px_margin_y
def calculate_stone_size(self, grid_size): def calculate_stone_size(self, grid_size):
return grid_size * Theme.STONE_SIZE return grid_size * Theme.STONE_SIZE
def initialize_gridpos(self): 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 y in range(len(self.initial_gridpos_y)):
for x in range(len(self.initial_gridpos_x)): for x in range(len(self.initial_gridpos_x)):
self.gridpos[y][x] = [self.initial_gridpos_x[x], self.initial_gridpos_y[y]] self.gridpos[y][x] = [self.initial_gridpos_x[x], self.initial_gridpos_y[y]]
def initialize_gridpos_x_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): 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 = [ gridpos_x = [
round(self.pos[0] + extra_px_margin_x + math.floor((grid_spaces_margin_x[0] + i) * grid_size + 0.5), 4) 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) for i in range(board_size_x)
@@ -365,20 +445,32 @@ class BadukPanWidget(Widget):
return gridpos_x, gridpos_y return gridpos_x, gridpos_y
def calculate_rotated_gridpos(self): def calculate_rotated_gridpos(self):
board_size_y, board_size_x = self.katrain.game.board_size board_size_y, board_size_x = self.katrain.game.board_size
grid_spaces_margin_x, grid_spaces_margin_y = self.get_grid_spaces_margins() grid_spaces_margin_x, grid_spaces_margin_y = self.get_grid_spaces_margins()
h = round(self.height, 4) h = round(self.height, 4)
w = round(self.width, 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(
grid_size = self.calculate_grid_size(w, h , x_grid_spaces, y_grid_spaces) board_size_x, board_size_y, grid_spaces_margin_x, grid_spaces_margin_y
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) 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(
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, 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): def resize_board(self):
rotated_gridpos_x, rotated_gridpos_y = self.calculate_rotated_gridpos() 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 yi in range(len(self.gridpos)):
for xi in range(len(self.gridpos[0])): for xi in range(len(self.gridpos[0])):
current_gridpos_x.append(self.gridpos[yi,xi,0]) current_gridpos_x.append(self.gridpos[yi][xi][0])
current_gridpos_y.append(self.gridpos[yi,xi,1]) current_gridpos_y.append(self.gridpos[yi][xi][1])
sorted_current_gridpos_x = list(set(current_gridpos_x)) sorted_current_gridpos_x = list(set(current_gridpos_x))
sorted_current_gridpos_x.sort() sorted_current_gridpos_x.sort()
sorted_current_gridpos_y = list(set(current_gridpos_y)) sorted_current_gridpos_y = list(set(current_gridpos_y))
@@ -397,15 +489,16 @@ class BadukPanWidget(Widget):
for yi in range(len(self.gridpos)): for yi in range(len(self.gridpos)):
for xi in range(len(self.gridpos[0])): for xi in range(len(self.gridpos[0])):
index_x = sorted_current_gridpos_x.index(self.gridpos[yi,xi,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_y = sorted_current_gridpos_y.index(self.gridpos[yi][xi][1])
if self.rotation_degree == 90 or self.rotation_degree == 270: if self.rotation_degree == 90 or self.rotation_degree == 270:
self.gridpos[yi][xi] = [rotated_gridpos_x[index_x], rotated_gridpos_y[index_y]] self.gridpos[yi][xi] = [rotated_gridpos_x[index_x], rotated_gridpos_y[index_y]]
else: else:
self.gridpos[yi][xi] = [self.initial_gridpos_x[index_x], self.initial_gridpos_y[index_y]] self.gridpos[yi][xi] = [self.initial_gridpos_x[index_x], self.initial_gridpos_y[index_y]]
def draw_board_background(
def draw_board_background(self, katrain, gridpos_x, gridpos_y, x_grid_spaces, y_grid_spaces, grid_spaces_margin_x, grid_spaces_margin_y): 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: if katrain.game.insert_mode:
Color(*Theme.INSERT_BOARD_COLOR_TINT) # dreamy Color(*Theme.INSERT_BOARD_COLOR_TINT) # dreamy
else: else:
@@ -426,21 +519,17 @@ class BadukPanWidget(Widget):
for i in range(len(gridpos_y)): for i in range(len(gridpos_y)):
Line(points=[(gridpos_x[0], gridpos_y[i]), (gridpos_x[-1], gridpos_y[i])]) 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 draw_star_points(self, board_size_x, board_size_y):
def star_point_coords(size): def star_point_coords(size):
star_point_pos = 3 if size <= 11 else 4 star_point_pos = 3 if size <= 11 else 4
if size < 7: if size < 7:
return [] return []
return [star_point_pos - 1, size - star_point_pos] + ( return [star_point_pos - 1, size - star_point_pos] + ([int(size / 2)] if size % 2 == 1 and size > 7 else [])
[int(size / 2)] if size % 2 == 1 and size > 7 else []
)
starpt_size = self.grid_size * Theme.STARPOINT_SIZE starpt_size = self.grid_size * Theme.STARPOINT_SIZE
for x in star_point_coords(board_size_x): for x in star_point_coords(board_size_x):
for y in star_point_coords(board_size_y): 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): def draw_coordinates(self, gridpos_x, gridpos_y):
if self.draw_coords_enabled: if self.draw_coords_enabled:
@@ -466,38 +555,37 @@ class BadukPanWidget(Widget):
font_name="Roboto", font_name="Roboto",
) )
def get_x_coordinate_text(self, i, board_size_x): def get_x_coordinate_text(self, i, board_size_x):
x_text = Move.GTP_COORD[i] x_text = Move.GTP_COORD[i]
if (self.rotation_degree == 90): if self.rotation_degree == 90:
x_text = str(i + 1) 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] 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) x_text = str(board_size_x - i)
return x_text return x_text
def get_y_coordinate_text(self, i, board_size_y): def get_y_coordinate_text(self, i, board_size_y):
y_text = str(i + 1) 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] 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) y_text = str(board_size_y - i)
elif (self.rotation_degree == 270): elif self.rotation_degree == 270:
y_text = Move.GTP_COORD[i] y_text = Move.GTP_COORD[i]
return y_text return y_text
def draw_board_contents(self, *_args): def draw_board_contents(self, *_args):
if not (self.katrain and self.katrain.game): if not (self.katrain and self.katrain.game):
return return
katrain = self.katrain katrain = self.katrain
board_size_x, board_size_y = katrain.game.board_size 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 return # race condition
show_n_eval = self.trainer_config.get("eval_on_show_last", 3) show_n_eval = self.trainer_config.get("eval_on_show_last", 3)
ownership_grid = None
loss_grid = None
with self.canvas: with self.canvas:
self.canvas.clear() self.canvas.clear()
@@ -517,32 +605,15 @@ class BadukPanWidget(Widget):
[a - b for a, b in zip(current_node.children[-1].ownership, ownership)], [a - b for a, b in zip(current_node.children[-1].ownership, ownership)],
(board_size_x, board_size_y), (board_size_x, board_size_y),
) )
for y in range(board_size_y - 1, -1, -1): for y in range(board_size_y - 1, -1, -1):
for x in range(board_size_x): 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]) loss_grid[y][x] = max(
if loss > 0: 0, (-1 if current_node.children[-1].move.player == "B" else 1) * loss_grid[y][x]
Color(*Theme.EVAL_COLORS[self.trainer_config["theme"]][1][:3], loss) )
Rectangle( self.draw_territory(loss_grid, Theme.EVAL_COLORS[self.trainer_config["theme"]][1][:3])
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),
)
else: else:
ownership_grid = var_to_grid(ownership, (board_size_x, board_size_y)) ownership_grid = var_to_grid(ownership, (board_size_x, board_size_y))
for y in range(board_size_y - 1, -1, -1): self.draw_territory(ownership_grid)
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),
)
# stones # stones
all_dots_off = not katrain.analysis_controls.eval.active all_dots_off = not katrain.analysis_controls.eval.active
has_stone = {} has_stone = {}
@@ -567,6 +638,7 @@ class BadukPanWidget(Widget):
evalscale = min(1, max(0, realized_points_lost / points_lost)) evalscale = min(1, max(0, realized_points_lost / points_lost))
placements = node.placements placements = node.placements
for m in node.moves + 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 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 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: if move_eval_on and points_lost is not None:
@@ -582,6 +654,10 @@ class BadukPanWidget(Widget):
innercol=inner, innercol=inner,
evalcol=evalcol, evalcol=evalcol,
evalscale=evalscale, 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 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)))) pol_order = max(0, 5 + int(math.log10(max(1e-9, move_policy - 1e-9))))
if move_policy > text_lb: if move_policy > text_lb:
draw_circle( 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, self.stone_size * Theme.HINT_SCALE * 0.98,
Theme.APPROX_BOARD_COLOR, Theme.APPROX_BOARD_COLOR,
) )
@@ -628,14 +704,14 @@ class BadukPanWidget(Widget):
else: else:
scale = 0.5 scale = 0.5
draw_circle( 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, Theme.HINT_SCALE * self.stone_size * scale,
(*colors[pol_order][:3], Theme.POLICY_ALPHA), (*colors[pol_order][:3], Theme.POLICY_ALPHA),
) )
if move_policy > text_lb: if move_policy > text_lb:
Color(*Theme.HINT_TEXT_COLOR) Color(*Theme.HINT_TEXT_COLOR)
draw_text( 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] + "%", text=f"{100 * move_policy :.2f}"[:4] + "%",
font_name="Roboto", font_name="Roboto",
font_size=self.grid_size / 4, font_size=self.grid_size / 4,
@@ -645,8 +721,8 @@ class BadukPanWidget(Widget):
Color(*Theme.TOP_MOVE_BORDER_COLOR[:3], Theme.POLICY_ALPHA) Color(*Theme.TOP_MOVE_BORDER_COLOR[:3], Theme.POLICY_ALPHA)
Line( Line(
circle=( circle=(
self.gridpos[y,x,0], self.gridpos[y][x][0],
self.gridpos[y,x,1], self.gridpos[y][x][1],
self.stone_size - dp(1.2), self.stone_size - dp(1.2),
), ),
width=dp(2), width=dp(2),
@@ -664,15 +740,116 @@ class BadukPanWidget(Widget):
self.redraw_hover_contents_trigger() self.redraw_hover_contents_trigger()
def draw_roi_box(self, region_of_interest, width:float=2): def draw_territory(self, grid, loss_color=None):
xmin, xmax, ymin, ymax = region_of_interest 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) Color(*Theme.REGION_BORDER_COLOR)
Line( Line(
rectangle=( rectangle=(
self.gridpos[ymin,xmin,0] - self.grid_size / 3, min(x_start, x_end) - self.grid_size / 3,
self.gridpos[ymin,xmin,1] - self.grid_size / 3, min(y_start, y_end) - self.grid_size / 3,
(xmax - xmin + 2 / 3) * self.grid_size, abs(x_start - x_end) + (2 / 3) * self.grid_size,
(ymax - ymin + 2 / 3) * self.grid_size, abs(y_start - y_end) + (2 / 3) * self.grid_size,
), ),
width=width, width=width,
) )
@@ -746,6 +923,9 @@ class BadukPanWidget(Widget):
text_on = False text_on = False
alpha = Theme.HINTS_LO_ALPHA alpha = Theme.HINTS_LO_ALPHA
if scale <= 0: # if theme turns hints off, do not draw them
continue
if "pv" in move_dict: if "pv" in move_dict:
self.active_pv_moves.append((move.coords, move_dict["pv"], current_node)) self.active_pv_moves.append((move.coords, move_dict["pv"], current_node))
else: else:
@@ -754,14 +934,20 @@ class BadukPanWidget(Widget):
evalcol = self.eval_color(move_dict["pointsLost"]) evalcol = self.eval_color(move_dict["pointsLost"])
if text_on and top_moves_show: # remove grid lines using a board colored circle if text_on and top_moves_show: # remove grid lines using a board colored circle
draw_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, self.stone_size * scale * 0.98,
Theme.APPROX_BOARD_COLOR, Theme.APPROX_BOARD_COLOR,
) )
Color(*evalcol[:3], alpha) Color(*evalcol[:3], alpha)
Rectangle( 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), size=(2 * evalsize, 2 * evalsize),
texture=cached_texture(Theme.TOP_MOVE_TEXTURE), texture=cached_texture(Theme.TOP_MOVE_TEXTURE),
) )
@@ -791,7 +977,10 @@ class BadukPanWidget(Widget):
Color(*Theme.HINT_TEXT_COLOR) Color(*Theme.HINT_TEXT_COLOR)
draw_text( 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), text=fmt.format(**keys),
font_name="Roboto", font_name="Roboto",
markup=True, markup=True,
@@ -804,8 +993,8 @@ class BadukPanWidget(Widget):
Color(*Theme.TOP_MOVE_BORDER_COLOR) Color(*Theme.TOP_MOVE_BORDER_COLOR)
Line( Line(
circle=( circle=(
self.gridpos[move.coords[1],move.coords[0],0], 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]][1],
self.stone_size - dp(1.2), self.stone_size - dp(1.2),
), ),
width=dp(1.2), width=dp(1.2),
@@ -826,8 +1015,8 @@ class BadukPanWidget(Widget):
Color(*Theme.NEXT_MOVE_DASH_CONTRAST_COLORS[child_node.player]) Color(*Theme.NEXT_MOVE_DASH_CONTRAST_COLORS[child_node.player])
Line( Line(
circle=( circle=(
self.gridpos[move.coords[1],move.coords[0],0], 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]][1],
self.stone_size - dp(1.2), self.stone_size - dp(1.2),
), ),
width=dp(1.2), width=dp(1.2),
@@ -838,8 +1027,8 @@ class BadukPanWidget(Widget):
for s in range(0, 360, 30): for s in range(0, 360, 30):
Line( Line(
circle=( circle=(
self.gridpos[move.coords[1],move.coords[0],0], 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]][1],
self.stone_size - dp(1.2), self.stone_size - dp(1.2),
s, s,
s + dashed_width, s + dashed_width,
@@ -848,8 +1037,7 @@ class BadukPanWidget(Widget):
) )
if self.selecting_region_of_interest and len(self.region_of_interest) == 4: 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(self.region_of_interest, width=dp(2))
self.draw_roi_box([min(x1, x2), max(x1, x2), min(y1, y2), max(y1, y2)], width=dp(2))
else: else:
# hover next move ghost stone # hover next move ghost stone
if self.ghost_stone: if self.ghost_stone:
@@ -890,7 +1078,10 @@ class BadukPanWidget(Widget):
hide_node = cn hide_node = cn
while hide_node and hide_node.move and hide_node != node: while hide_node and hide_node.move and hide_node != node:
if not hide_node.move.is_pass: 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]) draw_circle(pos, self.stone_size, [0.85, 0.68, 0.40, 0.8])
hide_node = hide_node.parent hide_node = hide_node.parent
for i, gtpmove in enumerate(pv): 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, katrain.board_controls.pass_btn.pos[1] + katrain.board_controls.pass_btn.size[1] / 2,
] ]
else: 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 sizefac = 1
stone_size = self.stone_size * sizefac stone_size = self.stone_size * sizefac
@@ -957,10 +1148,10 @@ class BadukPanWidget(Widget):
x0 = self.initial_gridpos_x[0] x0 = self.initial_gridpos_x[0]
y0 = self.initial_gridpos_y[0] y0 = self.initial_gridpos_y[0]
pos = self.gridpos.tolist() pos = copy.deepcopy(self.gridpos)
for yi in range(len(self.gridpos)): for yi in range(len(self.gridpos)):
for xi in range(len(self.gridpos[0])): 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][:] gridpos_x = pos[len(self.gridpos) - 1 - yi][:]
else: else:
gridpos_x = pos[yi][:] gridpos_x = pos[yi][:]
@@ -973,9 +1164,11 @@ class BadukPanWidget(Widget):
elif y0 > x0: elif y0 > x0:
x = round(x - diff, 4) x = round(x - diff, 4)
y = round(y + diff, 4) y = round(y + diff, 4)
self.gridpos[yi][xi] = (x, y) self.gridpos[yi][xi] = [x, y]
else: 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 self.rotation_degree += 90
if self.rotation_degree == 360: if self.rotation_degree == 360:
@@ -990,9 +1183,10 @@ class BadukPanWidget(Widget):
class AnalysisDropDown(DropDown): class AnalysisDropDown(DropDown):
def open_game_analysis_popup(self, *_args): 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.popup = analysis_popup
analysis_popup.content.katrain = MDApp.get_running_app().gui
analysis_popup.open() analysis_popup.open()
def open_report_popup(self, *_args): def open_report_popup(self, *_args):
+19 -1
View File
@@ -839,9 +839,27 @@ class SaveSGFPopup(BoxLayout):
class ReAnalyzeGamePopup(BoxLayout): class ReAnalyzeGamePopup(BoxLayout):
katrain = ObjectProperty(None)
popup = 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): def on_submit(self):
self.button.trigger_action(duration=0) self.button.trigger_action(duration=0)
+14 -3
View File
@@ -65,6 +65,7 @@ class Theme:
LAST_MOVE_TEXTURE = "inner.png" LAST_MOVE_TEXTURE = "inner.png"
TOP_MOVE_TEXTURE = "topmove.png" TOP_MOVE_TEXTURE = "topmove.png"
BOARD_TEXTURE = "board.png" BOARD_TEXTURE = "board.png"
BOARD_TEXTURE = "wood6.jpg"
GRAPH_TEXTURE = "graph_bg.png" GRAPH_TEXTURE = "graph_bg.png"
# sounds # sounds
STONE_SOUNDS = [f"stone{i}.wav" for i in [1, 2, 3, 4, 5]] STONE_SOUNDS = [f"stone{i}.wav" for i in [1, 2, 3, 4, 5]]
@@ -98,12 +99,12 @@ class Theme:
# board theme # board theme
APPROX_BOARD_COLOR = [0.95, 0.75, 0.47, 1] # for drawing on top of / hiding what's under it 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 HINT_TEXT_COLOR = BLACK
REGION_BORDER_COLOR = LIGHTER_BACKGROUND_COLOR 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_COLOR = [0.45, 0.05, 0.45, 0.7]
PASS_CIRCLE_TEXT_COLOR = [0.85, 0.85, 0.85] PASS_CIRCLE_TEXT_COLOR = [0.85, 0.85, 0.85]
@@ -120,7 +121,17 @@ class Theme:
GHOST_ALPHA = 0.6 GHOST_ALPHA = 0.6
POLICY_ALPHA = 0.5 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_LO_ALPHA = 0.6
HINTS_ALPHA = 0.8 HINTS_ALPHA = 0.8
Binary file not shown.
@@ -945,3 +945,20 @@ msgstr "Ko allowed?"
#. TODO - Menu option for tsumego frame around position #. TODO - Menu option for tsumego frame around position
msgid "analysis:tsumegoframe" msgid "analysis:tsumegoframe"
msgstr "Tsumego Frame" 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 #. TODO - Menu option for tsumego frame around position
msgid "analysis:tsumegoframe" msgid "analysis:tsumegoframe"
msgstr "Tsumego Frame" 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" msgid "analysis:game"
msgstr "Deeper full game analysis" 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 #. Menu option for tsumego frame around position
msgid "analysis:tsumegoframe" msgid "analysis:tsumegoframe"
msgstr "Tsumego Frame" msgstr "Tsumego Frame"
@@ -210,6 +220,11 @@ msgstr "Equalizing analysis of candidate moves to {visits} visits"
msgid "game re-analysis" msgid "game re-analysis"
msgstr "Re-analyzing entire game using {visits} visits" 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 #. right panel buttons and below board
msgid "pass-button-text" msgid "pass-button-text"
msgstr "Pass" msgstr "Pass"
Binary file not shown.
@@ -984,3 +984,19 @@ msgstr "Permettre les kos"
msgid "analysis:tsumegoframe" msgid "analysis:tsumegoframe"
msgstr "Composer un problème" 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 #. Menu option for tsumego frame around position
msgid "analysis:tsumegoframe" msgid "analysis:tsumegoframe"
msgstr "詰碁用の外枠" 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" msgid "analysis:tsumegoframe"
msgstr "묘수풀이 틀" 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.
+26 -14
View File
@@ -722,9 +722,9 @@ msgstr "Изменение счёта"
msgid "show stats if" msgid "show stats if"
msgstr "Числа только если посещений больше чем" msgstr "Числа только если посещений больше чем"
#. TODO - point loss precision options #. point loss precision options
msgid "show two digits for point loss near zero" msgid "show two digits for point loss near zero"
msgstr "Show two digits for point loss near zero" msgstr "Показазывать две цифры после запятой для малой потери очков"
msgid "cache analysis to sgf" msgid "cache analysis to sgf"
msgstr "Кэшировать анализ в SGF" msgstr "Кэшировать анализ в SGF"
@@ -929,28 +929,40 @@ msgstr ""
msgid "Entire Game" msgid "Entire Game"
msgstr "Полная игра" msgstr "Полная игра"
#. TODO
msgid "ai:antimirror" 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 #. the
#. path UP TO this node #. path UP TO this node
msgid "Prune Branch" msgid "Prune Branch"
msgstr "Prune Branch" msgstr "Удалить ответвления"
#. TODO
msgid "aihelp:antimirror" 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" msgid "tsumego:walldistance"
msgstr "Distance of wall" msgstr "Расстояние до стены"
#. TODO - tsumego: ko is good enough? #. tsumego: ko is good enough?
msgid "tsumego:ko" 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" 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 #. TODO - point loss precision options
msgid "show two digits for point loss near zero" msgid "show two digits for point loss near zero"
msgstr "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 #. Menu option for tsumego frame around position
msgid "analysis:tsumegoframe" msgid "analysis:tsumegoframe"
msgstr "詰碁框" 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
View File
@@ -811,37 +811,73 @@
on_ref_press: app.webbrowser('engine:help') if args[1]=='engine_help' else app.gui('config-popup') on_ref_press: app.webbrowser('engine:help') if args[1]=='engine_help' else app.gui('config-popup')
<ReAnalyzeGamePopup>: <ReAnalyzeGamePopup>:
visits: visits visits: visits
orientation: 'vertical' orientation: 'vertical'
button: button button: button
move_range: move_range
start_move: start_move
end_move: end_move
mistakes: mistakes
BoxLayout: BoxLayout:
size_hint: 1, 1.5 size_hint: 1, 1.5
orientation: 'horizontal' orientation: 'vertical'
DescriptionLabel: GridLayout:
text: i18n._('reanalyze max visits') 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: AnchorLayout:
LabelledIntInput: size_hint: 1, 0.66
id: visits AutoSizedRoundedRectangleButton:
text: '2500' id: button
BoxLayout: padding_x: 15
size_hint: 1, 1.5 size_hint: None,0.66
orientation: 'horizontal' text: i18n._("analysis:game") # re-use string
DescriptionLabel: on_press:
text: i18n._('reanalyze mistakes only') 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)
AnchorLayout: root.popup.dismiss()
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()
<TsumegoFramePopup>: <TsumegoFramePopup>:
ko: ko ko: ko
-1
View File
@@ -10,4 +10,3 @@ Pillow==9.0.1
Pygments==2.7.4 Pygments==2.7.4
requests==2.25.0 requests==2.25.0
urllib3==1.26.5 urllib3==1.26.5
numpy==1.22.3
+2 -3
View File
@@ -42,16 +42,15 @@ setup(
"wheel", "wheel",
"setuptools", "setuptools",
"kivy[full]>=2.1.0", "kivy[full]>=2.1.0",
"kivymd==0.104.1", # 0.104.2 was breaking MRO "kivymd==0.104.1", # 0.104.2+ was breaking MRO
"ffpyplayer", "ffpyplayer",
"urllib3", "urllib3",
"numpy",
"pygame;platform_system=='Darwin'", # some mac versions need this for kivy "pygame;platform_system=='Darwin'", # some mac versions need this for kivy
"screeninfo;platform_system!='Darwin'", # for screen resolution, has problems on macos "screeninfo;platform_system!='Darwin'", # for screen resolution, has problems on macos
"chardet", # for automatic encoding detection "chardet", # for automatic encoding detection
], ],
dependency_links=["https://kivy.org/downloads/simple/"], dependency_links=["https://kivy.org/downloads/simple/"],
python_requires=">=3.7, <3.10", python_requires=">=3.7, <3.11",
entry_points={"console_scripts": ["katrain=katrain.__main__:run_app"]}, entry_points={"console_scripts": ["katrain=katrain.__main__:run_app"]},
classifiers=[ classifiers=[
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
+11 -3
View File
@@ -2,6 +2,7 @@
from kivy_deps import sdl2, glew from kivy_deps import sdl2, glew
from kivymd import hooks_path as kivymd_hooks_path from kivymd import hooks_path as kivymd_hooks_path
import subprocess import subprocess
import sys
block_cipher = None block_cipher = None
@@ -25,7 +26,7 @@ a = Analysis(
], ],
hiddenimports=["win32file", "win32timezone", "six"], # FileChooser in kivy loads win32file conditionally, mkl needs six hiddenimports=["win32file", "win32timezone", "six"], # FileChooser in kivy loads win32file conditionally, mkl needs six
hookspath=[kivymd_hooks_path], hookspath=[kivymd_hooks_path],
excludes=["matplotlib", "docutils", "scipy", "pandas"], excludes=["scipy", "pandas", "numpy", "matplotlib", "docutils", "mkl"],
win_no_prefer_redirects=False, win_no_prefer_redirects=False,
win_private_assemblies=False, win_private_assemblies=False,
cipher=None, cipher=None,
@@ -48,6 +49,11 @@ console_names = {True:"DebugKaTrain",False:"KaTrain"}
powershell = subprocess.Popen(["powershell"], stdout=subprocess.PIPE, stdin=subprocess.PIPE) powershell = subprocess.Popen(["powershell"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
# load and run script to buid VSVersionInfo object
sys.path.append(SPECPATH)
import file_version as versionModule
for console, name in console_names.items(): for console, name in console_names.items():
@@ -63,7 +69,8 @@ for console, name in console_names.items():
strip=False, strip=False,
upx=True, upx=True,
console=console, console=console,
icon="C:\\icon.ico", icon="..\\katrain\img\\icon.ico",
version=versionModule.versionInfo,
) )
coll = COLLECT( coll = COLLECT(
@@ -90,7 +97,8 @@ for console, name in console_names.items():
upx=True, upx=True,
name=name, name=name,
console=console, console=console,
icon="C:\\icon.ico", icon="..\\katrain\img\\icon.ico",
version=versionModule.versionInfo,
) )
powershell.stdin.write(f"Set-AuthenticodeSignature dist/{name}.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)\n".encode('ascii')) powershell.stdin.write(f"Set-AuthenticodeSignature dist/{name}.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)\n".encode('ascii'))
powershell.stdin.write(f"Set-AuthenticodeSignature dist/{name}/{name}.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)\n".encode('ascii')) powershell.stdin.write(f"Set-AuthenticodeSignature dist/{name}/{name}.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)\n".encode('ascii'))
+59
View File
@@ -0,0 +1,59 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.win32 import versioninfo as ver
import os
# only create VSVersionInfo object on Windows
versionInfo = None
if os.name == "nt":
# load constants.py to get version number and program name
import katrain.core.constants as constants
# build VSVersionInfo compatible version numbers
verParts = constants.VERSION.split(".")
numParts = len(verParts)
verList = list()
verString = ""
for i in range(4):
if i <= numParts - 1:
verList.append(int(verParts[i]))
verString = verString + verParts[i] + "."
else:
verList.append(int(0))
verString = verString + "0."
verString = verString.rstrip(".")
verTuple = tuple(verList)
versionInfo = ver.VSVersionInfo(
ffi=ver.FixedFileInfo(
filevers=verTuple, # must be 4 item tuple
prodvers=verTuple, # must be 4 item tuple
mask=0x3F, # Contains a bitmask that specifies the valid bits 'flags'r
flags=0x0, # Contains a bitmask that specifies the Boolean attributes of the file.
OS=0x40004, # The operating system for which this file was designed. 0x4 - NT and there is no need to change it.
fileType=0x1, # The general type of file. 0x1 - the file is an application.
subtype=0x0, # The function of the file. 0x0 - the function is not defined for this fileType
date=(0, 0), # Creation date and time stamp.
),
kids=[
ver.StringFileInfo(
[
ver.StringTable(
"040904B0",
[
ver.StringStruct("FileDescription", constants.PROGRAM_NAME),
ver.StringStruct("FileVersion", verString),
ver.StringStruct("LegalCopyright", constants.HOMEPAGE),
ver.StringStruct("OriginalFilename", constants.PROGRAM_NAME + ".exe"),
ver.StringStruct("ProductName", constants.PROGRAM_NAME),
ver.StringStruct("ProductVersion", verString),
],
)
]
),
ver.VarFileInfo([ver.VarStruct("Translation", [1033, 1200])]),
],
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 897 KiB