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
@@ -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']
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [3.7, 3.8, 3.9]
|
||||
python-version: ['3.7', '3.8', '3.9', '3.10']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -56,6 +56,8 @@ Many thanks to these additional authors:
|
||||
* "electricRGB" for help with adding configurable keyboard shortcuts.
|
||||
* "milescrawford" for work on restyling the territory estimate.
|
||||
* "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
|
||||
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
* Run `pip3 install -U katrain`
|
||||
* Run `pip3 install -U katrain` to install or upgrade.
|
||||
* Run the program by executing `katrain` in a terminal.
|
||||
|
||||
### <a name="LinuxSources"></a>Installation from sources
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
[](http://en.wikipedia.org/wiki/MIT_License)
|
||||
[](http://github.com/sanderland/katrain/releases)
|
||||
[](http://pepy.tech/project/katrain)
|
||||
[](http://github.com/sponsors/sanderland)
|
||||
[](http://discord.com/channels/417022162348802048/629446365688365067)
|
||||
|
||||
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.
|
||||
* **[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.
|
||||
* **[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.
|
||||
* **[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.
|
||||
@@ -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.
|
||||
* 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.
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
* 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
|
||||
|
||||
* To install a theme, simply unzip the theme.zip to your .katrain folder.
|
||||
|
||||
+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
|
||||
|
||||
+1
-2
@@ -9,5 +9,4 @@ kivymd==0.104.1
|
||||
Pillow==9.0.1
|
||||
Pygments==2.7.4
|
||||
requests==2.25.0
|
||||
urllib3==1.26.5
|
||||
numpy==1.22.3
|
||||
urllib3==1.26.5
|
||||
@@ -42,16 +42,15 @@ setup(
|
||||
"wheel",
|
||||
"setuptools",
|
||||
"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",
|
||||
"urllib3",
|
||||
"numpy",
|
||||
"pygame;platform_system=='Darwin'", # some mac versions need this for kivy
|
||||
"screeninfo;platform_system!='Darwin'", # for screen resolution, has problems on macos
|
||||
"chardet", # for automatic encoding detection
|
||||
],
|
||||
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"]},
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
+11
-3
@@ -2,6 +2,7 @@
|
||||
from kivy_deps import sdl2, glew
|
||||
from kivymd import hooks_path as kivymd_hooks_path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
block_cipher = None
|
||||
|
||||
@@ -25,7 +26,7 @@ a = Analysis(
|
||||
],
|
||||
hiddenimports=["win32file", "win32timezone", "six"], # FileChooser in kivy loads win32file conditionally, mkl needs six
|
||||
hookspath=[kivymd_hooks_path],
|
||||
excludes=["matplotlib", "docutils", "scipy", "pandas"],
|
||||
excludes=["scipy", "pandas", "numpy", "matplotlib", "docutils", "mkl"],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=None,
|
||||
@@ -48,6 +49,11 @@ console_names = {True:"DebugKaTrain",False:"KaTrain"}
|
||||
|
||||
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():
|
||||
|
||||
|
||||
@@ -63,7 +69,8 @@ for console, name in console_names.items():
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=console,
|
||||
icon="C:\\icon.ico",
|
||||
icon="..\\katrain\img\\icon.ico",
|
||||
version=versionModule.versionInfo,
|
||||
)
|
||||
|
||||
coll = COLLECT(
|
||||
@@ -90,7 +97,8 @@ for console, name in console_names.items():
|
||||
upx=True,
|
||||
name=name,
|
||||
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}/{name}.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)\n".encode('ascii'))
|
||||
|
||||
@@ -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 |
Reference in new issue
Block a user