* distributed option * i18n, pause, etc * fixes galore * max buffer etc * test override config * maxgame * visit * contribute menu * restore vars * i18n * actually start * retry distr and errors * pw eye / move speed * cfg/no log * shutdown fix * shutdown fix * download fix * wut * stats and desc * esc fix * Omit terminate queries from KataGoEngine.queries. (#353) * press key h to clear current node analysis. (#352) * clear current node all analysis * add reset key binding * reset all analysis off. * code refactor * code change according to review. * remove useless variable * code review changes * clean up reset node * oops * Setup position feature (#354) * go for zebras * setup working! * uhh * tweak * img * fix i18n * AEEEEEEEEEE and stop * link in readme * new game options for edit/new/setup (#357) * new game options for edit/new/setup * feedback * fix * ai thoughts * no tristate cb * branch fixing (#358) * new game options for edit/new/setup * feedback * fix * ai thoughts * no tristate cb * fix comma etc * normal katago * kata * dll * save window pos/size * BAD comma * new linux binary + allow default engine * fix * unused icon, build mac * exe * player new game fix * fix i18n * test test * just print * 한국어 * 활성화 * Add new Traditional Chinese translation (#362) Improve some translations as well. * cn (#363) * cn * rm old * rm old * informal you * mo * jp (#364) * config and readme * mo * kgtraining * fix score * lower stddev a bit * tweak weights * 1.8 French Po (#365) * fr and getattr fix * -DBUILD_DISTRIBUTED=1 * openssl * openssl * openssl * openssl * config * jp (num undos) (#366) * clarify undo in en, po->mo * fix player name * new kata * new kata * Load game option save config (#367) * in the load dialog, "load fast analysis" and "rewind to the start" option ,save to config file * in the load dialog, "load fast analysis" and "rewind to the start" option ,save to config file * default option * remove useless override * save check state when file dialog's submit event , * save config in callback * readme * active games * de * log games as json always * black * Russian localization for 1.8.0 (#375) * ru Co-authored-by: pdeblanc <peter@spaceandgames.com> Co-authored-by: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Co-authored-by: Liou, Guan-Cheng <Tony-Liou@users.noreply.github.com> Co-authored-by: kaorahi <38910552+kaorahi@users.noreply.github.com> Co-authored-by: Dontbtme <66470440+Dontbtme@users.noreply.github.com> Co-authored-by: Alexander Kiselev <satoshi.yoda.register.264@gmail.com>
173 lines
6.7 KiB
Python
173 lines
6.7 KiB
Python
import os
|
|
import shutil
|
|
import sys
|
|
|
|
from kivy import Config
|
|
from kivy.storage.jsonstore import JsonStore
|
|
|
|
from katrain.core.ai import ai_rank_estimation
|
|
from katrain.core.constants import (
|
|
PLAYER_HUMAN,
|
|
PLAYER_AI,
|
|
PLAYING_NORMAL,
|
|
PLAYING_TEACHING,
|
|
OUTPUT_INFO,
|
|
OUTPUT_ERROR,
|
|
OUTPUT_DEBUG,
|
|
AI_DEFAULT,
|
|
CONFIG_MIN_VERSION,
|
|
DATA_FOLDER,
|
|
)
|
|
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.sgf_rank = None
|
|
self.calculated_rank = None
|
|
self.name = ""
|
|
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:
|
|
USER_CONFIG_FILE = os.path.expanduser(os.path.join(DATA_FOLDER, "config.json"))
|
|
PACKAGE_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, force_package_config=False, debug_level=None, **kwargs):
|
|
self.debug_level = debug_level or 0
|
|
self.game = None
|
|
|
|
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
|
|
self.config_file = self._load_config(force_package_config=force_package_config)
|
|
self.debug_level = self.config("general/debug_level", OUTPUT_INFO) if debug_level is None else debug_level
|
|
|
|
Config.set("kivy", "log_level", "warning")
|
|
if self.debug_level >= OUTPUT_DEBUG:
|
|
Config.set("kivy", "log_enable", 1)
|
|
Config.set("kivy", "log_level", "debug")
|
|
# 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}")
|
|
elif self.debug_level >= level:
|
|
print(message)
|
|
|
|
def _load_config(self, force_package_config):
|
|
if len(sys.argv) > 1 and sys.argv[1].endswith("config.json"):
|
|
config_file = os.path.abspath(sys.argv[1])
|
|
self.log(f"Using command line config file {config_file}", OUTPUT_INFO)
|
|
else:
|
|
user_config_file = find_package_resource(self.USER_CONFIG_FILE)
|
|
package_config_file = find_package_resource(self.PACKAGE_CONFIG_FILE)
|
|
if force_package_config:
|
|
config_file = package_config_file
|
|
else:
|
|
try:
|
|
if not os.path.exists(user_config_file):
|
|
os.makedirs(os.path.split(user_config_file)[0], exist_ok=True)
|
|
shutil.copyfile(package_config_file, user_config_file)
|
|
config_file = user_config_file
|
|
self.log(f"Copied package config to local file {config_file}", OUTPUT_INFO)
|
|
else: # user file exists
|
|
try:
|
|
version = JsonStore(user_config_file).get("general")["version"]
|
|
except Exception: # noqa E722 broken file etc
|
|
version = "0.0.0"
|
|
if version < CONFIG_MIN_VERSION:
|
|
backup = user_config_file + f".{version}.backup"
|
|
shutil.copyfile(user_config_file, backup)
|
|
shutil.copyfile(package_config_file, user_config_file)
|
|
self.log(
|
|
f"Copied package config file to {user_config_file} as user file is outdated or broken ({version}<{CONFIG_MIN_VERSION}). Old version stored as {backup}",
|
|
OUTPUT_INFO,
|
|
)
|
|
config_file = user_config_file
|
|
self.log(f"Using user config file {config_file}", OUTPUT_INFO)
|
|
except Exception as e:
|
|
config_file = package_config_file
|
|
self.log(
|
|
f"Using package config file {config_file} (exception {e} occurred when finding or creating user config)",
|
|
OUTPUT_INFO,
|
|
)
|
|
try:
|
|
self._config_store = JsonStore(config_file, indent=4)
|
|
except Exception as e:
|
|
self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR)
|
|
sys.exit(1)
|
|
self._config = dict(self._config_store)
|
|
return config_file
|
|
|
|
def save_config(self, key=None):
|
|
if key is None:
|
|
for k, v in self._config.items():
|
|
self._config_store.put(k, **v)
|
|
else:
|
|
self._config_store.put(key, **self._config[key])
|
|
|
|
def config(self, setting, default=None):
|
|
try:
|
|
if "/" in setting:
|
|
cat, key = setting.split("/")
|
|
return self._config.get(cat, {}).get(key, default)
|
|
else:
|
|
return self._config.get(setting, default)
|
|
except KeyError:
|
|
self.log(f"Missing configuration option {setting}", OUTPUT_ERROR)
|
|
|
|
def update_player(self, bw, **kwargs):
|
|
self.players_info[bw].update(**kwargs)
|
|
self.update_calculated_ranks()
|
|
|
|
def update_calculated_ranks(self):
|
|
for bw, player_info in self.players_info.items():
|
|
if player_info.player_type == PLAYER_AI:
|
|
settings = self.config(f"ai/{player_info.strategy}")
|
|
player_info.calculated_rank = ai_rank_estimation(player_info.player_subtype, settings)
|
|
else:
|
|
player_info.calculated_rank = None
|
|
|
|
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]
|