* certificate fix

* Added a sound effect for the capturing of stones (#523)

* fix cache missing

* Fixes for some edge cases for the capturing sound effect (#526)

* Added a sound effect for the capturing of stones

* Fixed edge cases for the capturing sound effect

* minor bug fix

* Refactoring sounds for playing a move

* simplify capture sound, upgrade black

* fix handicap

* fix stone sound bug, note on sleep deadlock

* board rotation (#532)

* implementation of the rotation of the board view and some refactoring (#531)

* minor cleanup

Co-authored-by: Patrick <63347771+Funkenschlag1@users.noreply.github.com>
Co-authored-by: Sander Land <sander.land@cognite.com>

Co-authored-by: Sander Land <sander@chatdesk.com>
Co-authored-by: Patrick <63347771+Funkenschlag1@users.noreply.github.com>
Co-authored-by: Sander Land <sander.land@cognite.com>
This commit is contained in:
authored and GitHub committed 2022-06-23 19:14:56 +02:00
1 parent 2ffb79afa2
commit 485b55c42d
16 files changed
+311 -201

No files matched your search

+1
View File
@@ -55,6 +55,7 @@ Many thanks to these additional authors:
* "xiaoyifang" for adding the reset analysis option, feature to save options on the loading screen, and scrolling through variations.
* "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.
## Translators
+2 -1
View File
@@ -5,10 +5,11 @@ For on related licenses for these binaries and libraries see https://github.com/
2. Icons from www.flaticon.com, used with permission with the following attributions:
- Equalize icon and Thrash Icon: derived from work by bqlqn from www.flaticon.com
- Other Menu icons, Finish, Collaboration, and Flag icons: derived from work by Freepik from www.flaticon.com
- Other Menu icons, Finish, Collaboration and Flag icons: derived from work by Freepik from www.flaticon.com
- Collapse branch icon: derived from work by Kirill Kazachek from www.flaticon.com
- Prune icon: derived from work by Pixelmeetup from www.flaticon.com
- Reset icon: derived from work by Pixel Perfect from www.flaticon.com
- Rotate icon: derived from work by Frey Wazza from www.flaticon.com
3. The True Type Font DIGITAL-7 version 1.02 by Sizenko Alexander, which is free for non-commercial use.
+26 -8
View File
@@ -1,10 +1,9 @@
"""isort:skip_file"""
# first, logging level lower
import os
import sys
os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning")
# if "KIVY_AUDIO" not in os.environ: # trying default again
# os.environ["KIVY_AUDIO"] = "sdl2" # some backends hard crash / this seems to be most stable
import kivy
@@ -17,13 +16,19 @@ from kivy.utils import platform
ICON = find_package_resource("katrain/img/icon.ico")
Config.set("kivy", "window_icon", ICON)
Config.set("input", "mouse", "mouse,multitouch_on_demand")
# next, certificates on package builds https://github.com/sanderland/katrain/issues/414
if getattr(sys, "frozen", False):
import ssl
if ssl.get_default_verify_paths().cafile is None and hasattr(sys, "_MEIPASS"):
os.environ["SSL_CERT_FILE"] = os.path.join(sys._MEIPASS, "certifi", "cacert.pem")
import re
import signal
import json
import sys
import threading
import traceback
from queue import Queue
@@ -198,6 +203,7 @@ class KaTrainGui(Screen, KaTrainBase):
self.board_controls.mid_circles_container.clear_widgets()
self.board_controls.mid_circles_container.add_widget(bot)
self.board_controls.mid_circles_container.add_widget(top)
self.controls.players["W"].captures = prisoners["W"]
self.controls.players["B"].captures = prisoners["B"]
@@ -257,7 +263,7 @@ class KaTrainGui(Screen, KaTrainBase):
and not (teaching_undo and cn.auto_undo is None)
): # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly.
self._do_ai_move(cn)
Clock.schedule_once(self.board_gui.play_stone_sound, 0.25)
Clock.schedule_once(self._play_stone_sound, 0.25)
if self.engine:
if self.pondering:
self.game.analyze_extra("ponder")
@@ -331,6 +337,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.engine.on_new_game() # clear queries
self.game = Game(
self,
@@ -394,6 +401,8 @@ 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()
def _do_find_mistake(self, fn="redo"):
self.board_gui.animating_pv = None
@@ -403,10 +412,19 @@ class KaTrainGui(Screen, KaTrainBase):
self.board_gui.animating_pv = None
self.controls.move_tree.switch_branch(*args)
def _play_stone_sound(self,_dt=None):
play_sound(random.choice(Theme.STONE_SOUNDS))
def _do_play(self, coords):
self.board_gui.animating_pv = None
try:
old_prisoner_count = self.game.prisoner_count["W"] + self.game.prisoner_count["B"]
self.game.play(Move(coords, player=self.next_player_info.player))
if old_prisoner_count < self.game.prisoner_count["W"] + self.game.prisoner_count["B"]:
play_sound(Theme.CAPTURING_SOUND)
elif not self.game.current_node.is_pass:
self._play_stone_sound()
except IllegalMoveException as e:
self.controls.set_status(f"Illegal Move: {str(e)}", STATUS_ERROR)
@@ -443,7 +461,7 @@ class KaTrainGui(Screen, KaTrainBase):
self.controls.timer.paused = True
if not self.teacher_settings_popup:
self.teacher_settings_popup = I18NPopup(
title_key="teacher settings", size=[dp(800), dp(800)], content=ConfigTeacherPopup(self)
title_key="teacher settings", size=[dp(800), dp(825)], content=ConfigTeacherPopup(self)
).__self__
self.teacher_settings_popup.content.popup = self.teacher_settings_popup
self.teacher_settings_popup.open()
@@ -513,8 +531,8 @@ class KaTrainGui(Screen, KaTrainBase):
self.update_state(redraw_board=True)
def play_mistake_sound(self, node):
if self.config("timer/sound") and node.played_sound is None and Theme.MISTAKE_SOUNDS:
node.played_sound = True
if self.config("timer/sound") and node.played_mistake_sound is None and Theme.MISTAKE_SOUNDS:
node.played_mistake_sound = True
play_sound(random.choice(Theme.MISTAKE_SOUNDS))
def load_sgf_file(self, file, fast=False, 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)
time.sleep(0.01) # TODO: prevent deadlock if esc, check node in queries?
engine.check_alive(exception_if_dead=True)
return analysis
-1
View File
@@ -137,7 +137,6 @@ class KataGoContributeEngine(BaseEngine):
def queries_remaining(self):
return 1
def start(self):
try:
self.katrain.log(f"Starting Distributed KataGo with {self.command}", OUTPUT_INFO)
+1
View File
@@ -62,6 +62,7 @@ class BaseGame:
self.insert_mode = False
self.external_game = False # not generated by katrain at some point
if move_tree:
self.root = move_tree
self.external_game = PROGRAM_NAME not in self.root.get_property("AP", "")
+1 -1
View File
@@ -41,7 +41,7 @@ class GameNode(SGFNode):
def __init__(self, parent=None, properties=None, move=None):
super().__init__(parent=parent, properties=properties, move=move)
self.auto_undo = None # None = not analyzed. False: not undone (good move). True: undone (bad move)
self.played_sound = None
self.played_mistake_sound = None
self.ai_thoughts = ""
self.note = ""
self.move_number = 0
+2
View File
@@ -373,6 +373,8 @@ 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
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
+3
View File
@@ -272,6 +272,9 @@
icon: 'Next-Mistake.png'
color: Theme.MISTAKE_BUTTON_COLOR
on_press: root.katrain('find-mistake','redo')
NavIconButton:
icon: 'Rotate.png'
on_press: root.katrain('rotate')
ClickableLabel:
pos_hint: {'center_x': 0.85,'center_y':0.5}
size_hint: None, 1
+269 -78
View File
@@ -1,7 +1,7 @@
import math
import random
import time
from typing import List, Optional
import numpy as np
from kivy.clock import Clock
from kivy.core.window import Window
@@ -14,7 +14,6 @@ from kivy.uix.widget import Widget
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.floatlayout import MDFloatLayout
from katrain.gui.sound import play_sound
from katrain.core.constants import (
MODE_PLAY,
@@ -40,11 +39,12 @@ from katrain.gui.theme import Theme
class BadukPanWidget(Widget):
def __init__(self, **kwargs):
super(BadukPanWidget, self).__init__(**kwargs)
self.stones_sounds = []
self.trainer_config = {}
self.ghost_stone = []
self.gridpos_x = []
self.gridpos_y = []
self.gridpos = np.zeros((0,0,2))
self.initial_gridpos_x = []
self.initial_gridpos_y = []
self.rotation_degree = 0
self.grid_size = 0
self.stone_size = 0
self.selecting_region_of_interest = False
@@ -70,18 +70,25 @@ class BadukPanWidget(Widget):
def get_enable_coordinates(self):
return self.draw_coords_enabled
def play_stone_sound(self, *_args):
play_sound(random.choice(Theme.STONE_SOUNDS))
# stone placement functions
def _find_closest(self, pos, gridpos):
return sorted([(abs(p - pos), i) for i, p in enumerate(gridpos)])[0]
def _find_closest(self, pos_x, pos_y):
xd = abs(self.gridpos[0,0,0] - pos_x)
xp = 0
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)
xp = x
yd = abs(self.gridpos[y,x,1] - pos_y)
yp = y
return xd, xp, yd, yp
def check_next_move_ghost(self, touch):
if not self.gridpos_x:
if not self.initial_gridpos_x:
return
xd, xp = self._find_closest(touch.x, self.gridpos_x)
yd, yp = self._find_closest(touch.y, self.gridpos_y)
xd, xp, yd, yp = self._find_closest(touch.x, touch.y)
prev_ghost = self.ghost_stone
if max(yd, xd) < self.grid_size / 2 and (xp, yp) not in [m.coords for m in self.katrain.game.stones]:
self.ghost_stone = (xp, yp)
@@ -91,10 +98,9 @@ class BadukPanWidget(Widget):
self.redraw_hover_contents_trigger()
def update_box_selection(self, touch, second_point=True):
if not self.gridpos_x:
if not self.initial_gridpos_x:
return
_, xp = self._find_closest(touch.x, self.gridpos_x)
_, yp = self._find_closest(touch.y, self.gridpos_y)
_, xp, _, yp = self._find_closest(touch.x, touch.y)
if second_point and len(self.region_of_interest) == 4:
self.region_of_interest[1] = xp
self.region_of_interest[3] = yp
@@ -140,10 +146,10 @@ class BadukPanWidget(Widget):
near_move = [
(pv, node)
for move, pv, node in self.active_pv_moves
if move[0] < len(self.gridpos_x)
and move[1] < len(self.gridpos_y)
and abs(rel_pos[0] - self.gridpos_x[move[0]]) < self.grid_size / 2
and abs(rel_pos[1] - self.gridpos_y[move[1]]) < self.grid_size / 2
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
]
if near_move:
self.set_animating_pv(near_move[0][0], near_move[0][1])
@@ -156,7 +162,7 @@ class BadukPanWidget(Widget):
self.last_mouse_pos = pos
def on_touch_up(self, touch):
if ("button" in touch.profile and touch.button != "left") or not self.gridpos_x:
if ("button" in touch.profile and touch.button != "left") or not self.initial_gridpos_x:
return
katrain = self.katrain
if self.selecting_region_of_interest:
@@ -182,11 +188,8 @@ class BadukPanWidget(Widget):
)
else:
katrain("play", self.ghost_stone)
self.play_stone_sound()
elif not self.ghost_stone:
xd, xp = self._find_closest(touch.x, self.gridpos_x)
yd, yp = self._find_closest(touch.y, self.gridpos_y)
xd, xp, yd, yp = self._find_closest(touch.x, touch.y)
nodes_here = [
node for node in katrain.game.current_node.nodes_from_root if node.move and node.move.coords == (xp, yp)
]
@@ -222,7 +225,7 @@ class BadukPanWidget(Widget):
stone_size = self.stone_size * scale
Color(1, 1, 1, alpha)
Rectangle(
pos=(self.gridpos_x[x] - stone_size, self.gridpos_y[y] - 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]),
)
@@ -233,7 +236,7 @@ class BadukPanWidget(Widget):
)
Color(*evalcol)
Rectangle(
pos=(self.gridpos_x[x] - evalsize, self.gridpos_y[y] - 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),
)
@@ -241,7 +244,7 @@ class BadukPanWidget(Widget):
Color(*innercol)
inner_size = stone_size * 0.8
Rectangle(
pos=(self.gridpos_x[x] - inner_size, self.gridpos_y[y] - 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),
)
@@ -260,53 +263,171 @@ class BadukPanWidget(Widget):
with self.canvas.before:
self.canvas.before.clear()
# set up margins and grid lines
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)
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)
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.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)
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()
else:
current_gridpos_x = self.initial_gridpos_x[:]
current_gridpos_y = self.initial_gridpos_y[:]
# 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.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_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
grid_spaces_margin_y = [1.5, 0.75] # bottom, top
else: # no coordinates means remove the offset
grid_spaces_margin_x = [0.75, 0.75] # left, right
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
self.grid_size = math.floor(min(self.width / x_grid_spaces, self.height / y_grid_spaces) + 0.1)
board_width_with_margins = x_grid_spaces * self.grid_size
board_height_with_margins = y_grid_spaces * self.grid_size
extra_px_margin_x = (self.width - board_width_with_margins) / 2
extra_px_margin_y = (self.height - board_height_with_margins) / 2
self.stone_size = self.grid_size * Theme.STONE_SIZE
return math.floor(min(width / x_grid_spaces, height / y_grid_spaces) + 0.1)
self.gridpos_x = [
self.pos[0] + extra_px_margin_x + math.floor((grid_spaces_margin_x[0] + i) * self.grid_size + 0.5)
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))
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):
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)
]
self.gridpos_y = [
self.pos[1] + extra_px_margin_y + math.floor((grid_spaces_margin_y[0] + i) * self.grid_size + 0.5)
gridpos_y = [
round(self.pos[1] + extra_px_margin_y + math.floor((grid_spaces_margin_y[0] + i) * grid_size + 0.5), 4)
for i in range(board_size_y)
]
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)
def resize_board(self):
rotated_gridpos_x, rotated_gridpos_y = self.calculate_rotated_gridpos()
current_gridpos_x = []
current_gridpos_y = []
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])
sorted_current_gridpos_x = list(set(current_gridpos_x))
sorted_current_gridpos_x.sort()
sorted_current_gridpos_y = list(set(current_gridpos_y))
sorted_current_gridpos_y.sort()
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])
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):
if katrain.game.insert_mode:
Color(*Theme.INSERT_BOARD_COLOR_TINT) # dreamy
else:
Color(*Theme.BOARD_COLOR_TINT) # image is a bit too light
Rectangle(
pos=(
self.gridpos_x[0] - self.grid_size * grid_spaces_margin_x[0],
self.gridpos_y[0] - self.grid_size * grid_spaces_margin_y[0],
gridpos_x[0] - self.grid_size * grid_spaces_margin_x[0],
gridpos_y[0] - self.grid_size * grid_spaces_margin_y[0],
),
size=(self.grid_size * x_grid_spaces, self.grid_size * y_grid_spaces),
texture=cached_texture(Theme.BOARD_TEXTURE),
)
def draw_lines(self, gridpos_x, gridpos_y):
Color(*Theme.LINE_COLOR)
for i in range(board_size_x):
Line(points=[(self.gridpos_x[i], self.gridpos_y[0]), (self.gridpos_x[i], self.gridpos_y[-1])])
for i in range(board_size_y):
Line(points=[(self.gridpos_x[0], self.gridpos_y[i]), (self.gridpos_x[-1], self.gridpos_y[i])])
for i in range(len(gridpos_x)):
Line(points=[(gridpos_x[i], gridpos_y[0]), (gridpos_x[i], gridpos_y[-1])])
for i in range(len(gridpos_y)):
Line(points=[(gridpos_x[0], gridpos_y[i]), (gridpos_x[-1], gridpos_y[i])])
# star points
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:
@@ -318,33 +439,63 @@ class BadukPanWidget(Widget):
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_x[x], self.gridpos_y[y]), starpt_size, Theme.LINE_COLOR)
draw_circle((self.gridpos[y,x,0], self.gridpos[y,x,1]), starpt_size, Theme.LINE_COLOR)
# coordinates
def draw_coordinates(self, gridpos_x, gridpos_y):
if self.draw_coords_enabled:
board_size_x, board_size_y = self.katrain.game.board_size
Color(0.25, 0.25, 0.25)
coord_offset = self.grid_size * 1.5 / 2
coord_offset = round(self.grid_size * 1.5 / 2, 12)
if (self.rotation_degree == 90 or self.rotation_degree == 270) and board_size_x != board_size_y:
board_size_y, board_size_x = self.katrain.game.board_size
for i in range(board_size_x):
draw_text(
pos=(self.gridpos_x[i], self.gridpos_y[0] - coord_offset),
text=Move.GTP_COORD[i],
pos=(gridpos_x[i], gridpos_y[0] - coord_offset),
text=self.get_x_coordinate_text(i, board_size_x),
font_size=self.grid_size / 1.5,
font_name="Roboto",
)
for i in range(board_size_y):
draw_text(
pos=(self.gridpos_x[0] - coord_offset, self.gridpos_y[i]),
text=str(i + 1),
pos=(gridpos_x[0] - coord_offset, gridpos_y[i]),
text=self.get_y_coordinate_text(i, board_size_y),
font_size=self.grid_size / 1.5,
font_name="Roboto",
)
def get_x_coordinate_text(self, i, board_size_x):
x_text = Move.GTP_COORD[i]
if (self.rotation_degree == 90):
x_text = str(i + 1)
elif (self.rotation_degree == 180):
x_text = Move.GTP_COORD[board_size_x - i - 1]
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):
y_text = Move.GTP_COORD[board_size_y - i - 1]
elif (self.rotation_degree == 180):
y_text = str(board_size_y - i)
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 len(self.gridpos_x) < board_size_x or len(self.gridpos_y) < board_size_y:
if self.gridpos.shape[0] < board_size_x or self.gridpos.shape[1] < board_size_y:
return # race condition
show_n_eval = self.trainer_config.get("eval_on_show_last", 3)
@@ -374,8 +525,8 @@ class BadukPanWidget(Widget):
Color(*Theme.EVAL_COLORS[self.trainer_config["theme"]][1][:3], loss)
Rectangle(
pos=(
self.gridpos_x[x] - self.grid_size / 2,
self.gridpos_y[y] - self.grid_size / 2,
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),
)
@@ -388,7 +539,7 @@ class BadukPanWidget(Widget):
*Theme.STONE_COLORS[ix_owner][:3], abs(ownership_grid[y][x]) * Theme.OWNERSHIP_MAX_ALPHA
)
Rectangle(
pos=(self.gridpos_x[x] - self.grid_size / 2, self.gridpos_y[y] - self.grid_size / 2),
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),
)
@@ -469,7 +620,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_x[x], self.gridpos_y[y]),
(self.gridpos[y,x,0], self.gridpos[y,x,1]),
self.stone_size * Theme.HINT_SCALE * 0.98,
Theme.APPROX_BOARD_COLOR,
)
@@ -477,14 +628,14 @@ class BadukPanWidget(Widget):
else:
scale = 0.5
draw_circle(
(self.gridpos_x[x], self.gridpos_y[y]),
(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_x[x], self.gridpos_y[y]),
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,
@@ -494,8 +645,8 @@ class BadukPanWidget(Widget):
Color(*Theme.TOP_MOVE_BORDER_COLOR[:3], Theme.POLICY_ALPHA)
Line(
circle=(
self.gridpos_x[x],
self.gridpos_y[y],
self.gridpos[y,x,0],
self.gridpos[y,x,1],
self.stone_size - dp(1.2),
),
width=dp(2),
@@ -513,13 +664,13 @@ class BadukPanWidget(Widget):
self.redraw_hover_contents_trigger()
def draw_roi_box(self, region_of_interest, width=2):
def draw_roi_box(self, region_of_interest, width:float=2):
xmin, xmax, ymin, ymax = region_of_interest
Color(*Theme.REGION_BORDER_COLOR)
Line(
rectangle=(
self.gridpos_x[xmin] - self.grid_size / 3,
self.gridpos_y[ymin] - self.grid_size / 3,
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,
),
@@ -544,7 +695,7 @@ class BadukPanWidget(Widget):
next_player = current_node.next_player
board_size_x, board_size_y = katrain.game.board_size
if len(self.gridpos_x) < board_size_x or len(self.gridpos_y) < board_size_y:
if len(self.gridpos[0]) < board_size_x or len(self.gridpos) < board_size_y:
return # race condition
with self.canvas.after:
@@ -603,14 +754,14 @@ 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_x[move.coords[0]], self.gridpos_y[move.coords[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_x[move.coords[0]] - evalsize, self.gridpos_y[move.coords[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),
)
@@ -640,7 +791,7 @@ class BadukPanWidget(Widget):
Color(*Theme.HINT_TEXT_COLOR)
draw_text(
pos=(self.gridpos_x[move.coords[0]], self.gridpos_y[move.coords[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,
@@ -653,8 +804,8 @@ class BadukPanWidget(Widget):
Color(*Theme.TOP_MOVE_BORDER_COLOR)
Line(
circle=(
self.gridpos_x[move.coords[0]],
self.gridpos_y[move.coords[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),
@@ -675,8 +826,8 @@ class BadukPanWidget(Widget):
Color(*Theme.NEXT_MOVE_DASH_CONTRAST_COLORS[child_node.player])
Line(
circle=(
self.gridpos_x[move.coords[0]],
self.gridpos_y[move.coords[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),
@@ -687,8 +838,8 @@ class BadukPanWidget(Widget):
for s in range(0, 360, 30):
Line(
circle=(
self.gridpos_x[move.coords[0]],
self.gridpos_y[move.coords[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,
@@ -721,7 +872,7 @@ class BadukPanWidget(Widget):
else:
text = i18n._("board-pass")
Color(*Theme.PASS_CIRCLE_COLOR)
center = (self.gridpos_x[int(board_size_x / 2)], self.gridpos_y[int(board_size_y / 2)])
center = (self.initial_gridpos_x[int(board_size_x / 2)], self.initial_gridpos_y[int(board_size_y / 2)])
size = min(self.width, self.height) * 0.227
Ellipse(pos=(center[0] - size / 2, center[1] - size / 2), size=(size, size))
Color(*Theme.PASS_CIRCLE_TEXT_COLOR)
@@ -739,7 +890,7 @@ 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_x[hide_node.move.coords[0]], self.gridpos_y[hide_node.move.coords[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):
@@ -756,7 +907,7 @@ class BadukPanWidget(Widget):
katrain.board_controls.pass_btn.pos[1] + katrain.board_controls.pass_btn.size[1] / 2,
]
else:
board_coords = (self.gridpos_x[coords[0]], self.gridpos_y[coords[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
@@ -793,6 +944,46 @@ class BadukPanWidget(Widget):
return self.animating_pv_index
def rotate_gridpos(self):
board_size_x, board_size_y = self.katrain.game.board_size
if board_size_x != board_size_y:
if self.rotation_degree == 90 or self.rotation_degree == 270:
rotated_gridpos_x, rotated_gridpos_y = self.calculate_rotated_gridpos()
diff = round(abs(rotated_gridpos_x[0] - rotated_gridpos_y[0]), 4)
x0 = rotated_gridpos_x[0]
y0 = rotated_gridpos_y[0]
else:
diff = round(abs(self.initial_gridpos_x[0] - self.initial_gridpos_y[0]), 4)
x0 = self.initial_gridpos_x[0]
y0 = self.initial_gridpos_y[0]
pos = self.gridpos.tolist()
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):
gridpos_x = pos[len(self.gridpos) - 1 - yi][:]
else:
gridpos_x = pos[yi][:]
gridpos_x.reverse()
x = pos[yi][xi][1]
y = gridpos_x[xi][0]
if x0 > y0:
x = round(x + diff, 4)
y = round(y - diff, 4)
elif y0 > x0:
x = round(x - diff, 4)
y = round(y + diff, 4)
self.gridpos[yi][xi] = (x, y)
else:
self.gridpos = np.rot90(self.gridpos, -1, (0, 1))
self.rotation_degree += 90
if self.rotation_degree == 360:
self.rotation_degree = 0
if board_size_x != board_size_y:
self.resize_board()
Clock.schedule_once(self.redraw)
def show_pv_from_comments(self, pv_str):
self.set_animating_pv(pv_str[1:].split(" "), self.katrain.controls.active_comment_node.parent)
+1
View File
@@ -68,6 +68,7 @@ class Theme:
GRAPH_TEXTURE = "graph_bg.png"
# sounds
STONE_SOUNDS = [f"stone{i}.wav" for i in [1, 2, 3, 4, 5]]
CAPTURING_SOUND = "capturing.wav"
COUNTDOWN_SOUND = "countdownbeep.wav"
MINIMUM_TIME_PASSED_SOUND = "boing.wav"
MISTAKE_SOUNDS = []
+2 -2
View File
@@ -275,7 +275,7 @@ class LinkTree(TreeView):
sig_new = []
for path, name in get_drives():
if platform == "win":
text = u"{}({})".format((name + " ") if name else "", path)
text = "{}({})".format((name + " ") if name else "", path)
else:
text = name
nodes_new.append((text, path))
@@ -351,7 +351,7 @@ class I18NFileBrowser(BoxLayout):
.. versionchanged:: 1.1
"""
path = StringProperty(u"/")
path = StringProperty("/")
"""
:class:`~kivy.properties.StringProperty`, defaults to the current working
directory as a unicode string. It specifies the path on the filesystem that
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

+2 -2
View File
@@ -439,10 +439,10 @@
DescriptionLabel:
text: i18n._("save dots")
AnchorLayout:
size_hint: 1, 4.25
size_hint: 1, 5
GridLayout:
cols: 2
rows: 6
rows: 7
padding: 4
size_hint: 0.95, 1
DescriptionLabel:
Binary file not shown.
-107
View File
@@ -1,107 +0,0 @@
import os
import time
import pandas as pd
import numpy as np
from unidecode import unidecode
from katrain.core.ai import game_report
from katrain.core.base_katrain import KaTrainBase
from katrain.core.engine import KataGoEngine
from katrain.core.game import KaTrainSGF, Game
import matplotlib.pyplot as plt
pd.set_option("display.max_rows", 5000)
settings = {
"fast_visits": 25,
"max_visits": 500,
"threads": 64,
"model": "C:\\Users\\sande\\.katrain\\kata1-b40c256-s7907049728-d1917596640.bin.gz",
}
# settings["model"] = "C:\\Users\\sande\\.katrain\\g170e-b20c256x2-s5303129600-d1228401921.bin.gz"
def dan(rank):
rank = rank.lower()
if rank[-1] in ["d", "p"]:
return int(rank[:-1])
elif rank == "?":
return np.nan
else:
assert rank[-1] == "k", f"unexpected rank {rank}"
return 1 - int(rank[:-1])
def polyfit(x, y, degree=1):
coeffs = np.polyfit(x, y, degree)
correlation = np.corrcoef(x, y)[0, 1]
results = {"coef": coeffs.tolist(), "r": correlation, "rsq": correlation ** 2}
return results
katrain = KaTrainBase(force_package_config=True, debug_level=0)
combined_settings = {**katrain.config("engine"), **settings}
engine = KataGoEngine(katrain, {**katrain.config("engine"), **settings})
thresholds = katrain.config("trainer/eval_thresholds")
games = []
n = 0
for sgf in os.listdir("sgftest/"):
if sgf.lower().endswith("sgf"):
print(sgf)
with open(os.path.join("sgftest", sgf)) as f:
move_tree = KaTrainSGF.parse_sgf(f.read())
games.append(Game(katrain, engine, move_tree=move_tree, analyze_fast=False))
n += 1
if n >= 30000: # small test=3
break
while not engine.is_idle():
print(f"waiting for engine to finish...{engine.queries_remaining()} queries left")
time.sleep(0.5)
engine.shutdown(finish=None)
reports = []
for game in games:
sum_stats, _, _ = game_report(game, thresholds=thresholds)
for bw in "BW":
oppbw = "B" if bw == "W" else "W"
info = {
f"name": game.root.get_property(f"P{bw}", "??"),
"rank": game.root.get_property(f"{bw}R", "9p"),
"opp_rank": game.root.get_property(f"{oppbw}R", "9p"),
**sum_stats[bw],
}
reports.append(info)
df = pd.DataFrame(reports).sort_values(by="accuracy", ascending=False).reset_index(drop=True)
df.name = [unidecode(n) for n in df.name]
print(df)
df["numrank"] = [dan(rank) for rank in df["rank"]]
def subplot(sp, ynames):
global df
plt.subplot(2, 2, sp)
legend = []
xfull = np.array(range(df["numrank"].min(), df["numrank"].max() + 1))
cols = "bgr"
for i, yname in enumerate(ynames):
plt.plot(df["numrank"], df[yname], cols[i] + "x")
for i, yname in enumerate(ynames):
fit = polyfit(df["numrank"], df[yname])
a, b = fit["coef"]
plt.plot(xfull, xfull * a + b, cols[i] + ":")
legend.append(f"{yname}: r^2 = {fit['rsq']:.3f}")
plt.xlabel("dan rank")
plt.legend(legend)
plt.subplots(2, 2)
subplot(1, ["accuracy"])
subplot(2, ["complexity"])
subplot(3, ["ai_top_move", "ai_top5_move"])
subplot(4, ["mean_ptloss", "weighted_ptloss"])
plt.show()