* 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:
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
+2
View File
@@ -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
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:
* 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
+2 -2
View File
@@ -4,7 +4,6 @@
[![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)
[![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)
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.
+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.
* 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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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"
+3 -7
View File
@@ -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
View File
@@ -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":
+1 -1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+26 -14
View File
@@ -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
View File
@@ -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
View File
@@ -10,4 +10,3 @@ Pillow==9.0.1
Pygments==2.7.4
requests==2.25.0
urllib3==1.26.5
numpy==1.22.3
+2 -3
View File
@@ -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
View File
@@ -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'))
+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