player info

This commit is contained in:
Sander Land committed 2020-05-29 13:34:52 +02:00
1 parent 97dc12df10
commit 671a764c6a
25 files changed
+457 -213

No files matched your search

+5 -1
View File
@@ -4,6 +4,10 @@ import yappi
from katrain.__main__ import run_app
yappi.set_clock_type("cpu")
yappi.start()
run_app()
try:
run_app()
except:
pass
yappi.get_func_stats().save("callgrind.out." + datetime.now().isoformat(), "CALLGRIND")
+18 -19
View File
@@ -23,23 +23,16 @@ from kivy.storage.jsonstore import JsonStore
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import Screen
from kivymd.app import MDApp
from katrain.core.constants import *
from katrain.core.ai import ai_move
from katrain.core.utils import (
DEFAULT_LANGUAGE,
OUTPUT_DEBUG,
OUTPUT_ERROR,
OUTPUT_EXTRA_DEBUG,
OUTPUT_INFO,
OUTPUT_KATAGO_STDERR,
find_package_resource,
i18n,
MODE_PLAY,
switch_lang,
)
from katrain.core.constants import OUTPUT_ERROR, OUTPUT_KATAGO_STDERR, OUTPUT_INFO, OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG, MODE_PLAY
from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup, I18NPopup
from katrain.gui.ai_settings import ConfigAIPopupContents
from katrain.core.settings_and_logging import KaTrainSettings
from katrain.core.base_katrain import KaTrainBase
from katrain.core.engine import KataGoEngine
from katrain.core.game import Game, IllegalMoveException, KaTrainSGF
from katrain.core.sgf_parser import Move, ParseError
@@ -53,15 +46,15 @@ from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL
__version__ = "1.1.0"
class KaTrainGui(Screen, KaTrainSettings):
class KaTrainGui(Screen, KaTrainBase):
"""Top level class responsible for tying everything together"""
zen = BooleanProperty(False)
controls = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.engine = None
self.game = None
self.new_game_popup = None
self.fileselect_popup = None
@@ -109,7 +102,7 @@ class KaTrainGui(Screen, KaTrainSettings):
def update_state(self, redraw_board=False): # is called after every message and on receiving analyses and config changes
# AI and Trainer/auto-undo handlers
cn = self.game.current_node
last_player, next_player = self.game.players[cn.player], self.game.players[cn.next_player]
last_player, next_player = self.players_info[cn.player], self.players_info[cn.next_player]
if self.play_analyze_mode == MODE_PLAY:
teaching_undo = cn.player and last_player.being_taught
if teaching_undo and cn.analysis_ready and cn.parent and cn.parent.analysis_ready and not cn.children and not self.game.ended:
@@ -120,7 +113,7 @@ class KaTrainGui(Screen, KaTrainSettings):
# Handle prisoners and next player display
prisoners = self.game.prisoner_count
top, bot = [w.__self__ for w in self.board_controls.circles] # no weakref
if self.game.next_player.player == "W":
if self.next_player_info.player == "W":
top, bot = bot, top
self.controls.players["W"].active = True
self.controls.players["B"].active = False
@@ -148,6 +141,14 @@ class KaTrainGui(Screen, KaTrainSettings):
self.controls.update_evaluation()
self.controls.update_timer(1)
def update_player(self, bw, **kwargs):
super().update_player(bw, **kwargs)
if self.controls:
self.controls.update_players()
self.update_state()
for player_setup_block in PlayerSetupBlock.INSTANCES:
player_setup_block.update_players(bw,self.players_info[bw])
def set_note(self, note):
self.game.current_node.note = note
@@ -173,14 +174,12 @@ class KaTrainGui(Screen, KaTrainSettings):
self.board_gui.animating_pv = None
self.engine.on_new_game() # clear queries
self.game = Game(self, self.engine, move_tree=move_tree, analyze_fast=analyze_fast)
for bw, player_widget in self.nav_drawer_contents.player_setup.players.items():
self.game.players[bw].update(**player_widget.player_def)
self.controls.graph.initialize_from_game(self.game.root)
self.update_state(redraw_board=True)
def _do_ai_move(self, node=None):
if node is None or self.game.current_node == node:
mode = self.game.next_player.strategy
mode = self.next_player_info.strategy
settings = self.config(f"ai/{mode}")
if settings:
ai_move(self.game, mode, settings)
@@ -190,7 +189,7 @@ class KaTrainGui(Screen, KaTrainSettings):
def _do_undo(self, n_times=1):
if n_times == "smart":
n_times = 1
if self.play_analyze_mode == "play" and self.game.last_player.ai and self.game.next_player.human:
if self.play_analyze_mode == "play" and self.last_player_info.ai and self.next_player_info.human:
n_times = 2
self.board_gui.animating_pv = None
self.game.undo(n_times)
@@ -206,7 +205,7 @@ class KaTrainGui(Screen, KaTrainSettings):
def _do_play(self, coords):
self.board_gui.animating_pv = None
try:
self.game.play(Move(coords, player=self.game.next_player.player))
self.game.play(Move(coords, player=self.next_player_info.player))
except IllegalMoveException as e:
self.controls.set_status(f"Illegal Move: {str(e)}")
@@ -377,7 +376,7 @@ class KaTrainApp(MDApp):
def on_language(self, _instance, language):
self.gui.log(f"Switching language to {language}", OUTPUT_INFO)
switch_lang(language)
i18n.switch_lang(language)
def on_start(self):
self.gui.start()
+15 -27
View File
@@ -60,71 +60,59 @@
"lock_ai": false
},
"ai": {
"default": {
"ai:default": {
"_help_right": "No settings available for Default KataGo AI, strength is mainly affected by `max_visits` and `model` in the main settings `engine` section.",
"_help_left": ""
},
"jigo": {
"ai:jigo": {
"target_score": 0.5,
"_help_left": "Will try to win by `target_score`, without further restrictions.",
"_help_right": "Also affected by engine settings such as `max_visits`."
},
"scoreloss": {
"ai:scoreloss": {
"strength": 0.5
},
"Policy": {
"ai:policy": {
"opening_moves": 20
},
"P:Weighted": {
"_help_right": "pick_override` determines when top move is chosen without randomness, and `lower_bound` determines the lower bound policy value that is allowed.",
"_help_left": "Plays move with probability=policy^(1/weaken_fac),i.e. `weaken_fac` influences how much more likely weaker moves are picked.",
"ai:p:weighted": {
"pick_override": 1.0,
"lower_bound": 0.001,
"weaken_fac": 1.25
},
"P:Pick": {
"ai:p:pick": {
"pick_override": 0.95,
"pick_n": 5,
"pick_frac": 0.33,
"_help_left": "Picks `pick_n + pick_frac * <number of legal moves>` at random and plays the best one. Change `pick_frac` to make it see more moves.",
"_help_right": "Plays top move if policy value is above `pick_override` to avoid obvious mistakes."
"pick_frac": 0.33
},
"P:Local": {
"ai:p:local": {
"pick_override": 0.95,
"stddev": 1.5,
"pick_n": 15,
"pick_frac": 0.0,
"_help_left": "Samples `pick_n + pick_frac * <number of legal moves>` near the last move and plays the best one.",
"_help_right": "Lower `stddev` makes it prefer closer moves."
"pick_frac": 0.0
},
"P:Tenuki": {
"ai:p:tenuki": {
"pick_override": 0.85,
"stddev": 7.5,
"pick_n": 5,
"pick_frac": 0.5,
"endgame": 0.45,
"_help_left": "Samples `pick_n + pick_frac * <number of legal moves>` away from the last move and plays the best one.",
"_help_right": "Increase `stddev` makes it prefer moves further away. Stops tenukiing after the 'endgame' fraction of the board is filled."
"endgame": 0.45
},
"P:Influence": {
"ai:p:influence": {
"pick_override": 0.95,
"pick_n": 5,
"pick_frac": 0.4,
"threshold": 3.5,
"line_weight": 10,
"endgame": 0.4,
"_help_left": "Samples `pick_n + pick_frac * <number of legal moves>` and plays the best one, biased to above the `threshold` line.",
"_help_right": "Increase `line_weight` to penalize moves near the edge more. Stops strategy after the 'endgame' fraction of the board is filled."
"endgame": 0.4
},
"P:Territory": {
"ai:p:territory": {
"pick_override": 0.95,
"pick_n": 5,
"pick_frac": 0.4,
"threshold": 3.5,
"line_weight": 2,
"endgame": 0.4,
"_help_left": "Samples `pick_n + pick_frac * <number of legal moves>` and plays the best one, biased to below the `threshold` line.",
"_help_right": "Increase `line_weight` to penalize moves closer to the center more. Stops strategy after the 'endgame' fraction of the board is filled."
"endgame": 0.4
}
}
}
+2 -1
View File
@@ -4,7 +4,8 @@ import random
import time
from typing import Dict, List, Tuple
from katrain.core.utils import OUTPUT_DEBUG, OUTPUT_INFO, var_to_grid
from katrain.core.utils import var_to_grid
from katrain.core.constants import OUTPUT_INFO, OUTPUT_DEBUG
from katrain.core.engine import EngineDiedException
from katrain.core.game import Game, GameNode, Move
+111
View File
@@ -0,0 +1,111 @@
from kivy import Config
from kivy.properties import ObjectProperty
from kivy.storage.jsonstore import JsonStore
import os, sys
from katrain.core.constants import *
from katrain.core.utils import find_package_resource
class Player:
def __init__(self, player="B", player_type=PLAYER_HUMAN, player_subtype=PLAYING_NORMAL, periods_used=0):
self.player = player
self.update(player_type, player_subtype)
self.periods_used = periods_used
def update(self, player_type=PLAYER_HUMAN, player_subtype=PLAYING_NORMAL):
self.player_type = player_type
self.player_subtype = player_subtype
@property
def ai(self):
return self.player_type == PLAYER_AI
@property
def human(self):
return self.player_type == PLAYER_HUMAN
@property
def being_taught(self):
return self.player_type == PLAYER_HUMAN and self.player_subtype == PLAYING_TEACHING
@property
def strategy(self):
return self.player_subtype if self.ai else AI_DEFAULT
def __str__(self):
return f"{self.player_type} ({self.player_subtype})"
class KaTrainBase:
CONFIG_FILE = "katrain/config.json"
"""Settings, logging, and players functionality, so other classes like bots who need a katrain instance can be used without a GUI"""
def __init__(self, **kwargs):
self.debug_level = 0
self.game = None
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
self.config_file = self._load_config()
self.debug_level = self.config("general/debug_level", OUTPUT_INFO)
Config.set("kivy", "log_level", "error")
if self.debug_level >= OUTPUT_DEBUG:
Config.set("kivy", "log_enable", 1)
Config.set("kivy", "log_level", "warning")
if self.debug_level >= OUTPUT_EXTRA_DEBUG:
Config.set("kivy", "log_level", "trace")
self.players_info = {"B": Player("B"), "W": Player("W")}
self.reset_players()
def log(self, message, level=OUTPUT_INFO):
if level == OUTPUT_ERROR:
print(f"ERROR: {message}", file=sys.stderr)
elif self.debug_level >= level:
print(message)
def _load_config(self):
config_file = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else find_package_resource(self.CONFIG_FILE))
try:
self.log(f"Using config file {config_file}", OUTPUT_INFO)
self._config_store = JsonStore(config_file, indent=4)
self._config = dict(self._config_store)
return config_file
except Exception as e:
self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR)
sys.exit(1)
def save_config(self):
for k, v in self._config.items():
self._config_store.put(k, **v)
def config(self, setting, default=None):
try:
if "/" in setting:
cat, key = setting.split("/")
return self._config[cat].get(key, default)
else:
return self._config[setting]
except KeyError:
self.log(f"Missing configuration option {setting}", OUTPUT_ERROR)
def update_player(self, bw, **kwargs):
self.players_info[bw].update(**kwargs)
def reset_players(self):
self.update_player("B")
self.update_player("W")
for v in self.players_info.values():
v.periods_used = 0
@property
def last_player_info(self) -> Player:
return self.players_info[self.game.current_node.player]
@property
def next_player_info(self) -> Player:
return self.players_info[self.game.current_node.next_player]
+9
View File
@@ -1,8 +1,17 @@
HOMEPAGE = "https://github.com/sanderland/katrain"
PLAYER_HUMAN, PLAYER_AI = "player:human", "player:ai"
PLAYER_TYPES = [PLAYER_HUMAN, PLAYER_AI]
PLAYING_NORMAL, PLAYING_TEACHING = "game:normal", "game:teach"
GAME_TYPES = [PLAYING_NORMAL, PLAYING_TEACHING]
MODE_PLAY, MODE_ANALYZE = "play", "analyze"
AI_DEFAULT = "default"
AI_STRATEGIES = [AI_DEFAULT]
OUTPUT_ERROR = -1
OUTPUT_KATAGO_STDERR = -0.5
OUTPUT_INFO = 0
OUTPUT_DEBUG = 1
OUTPUT_EXTRA_DEBUG = 2
+2 -1
View File
@@ -6,7 +6,8 @@ import threading
import time
from typing import Callable, Optional
from katrain.core.utils import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR, find_package_resource, i18n
from katrain.core.utils import find_package_resource, i18n
from katrain.core.constants import OUTPUT_ERROR, OUTPUT_KATAGO_STDERR, OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG
from katrain.core.game_node import GameNode
+6 -49
View File
@@ -5,11 +5,11 @@ import threading
from datetime import datetime
from typing import Dict, List, Union, Optional
from katrain.core.utils import var_to_grid, OUTPUT_INFO, OUTPUT_DEBUG, i18n
from katrain.core.utils import var_to_grid, i18n
from katrain.core.constants import OUTPUT_INFO, OUTPUT_DEBUG, HOMEPAGE
from katrain.core.engine import KataGoEngine
from katrain.core.game_node import GameNode
from katrain.core.sgf_parser import SGF, Move
from katrain.core.constants import *
class IllegalMoveException(Exception):
@@ -20,46 +20,15 @@ class KaTrainSGF(SGF):
_NODE_CLASS = GameNode
class Player:
def __init__(self, player="B", player_type=PLAYER_HUMAN, player_subtype=PLAYING_NORMAL, periods_used=0):
self.player = player
self.update(player_type, player_subtype)
self.periods_used = periods_used
def update(self, player_type, player_subtype):
self.player_type = player_type
self.player_subtype = player_subtype
@property
def ai(self):
return self.player_type == PLAYER_AI
@property
def human(self):
return self.player_type == PLAYER_HUMAN
@property
def being_taught(self):
return self.player_type == PLAYER_HUMAN and self.player_subtype == PLAYING_TEACHING
@property
def strategy(self):
return self.player_subtype if self.ai else AI_DEFAULT
def __str__(self):
return f"{self.player_type} ({self.player_subtype})"
class Game:
"""Represents a game of go, including an implementation of capture rules."""
DEFAULT_PROPERTIES = {"GM": 1, "FF": 4, "AP": "KaTrain:https://github.com/sanderland/katrain"}
DEFAULT_PROPERTIES = {"GM": 1, "FF": 4, "AP": f"KaTrain:{HOMEPAGE}"}
def __init__(self, katrain, engine: Union[Dict, KataGoEngine], move_tree: GameNode = None, players: Dict = None, analyze_fast=False):
def __init__(self, katrain, engine: Union[Dict, KataGoEngine], move_tree: GameNode = None, analyze_fast=False):
self.katrain = katrain
if not isinstance(engine, Dict):
engine = {"B": engine, "W": engine}
self.players = players or {"B": Player("B"), "W": Player("W")}
self.engines = engine
self.game_id = datetime.strftime(datetime.now(), "%Y-%m-%d %H %M %S")
@@ -76,8 +45,6 @@ class Game:
handicap = katrain.config("game/handicap")
if handicap:
self.place_handicap_stones(handicap)
self.root.set_property("PW", str(self.players["W"]))
self.root.set_property("PB", str(self.players["B"]))
if not self.root.get_property("RU"):
self.root.set_property("RU", katrain.config("game/rules"))
@@ -85,14 +52,6 @@ class Game:
self.set_current_node(self.root)
threading.Thread(target=lambda: self.analyze_all_nodes(-1_000_000, analyze_fast=analyze_fast), daemon=True).start() # return faster, but bypass Kivy Clock
@property
def last_player(self) -> Player:
return self.players[self.current_node.player]
@property
def next_player(self) -> Player:
return self.players[self.current_node.next_player]
def analyze_all_nodes(self, priority=0, analyze_fast=False):
for node in self.root.nodes_in_tree:
node.analyze(self.engines[node.next_player], priority=priority, analyze_fast=analyze_fast)
@@ -287,10 +246,8 @@ class Game:
if eval_thresholds is None:
eval_thresholds = self.katrain.config("trainer/eval_thresholds")
black, white = self.root.get_property("PB"), self.root.get_property("PW")
black = re.sub(r"['<>:\"/\\|?*]", "", black or "Black")
white = re.sub(r"['<>:\"/\\|?*]", "", white or "White")
game_name = f"katrain_{black} vs {white} {self.game_id}"
player_names = {bw: re.sub(r"['<>:\"/\\|?*]", "", self.root.get_property("P"+bw) or str(self.katrain.players_info[bw])) for bw in "BW" }
game_name = f"katrain_{player_names['B']} vs {player_names['W']} {self.game_id}"
file_name = os.path.abspath(os.path.join(path, f"{game_name}.sgf"))
os.makedirs(os.path.dirname(file_name), exist_ok=True)
-1
View File
@@ -142,7 +142,6 @@ class GameNode(SGFNode):
else:
text += i18n._("Info:best move") + "\n"
if previous_top_move.get("pv") and (sgf or hints):
print("PREV", previous_top_move)
text += i18n._("Info:PV").format(pv=self.make_pv(single_move.player, previous_top_move["pv"], interactive)) + "\n"
if sgf or hints or teach:
-62
View File
@@ -1,62 +0,0 @@
from kivy import Config
from kivy.storage.jsonstore import JsonStore
import os, sys
from katrain.core.utils import (
OUTPUT_ERROR,
OUTPUT_INFO,
OUTPUT_KATAGO_STDERR,
find_package_resource,
OUTPUT_DEBUG,
OUTPUT_EXTRA_DEBUG,
)
class KaTrainSettings:
CONFIG_FILE = "katrain/config.json"
"""Settings and logging functionality, so other classes who need a katrain instance can be used without a GUI"""
def __init__(self, **kwargs):
self.debug_level = 0
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
self.config_file = self._load_config()
self.debug_level = self.config("general/debug_level", OUTPUT_INFO)
Config.set("kivy", "log_level", "error")
if self.debug_level >= OUTPUT_DEBUG:
Config.set("kivy", "log_enable", 1)
Config.set("kivy", "log_level", "warning")
# if self.debug_level >= OUTPUT_EXTRA_DEBUG:
# Config.set("kivy", "log_level", "trace")
def log(self, message, level=OUTPUT_INFO):
if level == OUTPUT_ERROR:
print(f"ERROR: {message}", file=sys.stderr)
elif self.debug_level >= level:
print(message)
def _load_config(self):
config_file = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else find_package_resource(self.CONFIG_FILE))
try:
self.log(f"Using config file {config_file}", OUTPUT_INFO)
self._config_store = JsonStore(config_file, indent=4)
self._config = dict(self._config_store)
return config_file
except Exception as e:
self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR)
sys.exit(1)
def save_config(self):
for k, v in self._config.items():
self._config_store.put(k, **v)
def config(self, setting, default=None):
try:
if "/" in setting:
cat, key = setting.split("/")
return self._config[cat].get(key, default)
else:
return self._config[setting]
except KeyError:
self.log(f"Missing configuration option {setting}", OUTPUT_ERROR)
-14
View File
@@ -5,8 +5,6 @@ from typing import List, Tuple, Callable
from kivy.lang import Observable
import gettext
from kivymd.uix.textfield import MDTextField
from katrain.gui.style import DEFAULT_FONT
try:
@@ -14,14 +12,6 @@ try:
except:
import importlib_resources as pkg_resources
OUTPUT_ERROR = -1
OUTPUT_KATAGO_STDERR = -0.5
OUTPUT_INFO = 0
OUTPUT_DEBUG = 1
OUTPUT_EXTRA_DEBUG = 2
MODE_PLAY, MODE_ANALYZE = "play", "analyze"
def var_to_grid(array_var: List[Number], size: Tuple[int, int]) -> List[List[Number]]:
"""convert ownership/policy to grid format such that grid[y][x] is for move with coords x,y"""
@@ -113,7 +103,3 @@ class Lang(Observable):
DEFAULT_LANGUAGE = "en"
i18n = Lang(DEFAULT_LANGUAGE)
def switch_lang(lang):
i18n.switch_lang(lang)
Binary file not shown.
+5 -5
View File
@@ -119,7 +119,7 @@
<AutoSizedRoundedRectangleButton@AutoSizedRectangleButton>:
background_radius: self.height/3.5
<BGMDBoxLayout>:
<BGBoxLayout>:
background_color: BOX_BACKGROUND_COLOR
<TransparentIconButton>:
@@ -695,7 +695,7 @@
notebox.size_hint_y = int(args[1]['notes'])*0.66+1e-9
notebox.opacity = int(args[1]['notes'])
options_height: root.tab_option_height
BGMDBoxLayout:
BGBoxLayout:
background_color: BOX_BACKGROUND_COLOR
orientation: 'vertical'
ScrollableLabel:
@@ -895,9 +895,9 @@
StyledSpinner:
id: player_subtype
value_refs: []
on_text: root.set_player()
on_text: root.update_global_player_info()
<PlayerSetupBlock@MDBoxLayout>:
<PlayerSetupBlock>:
orientation: 'vertical'
size_hint_y: None
spacing: 3*CP_SPACING
@@ -1023,7 +1023,7 @@
ScreenManager:
Screen:
name: 'gui'
BGMDBoxLayout:
BGBoxLayout:
background_color: BACKGROUND_COLOR
MDBoxLayout:
orientation: 'vertical'
+1 -1
View File
@@ -4,7 +4,7 @@ from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.uix.label import MDLabel
from katrain.core.utils import OUTPUT_DEBUG, OUTPUT_ERROR
from katrain.core.constants import OUTPUT_ERROR, OUTPUT_DEBUG
from katrain.gui.kivyutils import SizedButton, LightHelpLabel, ScaledLightLabel, StyledSpinner
from katrain.gui.popups import QuickConfigGui, ConfigPopup, InputParseError
+8 -4
View File
@@ -14,7 +14,8 @@ from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.menu import MDDropdownMenu, RightContent
from katrain.core.utils import OUTPUT_DEBUG, evaluation_class, i18n, MODE_PLAY
from katrain.core.utils import evaluation_class, i18n
from katrain.core.constants import OUTPUT_DEBUG, MODE_PLAY
from katrain.core.game import Move
from katrain.gui.kivyutils import draw_circle, draw_text
from katrain.core.utils import var_to_grid
@@ -50,9 +51,12 @@ class BadukPanWidget(Widget):
self.stone_size = 0
self.active_pv_moves = []
self.animating_pv = None
self.redraw_board_contents_trigger = Clock.create_trigger(self.draw_board_contents)
self.last_mouse_pos = (0, 0)
Window.bind(mouse_pos=self.on_mouse_pos)
self.redraw_board_contents_trigger = Clock.create_trigger(self.draw_board_contents)
self.redraw_trigger = Clock.create_trigger(self.redraw)
self.bind(size=self.redraw_trigger)
Clock.schedule_interval(self.animate_pv, 0.1)
# stone placement functions
@@ -132,7 +136,7 @@ class BadukPanWidget(Widget):
self.draw_hover_contents() # remove ghost
# drawing functions
def on_size(self, *_args):
def redraw(self,*_args):
self.draw_board()
self.draw_board_contents()
@@ -276,7 +280,7 @@ class BadukPanWidget(Widget):
Rectangle(pos=(self.gridpos_x[x] - rsz / 2, self.gridpos_y[y] - rsz / 2), size=(rsz, rsz))
policy = current_node.policy
if not policy and current_node.parent and current_node.parent.policy and katrain.game.last_player.ai and katrain.game.next_player.ai:
if not policy and current_node.parent and current_node.parent.policy and katrain.last_player_info.ai and katrain.next_player_info.ai:
policy = current_node.parent.policy # in the case of AI self-play we allow the policy to be one step out of date
pass_btn = katrain.board_controls.pass_btn
+9 -8
View File
@@ -7,7 +7,8 @@ from kivy.uix.popup import Popup
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.floatlayout import MDFloatLayout
from katrain.core.utils import MODE_PLAY, MODE_ANALYZE, i18n
from katrain.core.utils import i18n
from katrain.core.constants import MODE_PLAY, MODE_ANALYZE
class PlayAnalyzeSelect(MDFloatLayout):
@@ -38,9 +39,9 @@ class ControlsPanel(BoxLayout):
Clock.schedule_interval(self.update_timer, 0.07)
def update_players(self, *_args):
for player, info in self.katrain.game.players.items():
self.players[player].player_type = i18n._(info.player_type)
self.players[player].player_subtype = i18n._(info.player_subtype)
for bw, player_info in self.katrain.players_info.items():
self.players[bw].player_type = i18n._(player_info.player_type)
self.players[bw].player_subtype = i18n._(player_info.player_subtype)
def set_status(self, msg, at_node=None):
self.status_msg = msg
@@ -59,11 +60,11 @@ class ControlsPanel(BoxLayout):
self.status.text = ""
self.status_node = None
last_player_was_ai_playing_human = game.last_player.ai and game.next_player.human
last_player_was_ai_playing_human = katrain.last_player_info.ai and katrain.next_player_info.human
self.active_comment_node = current_node
if katrain.play_analyze_mode == MODE_PLAY and last_player_was_ai_playing_human:
if game.next_player.being_taught and current_node.children and current_node.children.auto_undo:
if katrain.next_player_info.being_taught and current_node.children and current_node.children.auto_undo:
self.active_comment_node = current_node.children[-1]
elif current_node.parent:
self.active_comment_node = current_node.parent
@@ -71,7 +72,7 @@ class ControlsPanel(BoxLayout):
hints = katrain.analysis_controls.hints.active
info = ""
if current_node.move and not current_node.is_root:
info = self.active_comment_node.comment(teach=game.players[self.active_comment_node.player].being_taught, hints=hints)
info = self.active_comment_node.comment(teach=katrain.players_info[self.active_comment_node.player].being_taught, hints=hints)
if self.active_comment_node.analysis_ready:
self.stats.score = self.active_comment_node.format_score() or ""
@@ -96,7 +97,7 @@ class ControlsPanel(BoxLayout):
self.last_timer_update = (current_node, now)
byo_len = max(1, self.katrain.config("timer/byo_length"))
byo_num = max(1, self.katrain.config("timer/byo_periods"))
player = self.katrain.game.next_player
player = self.katrain.next_player_info
ai = player.ai
if not self.timer.paused and not ai:
if last_update_node == current_node and not current_node.children:
+31 -11
View File
@@ -14,6 +14,7 @@ from kivy.properties import (
ObjectProperty,
)
from kivy.uix.behaviors import ToggleButtonBehavior, ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.checkbox import CheckBox
from kivy.uix.filechooser import FileChooserLayout, FileChooserListLayout
@@ -170,7 +171,7 @@ class CircleWithText(Widget):
min_size = NumericProperty(50)
class BGMDBoxLayout(MDBoxLayout, BackgroundMixin):
class BGBoxLayout(BoxLayout, BackgroundMixin):
pass
@@ -195,11 +196,17 @@ class StyledSpinner(Spinner):
except (ValueError, IndexError):
return 0, "", ""
def select_key(self, key):
try:
ix = self.value_refs.index(key)
self.text = self.values[ix]
except:
pass
def i18n_values(self, *_args):
if self.value_refs:
selected = self.selected[0]
self.values = [i18n._(ref) for ref in self.value_refs]
print(self.selected, self.values, self.value_refs)
self.text = self.values[selected]
self.font_name = i18n.font_name
self.update_dropdown_props()
@@ -221,6 +228,18 @@ class StyledSpinner(Spinner):
item.font_name = self.font_name
class PlayerSetupBlock(MDBoxLayout):
players = ObjectProperty(None)
INSTANCES = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
PlayerSetupBlock.INSTANCES.append(self)
def update_players(self, bw, player_info): # update sub widget based on gui state change
self.players[bw].update_widget(player_type=player_info.player_type, player_subtype=player_info.player_subtype)
class PlayerSetup(MDBoxLayout):
player = OptionProperty("B", options=["B", "W"])
@@ -237,19 +256,20 @@ class PlayerSetup(MDBoxLayout):
self.player_subtype_label.text = i18n._("aistrategy")
self.player_subtype.value_refs = MDApp.get_running_app().gui.ai_strategies # TODO:
self.player_subtype.text = self.player_subtype.values[0]
self.set_player()
self.update_global_player_info()
@property
def player_def(self):
def player_type_dump(self):
return {"player_type": self.player_type.selected[1], "player_subtype": self.player_subtype.selected[1]}
def set_player(self):
def update_widget(self, player_type, player_subtype):
self.player_type.select_key(player_type) # should trigger setup options
self.player_subtype.select_key(player_subtype) # should trigger setup options
def update_global_player_info(self):
katrain = MDApp.get_running_app().gui
game = katrain and katrain.game
if game:
game.players[self.player].update(**self.player_def)
katrain.controls.update_players()
katrain.update_state()
if katrain.game and katrain.game.current_node:
katrain.update_player(self.player, **self.player_type_dump)
class PlayerInfo(MDBoxLayout, BackgroundMixin):
@@ -260,7 +280,7 @@ class PlayerInfo(MDBoxLayout, BackgroundMixin):
active = BooleanProperty(True)
class Timer(BGMDBoxLayout):
class Timer(BGBoxLayout):
state = ListProperty([30, 5, 1])
+5 -2
View File
@@ -12,7 +12,7 @@ from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.selectioncontrol import MDCheckbox
from kivymd.uix.textfield import MDTextField
from katrain.core.utils import OUTPUT_DEBUG, OUTPUT_ERROR
from katrain.core.constants import OUTPUT_ERROR, OUTPUT_DEBUG
from katrain.core.engine import KataGoEngine
from katrain.core.game import Game, GameNode
from katrain.gui.kivyutils import StyledSpinner
@@ -163,7 +163,7 @@ class ConfigTimerPopup(QuickConfigGui):
def update_config(self, save_to_file=True):
super().update_config(save_to_file=save_to_file)
for p in self.katrain.game.players.values():
for p in self.katrain.players_info.values():
p.periods_used = 0
self.katrain.controls.timer.paused = True
self.katrain.game.current_node.time_used = 0
@@ -183,6 +183,9 @@ class NewGamePopup(QuickConfigGui):
self.katrain.engine.restart()
self.katrain("new-game")
# for bw, player_widget in self.nav_drawer_contents.player_setup.players.items():
# self.players[bw].update(**player_widget.player_def)
class ConfigPopup(QuickConfigGui):
def __init__(self, katrain, popup: Popup, config: Dict, ignore_cats: Tuple = (), **kwargs):
Binary file not shown.
+62 -6
View File
@@ -311,7 +311,7 @@ msgstr ""
msgid "Engine died unexpectedly"
msgstr ""
"Engine died unexpectedly without sending output, possibly due to out of "
"memory: {e}"
"memory: {error}"
#. AI names, help etc
msgid "strength:kyu"
@@ -320,7 +320,7 @@ msgstr "k"
msgid "strength:dan"
msgstr "d"
msgid "ainame:default"
msgid "ai:default"
msgstr "KataGo"
msgid "aihelp:default"
@@ -328,7 +328,7 @@ msgstr ""
"Full strength KataGo AI. Strength is mainly affected by `max_visits` and "
"`model` in the general settings `engine` section."
msgid "ainame:jigo"
msgid "ai:jigo"
msgstr "KataJigo"
msgid "aihelp:jigo"
@@ -336,7 +336,7 @@ msgstr ""
"Will try to win by a set amount of points (default 0.5), without further "
"restrictions."
msgid "ainame:jigo"
msgid "ai:jigo"
msgstr "KataJigo"
msgid "aihelp:jigo"
@@ -344,7 +344,7 @@ msgstr ""
"Will try to win by a set amount of points (default 0.5), without further "
"restrictions."
msgid "ainame:scoreloss"
msgid "ai:scoreloss"
msgstr "Score Loss"
msgid "aihelp:scoreloss"
@@ -352,7 +352,7 @@ msgstr ""
"Plays moves that lead to more points lost with lower probability. Likely to "
"play more varied/weaker with higher visits settings."
msgid "ainame:policy"
msgid "ai:policy"
msgstr "Policy"
msgid "aihelp:policy"
@@ -360,3 +360,59 @@ msgstr ""
"Plays the top move from the policy network, without any reading. Strength is"
" mainly affected by `model` in engine settings. The opening moves setting "
"affects how many moves in the opening are more random."
msgid "ai:p:weighted"
msgstr "Policy Weighted"
msgid "aihelp:p:weighted"
msgstr ""
"Plays moves according to policy probability, with `weaken_fac` influencing "
"how much more likely weaker moves are played. A high negative value plays "
"like the `Policy` AI.`pick_override` determines when top move is chosen "
"without randomness, and `lower_bound` determines the lower bound policy "
"value that is allowed."
msgid "ai:p:pick"
msgstr "Blinded Policy"
msgid "aihelp:p:pick"
msgstr ""
"Picks `pick_n + pick_frac * <number of legal moves>` at random and plays the"
" best one. For `pick_frac=1` plays like the `Policy` AI. Plays top move if "
"policy value is above `pick_override` to avoid obvious mistakes."
msgid "ai:p:local"
msgstr "Local Response Style"
msgid "aihelp:p:local"
msgstr ""
"Picks `pick_n + pick_frac * <number of legal moves>` weighted towards the "
"last move and plays the best one. Plays top move if policy value is above "
"`pick_override` to avoid obvious mistakes."
msgid "ai:p:tenuki"
msgstr "Tenuki Style"
msgid "aihelp:p:tenuki"
msgstr ""
"Picks `pick_n + pick_frac * <number of legal moves>` weighted away from the "
"last move and plays the best one. Plays top move if policy value is above "
"`pick_override` to avoid obvious mistakes."
msgid "ai:p:influence"
msgstr "Influential Style"
msgid "aihelp:p:influence"
msgstr ""
"Picks moves biased above the `threshold` line and plays the best "
"one.Increase `line_weight` to penalize moves near the edge more. Stops "
"strategy after the 'endgame' fraction of the board is filled."
msgid "ai:p:territory"
msgstr "Respond Locally"
msgid "aihelp:p:territory"
msgstr ""
"Picks moves biased below the `threshold` line and plays the best "
"one.Increase `line_weight` to penalize moves near the center more. Stops "
"strategy after the 'endgame' fraction of the board is filled."
Binary file not shown.
@@ -398,3 +398,87 @@ msgstr "k"
#. TODO
msgid "strength:dan"
msgstr "d"
#. TODO
msgid "aihelp:p:weighted"
msgstr ""
"Plays moves according to policy probability, with `weaken_fac` influencing "
"how much more likely weaker moves are played. A high negative value plays "
"like the `Policy` AI.`pick_override` determines when top move is chosen "
"without randomness, and `lower_bound` determines the lower bound policy "
"value that is allowed."
#. TODO
msgid "aihelp:p:pick"
msgstr ""
"Picks `pick_n + pick_frac * <number of legal moves>` at random and plays the"
" best one. For `pick_frac=1` plays like the `Policy` AI. Plays top move if "
"policy value is above `pick_override` to avoid obvious mistakes."
#. TODO
msgid "ai:p:influence"
msgstr "Influential Style"
#. TODO
msgid "ai:p:local"
msgstr "Respond Locally"
#. TODO
msgid "aihelp:p:tenuki"
msgstr ""
"Picks `pick_n + pick_frac * <number of legal moves>` weighted away from the "
"last move and plays the best one. Plays top move if policy value is above "
"`pick_override` to avoid obvious mistakes."
#. TODO
msgid "ai:scoreloss"
msgstr "Score Loss"
#. TODO
msgid "aihelp:p:influence"
msgstr ""
"Picks moves biased above the `threshold` line and plays the best "
"one.Increase `line_weight` to penalize moves near the edge more. Stops "
"strategy after the 'endgame' fraction of the board is filled."
#. TODO
msgid "ai:default"
msgstr "KataGo"
#. TODO
msgid "ai:p:tenuki"
msgstr "Tenuki Style"
#. TODO
msgid "ai:p:pick"
msgstr "Blinded Policy"
#. TODO
msgid "ai:p:weighted"
msgstr "Policy Weighted"
#. TODO
msgid "ai:policy"
msgstr "Policy"
#. TODO
msgid "ai:jigo"
msgstr "KataJigo"
#. TODO
msgid "aihelp:p:local"
msgstr ""
"Picks moves biased below the `threshold` line and plays the best "
"one.Increase `line_weight` to penalize moves near the center more. Stops "
"strategy after the 'endgame' fraction of the board is filled."
#. TODO
msgid "aihelp:p:territory"
msgstr ""
"Picks moves biased below the `threshold` line and plays the best "
"one.Increase `line_weight` to penalize moves near the center more. Stops "
"strategy after the 'endgame' fraction of the board is filled."
#. TODO
msgid "ai:p:territory"
msgstr "Respond Locally"
Binary file not shown.
@@ -430,3 +430,87 @@ msgstr "k"
#. TODO
msgid "strength:dan"
msgstr "d"
#. TODO
msgid "aihelp:p:weighted"
msgstr ""
"Plays moves according to policy probability, with `weaken_fac` influencing "
"how much more likely weaker moves are played. A high negative value plays "
"like the `Policy` AI.`pick_override` determines when top move is chosen "
"without randomness, and `lower_bound` determines the lower bound policy "
"value that is allowed."
#. TODO
msgid "aihelp:p:pick"
msgstr ""
"Picks `pick_n + pick_frac * <number of legal moves>` at random and plays the"
" best one. For `pick_frac=1` plays like the `Policy` AI. Plays top move if "
"policy value is above `pick_override` to avoid obvious mistakes."
#. TODO
msgid "ai:p:influence"
msgstr "Influential Style"
#. TODO
msgid "ai:p:local"
msgstr "Respond Locally"
#. TODO
msgid "aihelp:p:tenuki"
msgstr ""
"Picks `pick_n + pick_frac * <number of legal moves>` weighted away from the "
"last move and plays the best one. Plays top move if policy value is above "
"`pick_override` to avoid obvious mistakes."
#. TODO
msgid "ai:scoreloss"
msgstr "Score Loss"
#. TODO
msgid "aihelp:p:influence"
msgstr ""
"Picks moves biased above the `threshold` line and plays the best "
"one.Increase `line_weight` to penalize moves near the edge more. Stops "
"strategy after the 'endgame' fraction of the board is filled."
#. TODO
msgid "ai:default"
msgstr "KataGo"
#. TODO
msgid "ai:p:tenuki"
msgstr "Tenuki Style"
#. TODO
msgid "ai:p:pick"
msgstr "Blinded Policy"
#. TODO
msgid "ai:p:weighted"
msgstr "Policy Weighted"
#. TODO
msgid "ai:policy"
msgstr "Policy"
#. TODO
msgid "ai:jigo"
msgstr "KataJigo"
#. TODO
msgid "aihelp:p:local"
msgstr ""
"Picks moves biased below the `threshold` line and plays the best "
"one.Increase `line_weight` to penalize moves near the center more. Stops "
"strategy after the 'endgame' fraction of the board is filled."
#. TODO
msgid "aihelp:p:territory"
msgstr ""
"Picks moves biased below the `threshold` line and plays the best "
"one.Increase `line_weight` to penalize moves near the center more. Stops "
"strategy after the 'endgame' fraction of the board is filled."
#. TODO
msgid "ai:p:territory"
msgstr "Respond Locally"
-1
View File
@@ -31,7 +31,6 @@
<LabelledFloatInput>:
font_size: INPUT_FONT_SIZE
on_text: print(self.size,self.font_size)
<LabelledCheckBox>:
color: WHITE